SQL Row_Number() not sorting dates - sql

I have a query in which I am trying to get the most recent date from my ROW_NUMBER() selection. I have tried both MAX() and DESC in my ORDER BY clause. It does not show the most recent date as RowNum 1.
This is my query:
;WITH cte3 AS
(
SELECT
o.PartNo,
o.JobNo,
MAX(tt.TicketDate) as rawr,
ROW_NUMBER() OVER (PARTITION BY o.JobNo, o.PartNo
ORDER BY tt.TicketDate DESC) as RowNum
FROM
OrderDet AS o
INNER JOIN
TimeTicketDet AS tt ON o.JobNo = tt.JobNo
WHERE
o.Status = 'Open'
GROUP BY
tt.TicketDate, o.JobNo, o.PartNo
)
SELECT *
FROM cte3
When I get it giving me the correct results, I will add a WHERE RowNum = 1 in the cte query.
With my current query, this is the result:
+--------+-------+-----------+--------+
| PartNo | JobNo | rawr | RowNum |
+--------+-------+-----------+--------+
| 1234 | 20 | 5/30/2012 | 1 |
| 1234 | 20 | 5/29/2012 | 2 |
| 1234 | 20 | 5/25/2012 | 3 |
| 1234 | 20 | 5/24/2012 | 4 |
| 1234 | 20 | 5/23/2012 | 5 |
| 1234 | 20 | 5/22/2012 | 6 |
| 1234 | 20 | 5/16/2012 | 7 |
| 1234 | 20 | 5/15/2012 | 8 |
| 1234 | 20 | 5/14/2012 | 9 |
| 1234 | 20 | 5/11/2012 | 10 |
| 1234 | 20 | 5/10/2012 | 11 |
| 1234 | 20 | 5/9/2012 | 12 |
| 1234 | 20 | 3/27/2015 | 13 |
| 1234 | 20 | 1/3/2013 | 14 |
| 1234 | 20 | 1/2/2013 | 15 |
+--------+-------+-----------+--------+
RowNum = 13 is the most recent date. Am I organizing my sorts incorrectly or incorrectly converting my dates?
EDIT:
TimeTicketDet Table Sample Data:
+------------+-------+
| TicketDate | JobNo |
+------------+-------+
| 5/9/2012 | 20 |
| 5/10/2012 | 20 |
| 5/24/2012 | 20 |
| 3/27/2015 | 20 |
| 5/22/2012 | 20 |
| 5/10/2012 | 20 |
| 5/11/2012 | 20 |
| 5/9/2012 | 100 |
| 5/10/2012 | 100 |
| 5/24/2012 | 100 |
| 3/27/2015 | 100 |
| 5/22/2012 | 100 |
| 5/10/2012 | 100 |
| 5/11/2012 | 100 |
+------------+-------+
OrderDet Table Sample Data:
+--------+--------+-------+
| PartNo | Status | JobNo |
+--------+--------+-------+
| 1234 | Open | 20 |
| 1234 | Open | 100 |
+--------+--------+-------+
Desired Result:
+--------+------------+-------+--------+
| PartNo | TicketDate | JobNo | RowNum |
+--------+------------+-------+--------+
| 1234 | 3/27/2015 | 20 | 1 |
| 1234 | 3/27/2015 | 100 | 1 |
+--------+------------+-------+--------+

As I mentioned in my comment, since your TicketDate column is a char, you need to convert it to a datetime in order to sort it by actual date. Right now, you are sorting it by string value which isn't correct.
I'd recommend changing your query to something like this:
;WITH cte3 AS
(
SELECT
o.PartNo,
o.JobNo,
MAX(tt.TicketDate) as rawr,
ROW_NUMBER() OVER (PARTITION BY o.JobNo, o.PartNo
ORDER BY cast(tt.TicketDate as datetime) DESC) as RowNum
FROM
OrderDet AS o
INNER JOIN
TimeTicketDet AS tt ON o.JobNo = tt.JobNo
WHERE
o.Status = 'Open'
GROUP BY
cast(tt.TicketDate as datetime), o.JobNo, o.PartNo
)
SELECT *
FROM cte3
where RowNum = 1;
Here is a demo. By casting your char to a datetime in your row_number you will be sorting the data by date instead of string.
Additionally, you don't really need the max() and the GROUP BY since by casting the TicketDate to a datetime you will return the correct row:
;WITH cte3 AS
(
SELECT
o.PartNo,
o.JobNo,
tt.TicketDate as rawr,
ROW_NUMBER() OVER (PARTITION BY o.JobNo, o.PartNo
ORDER BY cast(tt.TicketDate as datetime) DESC) as RowNum
FROM
#OrderDet AS o
INNER JOIN
#TimeTicketDet AS tt ON o.JobNo = tt.JobNo
WHERE
o.Status = 'Open'
)
SELECT *
FROM cte3
where RowNum =1;

