I have written a complex JPA 2 Criteria API query (my provider is EclipseLink), where I find myself re-using the same subquery over and over again. Unless the DB (Oracle) does something clever, I think that the subquery will be executed each time it is found in the query. I am looking for a way to execute the subquery only once.
We have field-level access, which means that a user has visibility to a DB Column if certain conditions are met. In the example below, the user has the following access:
COLUMN_1 is visible if the result belongs to category 1
COLUMN_2 is visible if the result belongs to category 2
COLUMN_3 is visible if the result belongs to category 1 or category 2
This is a pseudo-query:
SELECT T.PK
FROM MY_TABLE T
WHERE
(
T.COLUMN_1 = 'A'
AND
T.PK IN (SELECT PKs of category 1)
)
AND
(
T.COLUMN_2 = 'B'
AND
T.PK IN (SELECT PKs of category 2)
)
AND
(
T.COLUMN_3 = 'C'
AND
(
T.PK IN (SELECT PKs of category 1)
OR
T.PK IN (SELECT PKs of category 2)
)
)
If I would write it by hand in SQL, I would write it by OUTER JOINing the two queries, like this:
SELECT T.PK
FROM MY_TABLE T
LEFT OUTER JOIN (SELECT PKs of category 1) IS_CAT_1 ON T.PK = IS_CAT_1.PK
LEFT OUTER JOIN (SELECT PKs of category 2) IS_CAT_2 ON T.PK = IS_CAT_2.PK
WHERE
(
T.COLUMN_1 = 'A'
AND
IS_CAT_1.RESULT = true
)
AND
(
T.COLUMN_2 = 'B'
AND
IS_CAT_2.RESULT = true
)
AND
(
T.COLUMN_3 = 'C'
AND
(
IS_CAT_1.RESULT = true
OR
IS_CAT_2.RESULT = true
)
)
Can I join a query as a table with the Criteria API? Creating a View would by my very last choice (the DB is not maintained by me).
Note: I have seen that EclipseLink provides such vendor-specific support in JPQL (link), but I haven't seen this available for Criteria.
In the end, we created a DB View, mapped it as a new JPA entity and joined it to the other entities.
Related
This is my query:
SELECT "vehicle"."id",
"vehicle"."description",
"tag"."id" AS "tag_id",
"tag"."name" AS "tag_name"
FROM "vehicle"
INNER JOIN "vehicle_tag_pivot" ON "vehicle"."id" = "vehicle_tag_pivot"."vehicle_id"
INNER JOIN "tag" ON "vehicle_tag_pivot"."tag_id" = "tag"."id"
WHERE "tag"."name" IN ('car', 'busses')
AND "vehicle"."category_id" = '1E4FD2C5-C32E-4E3F-91B3-45478BCF0185'
I only have one vehicle in my database. It has two tags -> car and busses (this is test data).
So when I run the query, it returns The exact same vehicle showing the 2 tags it has.
How do I get it to return the vehicle once? I do not really want to return the tag_name. I only want to filter and return all the vehicles that has the both tags car and busses. If one vehicle has both those tags, then it should return that vehicle only. But instead it is returning the same vehicle twice showing its tags.
This should work for you.
SELECT i.*
FROM "interest" as "i"
where i.id in (
select it.interest_id
from interest_tag_pivot it
join tag t on it.tag_id = t.id
where t.name in ('car', 'busses')
group by it.interest_id
having count (*) = 2
)
and i.category_id = '1E4FD2C5-C32E-4E3F-91B3-45478BCF0185'
Do not JOIN - joins leads to duplications. Put all tags logic to WHERE EXISTS(...) or similar.
Here two scalar subqueries comparison in WHERE, try this (important! - it is assumed that tags for each vehicle can't duplicate, so we can compare its counts):
WITH required_tags(val) AS (
VALUES ('car'),
('busses')
)
SELECT "vehicle"."id",
"vehicle"."description",
"tag"."id" AS "tag_id",
"tag"."name" AS "tag_name"
FROM "vehicle"
WHERE "vehicle"."category_id" = '1E4FD2C5-C32E-4E3F-91B3-45478BCF0185'
AND (
-- count matching tags...
SELECT count(1)
FROM "vehicle_tag_pivot"
INNER JOIN "tag" ON "vehicle_tag_pivot"."tag_id" = "tag"."id"
WHERE "vehicle"."id" = "vehicle_tag_pivot"."vehicle_id"
AND "tag"."name" IN (SELECT val FROM required_tags)
) = (
-- ...equals to count required tags
SELECT count(1)
FROM required_tags
)
I have a many to many relation with three columns, (owner_id,property_id,ownership_perc) and for this table applies (many owners have many properties).
So I would like to find all the owner_id who has many properties (property_id) and connect them with other three tables (Table 1,3,4) in order to get further information for the requested result.
All the tables that I'm using are
Table 1: owner (id_owner,name)
Table 2: owner_property (owner_id,property_id,ownership_perc)
Table 3: property(id_property,building_id)
Table 4: building(id_building,address,region)
So, when I'm trying it like this, the query runs but it returns empty.
SELECT address,region,name
FROM owner_property
JOIN property ON owner_property.property_id = property.id_property
JOIN owner ON owner.id_owner = owner_property.owner_id
JOIN building ON property.building_id=building.id_building
GROUP BY owner_id,address,region,name
HAVING count(owner_id) > 1
ORDER BY owner_id;
Only when I'm trying the code below, it returns the owner_id who has many properties (see image below) but without joining it with the other three tables:
SELECT a.*
FROM owner_property a
JOIN (SELECT owner_id, COUNT(owner_id)
FROM owner_property
GROUP BY owner_id
HAVING COUNT(owner_id)>1) b
ON a.owner_id = b.owner_id
ORDER BY a.owner_id,property_id ASC;
So, is there any suggestion on what I'm doing wrong when I'm joining the tables? Thank you!
This query:
SELECT owner_id
FROM owner_property
GROUP BY owner_id
HAVING COUNT(property_id) > 1
returns all the owner_ids with more than 1 property_ids.
If there is a case of duplicates in the combination of owner_id and property_id then instead of COUNT(property_id) use COUNT(DISTINCT property_id) in the HAVING clause.
So join it to the other tables:
SELECT b.address, b.region, o.name
FROM (
SELECT owner_id
FROM owner_property
GROUP BY owner_id
HAVING COUNT(property_id) > 1
) t
INNER JOIN owner_property op ON op.owner_id = t.owner_id
INNER JOIN property p ON op.property_id = p.id_property
INNER JOIN owner o ON o.id_owner = op.owner_id
INNER JOIN building b ON p.building_id = b.id_building
ORDER BY op.owner_id, op.property_id ASC;
Always qualify the column names with the table name/alias.
You can try to use a correlated subquery that counts the ownerships with EXISTS in the WHERE clause.
SELECT b1.address,
b1.region,
o1.name
FROM owner_property op1
INNER JOIN owner o1
ON o1.id_owner = op1.owner_id
INNER JOIN property p1
ON p1.id_property = op1.property_id
INNER JOIN building b1
ON b1.id_building = p1.building_id
WHERE EXISTS (SELECT ''
FROM owner_property op2
WHERE op2.owner_id = op1.owner_id
HAVING count(*) > 1);
Need some help working out the SQL. Unfortunately the version of tsql is SybaseASE which I'm not too familiar with, in MS SQL I would use a windowed function like RANK() or ROW_NUMBER() in a subquery and join to those results ...
Here's what I'm trying to resolve
TABLE A
Id
1
2
3
TABLE B
Id,Type
1,A
1,B
1,C
2,A
2,B
3,A
3,C
4,B
4,C
I would like to return 1 row for each ID and if the ID has a type 'A' record that should display, if it has a different type then it doesn't matter but it cannot be null (can do some arbitrary ordering, like alpha to prioritize "other" return value types)
Results:
1, A
2, A
3, A
4, B
A regular left join (ON A.id = B.id and B.type = 'A') ALMOST returns what I am looking for however it returns null for the type when I want the 'next available' type.
You can use a INNER JOIN on a SubQuery (FirstTypeResult) that will return the minimum type per Id.
Eg:
SELECT TABLEA.[Id], FirstTypeResult.[Type]
FROM TABLEA
JOIN (
SELECT [Id], Min([Type]) As [Type]
FROM TABLEB
GROUP BY [Id]
) FirstTypeResult ON FirstTypeResult.[Id] = TABLEA.[Id]
I have a tree structure table with columns:
id,parent,name.
Given a tree A->B->C,
how could i get the most top parent A's ID according to C's ID?
Especially how to write SQL with "with recursive"?
Thanks!
WITH RECURSIVE q AS
(
SELECT m
FROM mytable m
WHERE id = 'C'
UNION ALL
SELECT m
FROM q
JOIN mytable m
ON m.id = q.parent
)
SELECT (m).*
FROM q
WHERE (m).parent IS NULL
To implement recursive queries, you need a Common Table Expression (CTE).
This query computes ancestors of all parent nodes. Since we want just the top level, we select where level=0.
WITH RECURSIVE Ancestors AS
(
SELECT id, parent, 0 AS level FROM YourTable WHERE parent IS NULL
UNION ALL
SELECT child.id, child.parent, level+1 FROM YourTable child INNER JOIN
Ancestors p ON p.id=child.parent
)
SELECT * FROM Ancestors WHERE a.level=0 AND a.id=C
If you want to fetch all your data, then use an inner join on the id, e.g.
SELECT YourTable.* FROM Ancestors a WHERE a.level=0 AND a.id=C
INNER JOIN YourTable ON YourTable.id = a.id
Assuming a table named "organization" with properties id, name, and parent_organization_id, here is what worked for me to get a list that included top level and parent level org ID's for each level.
WITH RECURSIVE orgs AS (
SELECT
o.id as top_org_id
,null::bigint as parent_org_id
,o.id as org_id
,o.name
,0 AS relative_depth
FROM organization o
UNION
SELECT
allorgs.top_org_id
,childorg.parent_organization_id
,childorg.id
,childorg.name
,allorgs.relative_depth + 1
FROM organization childorg
INNER JOIN orgs allorgs ON allorgs.org_id = childorg.parent_organization_id
) SELECT
*
FROM
orgs order by 1,5;
tblUserProfile - I have a table which holds all the Profile Info (too many fields)
tblMonthlyProfiles - Another table which has just the ProfileID in it (the idea is that this table holds 2 profileids which sometimes become monthly profiles (on selection))
Now when I need to show monthly profiles, I simply do a select from this tblMonthlyProfiles and Join with tblUserProfile to get all valid info.
If there are no rows in tblMonthlyProfile, then monthly profile section is not displayed.
Now the requirement is to ALWAYS show Monthly Profiles. If there are no rows in monthlyProfiles, it should pick up 2 random profiles from tblUserProfile. If there is only one row in monthlyProfiles, it should pick up only one random row from tblUserProfile.
What is the best way to do all this in one single query ?
I thought something like this
select top 2 * from tblUserProfile P
LEFT OUTER JOIN tblMonthlyProfiles M
on M.profileid = P.profileid
ORder by NEWID()
But this always gives me 2 random rows from tblProfile. How can I solve this ?
Try something like this:
SELECT TOP 2 Field1, Field2, Field3, FinalOrder FROM
(
select top 2 Field1, Field2, Field3, FinalOrder, '1' As FinalOrder from tblUserProfile P JOIN tblMonthlyProfiles M on M.profileid = P.profileid
UNION
select top 2 Field1, Field2, Field3, FinalOrder, '2' AS FinalOrder from tblUserProfile P LEFT OUTER JOIN tblMonthlyProfiles M on M.profileid = P.profileid ORDER BY NEWID()
)
ORDER BY FinalOrder
The idea being to pick two monthly profiles (if that many exist) and then 2 random profiles (as you correctly did) and then UNION them. You'll have between 2 and 4 records at that point. Grab the top two. FinalOrder column is an easy way to make sure that you try and get the monthly's first.
If you have control of the table structure, you might save yourself some trouble by simply adding a boolean field IsMonthlyProfile to the UserProfile table. Then it's a single table query, order by IsBoolean, NewID()
In SQL 2000+ compliant syntax you could do something like:
Select ...
From (
Select TOP 2 ...
From tblUserProfile As UP
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Order By NewId()
) As RandomProfile
Union All
Select MP....
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From (
Select TOP 1 ...
From tblUserProfile As UP
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
Order By NewId()
) As RandomProfile
Using SQL 2005+ CTE you can do:
With
TwoRandomProfiles As
(
Select TOP 2 ..., ROW_NUMBER() OVER ( ORDER BY UP.ProfileID ) As Num
From tblUserProfile As UP
Order By NewId()
)
Select MP.Col1, ...
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From TwoRandomProfiles
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Union All
Select ...
From TwoRandomProfiles
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
And Num = 1
The CTE has the advantage of only querying for the random profiles once and the use of the ROW_NUMBER() column.
Obviously, in all the UNION statements the number and type of the columns must match.