Getting oldest Date SQL Complexity - sql

I have a problem which I cannot resolve no matter what without using code, instead of SQL SCRIPT.
I have 2 tables
Person
ID Name Type
1 A A1
2 B A2
3 C A3
4 D A4
5 E A6
PersonHomes
HOMEID Location PurchaseDate PersonID
1 CA 20160101 1
2 CT 20160202 1
3 DT 20160101 2
4 BT 20170102 3
5 CT 20160303 1
6 CA 20160101 2
PersonID is foreign key of Person Table
There are no other rowz in the tables
So, we have to show detail of EACH person WITH home
The rule to write output is
IF Person has SINGLE entry in PersonHomes then use it
IF Person has MORE than ONE entry in PersonHomes then we have to look at purchase date, IF they are different then USE the PersonHomes ROW with OLDEST date in it. AND DELETE OTHER ROWS OF HIM
IF Person has MORE than ONE entry in PersonHomes then we have to look at purchase date, and IF DATES are SAME then USE the ROW with LOWER ID AND DELETE THE OTHER ROWS of HIM
This is very easy to do in code but using SQL it is complex
What I tried was to
WITH PERSON (
SELECT * FROM Person)
SELECT * FROM PERSON
INNER JOIN PersonHomes ON Person.ID = PersonHomes.PersonID
WHERE PersonHomes.PersonID = CASE WHEN (COUNT (*) FROM PersonHomes...)
Then I think I can write SQL function ?
I am stuck, Please help!
SAMPLE OUTPUT for PERSON A
ID NAME Type HOMEID Location PurchaseDate
1 A A1 5 CT 20160303
For PERSON B
ID NAME Type HOMEID Location PurchaseDate
1 A A2 3 DT 20160101
Aiden

It is not so easy to get desired output with SQL. we should write more than one sql queries.
First I created a temp table which consists of home details:
select PersonID, count(*) as HomeCount, count(distinct PurchaseDate) as
PurchaseDateCount, min(PurchaseDate) oldestPurchaseDate, min(HOMEID) as
LowerHomeID into #PersonHomesAbstractTable from PersonHomes group by PersonID
Then for the output of your first rule:
select p.ID, p.NAME, p.Type, ph.HOMEID, ph.Location, ph.PurchaseDate from Person p
inner join #PersonHomesAbstractTable a on p.ID = a.PersonID
inner join PersonHomes ph on p.ID = ph.PersonID
where a.HomeCount = 1
For the output of your second rule:
select p.ID, p.NAME, p.Type, ph.HOMEID, ph.Location, ph.PurchaseDate
from Person p inner join #PersonHomesAbstractTable a on p.ID = a.PersonID
inner join PersonHomes ph on p.ID = ph.PersonID and
ph.PurchaseDate = a.oldestPurchaseDate
where a.HomeCount > 1 and a.PurchaseDateCount <> 1
And finally for the output of your third rule:
select p.ID, p.NAME, p.Type, ph.HOMEID, ph.Location, ph.PurchaseDate
from Person p inner join #PersonHomesAbstractTable a on p.ID = a.PersonID
inner join PersonHomes ph on p.ID = ph.PersonID and
ph.HOMEID = a.LowerHomeID
where a.HomeCount > 1 and a.PurchaseDateCount = 1
Of course there are some other ways, but now this way is come to my mind.
If you want to delete undesired rows, you can use scripts below:
delete from PersonHomes where HOMEID in
(
select ph.HOMEID from #PersonHomesAbstractTable a
inner join PersonHomes ph on a.PersonID = ph.PersonID and
ph.PurchaseDate <> a.oldestPurchaseDate
where a.HomeCount > 1 and a.PurchaseDateCount <> 1
union
select p.HOMEID from #PersonHomesAbstractTable a
inner join PersonHomes ph on a.PersonID = ph.PersonID and
ph.HOMEID <> a.LowerHomeID
where a.HomeCount > 1 and a.PurchaseDateCount = 1
)

You seem to have a prioritization query. I would solve this using row_number():
select ph.*
from (select ph.*,
row_number() over (partition by personid
order by purchasedate asc, homeid asc
) as seqnum
from personhomes ph
) ph
where seqnum = 1;
This doesn't actually change the data in the table. Although you say delete, it seems like you just want a result set with one home per person.

This is shortest approach got by Link
;WITH cte AS
(
SELECT *, RowN = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AddressMoveDate DESC) FROM Address
)
DELETE FROM cte WHERE RowN > 1

Related

How to select only 1 record from a group with a unique condition

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

Return only the row where the date in one column is closest to the date in another column?