As Ollie suggest you can CAST your string to DATETIME And you dont need the additional Group By
SQL DEMO
;WITH cte3 AS
(
SELECT
o.PartNo,
o.JobNo,
tt.TicketDate as rawr,
ROW_NUMBER() OVER (PARTITION BY o.JobNo, o.PartNo
ORDER BY cast(tt.TicketDate as datetime) DESC) as RowNum
FROM OrderDet AS o
JOIN TimeTicketDet AS tt
ON o.JobNo = tt.JobNo
WHERE
o.Status = 'Open'
)
SELECT *
FROM cte3
WHERE RowNum = 1
OUTPUT

Related

Finding the Third Most Recent Value of a Field for Each ID in Each Date in SQL

I have a similar table as below:
+----+----------+-------+
| ID | Date | Value |
+----+----------+-------+
| A | 20200620 | 150 |
+----+----------+-------+
| A | 20200621 | 130 |
+----+----------+-------+
| A | 20200622 | 140 |
+----+----------+-------+
| A | 20200623 | 200 |
+----+----------+-------+
| B | 20200622 | 300 |
+----+----------+-------+
| B | 20200623 | 350 |
+----+----------+-------+
| B | 20200624 | 400 |
+----+----------+-------+
| B | 20200625 | 150 |
+----+----------+-------+
I need to add a column that for each ID and in each date, it shows the value for two business days prior that date (for example for A in '20200623' it should show the value of the day '20200621'). The output should be something similar to the below:
+----+----------+-------+--------------------------+
| ID | Date | Value | Value_AsoF_TwoDaysBefore |
+----+----------+-------+--------------------------+
| A | 20200620 | 150 | NULL |
+----+----------+-------+--------------------------+
| A | 20200621 | 130 | NULL |
+----+----------+-------+--------------------------+
| A | 20200622 | 140 | 150 |
+----+----------+-------+--------------------------+
| A | 20200623 | 200 | 130 |
+----+----------+-------+--------------------------+
| B | 20200622 | 300 | NULL |
+----+----------+-------+--------------------------+
| B | 20200623 | 350 | NULL |
+----+----------+-------+--------------------------+
| B | 20200624 | 400 | 300 |
+----+----------+-------+--------------------------+
| B | 20200625 | 150 | 350 |
+----+----------+-------+--------------------------+
Could you please let me know a way to do that? I appreciate all the helps.
Try this below option with ROW_NUMBER and Self Joining-
Demo Here
WITH CTE
AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date) RN
FROM your_table
)
SELECT A.ID, A.Value, A.Date, B.Value
FROM CTE A
LEFT JOIN CTE B ON A.RN = B.RN + 2 AND A.ID = B.ID
Assuming you have a row for every date, use lag():
select t.*,
lag(value, 2) over (partition by id order by date) as Value_AsoF_TwoDaysBefore
from t;
If you don't have a value for every day, then use a left join:
select t.*, tprev.date as Value_AsoF_TwoDaysBefore
from t left join
t tprev
on tprev.id = t.id and tprev.date = dateadd(day, -2, t.date) ;
Note: Both of these return NULL for the missing values rather than -. NULLs make much more sense in SQL.

SQL group by an column but segmented based on another column

