SQL Server : smallest ROW_NUM in where condition, with subgroup pre-condition - sql

Thanks all in advance! I am trying to describe this as clear as I can.
I got two sub-tables, 1st table retrieves Comfirmed_Date and the 2nd table retrieves Mail_Date with condition Mail_Date >= Comfirmed_Date.
select
a.ID
,g.ROWNUM
,f.CORM_DT
,g.MAIL_DT
from
SOURCE_U a
left join
(select
a.SOURCE_ID
, Max(Cast(b.ATUF_DATE3 as date)) as [CORM_DT]
from
ATTACH_U a
inner join
USERFLD_D b on a.DEST_CK = b.DEST_CK
group by
a.SOURCE_ID) f on f.SOURCE_ID = a.SOURCE_ID
left join
(select
a.SOURCE_ID
, cast(b.MAILED_DT as date) as MAIL_DT
, row_number() over (partition by SOURCE_ID order by CREATE_DT) as ROWNUM
from
ATTACH_U a
left join
LETTER_D b on b.DEST_CK = a.DEST_CK) g on g.SOURCE_ID = a.SOURCE_ID
and g.MAIL_DT >= f.CORM_DT
I need the first line (smallest row_num) for the tables, how can I achieve that?
Original I think I can make condition like
where g.ROWNUM = 1
but because I have the condition on joint table, it does not work for below situations.
ID gROWNUM CORM_DT MAIL_DT
1001 3 2020-10-20 2020-10-22
1001 4 2020-10-20 2020-10-30
1002 2 2020-10-20 2020-10-21
1002 3 2020-10-20 2020-10-23
1002 4 2020-10-20 2020-10-28
1003 1 2020-10-20 2020-10-30
1004 1 2020-10-20 2020-10-21
1004 2 2020-10-20 2020-10-23
1005 4 2020-10-20 2020-10-28
1006 1 2020-10-20 2020-10-30
I only want one line for each ID here.

Try this:
SELECT TOP 1
a.ID
, g.ROWNUM
, f.CORM_DT
, g.MAIL_DT
FROM SOURCE_U a
LEFT JOIN (
SELECT
a.SOURCE_ID
, Max(Cast(b.ATUF_DATE3 as date)) as [CORM_DT]
FROM ATTACH_U a
INNER JOIN USERFLD_D b
ON a.DEST_CK = b.DEST_CK
GROUP BY a.SOURCE_ID
) f
ON f.SOURCE_ID = a.SOURCE_ID
LEFT JOIN (
SELECT
a.SOURCE_ID
, CAST( b.MAILED_DT AS date) AS MAIL_DT
, ROW_NUMBER() OVER( PARTITION BY SOURCE_ID ORDER BY CREATE_DT ) AS ROWNUM
FROM ATTACH_U a
LEFT JOIN LETTER_D b
ON b.DEST_CK = a.DEST_CK
) g
ON g.SOURCE_ID = a.SOURCE_ID
AND g.MAIL_DT >= f.CORM_DT
ORDER BY
g.ROWNUM;

All you need is a window function in your select.
select rows, columns... from (
select dense_rank() over ( partition by a.ID order by MAIL_DT) as rows, columns...
...
)
where rows = 1

Related

Join two tables on multiple columns with OR and label

