How to search two tables sharing a foreign key (I think I'm asking this right....)? - sql

Dog entity
#Entity(tableName = "dog_table")
public class DogEntity {
private int mId;
private String mName, mBreed;
etc..
}
Toy entity
#Entity(tableName = "toy_table")
public class ToyEntity {
private int mId;
private String mName, mBrand;
etc..
}
DogAndToy join table entity
#Entity(tableName = "dog_and_toy_join_table",
primaryKeys = {"mDogID", "mToyId"},
foreignKeys = {
#ForeignKey(
entity = DogEntity.class,
parentColumns = "mId",
childColumns = "mDogID",
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
#ForeignKey(
entity = ToyEntity.class,
parentColumns = "mId",
childColumns = "mToyId",
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {#Index("mDogID"), #Index("mToyId")}
)
public class DogAndToyJoinEntity{
private final int mDogID, mToyId;
public DogAndToyJoinEntity(int mDogID, int mToyId) {
this.mDogID = mDogID;
this.mToyId = mToyId;
}
etc..
}
DogAndToy data class
public class DogAndToy {
#Embedded
public Dog mDog;
#Relation(
parentColumn = "mId",
entityColumn = "mId",
entity = ToyEntity.class,
associateBy =
#Junction(
value = DogAndToyJoinEntity.class,
parentColumn = "mDogId",
entityColumn = "mToyId"
)
)
public List<ToyEntity> toyList;
}
notes: All dogs can have multiple toys, and toys can be associated with multiple dogs. Dog & Toy entities don't share any fields (eg - Dog doesn't have toyId, etc)
I've been trying for a few days to wrap my head around
how to query/get all dogs associated with one Toy (by name)
I use the DogAndToy data class for display purposes in my RecyclerView.
JOIN and INNER JOIN queries are baffling to me and I've been trying multiple variations but keep ending up with zero search results. Here's my most recent try:
#Transaction
#Query("SELECT dog_table.* FROM dog_table" +
"INNER JOIN dog_and_toy_join_table ON dog_table.mId = dog_and_toy_join_table.mDogId" +
"INNER JOIN toy_table ON toy_table.mId = dog_and_toy_join_table.mToyId " +
"WHERE toy_table.mName LIKE :query")
LiveData<List<DogAndToy>> findDogsByToyName(String query);
Can anyone suggest a step-by-step description of these queries in Android Room? Any of the JOIN articles/examples I find here or anywhere on the internets don't have a "join" (foreign key) reference...
Am I even trying this in the right manner?
update: to clarify, I have FTS tables and my "basic" searches work fine (eg - search by name, etc)

Replace :toyName with the variable
SELECT d.mName FROM dog_table AS d
WHERE d.mId IN (
SELECT j.mDogID FROM dog_and_toy_join_table AS j
WHERE j.mToyId = (
SELECT t.mId FROM toy_table AS t
WHERE t.mName = :toyName));
EDIT
TBH, no idea why it only selects one row. Maybe someone else can answer it.
It the mean time take this:
select d.mName
from dog_table d
INNER join dog_and_toy_join_table dt
on d.mid = dt.mDogID
INNER JOIN toy_table t
ON dt.mToyId = t.mId
WHERE t.mName = 'toy1'

Related

How to do a write a JPQL query to find records not found in this join?