I have this table which contain roughly more than 100000 rows and with 3 columns:
Account_number
Report_date
Outstanding_amount
I need to find a statement that group the outstanding amount by account but also cut it based on the date. Sample data for 1 account:
+----------------+-------------+--------------------+--+
| account_number | report_date | outstanding_amount | |
+----------------+-------------+--------------------+--+
| 1 | 02/01/2019 | 100 | |
| 1 | 03/01/2019 | 100 | |
| 1 | 06/01/2019 | 200 | |
| 1 | 07/01/2019 | 300 | |
| 1 | 10/01/2019 | 200 | |
| 1 | 11/01/2019 | 200 | |
| 1 | 12/01/2019 | 100 | |
+----------------+-------------+--------------------+--+
So if I run this statement:
select * from (select account_number, min(report_date) mindate, max(report_date) maxdate, outstading_amount from table1 grouped by account_number, outstanding_amount)
The result of this statement should be similar to this:
+----------------+------------+------------+--------------------+
| account_number | mindate | maxdate | outstanding_amount |
+----------------+------------+------------+--------------------+
| 1 | 02/01/2019 | 12/01/2019 | 100 |
| 1 | 06/01/2019 | 11/01/2019 | 200 |
| 1 | 07/01/2019 | 07/01/2019 | 300 |
+----------------+------------+------------+--------------------+
So here I want to separate the result so that the days between mindate and maxdate of one row won't overlap the days in the next row. The result I'm looking is something like this:
+----------------+------------+------------+--------------------+
| account_number | mindate | maxdate | outstanding_amount |
+----------------+------------+------------+--------------------+
| 1 | 02/01/2019 | 03/01/2019 | 100 |
| 1 | 06/01/2019 | 06/01/2019 | 200 |
| 1 | 07/01/2019 | 07/01/2019 | 300 |
| 1 | 10/01/2019 | 11/01/2019 | 200 |
| 1 | 12/01/2019 | 12/01/2019 | 100 |
+----------------+------------+------------+--------------------+
Is it possible to construct this statement?
This is a gaps-and-islands problem. In this case, the simplest solution is probably the difference of row numbers:
select account_number, outstanding_amount,
min(report_date), max(report_date)
from (select t.*,
row_number() over (partition by account_number order by report_date) as seqnum,
row_number() over (partition by account_number, outstanding_amount order by report_date) as seqnum_o
from t
) t
group by account_number, outstanding_amount, (seqnum - seqnum_o)
order by account_number, min(report_date);
Why this works is a little tricky to explain. But if you look at the results of the subquery, you will be able to see how the difference of row numbers defines the adjacent rows with the same amount.
To flatten the data, squish it by calculated rank.
select account_number
, min(report_date) as mindate
, max(report_date) as maxdate
, outstanding_amount
from
(
select q1.*
, sum(flag) over (partition by account_number order by report_date) as rnk
from
(
select t.*
, case when outstanding_amount = lag(outstanding_amount, 1) over (partition by account_number order by report_date) then 0 else 1 end as flag
from table1 t
) q1
) q2
group by account_number, outstanding_amount, rnk
order by account_number, mindate;
A test on db<>fiddle here

Max date in SQL Server consultation