I have two tables as shown here:
orderID
customerID
1
1001
2
1002
3
1003
4
1003
and the other one is like:
userID
Service1FirstOrderID
Serice2FirstOrderID
Service3FirstOrderID
1001
null
1
null
1002
2
null
null
1003
3
null
4
Now I want to join these two tables so that I can get every customer id with ServiceID that have been purchased.
UserID
Service
1001
2
1002
1
1003
1
1003
3
Any help would be appreciated.
It's possible to join on an IN
SELECT
so.userID
, CASE
WHEN o.OrderID = so.Service1FirstOrderID THEN 1
WHEN o.OrderID = so.Service2FirstOrderID THEN 2
WHEN o.OrderID = so.Service3FirstOrderID THEN 3
END AS Service
FROM Orders o
INNER JOIN ServiceOrders so
ON so.userID = o.customerID
AND o.OrderID IN (so.Service1FirstOrderID, so.Service2FirstOrderID, so.Service3FirstOrderID)
ORDER BY o.customerID;
userID
Service
1001
2
1002
1
1003
1
1003
3
Demo on db<>fiddle here
You have a heavily denormalized table structure, but it appears that this is not a join at all.
It seems to be purely a conditional unpivot of the second table, which you can do with CROSS APPLY
SELECT
t2.UserId,
v.*
FROM table2 t2
CROSS APPLY (
SELECT 1
WHERE Service1FirstOrderID IS NOT NULL
UNION ALL
SELECT 2
WHERE Service2FirstOrderID IS NOT NULL
UNION ALL
SELECT 3
WHERE Service3FirstOrderID IS NOT NULL
) v(Service);
db<>fiddle
You can achieve this using CTE.
;WITH CTE_ServiceOrder as
(
SELECT UserId, 1 AS ServiceId, Service1FirstOrderID as orderId
from ServiceOrders
where Service1FirstOrderId is not null
union all
SELECT UserId, 2, Service2FirstOrderID as orderId
from ServiceOrders
where Service2FirstOrderId is not null
union all
SELECT UserId, 3, Service3FirstOrderID as orderId
from ServiceOrders
where Service3FirstOrderId is not null
)
SELECT o.customerID, s.ServiceId FROM CTE_ServiceOrder as s
INNER JOIN Orders as o
on o.orderID = s.orderid
order by o.customerID
Thanks to #Lukstorms for create script.
You can refer to the dbfiddle

SQL Server - ROW_NUMBER() with PARTITION, how to get multiple records?

I'm struggling with my query
SELECT * FROM (
SELECT ins.ID, ins.UnitElement_ID, ins.Date, ue.Code,
ROW_NUMBER() OVER (PARTITION BY ins.UnitElement_ID ORDER BY ins.Date DESC) AS lastAnomaly
FROM Inspection ins
INNER JOIN UnitElement ue ON ins.UnitElement_ID = ue.ID
INNER JOIN InspectionedAnomaly ia ON ia.Inspection_ID = ins.ID
WHERE ins.UnitElement_ID IN (3,10)
AND ins.Evaluation IS NOT NULL
) selectedAnomaly
The output result is
ID UnitElement_ID Date Code lastAnomaly
0 3020217 3 2020-10-30 12:09:50 F01001G2 1
1 3020217 3 2020-10-30 12:09:50 F01001G2 2
2 3020217 3 2020-10-30 12:09:50 F01001G2 3
3 3009055 10 2020-05-04 00:00:00 F01001M1 1
4 3009055 10 2020-05-04 00:00:00 F01001M1 2
5 3020224 10 2020-05-04 00:00:00 F01001M1 3
6 3020224 10 2020-05-04 00:00:00 F01001M1 4
7 670231 10 2019-07-23 00:00:00 F01001M1 5
8 670231 10 2019-07-23 00:00:00 F01001M1 6
9 576227 10 2018-11-05 00:00:00 F01001M1 7
When i add the Where clause WHERE lastAnomaly = 1 it works pretty fine, but the problem happens when i have the same exact date as "most recent" date (for example rows 0,1 and 2).
Is there a way, if the most recent date is the same, to extract all 3 rows inside the sql query?
Thank you everyone
Use rank() and filtering:
SELECT *
FROM (SELECT ins.ID, ins.UnitElement_ID, ins.Date, ue.Code,
RANK() OVER (PARTITION BY ins.UnitElement_ID ORDER BY ins.Date DESC) AS lastAnomaly
FROM Inspection ins JOIN
UnitElement ue
ON ins.UnitElement_ID = ue.ID JOIN
InspectionedAnomaly ia
ON ia.Inspection_ID = ins.ID
WHERE ins.UnitElement_ID IN (3, 10) AND
ins.Evaluation IS NOT NULL
) sa
WHERE lastAnomaly = 1;
Or if you prefer, you can use MAX():
MAX(ins.DATE) OVER (PARTITION BY ins.UnitElement_ID) AS lastAnomalyDate
. . .
WHERE lastAnomalyDate = DATE
Use rank() instead of row_number(). It assigns the same rank to rows that are ties as regard to the ORDER BY of the OVER() clause.
So:
SELECT *
FROM (
SELECT ins.ID, ins.UnitElement_ID, ins.Date, ue.Code,
RANK() OVER (PARTITION BY ins.UnitElement_ID ORDER BY ins.Date DESC) AS lastAnomaly
FROM Inspection ins
INNER JOIN UnitElement ue ON ins.UnitElement_ID = ue.ID
INNER JOIN InspectionedAnomaly ia ON ia.Inspection_ID = ins.ID
WHERE ins.UnitElement_ID IN (3,10)
AND ins.Evaluation IS NOT NULL
) selectedAnomaly
WHERE lastAnomaly = 1

