I have PostgreSQL database and I try to print all my users (Person).
When I execute this query
-- show owners
-- sorted by maximum cars amount
SELECT p.id
FROM car c JOIN person p ON c.person_id = p.id
GROUP BY p.id
ORDER BY COUNT(p.name) ASC;
I get all owners sorted by cars amount
Output: 3 2 4 1
And all order goes wrong when I try to link owner id.
SELECT *
FROM person p
WHERE p.id IN (
SELECT p.id
FROM car c JOIN person p ON c.person_id = p.id
GROUP BY p.id
ORDER BY COUNT(p.name) ASC);
Output: 1 2 3 4 and other data
You see than order is wrong. So here is my question how can I save that order?
Instead Of subquery use join. Try this.
SELECT p.*
FROM person p
JOIN (SELECT p.id,
Count(p.NAME)cnt
FROM car c
JOIN person p
ON c.person_id = p.id
GROUP BY p.id) b
ON p.id = b.id
ORDER BY cnt ASC
Untangle the mess. Aggregate first, join later:
SELECT p.*
FROM person p
JOIN (
SELECT person_id, count(*) AS ct
FROM car
GROUP BY person_id
) c ON c.person_id = p.id
ORDER BY c.cnt;
No need to join to person twice. This should be fastest if you count most or all rows.
For a small selection, correlated subqueries are faster:
SELECT p.*
FROM person p
ORDER BY (SELECT count(*) FROM car c WHERE c.person_id = p.id)
WHERE p.id BETWEEN 10 AND 20; -- some very selective predicate
As for your original: IN takes a set on the right hand, order of elements is ignored, so ORDER BY is pointless in the subuery.
Related
I have the following query. This query allows me to produce a list of children and their familymember contacts (contactpupilID).
select s.studentnr,pc.pupilid, pc.contactpupilid, p2.mainmail
from student s
join pupil p on p.id = s.pupilid
join pupilcontact pc on pc.pupilid = p.id
join pupil p2 on p2.id = pc.contactpupilid
where CURRENT_DATE between pc.validfrom and pc.validuntil
order by pc.pupilid
Each child can have 0 to 3 familycontacts (0 because no contact has been added during registration).
Each familycontact (contactpupilid) has an email field. However there are cases where all familycontacts have an email or 1 of them or none of them.
My list needs to select a child with a familycontact(contactpupilid) that has an email. The familycontact that is selected should be the one that has an email.
If none of the familycontacts have an email then it should select the 1st familycontact by default.
This is how it needs to look like
How would I complete this task?
I don't know what you mean by "first record" because SQL tables are unordered. I can assume you mean the one with the smallest contactpupilid.
What you have described is what distinct on does:
select distinct on (s.studentnr) s.studentnr, pc.pupilid, pc.contactpupilid, p2.mainmail
from student s join
pupil p
on p.id = s.pupilid join
pupilcontact pc
on pc.pupilid = p.id join
pupil p2
on p2.id = pc.contactpupilid
where CURRENT_DATE between pc.validfrom and pc.validuntil
order by s.studentnr, (p2.mainmail is not null) desc;
Use ROW_NUMBER() window function in your query to rank the rows that contain an email first:
with cte as (
select s.studentnr, pc.pupilid, pc.contactpupilid, p2.mainmail,
row_number() over (partition by s.studentnr order by p2.mainmail is not null desc, pc.contactpupilid) rn
from student s
join pupil p on p.id = s.pupilid
join pupilcontact pc on pc.pupilid = p.id
join pupil p2 on p2.id = pc.contactpupilid
where CURRENT_DATE between pc.validfrom and pc.validuntil
)
select studentnr, pupilid, contactpupilid, mainmail
from cte
where rn = 1
order by pupilid;
You can do it with CTE like this
with temp as (
select s.studentnr,pc.pupilid, pc.contactpupilid, p2.mainmail,row_number() over (partition by pupilid order by pupilid) as row_number
from student s
join pupil p on p.id = s.pupilid
join pupilcontact pc on pc.pupilid = p.id
join pupil p2 on p2.id = pc.contactpupilid
where CURRENT_DATE between pc.validfrom and pc.validuntil
order by pc.pupilid
)
select *
from temp
where row_number = 1
I have interconnected tables.
movies (main, parent) : id | title | year
people (child) : people_id | name | birthyear
ratings (child) : movie_id | rating | votes
stars (child) : movie_id | person_id
I need to make a query ang get a sinle column output from tables "movies-people-stars" and order that by column from the table "rating" without joining column "rating" to my output.
My code:
SELECT title from movies
where id in (select movie_id from stars
where person_id in(select id from people where name = "Chadwick Boseman"))LIMIT 5;
It returns all titles of movies where Chadwick Boseman plays. I need to order them by rating. How to do it?
Although this would never be done without a join, since it is homework, you can use a correlated subquery for the table ratings in the ORDER BY clause:
select m.title
from movies m
inner join stars s on s.movie_id = m.id
inner join people p on p.people_id = s.person_id
where p.name = 'Chadwick Boseman'
order by (select r.rating from ratings r where r.movie_id = m.id) desc
limit 5
You could also use your query and add the ORDER BY clause:
select m.title
from movies m
where m.id in (
select movie_id
from stars
where person_id in(
select id
from people
where name = 'Chadwick Boseman'
)
)
order by (select r.rating from ratings r where r.movie_id = m.id) desc
limit 5;
You need to include the column in the select list to order by that column. Order by sorts your output in the order of the column you specify. Also, why can't you use JOINs for your query like below.
SELECT m.title,d.rating
FROM movies m
JOIN stars s ON s.movie_id = m.id
JOIN people p ON p.id = s.person_id
JOIN tbl d ON d.xx = z.yy ----- JOIN the table d here and use it in select . replace z,xx and yy with actual table name and columns.
WHERE p.name = "Chadwick Boseman"
ORDER BY d.rating
LIMIT 5
updated* - It might work but not able to test as I don't have access to actual data and tables.
SELECT m.title
FROM movies m
JOIN stars s ON s.movie_id = m.id
JOIN people p ON p.id = s.person_id
WHERE p.name = 'Chadwick Boseman'
AND m.id in (SELECT top 5 movie_id
FROM ratings r
WHERE r.movie_id = m.id
ORDER BY ratings desc)
I have two tables in Oracle DB
Person (
id
)
Bill (
id,
date,
amount,
person_id
)
I need to get person and amount from last bill if exist.
I trying to do it this way
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN Bill b
ON b.person_id = p.id AND b.date = (SELECT MAX(date) FROM Bill WHERE person_id = 1)
WHERE p.id = 1;
But this query works only with INNER JOIN. In case of LEFT JOIN it throws ORA-01799 a column may not be outer-joined to a subquery
How can I get amoun from the last bill using left join?
Please try the below avoiding sub query to be outer joined
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN(select * from Bill where date =
(SELECT MAX(date) FROM Bill b1 WHERE person_id = 1)) b ON b.person_id = p.id
WHERE p.id = 1;
What you are looking for is a way to tell in bills, for each person, what is the latest record, and that one is the one to join with. One way is to use row_number:
select * from person p
left join (select b.*,
row_number() over (partition by person_id order by date desc) as seq_num
from bills b) b
on p.id = b.person_id
and seq_num = 1
You cannot have a subquery inside an ON statement.
Instead you need to convert your LEFT JOIN statement into a whole subquery.
Not tested but this should work.
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN (
SELECT id FROM Bill
WHERE person_id = p.id
AND date = (SELECT date FROM Bill WHERE person_id = 1)) b
WHERE p.id = 1;
I'm not quite sure why you would want to filter for the date though.
Simply filtering for the person_id should do the trick
you should join Person and Bill to the result for max date in bill related to person_id
select Person.id, bill.amount
from Person
left join bill on bill.person_id = person.id
left join (
select person_id, max(date) as max_date
from bill
group by person_id ) t on t.person_id = Person.id and b.date = t.max_date
Hey you can do like this
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN Bill b
ON b.person_id = p.id AND b.date = (SELECT max(date) FROM Bill WHERE person_id = 1)
WHERE p.id = 1
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN Bill b
ON b.person_id = p.id
WHERE (SELECT max(date) FROM bill AS sb WHERE sb.person_id=p.id LIMIT 1)=b.date;
SELECT
p.id,
c.amount
FROM Person p
LEFT JOIN (select b.person_id as personid,b.amount as amount from Bill b where b.date1= (select max(date1) from Bill where person_id=1)) c
ON c.personid = p.id
WHERE p.id = 1;
try this
select * from person p
left join (select MAX(id) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
from bills b) b
on p.id = b.person_id
I use GREATEST() function in join condition:
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN Bill b
ON b.person_id = p.id
AND b.date = GREATEST(b.date)
WHERE p.id = 1
This allows you to grab the whole row if necessary and grab the top x rows
SELECT p.id
,b.amount
FROM person p
LEFT JOIN
(
SELECT * FROM
(
SELECT date
,ROW_NUMBER() OVER (PARTITION BY person_id ORDER BY date DESC) AS row_num
FROM bill
)
WHERE row_num = 1
) b ON p.id = b.person_id
WHERE p.id = 1
;
I have the following query:
SELECT P.ID,
(
SELECT F.VALUE_ID FROM FORM F
INNER JOIN F.PERSON_ID = P.ID
) AS LATEST_FORM
FROM FORM F1 INNER JOIN PERSON P
ON P.ID = F1.PERSON_ID
WHERE F1.NO_PCP_IND IS NOT NULL
ORDER BY P.ID
Each person can have multiple forms and I'm just trying to get the latest form they have submitted. I tried using ROWNUM inside the subquery and I understand why it doesn't work but not sure how to make it generate the right result set with Oracle.
This is the query:
SELECT P.ID,
(SELECT F.VALUE_ID
FROM FORM F INNER JOIN
F.PERSON_ID = P.ID
) AS LATEST_FORM
FROM FORM F1 INNER JOIN
PERSON P
ON P.ID = F1.PERSON_ID
WHERE F1.NO_PCP_IND IS NOT NULL
ORDER BY P.ID;
In addition to being syntactically incorrect, this would return one row per person and user if it worked. I think your original query should be more like:
SELECT P.ID,
(SELECT F.VALUE_ID
FROM FORM F
WHERE F1.NO_PCP_IND IS NOT NULL AND F.PERSON_ID = P.ID AND
ROWNUM = 1
) AS LATEST_FORM
FROM PERSON P
ORDER BY P.ID;
This doesn't fix your problem, because you want to keep the last value. For that, use the keep functionality:
SELECT P.ID,
(SELECT MAX(F.VALUE_ID) KEEP (DENSE_RANK FIRST ORDER BY XXX DESC)
FROM FORM F
WHERE F1.NO_PCP_IND IS NOT NULL AND F.PERSON_ID = P.ID
) AS LATEST_FORM
FROM PERSON P
ORDER BY P.ID;
In this case XXX is the name of the column that defines the ordering of the forms for each person.
I believe its the ValueId which tells about the latest form
So wouldn't this work??
SELECT max(F.VALUEID), P.ID
FROM FORM F, PERSON P
where F.PID = P.ID
Currently I am performing a left join on two tables. The first table has an id and a persons name, the second table has an id, the id of a person from table 1, and then a timestamp (of a flight).
People Flights
id | name id | person_id | time
------------ ---------------------------
1 Dave 1 1 1284762115
2 Becky 2 1 1284787352
3 2 1284772629
4 2 1286432934
5 1 1283239480
When I perform my left join, I get a list of people and their flight times, but what I would like is just the list of people with the flight time with the highest ID
I have been using
SELECT p.id, p.name max(f.time)
FROM People p
LEFT JOIN Flights f ON p.id = f.person_id
GROUP BY p.id, p.name
However, this just gives me the LAST flight time, rather than the last flight time uploaded into the system (ie, highest ID).
1 Dave 1284787352
2 Becky 1286432934
So to reiterate, I would like to see the name of the person, along with the flight time of their last UPLOADED (highest ID) flight time.
1 Dave 1283239480
2 Becky 1286432934
Use:
SELECT p.id,
p.name,
f.time
FROM PEOPLE p
JOIN FLIGHTS f ON f.person_id = p.id
JOIN (SELECT f.person_id,
MAX(f.id) AS max_id
FROM FLIGHTS f
GROUP BY f.person_id) x ON x.person_id = f.person_id
AND x.max_id = f.id
If you are using a database that supports analytics:
SELECT p.id,
p.name,
x.time
FROM PEOPLE p
JOIN (SELECT f.person_id,
f.time,
ROW_NUMBER() OVER(PARTITION BY f.person_id
ORDER BY f.id DESC) AS rk
FROM FLIGHTS f) x ON x.person_id = p.id
AND x.rk = 1
If you want people, including those without flights:
SELECT p.id,
p.name,
f.time
FROM PEOPLE p
LEFT JOIN FLIGHTS f ON f.person_id = p.id
JOIN (SELECT f.person_id,
MAX(f.id) AS max_id
FROM FLIGHTS f
GROUP BY f.person_id) x ON x.person_id = f.person_id
AND x.max_id = f.id
...and the analytic version:
SELECT p.id,
p.name,
x.time
FROM PEOPLE p
LEFT JOIN (SELECT f.person_id,
f.time,
ROW_NUMBER() OVER(PARTITION BY f.person_id
ORDER BY f.id DESC) AS rk
FROM FLIGHTS f) x ON x.person_id = p.id
AND x.rk = 1
I think you are looking for something like the below. group by the person_id and select the max id then use that list to select from the flights. This is my first thought there may be a more efficent way.
EDITED:
SELECT p.id, p.name MAX(f.time)
FROM People p
LEFT JOIN Flights f ON p.id = f.person_id
WHERE f.id in(SELECT MAX(id) FROM flights GROUP BY person_id)