I need to get the row with the maximum date by the ItemKey.
I need the whole row.
I have this table:
num | ItemKey | Serial | Qty | ItemName | Tdate
----+---------+--------+-----+----------+-------------------------
1 | 111 | 5 | 10 | AAA | 2010-03-25 00:00:00.000
2 | 111 | 0 | 12 | AAA | 2010-03-26 00:00:00.000
3 | 222 | 6 | 13 | BBB | 2010-03-25 00:00:00.000
4 | 222 | 2 | 11 | BBB | 2010-03-28 00:00:00.000
5 | 333 | 3 | 15 | CCC | 2010-03-25 00:00:00.000
6 | 333 | 4 | 16 | CCC | 2010-03-26 00:00:00.000
7 | 333 | 0 | 17 | CCC | 2010-03-27 00:00:00.000
I need to get this:
num | ItemKey | Serial | Qty | ItemName | Tdate
----+---------+--------+-----+----------+--------------------------
2 | 111 | 0 | 12 | AAA | 2010-03-26 00:00:00.000
4 | 222 | 2 | 11 | BBB | 2010-03-28 00:00:00.000
7 | 333 | 0 | 17 | CCC | 2010-03-27 00:00:00.000
I tried this SQL statement:
select *
from MyTBL
where Tdate = (select MAX(Tdate) from MyTBL)
But unfortunately it does not work
Thanks
you can use ROW_NUMBER to achieve this
SELECT * FROM (
select *,
ROW_NUMBER() OVER (PARTITION BY ItemKey ORDER BY Tdate DESC) as rn from MyTBL) AS T1
WHERE rn = 1
or in another way (using common table expressions)
WITH CTE_1 AS (
select *,ROW_NUMBER() OVER (PARTITION BY ItemKey ORDER BY Tdate DESC) as rn from MyTBL)
SELECT * FROM CTE_1 WHERE rn = 1
Just try like this;
select * from MyTBL M1 inner join
(select ItemName,max(Tdate) as Tdate from MyTBL group by ItemName) M2
ON M1.ItemName = M2.ItemName and M1.Tdate = M2.Tdate
SQL HERE
You can use this :
select t1.* from table_name t1
join (select ItemKey, max(Tdate) Tdate from table_name group by ItemKey) as t2
on t1.ItemKey=t2.ItemKey and t1.Tdate=t2.Tdate
order by t1.ItemKey
Try this
WITH t AS ( SELECT *,
RANK() OVER (PARTITION BY ItemName ORDER BY TDate DESC ) as myRank
from MyTBL)
SELECT [num], [ItemKey], [Serial], [Qty], [ItemName], [TDate] FROM t
WHERE t.myRank = 1
SQL fiddle

SELECT columns outside applied WHERE filter

+----------+---------+--------+------+------------+-----------+
| PersonID | OrderID | PlanID | Plan | Sdate | Edate |
+----------+---------+--------+------+------------+-----------+
| 1 | 1 | 312 | M | 10/14/2016 | 1/30/2017 |
| 1 | 4 | 125 | A | 10/18/2016 | 2/3/2017 |
| 1 | 7 | 411 | B | 10/25/2016 | 4/7/2017 |
| 2 | 1 | 111 | E | 10/31/2016 | 4/21/2017 |
| 2 | 3 | 312 | M | 11/4/2016 | 4/28/2017 |
| 2 | 5 | 253 | L | 11/29/2016 | 5/3/2017 |
| 3 | 1 | 50 | Q | 12/2/2016 | 5/8/2017 |
| 3 | 2 | 12 | W | 12/8/2016 | 6/8/2017 |
| 3 | 4 | 312 | M | 12/22/2016 | 6/26/2017 |
| 3 | 6 | 53 | Z | 12/27/2016 | 7/10/2017 |
+----------+---------+--------+------+------------+-----------+
I need the following output:
+----------+------------+-----------+----------+----------+
| PersonID | SDateM | EDateM | MinPlan1 | MinPlan2 |
+----------+------------+-----------+----------+----------+
| 1 | 10/14/2016 | 1/30/2017 | M | A |
| 2 | 11/4/2016 | 4/28/2017 | E | M |
| 3 | 12/22/2016 | 6/26/2017 | Q | W |
+----------+------------+-----------+----------+----------+
where SDateM is SDate when Plan=M, EDateM is Edate when Plan=M, MinPlan1 is the Plan for the minimum OrderID for a person. And MinPlan2 is the Plan for the 2nd lowest OrderID for a person.
I apply PlanID=312 in the WHERE clause but that restricts my ability to select the MinPlans. I don't know how to write the code to extract MinPlan1 and MinPlan2.
Attempt:
SELECT
PersonID
,Sdate AS SDateM
,Edate AS EDateM
,MinPlan1
,MinPlan2
FROM
T1
WHERE
1 = 1
AND PlanID = 312
CREATE TABLE t_plan
(personID INT,
orderID INT,
planID INT,
plan_n VARCHAR(20),
sdate DATE,
edate DATE
)
INSERT INTO t_plan
VALUES(1,1,312,'M','10/14/2016','1/30/2017'),
(1,4,125,'A','10/18/2016','2/3/2017'),
(1,7,411,'B','10/25/2016','4/7/2017'),
(2,1,111,'E','10/31/2016','4/21/2017'),
(2,3,312,'M','11/4/2016','4/28/2017'),
(2,5,253,'L','11/29/2016','5/3/2017'),
(3,1,50 ,'Q','12/2/2016','5/8/2017'),
(3,2,12 ,'W','12/8/2016','6/8/2017'),
(3,4,312,'M','12/22/2016','6/26/2017'),
(3,6,53 ,'Z','12/27/2016','7/10/2017')
SELECT personid,
MIN(CASE WHEN plan_n = 'M' THEN sdate END) sdateM,
MIN(CASE WHEN plan_n = 'M' THEN edate END) edateM,
MAX(CASE WHEN rnum = 1 THEN plan_n END) minplan1,
MAX(CASE WHEN rnum = 2 THEN plan_n END) minplan2
FROM (SELECT *,
ROW_NUMBER() OVER (PARTITION BY personid ORDER BY orderid) rnum
FROM t_plan
) t
GROUP BY personid;
Result
personid sdateM edateM minplan1 minplan2
1 2016-10-14 2017-01-30 M A
2 2016-11-04 2017-04-28 E M
3 2016-12-22 2017-06-26 Q W
I think a combination of conditional aggregation with row_number() does the trick:
select personid,
min(case when plan = 'M' then sdate end) as sdateM,
min(case when plan = 'M' then edate end) as edateM,
max(case when seqnum = 1 then plan end) as plan_1,
max(case when seqnum = 2 then plan end) as plan_2
from (select t1.*,
row_number() over (partition by personid order by orderid) as seqnum
from t1
) t1
group by person_id;
You can do this using row_number and conditional aggregation.
SELECT PersonID
,MAX(CASE WHEN plan='M' then Sdate END) AS SDateM
,MAX(CASE WHEN plan='M' then Edate END) AS EDateM
,MAX(CASE WHEN rnum=1 then plan END) AS MinPlan1
,MAX(CASE WHEN rnum=2 then plan END) AS MinPlan2
FROM (SELECT t1.*,row_number() over(partition by personId order by orderId) as rnum
FROM T1
) t
GROUP BY PersonID