SQL get closest value by date

can't wrap my mind around the next problem
I have a table with historical data TableA:
uniq_id item_id item_clust date
11111 1 a 2020-02-12
11112 1 a 2020-01-13
11113 1 b 2020-02-01
11114 2 b 2020-01-01
I also have a table with historical data for clusters TableB:
item_id item_clust item_pos date
1 a 1 2020-01-01
1 a 2 2020-02-01
1 a 3 2020-03-01
1 b 1 2020-01-10
I would like to receive the latest position for every item_id + item_clust for date based on dates in TableB
If no rows found, I would like to insert item_pos = 0
Desired result:
uniq_id item_id item_clust date item_pos
11111 1 a 2020-02-12 2
11112 1 a 2020-01-13 1
11113 1 b 2020-02-01 1
11114 2 b 2020-01-01 0
So, for item 1 in cluster a on 2020-02-12 the latest position is at 2020-02-01 = 2.
This looks like a left join:
select a.*, coalesce(b.item_pos, 0) as item_pos
from a left join
(select distinct on (b.item_id, b.item_clust) b.*
from b
order by b.item_id, b.item_clust, b.date desc
) b
using (item_id, item_clust);
Or a lateral join:
select a.*, coalesce(b.item_pos, 0) as item_pos
from a left join lateral
(select b.*
from b
where b.item_id = a.item_id and
b.item_clust = a.item_clust
order by b.date desc
limit 1
) b
on true; -- always do the left join even when there are no matches
EDIT:
If you want the most recent position "as of" the date in A, then use the lateral join:
select a.*, coalesce(b.item_pos, 0) as item_pos
from a left join lateral
(select b.*
from b
where b.item_id = a.item_id and
b.item_clust = a.item_clust and
b.date <= a.date
order by b.date desc
limit 1
) b
on true; -- always do the left join even when there are no matches

How to get the value from table B whose max date is less than the date in table A