For the life of me, I can't figure out how to construct this JPA query.
I need to find TransactionLogs which have not been transmitted under a given SyncSendingConfig, ordered by ID.
Researching it on SO, I figure it should be possible in SQL to do an outer join where the IDs are null for the one side, as in this diagram:
Here's the Entities I have to work with.
#Entity
public class SyncSendingConfig {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "sendingConfig")
private Set<SyncJob> sendJobs = new HashSet<>();
}
#Entity
public class SyncJob {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "sending_config_id")
private SyncSendingConfig sendingConfig;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "SyncJob_TransactionLog",
joinColumns = { #JoinColumn(name = "sync_job_id") },
inverseJoinColumns = { #JoinColumn(name = "transaction_log_id") }
)
private Set<TransactionLog> transmitted = new HashSet<>();
}
#Entity
public class TransactionLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#ManyToMany(mappedBy = "transmitted")
private Set<SyncJob> syncJobs = new HashSet<>();
}
And the DAO I'm trying to write:
public interface SyncSendingConfigDao extends JpaRepository<SyncSendingConfig, Long> {
// TODO: This is the query I'm trying to get to work
/** Returns those transactions that were never sent for the given SyncSenderConfig, ordered by ID */
#Query("SELECT tl FROM SyncJob sj "+
"JOIN SyncSendingConfig ssc ON sj.sendingConfig = ssc.id AND ssc.id= :sendingConfigId "+
"RIGHT JOIN TransactionLog tl on tl.syncJobs = sj "+
"WHERE sj.id is null"
)
Stream<TransactionLog> findTransactionsNotSentForSyncSendingConfigId(#Param("sendingConfigId") long sendingConfigId);
// If this part is relevant, this join shows how I can get only those SyncJobs which are related to the SyncSendingConfig of interest
#Query("SELECT sj FROM SyncJob sj JOIN SyncSendingConfig ssc ON sj.sendingConfig = ssc.id WHERE ssc.id= :sendingConfigId ")
#QueryHints(value = #QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE, value = "500"))
Stream<SyncJob> findJobs(#Param("sendingConfigId") long sendingConfigId);
}
The query above on the DAO shows what I'm attempting to do. I'm really unsure of how to translate SQL to JPQL... especially on the join conditions and order.
Update:
Here's the exact SQL query which I'm trying to translate. It matches all the relationships defined by hibernate in the classes above.
select tl.*
from sync_job sj
join sync_sending_config ssc
on ssc.id = sj.sending_config_id and ssc.id=2
join sync_job_transaction_log sjtl
on sjtl.sync_job_id = sj.id
RIGHT JOIN transaction_log tl
on tl.id = sjtl.transaction_log_id
where sjtl.sync_job_id is null
When this query is run directly, it returns the exact results being sought.
If anyone can offer help, I'd greatly appreciate it. I've been running against a wall trying to figure the JPQL syntax out.
Thanks
Update 2
After working with '#S B', it appears that JPQL doesn't support a right join. Short of finding out how to write this in JPQL with a left join (if possible), I went with a native query:
#Query(value = "select tl.* from sync_job sj "+
"join sync_sending_config ssc on ssc.id = sj.sending_config_id and ssc.id = :sendingConfigId "+
"join sync_job_transaction_log sjtl on sjtl.sync_job_id = sj.id "+
"RIGHT JOIN transaction_log tl on tl.id = sjtl.transaction_log_id "+
"where sjtl.sync_job_id is null",
nativeQuery = true)
#QueryHints(value = #QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE, value = "500"))
Stream<TransactionLog> findTransactionsNotSentForSyncSendingConfigId(#Param("sendingConfigId") long sendingConfigId);
Assuming the below dummy data setup:
Transaction Log IDs: 1, 2, 3, 4
SyncSendingConfig IDs: 1, 2
Sync Jobs:
ID 1, SyncSendingConfigID 1
ID 2, SyncSendingConfigID 1
ID 3, SyncSendingConfigID 2
ID 4, SyncSendingConfigID 2
sync_job_transaction_log
SyncJobId 1, TransactionLogId 1
SyncJobId 1, TransactionLogId 2
SyncJobId 2, TransactionLogId 1
SyncJobId 2, TransactionLogId 2
TransactionLogs 1 and 2 are transmitted under SyncSendingConfig ID 1 as per the mapping in sync_job_transaction_log table.
Therefore, TransactionLogs not transmitted under SyncSendingConfig ID 1 would be 3 and 4.
So, in order to find TransactionLogs which have not been transmitted under a given SyncSendingConfig, corresponding JPQL is -
#Query("select t from TransactionLog t where t not in (" +
"select t1 from TransactionLog t1 join t1.syncJobs tsj where tsj in "
+ "(select sj from SyncJob sj where sj.sendingConfig.id = :sendingConfigId)"
+ ")")
Consider JPQL as SQL applied to Java objects with entities representing tables, their properties representing columns and the has-a relationship as expressing the mapping relationship.
Now, when you want to join two tables, just refer to the corresponding entities and so long as the join columns are correctly specified, the SQL query will be correctly formed on the join tables and columns.
Example SQL -
select column(s) from table1 <type of> join table2 on table1.column1 = table2.column1 where <filter conditions here>
Corresponding JPQL setup -
Entity1 (corresponds to table1) ->
property1 (corresponds to column)
property2 (corresponds to mapping relationship, has #JoinColumn details)
JPQL for above setup -
select property1 from entity1 <type of> join entity1.property2 where <filter condition here>
Update after discussion in comments -
Since a right join in the current setup is not possible, suggest to evaluate JPQL on performance parameters or alternatively to use the working SQL as nativeQuery.
#Query(value = "select tl.* from sync_job sj "+
"join sync_sending_config ssc on ssc.id = sj.sending_config_id "+
"join sync_job_transaction_log sjtl on sjtl.sync_job_id = sj.id "+
"RIGHT JOIN transaction_log tl on tl.id = sjtl.transaction_log_id "+
"where sjtl.sync_job_id is null and ssc.id = :sendingConfigId",
nativeQuery = true)

