JPA update with a join - mysql

I use spring-jpa with hibernate implementation.
I use mariadb
I try to do an update with a join.
My object structure
#Entity
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long roomId;
#ManyToOne
#JoinColumn(name = "appartment_id")
private Appartment appartment;
}
#Entity
public class Appartment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long appartmentId;
#OneToMany
#JoinColumn(name="appartment_id")
private Set<Room> roomList;
}
My sql query
update Room r1 set r1.available = :availability
where r1.roomId in (
SELECT r2.roomId
from Room r2
JOIN r2.appartment a1
WHERE a1.appartmentId = :appartmentId
tried also
update Room r1 set r1.available = :availability
where exists
( SELECT r2
from Room r2
JOIN r2.appartment a1
where a1.appartmentId= :appartmentId
)
I get this error
java.sql.SQLException: Table 'room' is specified twice, both as a target for 'UPDATE' and as a separate source for data
Seem like update and join with mysql seem impossible?

Everytime you get the unexpected token exception, look for syntax errors.
In your case the update query should look like this:
UPDATE Room r1
SET r1.available = :availability
WHERE r1.roomId in
( SELECT r2.roomId FROM Room r2 JOIN r2.appartment a1 WHERE a1.appartmentId = :appartmentId )
EDIT (follow-up issue):
Here you may find some help for your follow-up question
The accepted answer features a MySQL example on how to perform an update with a JOIN. I'm quite sure that's what you're after. Now it would look something like this:
UPDATE Room r1 JOIN r1.appartment a1
SET r1.available = :availability
WHERE a1.appartmentId = :appartmentId
try it with your dialect but if it doesn't work, the following one should do the trick:
UPDATE Room r1 SET r1.available = :availability WHERE r1.appartment.appartmentId = :appartmentId

IN ( SELECT ... ) has poor performance characteristics. Since you are looking at an UPDATE, I recommend a multi-table UPDATE. Or EXISTS is good -- but not that EXISTS. You have not tied the two instances of Room together; the UPDATE will change all or none of the rows!
Since you have not explained what the query is supposed to do, I cannot advise you on how to do it.

Related

JPQL join translation policy

I am having trouble searching in my application database due to the translated query resulting from my JPQL query.
Consider the following Structure:
#Entity
#Table(name="pc")
public class PC {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToOne
#JoinColumn(name="DEFAULTOS")
private Software defaultOS;
...}
#Entity
#Table(name="software")
public class Software {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
...}
Here is the search query I'm doing:
public List<PC> getPcsInstalled(Software soft){
TypedQuery<PC> lQuery = em.createQuery("SELECT lPC FROM PC lPC"
+ " WHERE lPC.defaultOS IS NULL"
+ " OR lPC.defaultOS.name NOT LIKE :param", PC.class);
lQuery.setParameter("param", soft.getName());
return lQuery.getResultList();
}
Which translates to:
SELECT t1.ID, t1.NAME, t1.DEFAULTOS FROM software t0, pc t1
WHERE (((t1.DEFAULTOS IS NULL) OR NOT (t0.NAME LIKE "Linux"))
AND (t0.ID = t1.DEFAULTOS))
The tables are "joined" with the latest condition (t0.ID = t1.DEFAULTOS). Which automatically filters any PC having DEFAULTOS = NULL (which is not what I want obviously).
The query I expected was:
SELECT t1.ID, t1.NAME, t1.DEFAULTOS FROM pc t1
LEFT JOIN software t0 ON t1.DEFAULTOS = t0.ID
WHERE (((t1.DEFAULTOS IS NULL) OR NOT (t0.NAME LIKE "Linux")))
Of course I can rewrite the JPQL query to specify the LEFT JOIN explicitely, but I was wondering : Is there a way to have the LEFT JOIN as the default policy to join tables?
In fact this is just a simple example, in my project I build much more complex queries and adding the needed LEFT JOIN would be very complex in the actual code.
For information, I'm using Glassfish 4.0 with EclipseLink and a MySQL database.

JPA's Map<KEY, VALUE> query by JPQL failed

I am storing a Map in JPA , which stores a keyword translation in each language . such as one object stores Locale.ENGLISH -> "Father" , Locale.CHINESE -> "PaPa". And another object stores Locale.ENGLISH -> "Mother" , Locale.CHINESE -> "MaMa";
Here is my working design :
public class Relation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ElementCollection
#MapKeyColumn(name="locale")
#Column(name="value")
#CollectionTable(name = "RelationName", joinColumns = #JoinColumn(name = "relation_id"))
private Map<Locale, String> langMap = new HashMap<>();
// other fields skipped
}
It works well , I can store many keyword translations to DB. But when query with JPQL , it has some problems :
For example , I want to find which Relation has English key with value "Father" :
This is my code :
Relation r = em.createQuery("select r from Relation r join r.langMap m where ( KEY(m) = :locale and VALUE(m) = :value ) " , Relation.class)
.setParameter("locale" , locale)
.setParameter("value" , value)
.getSingleResult();
It generates this strange / not-working SQL :
Hibernate:
select
relation0_.id as id1_18_
from
Relation relation0_
inner join
RelationName langmap1_
on relation0_.id=langmap1_.relation_id
where
langmap1_.locale=?
and (
select
langmap1_.value
from
RelationName langmap1_
where
relation0_.id=langmap1_.relation_id
)=?
00:16:12.038 WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 1242, SQLState: 21000
00:16:12.038 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Subquery returns more than 1 row
I don't know why it generates that strange subquery.
I can solve this problem by Criteria :
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Relation> criteria = builder.createQuery(Relation.class);
Root<Relation> root = criteria.from(Relation.class);
criteria.select(root);
MapJoin<Relation , Locale , String> mapJoin = root.joinMap("langMap");
criteria.where(builder.and(
builder.equal(mapJoin.key(), locale) ,
builder.equal(mapJoin.value() , value))
);
return em.createQuery(criteria).getSingleResult();
It generates correct SQL ( where langmap1_.locale=? and langmap1_.value=? ) and works well.
But I feel Criteria is too complicated. And I wonder why the JPQL failed? How to correct the JPQL ?
Thanks.
Env :
JPA2 , Hibernate 4.2.3 , MySQL dialect
I had the same problem. It looks like accessing map by ref (without VALUE()) already gives you a map entry value, i.e. the next JPQL should be transformed to a valid SQL:
select r from Relation r join r.langMap m where ( KEY(m) = :locale and m = :value )
I had a similar problem using the JPQL VALUE() operator with Hibernate. It seems that Hibernate implements the VALUE() operator like the java.util.Map.values() method in Java. It generates a subquery that returns all values in the map, i.e. all rows of the mapping table that are related to the entity holding the Map attribute. As soon as you have more then one key/value pair in the map, a comparison expression, which expects scalar expressions as operands, will fail.
What you can do is to turn the comparison expression around and convert it to an IN expression.
Instead of:
select r from Relation r join r.langMap m
where ( KEY(m) = :locale and VALUE(m) = :value )
You can write:
select r from Relation r join r.langMap m
where ( KEY(m) = :locale and :value in (VALUE(m)) )
I hope this way your query will work.
The correct JPQL could be like this:
SELECT r FROM Relation r JOIN r.langMap map
WHERE map[:locale] = :value

