Many to Many query Find all "recipes" with XX ingredients - java

I'm doing a school project so I'm really not an advanced programmer and need a little help with a query that I need to make.
My db structure looks like this.
RECIPE table:
ID BIGINT 19
DESCRIPTION CLOB 2147483647
NAME VARCHAR 255
INGREDIENT table:
ID BIGINT 19
NAME VARCHAR 255
RECIPE_INGREDIENT table:
RECIPE_ID BIGINT 19
INGREDIENTS_ID BIGINT 19
This is my model class Recipe
#Entity
public class Recipe extends Model {
#ManyToMany(cascade=CascadeType.ALL)
public List<Ingredient> ingredients;
#Required
#Unique
public String name;
#Lob
#Required
public String description;
This is my model class Ingredient
#Entity
public class Ingredient extends Model {
#Required
#Unique
public String name;
The query I want to make is... I have a string with comma separated ingredients like milk,flour,egg for example. With that string I'd like to fetch all recipes which has exact those ingredients, lets say one Recipe only has flour as ingredient then that's true. Also a recipe with all mentioned etc etc.
I've modified a query string I found yesterday(can't remember where) but I can't get it to work.
I get this error:
IllegalArgumentException occured : org.hibernate.hql.ast.QuerySyntaxException: unexpected token: on near line 1, column 68 [select Recipe.name from models.Recipe inner join Recipe_Ingredient on Recipe.ID = Recipe_Ingredient.Recipe_ID inner join Ingredient on Recipe_Ingredient.Ingredient_ID = Ingredient.ID where Ingredient.name in ( 'milk') group by Recipe.name having count(*) = 2]
Any help would be much appreciated! And sorry if I made any mistakes with this post. It is my first here :)
Edit:
Query right now looks like this:
Query query = JPA.em().createQuery("SELECT Recipe.name "
+ "FROM Recipe INNER JOIN "
+ "Recipe_Ingredient ON Recipe.ID = Recipe_Ingredient.Recipe_ID "
+ "INNER JOIN Ingredient "
+ "ON Recipe_Ingredient.Ingredient_ID = Ingredient.ID "
+ "WHERE Ingredient.name "
+ "IN ("+ingredientsCSV+") "
+ "GROUP BY Recipe.name HAVING count(*) = 2");

I don't think you can do that with a simple query.
Why:
- You need to have way yo know if all ingredients are within your ingredient string.
Using having will not solve the problem is different recipes will have different amount of ingredients.
I'm not very familiar with how JPA works, but in pure sql for example mysql you could use a view to organize the data before you process it with a new query.
I'm Sorry that I can't help you with that query in JPA.

Looks like you need quotes on:
... Ingredient.name in ('milk', 'flour', 'egg') group by....

Related

JPQL Extract pk of an relation entity without a second query