Hibernate SQL query matching more than one members of an ElementCollection

I have Pojo mapped with JPA annotations like this
#Entity
#Table(name = "record")
public class SearchRecord {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int id;
private String vidName;
#ElementCollection
private List<String> videoLabels = new ArrayList<String>();
and would like to run a Hibernate query that filters out all SearchRecords that match/contain 1..n videoLabels. (only objects that match all of the videoLabels)
I was able to search for SearchRecords that match a single label by running the following query:
String labelCar = "Car";
String labelPerson = "Person";
TypedQuery<SearchRecord> query = em.createQuery("SELECT b FROM SearchRecord b JOIN b.videoLabels l WHERE l = :param1",SearchRecord.class);
query.setParameter("param1", labelCar);
List<SearchRecord> results = query.getResultList();
But how can I execute a query filtering out all SearchResults matching Car and Person?
Thanks for your support
I was able to solve the problem with the following query
SELECT DISTINCT a FROM SearchRecord a JOIN a.labels b JOIN a.labels c WHERE b.name = 'Car' and c.name = 'Person'

How to map ONE-TO-MANY native query result into a POJO class using #SqlResultSetMapping

Im working in a backend API using Java and MySql, and I'm trying to use #SqlResultSetMapping in JPA 2.1 for mapping a ONE-TO-MANY native query result into a POJO class, this is the native query:
#NamedNativeQuery(name = "User.getAll”, query = "SELECT DISTINCT t1.ID, t1.RELIGION_ID t1.gender,t1.NAME,t1.CITY_ID , t2.question_id, t2.answer_id FROM user_table t1 inner join user_answer_table t2 on t1.ID = t2.User_ID“,resultSetMapping="userMapping")
And, here is my result SQL mapping:
#SqlResultSetMapping(
name = "userMapping",
classes = {
#ConstructorResult(
targetClass = MiniUser.class,
columns = {
#ColumnResult(name = "id"),
#ColumnResult(name = "religion_id"),
#ColumnResult(name = "gender"),
#ColumnResult(name = "answers"),
#ColumnResult(name = "name"),
#ColumnResult(name = "city_id")
}
),
#ConstructorResult(
targetClass = MiniUserAnswer.class,
columns = {
#ColumnResult(name = "question_id"),
#ColumnResult(name = "answer_id")
}
)
})
And, here is the implementation of the POJO classes: (I just removed the constructor and the getters/setter)
MiniUser class
public class MiniUser {
String id;
String religionId;
Gender gender;
List<MiniUserAnswer> answers;
String name;
String city_id;
}
and the MiniUserAnswer class
public class MiniUserAnswer {
String questionId;
String answerId;
}
My goal is to execute this Query and return a list of MiniUser, and in each MiniUser: a list of his “answers", which is a list of MiniUserAnswer.
after running this code, I got this error:
The column result [answers] was not found in the results of the query.
I know why, it's because there is no “answers" field in the query select statement.
So, how can I accomplish something like this, considering the performance? This answers list may reach 100.
I really appreciate your help, Thanks in advance!
The query "SELECT DISTINCT t1.ID, t1.RELIGION_ID t1.gender, t1.NAME, t1.CITY_ID, t2.question_id, t2.answer_id" does not return a parameter called answers.
To obtain the result you are looking for I would use:
Option 1 (Criteria Builder)
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<UserTableEntity> cq = cb.createQuery(UserTableEntity.class);
Root<UserTableEntity> rootUserTable = cq.from(UserTableEntity.class);
Join<UserTableEntity,UserAnswerTableEntity> joinAnswerTable = rootUserTable.join(rootUserTable_.id) // if the relationship is defined as lazy, use "fetch" instead of "join"
//cq.where() NO WHERE CLAUSE
cq.select(rootUserTable)
entityManager.createQuery(cq).getResultList();
Option 2 (Named query, not native)
#NamedQuery(name = "User.getAll”, query = "SELECT t1 FROM UserTableEntityt1 join fetch t1.answers)
Option 3 (Entity subgraph, new in JPA 2.1)
In User Entity class:
#NamedEntityGraphs({
#NamedEntityGraph(name = "graph.User.Answers", attributeNodes = #NamedAttributeNode("answers"))
})
In DAO set hints in the entity manager:
EntityGraph graph = this.em.getEntityGraph("graph.User.Answers");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