I'm working on a query but it's returning some duplicate values and I need to return only the row where the date in one column is closest to the date in another column.
My query looks something like this:
SELECT p.Id, r.ReferralDate, s.SupervisionDate
FROM person p
INNER JOIN referral r on r.PersonId = p.Id
INNER JOIN supervision s on s.PersonId = p.Id
Which returns something like this:
Id
Supervision Date
Referral Date
123
2015-09-30
2015-08-30
123
2020-02-30
2015-08-30
123
2020-06-30
2015-08-30
456
2010-06-30
2010-07-30
456
2005-06-30
2010-07-30
How can I write a query that returns the Supervision Date that is closest to the Referral Date? So that the final output looks like this:
Id
Supervision Date
Referral Date
123
2015-09-30
2015-08-30
456
2010-06-30
2010-07-30
You can use two ways in this scenario to select the record that you need.
I prefer this way as it is very effective solution. We get the row order by date difference and select the mini date difference. first way you can select what columns to show. This is also more generic solution.
SELECT A.Id
, A.ReferalDate
, A.Supervision
FROM
(
SELECT
p.Id
, r.ReferalDate
, s.Supervision
,ROW_NUMBER() OVER (PARTITION BY p.Id ORDER BY DATEDIFF(DD,r.ReferalDate,s.Supervision) ASC) as [row_num]
FROM person p
INNER JOIN referral r
on r.pid = p.Id
INNER JOIN supervision s
on s.pid = p.Id
) AS A
WHERE A.row_num = 1
this way we order by date difference and get the top 1 record. If you add this in to CTE or Derived table, you can still return only the columns you need. This query is scenario specific as we do not consider partitions.
SELECT top 1
p.Id
, r.ReferalDate
, s.Supervision
,DATEDIFF(DD,r.ReferalDate,s.Supervision) as [date dif]
FROM person p
INNER JOIN referral r
on r.pid = p.Id
INNER JOIN supervision s
on s.pid = p.Id
ORDER BY [date dif] ASC
Both ways return the same result as our order by is placed on date difference.
here is one way:
select p.Id, r.ReferralDate, s.SupervisionDate from (
select p.Id, r.ReferralDate, s.SupervisionDate , row_number() over (partition by id order by abs(datediff(day , ReferralDate,SupervisionDate))) rn
from person p
join referral r on r.PersonId = p.Id
join supervision s on s.PersonId = p.Id
) t
where rn = 1
another way:
select ID, SUP from
( SELECT
p.Id 'ID',
r.ReferralDate 'REF',
s.SupervisionDate 'SUP',
min(julianday(s.SupervisionDate) - julianday(r.ReferralDate) ) 'diff'
FROM person p
INNER JOIN referral r on r.PersonId = p.Id
INNER JOIN supervision s on s.PersonId = p.Id
GROUP BY p.ID
)
This will return ID and SupervisionDate as reqested.

How to GROUP BY max date in left join

I have three tables in Oracle DB
House (
id
)
Person (
id,
house_id
)
Bill (
id,
date,
amount,
person_id
)
I need to get list if person id and amount from last bill if exist by house id. Last bill is the bill with the oldest date field.
I can get it by person id this way:
SELECT
p.id,
b.amount
FROM Person p
LEFT JOIN
(SELECT amount 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;
Haw can I get list of person ids with amounts of latest bill by house id?
Sample data:
House(id:1)
House(id:2)
Person(id:1, house_id:1)
Person(id:2, house_id:1)
Person(id:3, house_id:2)
Bill(id:1, date:01-11-2011, amount:100, person_id:1)
Bill(id:2, date:01-11-2012, amount:200, person_id:1)
Bill(id:3, date:01-11-2011, amount:90, person_id:2)
Bill(id:4, date:01-11-2012, amount:10, person_id:2)
Bill(id:5, date:01-11-2011, amount:190, person_id:3)
Result for select by house_id = 1:
person_id:1, amount:200
person_id:2, amount:10
You can do this with aggregation:
select p.person_id,
max(b.amount) keep (dense_rank first order by b.date desc) as most_recent_amount
from bill b join
person p
on b.person_id = p.id
where p.house_id = 1
group by p.person_id;

How to make LEFT JOIN with row having max date?

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
;

SQL First row only following aggregation Rules

I need to select only the most significant value from a table. Using Postgre SQL (last version) Follows the data sample:
Table Company
Id, Name, ExternalId, StartAt
1 Comp1 54123 21/05/2000
2 Comp2 23123 21/05/2000
Table Address
Id, Company, Address_Type, City
1 1 7 A
2 2 2 B
3 2 62 C
Table Adress_Type
Id, Name, importance_order
62 Adt1 1
7 Adt2 2
2 Adt3 2
What i need to do is to get the company and its major Address, based on the "importance_order". There is already a function that returns this result:
Create function~~~~
Select * from Company c
join Address a on c.address_id = a.id
Join AddressType at on a.adresstype_id = at.id
ORDER by at.importance_order
Limit 1
My problem now is that this function is called one time for every row in the query, and it take so much time (about 20 min.). Should it be possible to do this similar aproach by joinning tables? I need this join to get the First "most important"address, and then get the City name, but need to do this in a "faster" way. I need to reduce subquery`s number to its minimal.
Select * from table t
inner join Company c on t.company_id = c.id
left join address a on (c.company_id = c.id)
left join addresstype at on (a.adresstype_id = at.id)
where at.id = (
select max(id) from addresstype
where adresstype in (
select adresstype from adress where company_id = c.id
)
)
If it is not clear tell me that i get more into details.
Thanks.
For this you need PostgreSQL 8.4+ I suppose
SELECT T.*
FROM TABLE AS T
INNER JOIN
(
SELECT * FROM
(
SELECT C1.*, ROW_NUMBER() OVER(partition by C1.ID ORDER BY T.IMPORTANCE_ORDER) AS RN
FROM COMPANY AS C1
INNER JOIN ADDRESS AS A
ON C1.ID = A.COMPANY
INNER JOIN ADDRESS_TYPE AS T
ON T.ID = A.ADDRESS_TYPE_ID
) A
WHERE RN = 1
) AS B
ON B.ID= T.COMPANY_ID