I have these entities (is an example because i cant share real name entities):
#Entity
public class User { #Id private BigDecimal id; private String name, private Color favouriteColor }
#Entity
public class Color { #Id private Long colorId; private String colorName;}
In the table I have this data:
USER
ID|NAME|FavColor
1 |John| 1
2 |Sarah| 2
3 |Mike| 1
COLOR
1|Red
2|Blue
Now I want make a query that recover all my user data without select Color entity, only its ids.
#Query("new myDto(u.iduser,u.username,u.favcolor) from user u where favcolor in :listcolors")
This makes me an query of the two tables, I want a unique query because i dont need color entities, only the ids.
--
Other option that I am testing is making a implementation of a nativequery like this:
final List<MyDTO> result = new ArrayList<>();
Query q = entityManager.createNativeQuery("SELECT " +
" USER_ID, " +
" USER_NAME, " +
" FAV_COLOR " + +
"FROM USER " +
"WHERE FAV_COLOR IN (?)");
q.setParameter(1, colors.toString().replace("[","").replace("]",""));
Long TRUE = new Long(1L);
final List<Object[]> resultList = q.getResultList();
for (Object[] objects : resultList) {
MyDTOdto = new MyDTO();
dto.userId(((((BigDecimal) objects[0]) != null) ? ((BigDecimal) objects[0]).longValue() : null));
dto.userName(((((String) objects[0]) != null) ? ((String) objects[0]).longValue() : null));
dto.favColor(((((BigDecimal) objects[0]) != null) ? ((BigDecimal) objects[0]).longValue() : null));
result.add(dto);
}
return result;
In this case, I am getting error code (ORA-1722 - Number Not valid). I don't know what I can test now. Some ideas? Thanks
I am guessing you have issues with the SQL generated and your use of the inner join: when you call "u.favcolor" in the select clause, you are telling JPA to perform an inner join from User to Color based on the favcolor relationship. As favcolor is a Color reference, you are going to get the full color row, where as your native query implies you just want the foreign key value. If all you want is the fk/ID value from Color, the query should be:
"SELECT new myDto(u.iduser, u.username, color.id) FROM user u join u.favcolor color WHERE color.id in :listcolors"
This still might perform an inner join from user to color, but it should be in a single statement.
If you want to ensure you avoid the join:
Use EclipseLink's COLUMN JPQL extension to access the foreign key column directly. Something like:
"SELECT new myDto(u.iduser, u.username, COLUMN('FAV_COLOR', u) FROM user u WHERE COLUMN('FAV_COLOR', u) in :listcolors"
Use EclipseLink native query key functionality to access the "FAV_COLOR" foreign key column in the USER table directly for your JPQL queries. This requires a descriptor customizer to access, but allows you to use the foreign key value in JPQL queries directly without having to map it, and without the COLUMN mechanism tying your JPQL queries to a particular database table detail. This would allow a query of the form:
"SELECT new myDto(u.iduser, u.username, u.favColorVal FROM user u join u.favcolor color WHERE u.favColorVal in :listcolors"
Just map the FAV_COLOR as a basic mapping, in addition to the existing favColor reference mapping (or replacing it if you want):
#Basic
#Column(name="FAV_COLOR", updatable=false, insertable=false)
BigDecimal favColorId
This then allows you to use query "SELECT new myDto(u.iduser, u.username, u.favColorId FROM user u join u.favColorId color WHERE u.favColorId in :listcolors" to the same effect, but you can also just return the User instance (marking favColor as lazy and not serializable) as it will have the same data anyway.

ManyToMany POJOS in RoomDB: how can I obtain related objects's list sorted by junction sorting number?

In the same code that I used in this question, I would like to obtain the "composite ManyToMany POJO"s with it's "related objects list" sorted by a number column in the junction table.
My DAO's SQL STATEMENT
#Query("SELECT * FROM teacher " +
"INNER JOIN teacherscourses AS tc ON teacher.t_id = tc.teacher_id " +
"INNER JOIN course AS c ON c.c_id = tc.course_id " +
"WHERE tc.course_id = :course_id " +
"ORDER BY teacher.t_id ASC, tc.course_order ASC"
)
public abstract List<TeacherWithCourses> getTeachersByCourseId(short course_id);
The generated DAO's method obtains the list of TeacherWithCourses objects as expected. These objects' courses list property gets the related objects as expected; but the "ORDER BY" clause doesn't seem to affect how these courses list is sorted at all.
I expected each internal list in these objects (List<Course> courses) to be sorted according to the junction table's tc.course_order number; but the obtained objects seem to come sorted arbitrarily.
The "composite" POJO
public class TeacherWithCourses implements Serializable {
#Embedded public Teacher teacher;
#Relation(
parentColumn = "t_id",
entity = Course.class,
entityColumn = "c_id",
associateBy = #Junction(
value = TeachersCourses.class,
parentColumn = "teacher_id",
entityColumn = "course_id"
)
)
public List<Course> courses;
}
Is this kind of sorting possible through SQL query, or must I implement it some other way?
[EDIT] The Junction Entity
I store the ordering criteria inside an extra column here:
#Entity(primaryKeys = {"teacher_id", "course_id"})
public class TeachersCourses implements Serializable {
#ColumnInfo(name = "teacher_id")
public int teacherId;
#ColumnInfo(name = "course_id")
public short courseId;
#ColumnInfo(index = true, name = "course_order")
public short courseOrder;
}
but the obtained objects seem to come sorted arbitrarily.
This is because of how #Relation works. It does not include/consider the child objects from the query, it uses the query just to extract and build the parent. It then gets, irrespective of the supplied query, ALL of the children for the parent via a query that is build according to the attributes of the #Relation.
As such to filter and or order the children you need to supplement #Relation by overwriting the list/array of children.
For convenience based upon my previous answer (so using AltCourse with changed column names) here's an example of one way that is easy to implement (but not the most efficient).
First the additional #Dao components :-
#Query("SELECT altcourse.* FROM altcourse " +
"JOIN teacherscourses ON teacherscourses.course_id = altcourse.courseid " +
"JOIN teacher ON teacher.id = teacherscourses.teacher_id " +
"WHERE teacher.id=:id ORDER BY teacherscourses.course_order ASC")
public abstract List<AltCourse> getSortedCoursesForATeacher(int id);
#Transaction
public List<AltTeacherWithCourses> getAltTeachersByCourseIdSorted(short course_id) {
List<AltTeacherWithCourses> rv = getAltTeachersByCourseId(course_id);
for (AltTeacherWithCourses twc: rv) {
twc.courses = getSortedCoursesForATeacher(twc.teacher.id);
}
return rv;
}
obviously change anything starting with Alt accordingly
So a query that extracts the courses in the correct order, and
A method that gets the original teachersWithCourses where the Courses are not sorted as expected and replaces the list of courses with the sorted courses as extracted by the additional query.
Alt version have been used in the above for testing so with the tables looking like :-
and
Running :-
for(Course c: dao.getAllCourses()) {
for (AltTeacherWithCourses tbc: dao.getAltTeachersByCourseIdSorted(c.id)) {
Log.d("TEACHER_SORTC","Teacher is " + tbc.teacher.name + " Courses = " + tbc.courses.size());
for(AltCourse course: tbc.courses) {
Log.d("COURSE_SORTC","\tCourse is " + course.coursename);
}
}
}
Results in :-
2021-11-11 15:26:01.559 D/TEACHER_SORTC: Teacher is Teacher1 Courses = 3
2021-11-11 15:26:01.559 D/COURSE_SORTC: Course is AltCourse3
2021-11-11 15:26:01.559 D/COURSE_SORTC: Course is AltCourse2
2021-11-11 15:26:01.559 D/COURSE_SORTC: Course is AltCourse1
2021-11-11 15:26:01.565 D/TEACHER_SORTC: Teacher is Teacher1 Courses = 3
2021-11-11 15:26:01.565 D/COURSE_SORTC: Course is AltCourse3
2021-11-11 15:26:01.565 D/COURSE_SORTC: Course is AltCourse2
2021-11-11 15:26:01.565 D/COURSE_SORTC: Course is AltCourse1
2021-11-11 15:26:01.568 D/TEACHER_SORTC: Teacher is Teacher1 Courses = 3
2021-11-11 15:26:01.568 D/COURSE_SORTC: Course is AltCourse3
2021-11-11 15:26:01.569 D/COURSE_SORTC: Course is AltCourse2
2021-11-11 15:26:01.569 D/COURSE_SORTC: Course is AltCourse1
2021-11-11 15:26:01.569 D/TEACHER_SORTC: Teacher is Teacher2 Courses = 1
2021-11-11 15:26:01.569 D/COURSE_SORTC: Course is AltCourse3
Additional
An alternative/more efficient (SQLite wise) approach is to use a more complex query along with a POJO that uses #Embedded for Teacher and Course.
Thus you extract objects that have a Teacher and a Course and then you can build the TeachersWithCourses from the extract.
This does not need additional queries as are run when using #Relation and thus there is no need for an #Transaction.
note the example is using the original Teacher and Course without unique names so would have to be modified accordingly.
First the TeacherCourse POJO :-
class TeacherCourse {
#Embedded
Teacher teacher;
#Embedded(prefix = "course")
Course course;
}
prefix used to circumvent duplicate column names.
The query :-
#Query("WITH teacher_in_course AS (" +
"SELECT teacher.id " +
"FROM teacher " +
"JOIN teacherscourses ON teacher.id = teacherscourses.teacher_id " +
"WHERE course_id = :courseId" +
")" +
"SELECT course.id AS courseid, course.name as coursename, teacher.* " +
"FROM course " +
"JOIN teacherscourses ON course.id = teacherscourses.course_id " +
"JOIN teacher ON teacher.id = teacherscourses.teacher_id " +
"WHERE teacher.id IN (SELECT * FROM teacher_in_course) " +
"ORDER BY teacher.id ASC, course_order ASC" +
";")
public abstract List<TeacherCourse> getTeachersCoursesSortedFromCourseId(short courseId);
the CTE (Common Table Expression) teacher_in_course retrieves the list of teachers who have the specified courseid. This is used in the actual query to get the teachers and ALL courses for each of the teachers ordered accordingly. As such there is no need for #Transaction as all the data is extracted in the single query.
However, a TeachersWithCourses list needs to be built from the list of TeacherCourse objects e.g. :-
public List<TeacherWithCourses> buildTeacherWithCoursesListFromTeacherCourseList(List<TeacherCourse> teacherCourseList) {
ArrayList<TeacherWithCourses> rv = new ArrayList<>();
boolean afterFirst = false;
TeacherWithCourses currentTWC = new TeacherWithCourses();
currentTWC.teacher = new Teacher(-1,"");
ArrayList<Course> currentCourseList = new ArrayList<>();
currentTWC.courses = currentCourseList;
for (TeacherCourse tc: teacherCourseList) {
if(currentTWC.teacher.id != tc.teacher.id) {
if (afterFirst) {
currentTWC.courses = currentCourseList;
rv.add(currentTWC);
currentCourseList = new ArrayList<>();
currentTWC = new TeacherWithCourses();
}
currentTWC.teacher = tc.teacher;
currentTWC.courses = new ArrayList<>();
}
currentCourseList.add(tc.course);
afterFirst = true;
}
if (afterFirst) {
currentTWC.courses = currentCourseList;
rv.add(currentTWC);
}
return rv;
}
From what it looks like you have two options.
Here is an article explaining both methods https://newbedev.com/using-room-s-relation-with-order-by
You can sort your List after its been pulled from the database.
Using Collections.Sort. This will remove a lot of complexity from your query. And is probably the best option.
or
You can split your query into 3 parts and manually create a List of TeacherWithCourses, a query pulls a List of Teacher and then uses each teacher to pull their Courses in the order you desire. This is more queries but can be down under a single #Transaction so there will be little felt repercussions on the database side. But be careful this can create a lot of objects fast.