I have two tables; table A and table B. Table A has StoreNumber, MatNumber and Date. Table B has StoreNumber, MatNumber, Date and ShipmentValue. I have to get the Shipment value from table B for StoreNumber and MatNumber given that the Maximum Date in Table B for the StoreNumber and MatNumber should be less than the Date for the same StoreNumber and MatNumber in Table A (each row in Table A) . Please see the output table.
Table A:
StoreNumber MatNumber Date
A 9 3/30/2020
A 9 3/30/2020
B 10 3/18/2020
B 10 3/18/2020
A 9 3/13/2020
Table B:
StoreNumber MatNumber Date ShipmentValue
A 9 3/10/2020 2
A 9 3/12/2020 3
A 9 3/18/2020 4
B 10 3/4/2020 7
B 10 3/7/2020 9
B 10 3/16/2020 10
Output:
StoreNumber MatNumber A.Date B.Date ShipmentValue
A 9 3/30/2020 3/18/2020 4
A 9 3/30/2020 3/18/2020 4
B 10 3/18/2020 3/16/2020 10
B 10 3/18/2020 3/16/2020 10
A 9 3/13/2020 3/12/2020 3
Tried with ROW_NUMBER and selecting 1st row after ordering date by desc.
SELECT A.StoreNumber
,A.MatNumber
,A.Date
,B.Date AS B_Date
,B.ShipmentValue
FROM TableA A
LEFT JOIN
(
SELECT StoreNumber ,MatNumber , Date , ShipmentValue
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY StoreNumber, MatNumber ORDER BY DATE DESC ) AS ID,*
FROM TableB
) A
WHERE ID = 1
) B
ON A.StoreNumber = B.StoreNumber
AND A.MatNumber = B.MatNumber
This is a place where a lateral join is handy:
select a.*, b.date, b.shipmentvalue
from a left join lateral
(select b.*
from b
where b.storenumber = a.storenumber and
b.matnumber = a.matnumber and
b.date <= a.date
order by b.date desc
fetch first 1 row only
) b
on 1=1; -- returns rows in a even when there are no matches
EDIT:
Wow. Snowflake implements lateral joins and then limits them in a fundamental way. Another method is more expensive but should work:
select ab.*, b.shipmentValue
from (select a.StoreNumber, a.MatNumber, a.Date, max(b.date) as b_date, b.shipmentvalue
from a left join
b
on b.storenumber = a.storenumber and
b.matnumber = a.matnumber and
b.date <= a.date
group by a.StoreNumber, a.MatNumber, a.Date
) ab join
b
on b.storenumber = ab.storenumber and
b.matnumber = ab.matnumber and
b.date <= ab.b_date

How to SUM Only One Time Per UniqueId in SQL?

I have two tables that look roughly like this:
Table A
DocumentId (*is unique) DocumentDate
1 2016-01-01
2 2016-01-01
3 2016-02-01
4 2016-03-01
and Table B
ContractId SnapshotTimeId NetFinanced
1 20160231 300
1 20160331 300
1 20160431 300
2 20160231 450
2 20160331 450
2 20160431 450
3 20160331 500
3 20160431 500
4 20160431 150
I would like the final table to look something like this:
DocumentDate NetFinanced
2016-01-01 750
2016-02-01 500
2016-03-01 150
I have tried the following and it doesn't work:
SELECT A.DocumentDate, SUM(B.NetFinanced)
FROM A
JOIN B on B.ContractId=A.DocumentId
GROUP BY A.DocumentDate
Any ideas? Thanks in advance
you can use distinct
SELECT A.DocumentDate,
SUM(B.NetFinanced)
FROM A
JOIN (SELECT DISTINCT
ContractId,
NetFinanced
FROM B
) B ON B.ContractId = A.DocumentId
GROUP BY A.DocumentDate
the result of this will be different if the NetFinanced amount changes per SnapshotTimeId
if you want the most recent NetFinanced amount, you can use Row_number() to order the values.
SELECT A.DocumentDate,
SUM(B.NetFinanced)
FROM A
JOIN (SELECT ROW_NUMBER() OVER (PARTITION BY ContractId ORDER BY SnapshotTimeId DESC) Rn,
ContractId,
NetFinanced
FROM B
) B ON B.ContractId = A.DocumentId AND B.Rn = 1
GROUP BY A.DocumentDate
You have duplicate values for NetFinanced in TableB, of course the results won't give you what you want. You need to join TableA with the unique values (I assume) of ContractId and NetFinanced columns from TableB:
SELECT A.DocumentDate,
SUM(B.NetFinanced) NetFinanced
FROM dbo.TableA A
INNER JOIN (SELECT DISTINCT ContractId, NetFinanced
FROM dbo.TableB) B
ON A.DocumentId = B.ContractId
GROUP BY A.DocumentDate;
Try Like this
SELECT A.DocumentDate, SUM(B.NetFinanced)
FROM A
JOIN (SELECT MAX(ContractId) ContractId, MAX(SnapshotTimeId)SnapshotTimeId,
MAX(NetFinanced)NetFinanced
FROM B GROUP BY ContractId) B ON B.ContractId = A.DocumentId
GROUP BY A.DocumentDate