Comparing two collections of enums with Hibernate or SQL

I have an entity 'Parent' which has a Set of enums
private Set<MyEnum> myEnums = EnumSet.noneOf(MyEnum.class);
#CollectionOfElements(targetElement=MyEnum.class)
#JoinTable
(name="PARENT_MY_ENUM",
joinColumns=#JoinColumn(name="PARENT_ID"))
#Enumerated(EnumType.STRING)
#Column (name="MY_ENUM", nullable=false)
public Set<MyEnum> getMyEnums(){
return myEnums;
}
public MyEnum {
ENUM_A,
ENUM_B,
ENUM_C,
ENUM_D;
}
Now I want to search for this entity with a collection of MyEnums. Only entities where all enums are set as in the search collection should be returned.
So if entity A has ENUM_A, ENUM_B and ENUM_C and entity B has ENUM_B, ENUM_C, ENUM_D a search with the search collectoin ENUM_A, ENUM_B, ENUM_C should only return entity A. A search for ENUM_B and ENUM_C should return nothing.
How would I do that in Hibernate?
if I do
select p from Parent p where p.myEnums IN (:searchCollection) and size(p.myEnums) = size(:searchCollection)
then this would return both entities for the first search.
Any ideas?
Update: I got a step further by figuring out how to do it in MySQL but applying this to Hibernate generates invalid SQL.
You would use a subquery with EXISTS for it like:
WHERE EXISTS(
SELECT pa.PARENT_ID, count(pme.MY_ENUM) FROM PARENT pa, PARENT_MY_ENUM pme
where pa.PARENT_ID = pme.PARENT_ID
AND pme.MY_ENUM IN ('ENUM_A','ENUM_B')
GROUP BY pa.PARENT_ID HAVING count(pme.MY_ENUM) = 2
)
But when I try to do the same in Hibernate:
select pa.ParentId, count(pa.myEnums) from Parent pa
WHERE pa.myEnums IN ('ENUM_A','ENUM_B')
GROUP BY pa.ParentId HAVING count(pa.myEnums) = 2
Hiberante creates this SQL statement:
select pa.CONTAINER_RELEASE_REFERENCE_ID as col_0_0_, count(.) as col_1_0_ from PARENT pa, PARENT_MY_ENUM enum1, PARENT_MY_ENUM enum2, PARENT_MY_ENUM enum3
where pa.PARENT_ID=enum1.PARENT_ID and pa.PARENT_ID=enum2.PARENT_ID and pa.PARENT_ID=enum3.PARENT_ID
and (. in ('ENUM_A' , 'ENUM_B'))
group by pa.PARENT_ID having count(.)=2
MySQL complains about the '.', Where is that coming from and why is Hibernate using 3 joins to PARENT_MY_ENUM?
Is this a Hibernate bug or what am I doing wrong?
Give the following a try for you exists subselect
select pa.ParentId, count(en) from Parent pa join pa.myEnums as en
WHERE en IN ('ENUM_A','ENUM_B')
GROUP BY pa.ParentId HAVING count(en) = 2
Otherwise, I wonder if something like that might not do the job
select p from Parent p join p.myEnums em
where (:results) = elements(em)
or
select p from Parent p join p.myEnums em
where (:results) in elements(em)
I think you can do that in java. Execute your initially proposed query, iterate the result and exclude (iterator.remove()) the false positives. It should be O(n), and I believe MySQL will require the same time to filter your result.

Update a single table based on multiple tables - appears to works MySql not ACCESS 2003

Mysql Version Works
UPDATE results SET rCARRIER = (
SELECT cellCarrierName
FROM tblImportedTempTable, user, cellCarrier
WHERE
userEmployeeNumber = tblImportedTempTable.EMPLOYEENUMBER
AND userId = results.rUserId
AND results.rPHONENUMBER = tblImportedTempTable.PHONENUMBER
AND CARRIER = cellCarrierId )
I have written this sql that works fine in MySql(above) and fails in access 2003(below) any suggestions? Is one or both of the 2 nonstandard sql? Does Access hav an admin problem?
Sorry the field and table names are diferent this is the ACCESS version.
Access version
UPDATE tblWorkerPhoneNumber SET tblWorkerPhoneNumber.PhoneCarrier = (
SELECT PhoneCarrierType.CarrierName
FROM tblImportedPhoneCarrier, tblWorkerMaster, PhoneCarrierType
WHERE
tblWorkerMaster.EmployeeNumber = tblImportedPhoneCarrier.Emp
AND tblWorkerMaster.WorkerID = tblWorkerPhoneNumber.WorkerID
AND tblWorkerPhoneNumber.PhoneNumber = tblImportedPhoneCarrier.Cell
AND tblImportedPhoneCarrier.CarrierCode = PhoneCarrierType.CarrierID )
Error Message
Operation must use and updateable query
Thanks
In MS Access, something like this:
UPDATE tblWorkerPhoneNumber
INNER JOIN tblWorkerMaster ON tblWorkerMaster.WorkerID = tblWorkerPhoneNumber.WorkerID
INNER JOIN tblImportedPhoneCarrier ON tblWorkerPhoneNumber.PhoneNumber = tblImportedPhoneCarrier.Cell
INNER JOIN PhoneCarrierType ON tblImportedPhoneCarrier.CarrierCode = PhoneCarrierType.CarrierID
SET tblWorkerPhoneNumber.PhoneCarrier = PhoneCarrierType.CarrierName
WHERE tblWorkerMaster.EmployeeNumber = tblImportedPhoneCarrier.Emp
(Might need to change the join conditions; I'm not familiar with your schema)

Django ORM Creates Phantom Alias in SQL Join

I am executing the following code (names changed to protect the innocent, so the model structure might seem weird):
memberships =
models.Membership.objects.filter(
degree__gt=0.0,
group=request.user.get_profile().group
)
exclude_count =
memberships.filter(
member__officerships__profile=request.user.get_profile()
).count()
if exclude_officers_with_profile:
memberships = memberships.exclude(
member__officerships__profile=request.user.get_profile()
)
total_count = memberships.count()
which at this point results in:
OperationalError at /
(1054, "Unknown column 'U1.id' in 'on clause'")
The SQL generated is:
SELECT
COUNT(*)
FROM
`membership`
WHERE (
`membership`.`group_id` = 2 AND
`membership`.`level` > 0.0 AND
NOT (
`membership`.`member_id`
IN (
SELECT
U2.`member_id`
FROM
`membership` U0 INNER JOIN `officers` U2
ON (U1.`id` = U2.`member_id`)
WHERE U2.`profile_id` = 2
)
)
)
It appears that the SQL Join's ON statement is referencing an alias that hasn't been defined. Any ideas why!? I dropped my MySQL database and re-synced the tables from my models to try and ensure that there weren't any inconsistencies there.
The structure of the models I'm using are:
class Membership(models.Model):
member = models.ForeignKey(Member, related_name='memberships')
group = models.ForeignKey(Group, related_name='memberships')
level = models.FloatField(default=0.0)
class Member(models.Model):
name = models.CharField(max_length=32)
class Officer(models.Model):
member = models.ForeignKey(Member, related_name='officerships')
profile = models.ForeignKey(UserProfile)
class UserProfile(models.Model)
group = models.ForeignKey(Group)
class Group(models.Model)
pass
I think this may be a symptom of:
http://code.djangoproject.com/ticket/9188
which was fixed as of django revision 9589, I think. Now how to figure out which revision I'm working from...
Confirmed. When I implemented the change referenced in the ticket above:
http://code.djangoproject.com/changeset/9589
my error went away.