spring-data-jdbc: query containing entity with a 1-n relation

How can I write Queries for entities containing a 1-n reference?
Based on the spring-data-jdbc examples I will explain it with the following unit-test:
#Test
public void customQuery_ReferenceMultipleInstances() {
// prepare
LegoSet smallCar = createLegoSet("Small Car 01", 5, 12);
smallCar.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
smallCar.addModel("suv", "SUV with sliding doors.");
smallCar.addModel("roadster", "Slick red roadster.");
repository.save(smallCar);
// execute
List<LegoSet> actual = repository.findByName("Small Car 01");
Iterable<LegoSet> compare = repository.findAll();
// verify
assertThat(actual).as("same number of lego sets").hasSize(Lists.newArrayList(compare).size());
assertThat(actual.get(0).getModels()).as("same number of models").hasSize(Lists.newArrayList(compare).get(0).getModels().size());
assertThat(actual.get(0).getModels().get(0)).as("model must not be null").isNotNull();
assertThat(actual.get(0).getModels().get(0).getName()).as("model must have a name").isNotEmpty();
}
This models a LegoSet referencing 2 Models. The repository.findByName() is annotated with a custom query; the repository.findAll() is the standard spring-boot-data method of the CrudRepository (just as reference).
The custom query in version 1:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
In this version the test fails w/
java.lang.AssertionError: [model must not be null]
Expecting actual not to be null
Okay, after that I add another JOIN to model:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text, " +
"m.* " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"JOIN model m ON ls.id = m.lego_set " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
Now the test fails w/
java.lang.AssertionError: [same number of lego sets]
Expected size:<1> but was:<2> in:
<[LegoSet(id=1, name=Small Car 01, minimumAge=P5Y, maximumAge=P12Y, manual=Manual(id=1, author=Jens Schauder , text=Just put all the pieces together in the right order), models={suv=Model(name=suv, description=SUV with sliding doors.), roadster=Model(name=roadster, description=Slick red roadster.)}),
LegoSet(id=1, name=Small Car 01, minimumAge=P5Y, maximumAge=P12Y, manual=Manual(id=1, author=Jens Schauder
So how do I have to write that query correctly?
The first query is actually the correct one and it is working fine.
The problem is in your test. models is a Map, but you treat it like a List which compiles because a list index is also a valid map key.
If you change the last two assertions in your test like this they will succeed:
assertThat(actual.get(0).getModels().get("suv")).as("model must not be null").isNotNull();
assertThat(actual.get(0).getModels().get("suv").getName()).as("model must have a name").isNotEmpty();
// ----------------------------------------^
An alternative is to use the second query but with a custom ResultSetExtractor to collect multiple rows for the multiple models into a single LegoSet.
I'm in a similar situation, but i don't see what is different in my situation.
I have three models:
Client: id, name..
Project: id, name, clientId, List
ProjectMember
Here is my had coded query
#Query("""
select
project.id,
project.legacy_id,
project.code,
project.client_id,
project.is_archived,
project.name,
project.budget_type,
project.hours_budget_max,
project.money_budget_max
from project
join client on client.id = project.client_id
where client.name ilike '%:name%'
""")
List<Project> findByClientNameContainingIgnoreCase(final String name);
As you can see i'm looking for project where client name (using a join) match a pattern.
Executing the request by hand give me the expected results. But going through spring data jdbc give me no results

How do I deal with nested objects when not using an ORM?

I'm making the switch away from ORMs in Java and I was wondering what was the best way of dealing with many-to-one and many-to-one relationships in a non-ORM setting.
In my Customer.java class I have:
private Long id;
private String name;
private Date dob;
//About 10 more fields
private List<Pet> pets;
In Pet.java I have:
private String id;
private String name;
private Customer owner;
My database table for Pet looks like this
id BIGSERIAL PRIMARY KEY,
name VARCHAR(20),
owner_id BIGSERIAL REFERENCES...
Now I realize that if I run a query that joins the two tables, I get a "flat" data structure returned which contains the fields for both Customer and Pet as well as any foreign keys. What is the common/most efficient way to treat the data in this scenario?
a. Rebuild the object graph manually by calling customer.setName(resultSet.getString(("name"))...?
b. Use the returned data as is by converting it to a Map<String, Object>?
The data flow is: Data is read from the database -> rendered to JSON for use by an AngularJS front end -> modified data is sent back to the server for validation -> domain logic applied -> saved to database.
If you want to read both Customer and Pet in a single query for better performance, you can do something like this:
List<Customer> customers = new ArrayList<>();
String sql = "SELECT c.id AS cust_id" +
", c.name AS cust_name" +
", c.dob AS cust_dob" +
", p.id AS pet_id" +
", p.name AS pet_name" +
" FROM Customer c" +
" LEFT JOIN Pet p ON p.owner_id = c.id" +
" WHERE c.name LIKE ?" +
" ORDER BY c.id";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, "%DOE%");
try (ResultSet rs = stmt.executeQuery()) {
Customer customer = null;
while (rs.next()) {
long cust_id = rs.getLong("cust_id");
if (customer == null || customer.getId() != cust_id) {
customer = new Customer();
customer.setId(cust_id);
customer.setName(rs.getString("cust_name"));
customer.setDob(rs.getDate("cust_dob"));
customers.add(customer);
}
long pet_id = rs.getLong("pet_id");
if (pet_id != 0) {
Pet pet = new Pet();
pet.setId(pet_id);
pet.setName(rs.getString("pet_name"));
pet.setOwner(customer);
customer.addPet(pet);
}
}
}
}
The best option at this time are:
Spring JDBC (it has convenience of ORM like bean to object mapping etc.)
iBatis (allows to write SQL queries manually although it is ORM but a thin layer)
Write your own DAO layer implementation.
In all these cases you write your own sql queries and mostly they will result in join queries. By the way the example you have given are not nested objects.

how to write a php code interminal

I am getting this exception from fetching record through database based on column portfolio
Query:
String queryString= "select entity from " +Name + " entity WHERE entity."+Column+" = "+ searchId.toString();
Query query = _em.createQuery(queryString);
Many things are wrong here and a lot of important information is missing (entity code?).
className in your example should be just Portfolio instead of class com.hexgen.orm.Portfolio (assuming Portfolio is entity's name, which doesn't have to be the same as class name). If you are using someEntity.getClass().getName() to get it, change to someEntity.getClass().getSimpleName()
searchColumn should be the field name of Portfolio class, not column name in the database. Asssuming PORTFOLIO column is mapped to portfolio field, it should be portfolio.
String queryString = "select entity from " + simpleClassName + " entity WHERE entity." + searchColumn + " = :searchId";
Query query = _em.createQuery(queryString);
query.setParameter("searchId", searchId.toString());
If you don't switch to using query parameters, searchId.toString() should be enclosed in single quotes.
So, the valid query should look something like this
select entity from Portfolio entity WHERE entity.portfolio = 'HEXAGON20'
You have several problems in your query syntax and if you look at the message it's quite explicit:
You build your query with
"select entity from " +className + " entity WHERE entity."+searchColumn+" = "+ searchId.toString();
Which gives
select entity from class com.hexgen.orm.Portfolio entity WHERE entity.PORTFOLIO = HEXAGON20
1- the class should not be there. Ir looks like it comes from the variable className.
2- HEXAGON20 is a string value that should be 'HEXAGON20'

Categories