Count of records between min date range and other date

I'm trying to get the count of records of users who appear between a certain date range, specifically the min(date) for each unique user and that min(date) + 14 days. I've checked this link
SQL HAVING BETWEEN a date range
but it's not what I'm looking for. Here's an example of what I'm working with and what I've tried to do
+----+------------+
| ID | ServiceDt |
+----+------------+
| 10 | 2017-03-02 |
| 10 | 2017-03-05 |
| 10 | 2017-03-06 |
| 10 | 2017-03-14 |
| 10 | 2017-03-27 |
| 11 | 2017-03-10 |
| 11 | 2017-03-19 |
| 11 | 2017-04-02 |
| 11 | 2017-04-14 |
| 11 | 2017-04-23 |
| .. | .. |
The query is:
SELECT ID, COUNT(ServiceDt) AS date_count
FROM (
SELECT ID, ServiceDt
FROM tbl
GROUP BY ID, ServiceDt
HAVING ServiceDt BETWEEN MIN(ServiceDt) AND DATEADD(day, +14, MIN(ServiceDt))
) AS R1
GROUP BY ID
When I do the above query I get the following result.
+----+------------+
| ID | date_count |
+----+------------+
| 10 | 5 |
| 11 | 5 |
| .. | .. |
I also tried using CONVERT(date, ...), but I get the same resulting table above. I want the result to be
+----+------------+
| ID | date_count |
+----+------------+
| 10 | 4 |
| 11 | 2 |
| .. | .. |
Can someone please guide me on what I can do to get my desired output, thanks
Use window functions:
select id, count(*)
from (select t.*, min(servicedt) over (partition by id) as min_sd
from tbl t
) t
where servicedt <= dateadd(day, 14, min_sd)
group by id;
Another option is to use cross apply() to get the first ServiceDt for each id and use that in your where clause.
select id, count(*) as date_count
from t
cross apply (
select top 1
i.ServiceDt
from t i
where i.Id = t.Id
order by i.ServiceDt
) x
where t.ServiceDt <= dateadd(day,14,x.ServiceDt)
group by id
rextester demo: http://rextester.com/WXA46698
returns:
+----+------------+
| id | date_count |
+----+------------+
| 10 | 4 |
| 11 | 2 |
+----+------------+