join 2 columns and get only matched vlaues in hibernate

I have 2 tables:
course: id, name
student: id, course_id, age
Tables are tied with oneToMany annotation
I am trying to write a hibernate query against course that will return courses and students that are x years 0ld. That's my attempt:
SELECT c from Course c RIGHT JOIN c.student p ON p.age = 20
It returns a course that has at least one student that is 20. when there are more students that are not 20, they are included in the result set as well. Is there a way to restrict the joined table values?
Edit:
These are my entities:
public class Course implements Serializable {
private static final long serialVersionUID = 646349111982187812L;
#Id
#Column(name = "id", unique=true, nullable=false)
private String id;
#Column(name = "name", unique=false, nullable=false)
private String name;
#OneToMany(cascade={CascadeType.ALL, CascadeType.REMOVE},
fetch=FetchType.LAZY, mappedBy = "Course")
#OrderBy("age")
#JsonManagedReference
private Set<Student> students;
getters and setters ...
}
public class Student implements Serializable {
private static final long serialVersionUID = 646349221337813L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "age", unique=false, nullable=true)
private String age;
#ManyToOne()
#JsonBackReference
private Course course;
}
I am trying to use this method from my crudrepository to get the expected result:
#Query(?????)
public List<Course> findCoursesWithAdults(#Param("age")int age)
it sounds like you want an inner join instead of a right join
SQL
SELECT distinct c.* from Course c inner join Student p on (c.id=p.course_id and p.age=20)
or with hql
select distinct c from Course c inner join c.student p with p.age=20
in order to get the information you are requesting, you will either have to limit the Set in Course using #Where ("age = 20" ) or simply deal with a list of students instead of courses.
select p from Course c inner join c.student p with p.age=20
you can reference the attached course object through any one of the getCourse methods on the student objects. You could also use "group by" here to help you order things.
or you can use a filter within the hibernate Entity...
#Transient
public List<Students> getStudents(Integer age){
List<Students> tmp = new ArrayList<>();
for(Student s: getStudents())
if(s.getAge().equals(age))tmp.add(s);
return tmp;
}
one more edit and im done... there is another way to do this that i didnt mention..
a select wrapper query.
create a wrapper object for your course and student
public class CourseWrapper(){
private Course c;
private List<Students> p = new ArrayList<>();
....constructor ... getters ...setters
}
select new CourseWrapper(c, students)
from Course c
left outer join c.students p with p.age=20'
more info here
SELECT c.name, s.id
FROM course c
INNER JOIN student s ON c.id = s.course_id
WHERE s.age = 20

OneToMany + JoinTable + OrderColumn: Order is written correctly but not read

I have an entity that can consist of itself:
class Group {
// ...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(
name = "group_group",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "parentgroup_id")
)
#OrderColumn
private List<Group> groups = new ArrayList<Group>();
#ManyToOne
private Group parentGroup;
public List<Group> getGroups() {
return groups;
}
public Group getGroup() {
return parentGroup;
}
}
I can create a group and add two child groups:
Group parent = new Group();
Group child1 = new Group();
Group child2 = new Group();
parent.getGroups().add(child1);
parent.getGroups().add(child2);
parent = groupRepository.save(parent);
// parent.getGroups().get(0).getId() == child1.getId()
// parent.getGroups().get(1).getId() == child2.getId()
But this seems to be a coincidence. I am able to update the order (e.g. using Collections.sort) and the join table rows are updated correctly.
It does not matter how I load the parent group, the child groups are always in the order of creation. The executed SQL query is:
SELECT t1.ID, t1.PARENTGROUP_ID FROM GROUP t0, GROUP t1 WHERE ((t1.PARENTGROUP_ID = ?) AND (t0.ID = t1.PARENTGROUP_ID))
There is no ORDER BY which seems wrong. How can I persuade EclipseLink to add it?
The problem was that I tried to load the child group entities using a CrudRepository. The repository ignores the #OrderColumn annotation. I fixed it by fetching the parent group and using getGroups().
Try using the #OrderBy which does not rely on the database. You will need to provide a field from Group to order by (note field not column).
#OrderBy("fieldNameGoesHere DESC")
private List<Group> groups = new ArrayList<Group>();
OrderColumn will maintain the order of the list within the database, which carries additional performance and maintenance overhead. OrderBy puts the onus on the persistence provider to maintain the order of the entities, however the order will not persist across persistence contexts.
For more information, checkout this blog post I created regarding #OrderBy.