Related
Matthew earns $600 in three days. And each day how he should earn should be split into three different rows.
RDBMS is SQL Server.
id name start_date end_date Total_Dollars
---------------------------------------------------
1 Mathew 01/01/2021 03/01/2021 600
Output should be
id name start_date end_date Total_Dollars
--------------------------------------------------
1 Rahul 01/01/2021 01/01/2021 200
1 Rahul 02/01/2021 02/01/2021 200
1 Rahul 03/01/2021 03/01/2021 200
If you have a calendar table, use that:
WITH
-- need a calendar table with one row per calendar date
cal (dt) AS (
SELECT DATE '2021-01-01'
UNION ALL SELECT DATE '2021-01-02'
UNION ALL SELECT DATE '2021-01-03'
UNION ALL SELECT DATE '2021-01-04'
UNION ALL SELECT DATE '2021-01-05'
UNION ALL SELECT DATE '2021-01-06'
UNION ALL SELECT DATE '2021-01-07'
)
,
-- your input ...
indata(id,nam,start_date,end_date,total_dollars) AS (
SELECT 1,'Mathew',DATE '2021-01-01',DATE '2021-01-03',600
)
-- real query starts here, replace following comma with "WITH" ...
,
daycount(daycount) AS (
SELECT COUNT(*) FROM cal JOIN indata ON dt BETWEEN start_date AND end_date
)
SELECT
id
, nam
, dt AS start_date
, dt AS end_date
, total_dollars / daycount AS total_dollars
FROM cal
JOIN indata ON dt BETWEEN start_date AND end_date
CROSS JOIN daycount;
-- out id | nam | start_date | end_date | total_dollars
-- out ----+--------+------------+------------+---------------
-- out 1 | Mathew | 2021-01-01 | 2021-01-01 | 200
-- out 1 | Mathew | 2021-01-02 | 2021-01-02 | 200
-- out 1 | Mathew | 2021-01-03 | 2021-01-03 | 200
Or, also:
SELECT
id
, nam
, dt AS start_date
, dt AS end_date
, total_dollars // count(*) OVER(PARTITION BY id) AS total_dollars
FROM cal
JOIN indata ON dt BETWEEN start_date AND end_date
-- out id | nam | start_date | end_date | total_dollars
-- out ----+--------+------------+------------+---------------
-- out 1 | Mathew | 2021-01-01 | 2021-01-01 | 200
-- out 1 | Mathew | 2021-01-02 | 2021-01-02 | 200
-- out 1 | Mathew | 2021-01-03 | 2021-01-03 | 200
You may use a recursive query as the following:
WITH CTE AS
(
SELECT id, name, start_date SDT, end_date, Total_Dolllars
FROM T
UNION ALL
SELECT id, name, DATEADD(DAY, 1,SDT), end_date, Total_Dolllars
FROM CTE
WHERE DATEADD(DAY, 1,SDT) <= end_date
)
SELECT id, name, SDT start_date, SDT end_date,
Total_Dolllars *1.00 / COUNT(*) OVER (PARTITION BY id) Total_Dolllars
FROM CTE
ORDER BY ID, SDT;
See a demo.
I’ve got a table containing a list of patient appointments: the clinic they attended, and the date of their attendance.
I’m trying to write a query that gives me the following:
‘Which patients attended clinic ‘123-45’ at any point during the period April 2016 – March 2017, and what were the subsequent 2 appointments (the appointment date and clinic attended) for that patient’?
I’ve tried to come at this by first querying out the list of patient ID numbers for all those patients that attended clinic ‘123-45’ during the time frame, and then putting this list of Patient IDs into a WHERE clause and using ROW_NUMBER() OVER (PARTITION BY… to give me an ordered list of all appointments for each patient during the 12 month period.
SELECT
x.Patient_Id
,x.Clinic_Code
,x.Appointment_Date
,x.Row_No FROM
(
SELECT
Patient_Id
,Clinic_Code
,Appointment_Date
,ROW_NUMBER() OVER (PARTITION BY Patient_Id ORDER BY Patient_Id, Appointment_Date asc) [Row_No]
FROM
Appointments
WHERE
Appointment_Date BETWEEN '01/10/2016' AND '30/09/2017'
AND Patient_ID = 'BLO123'
) x
WHERE x.Row_No < 4
However, this has the unintended consequence of numbering any appointments that occurred prior to the clinic ‘123-45’ attendance.
So, if the following is my source:
Patient_ID | Clinic_Code | Appointment_Date
--------------------------------------------
BLO123 | QWE-QW | 01-04-2016
BLO123 | OPD-ZZ | 05-10-2016
BLO123 | 123-45 | 13-11-2016
BLO123 | 333-44 | 15-12-2016
BLO123 | 999-45 | 02-02-2017
BLO123 | 222-44 | 15-02-2017
BLO123 | 777-45 | 19-03-2017
What I'm trying to get is:
Patient_ID | Clinic_Code | Appointment_Date | Row_No
--------------------------------------------------------------
BLO123 | 123-45 | 13-11-2016 | 1
BLO123 | 333-44 | 15-12-2016 | 2
BLO123 | 999-45 | 02-02-2017 | 3
But by including the preceding appointments within the date range, I'm instead getting:
Patient_ID | Clinic_Code | Appointment_Date | Row_No
--------------------------------------------------------------
BLO123 | QWE-QW | 01-04-2016 | 1
BLO123 | OPD-ZZ | 05-10-2016 | 2
BLO123 | 123-45 | 13-11-2016 | 3
What I would like to query to do is to ignore any clinic appointments that precede the ‘123-45 attendance.
Please can anyone advise if it's possible to do this?
This approach uses a common table expression (CTE) to find the first appointment each patient has at clinic 123-45. The main body of the query returns all subsequent appointments.
Sample data:
DECLARE #Appointment TABLE
(
Patient_ID varchar(6),
Clinic_code varchar(6),
Appointment_Date date
)
;
INSERT INTO #Appointment
(
Patient_ID,
Clinic_code,
Appointment_Date
)
VALUES
('BLO123','QWE-QW','20160401'),
('BLO123','OPD-ZZ','20161005'),
('BLO123','123-45','20161113'),
('BLO123','333-44','20161215'),
('BLO123','999-45','20170202')
;
Query:
WITH
FirstAppointment AS
(
-- Find patients first vist to clinic 123-45.
SELECT
Patient_ID,
MIN(Appointment_Date) AS FirstAppointment_Date
FROM
#Appointment
WHERE
Appointment_Date >= '20160401'
AND Appointment_Date <= '20170331'
AND Clinic_code = '123-45'
GROUP BY
Patient_ID
)
SELECT
ROW_NUMBER() OVER (PARTITION BY a.Patient_ID ORDER BY a.Appointment_Date) AS Rn,
a.*
FROM
FirstAppointment AS fa
INNER JOIN #Appointment AS a ON a.Patient_ID = fa.Patient_ID
AND a.Appointment_Date >= fa.FirstAppointment_Date
;
with foo as
(
select
*
from (values
('BLO123','QWE-QW', cast('20160401' as date))
,('BLO123','OPD-ZZ',cast('20161005' as date))
,('BLO123','123-45',cast('20161113' as date))
,('BLO123','333-44',cast('20161215' as date))
,('BLO123','999-45',cast('20170202' as date))
) a(Patient_ID , Clinic_Code , Appointment_Date)
)
,lags as
(
select
*
,lag(Clinic_code,1) over (partition by Patient_id order by Appointment_Date) l1
,lag(Clinic_code,2) over (partition by Patient_id order by Appointment_Date) l2
,ROW_NUMBER() over (partition by Patient_id order by Appointment_Date) rn
from foo
)
select Patient_ID,Clinic_Code,Appointment_Date
,case when Clinic_Code='123-45' then 1
when l1='123-45' then 2
else 3 end Row_Nr
from lags
where '123-45' in (Clinic_Code,l1,l2)
The result:
+----------------------------------------------+
|Patient_ID|Clinic_Code|Appointment_Date|Row_No|
+----------------------------------------------+
|BLO123 |123-45 |2016-11-13 |1 |
|BLO123 |333-44 |2016-12-15 |2 |
|BLO123 |999-45 |2017-02-02 |3 |
+----------------------------------------------+
I have searched high and low for weeks now trying to find a solution to my problem.
As far as I can ascertain, my SQL Server version (2008r2) is a limiting factor on this but, I am positive there is a solution out there.
My problem is as follows:
A have a table with potential contiguous dates in the form of Customer-Status-DateStart-DateEnd-EventID.
I need to merge contiguous dates by customer and status - the status field can shift up and down throughout a customers pathway.
Some example data is as follows:
DECLARE #Tbl TABLE([CustomerID] INT
,[Status] INT
,[DateStart] DATE
,[DateEnd] DATE
,[EventID] INT)
INSERT INTO #Tbl
VALUES (1,1,'20160101','20160104',1)
,(1,1,'20160104','20160108',3)
,(1,2,'20160108','20160110',4)
,(1,1,'20160110','20160113',7)
,(1,3,'20160113','20160113',9)
,(1,3,'20160113',NULL,10)
,(2,1,'20160101',NULL,2)
,(3,2,'20160109','20160110',5)
,(3,1,'20160110','20160112',6)
,(3,1,'20160112','20160114',8)
Desired output:
Customer | Status | DateStart | DateEnd
---------+--------+-----------+-----------
1 | 1 | 2016-01-01| 2016-01-08
1 | 2 | 2016-01-08| 2016-01-10
1 | 1 | 2016-01-10| 2016-01-13
1 | 3 | 2016-01-13| NULL
2 | 1 | 2016-01-01| NULL
3 | 2 | 2016-01-09| 2016-01-10
3 | 1 | 2016-01-10| 2016-01-14
Any ideas / code will be greatly received.
Thanks,
Dan
Try this
DECLARE #Tbl TABLE([CusomerID] INT
,[Status] INT
,[DateStart] DATE
,[DateEnd] DATE
,[EventID] INT)
INSERT INTO #Tbl
VALUES (1,1,'20160101','20160104',1)
,(1,1,'20160104','20160108',3)
,(1,2,'20160108','20160110',4)
,(1,1,'20160110','20160113',7)
,(1,3,'20160113','20160113',9)
,(1,3,'20160113',NULL,10)
,(2,1,'20160101',NULL,2)
,(3,2,'20160109','20160110',5)
,(3,1,'20160110','20160112',6)
,(3,1,'20160112','20160114',8)
;WITH CTE
AS
(
SELECT CusomerID ,
Status ,
DateStart ,
COALESCE(DateEnd, '9999-01-01') AS DateEnd,
EventID,
ROW_NUMBER() OVER (ORDER BY CusomerID, EventID) RowId,
ROW_NUMBER() OVER (PARTITION BY CusomerID, Status ORDER BY EventID) StatusRowId FROM #Tbl
)
SELECT
A.CusomerID ,
A.Status ,
A.DateStart ,
CASE WHEN A.DateEnd = '9999-01-01' THEN NULL
ELSE A.DateEnd END AS DateEnd
FROM
(
SELECT
CTE.CusomerID,
CTE.Status,
MIN(CTE.DateStart) AS DateStart,
MAX(CTE.DateEnd) AS DateEnd
FROM
CTE
GROUP BY
CTE.CusomerID,
CTE.Status,
CTE.StatusRowId -CTE.RowId
) A
ORDER BY A.CusomerID, A.DateStart
Output
CusomerID Status DateStart DateEnd
----------- ----------- ---------- ----------
1 1 2016-01-01 2016-01-08
1 2 2016-01-08 2016-01-10
1 1 2016-01-10 2016-01-13
1 3 2016-01-13 NULL
2 1 2016-01-01 NULL
3 2 2016-01-09 2016-01-10
3 1 2016-01-10 2016-01-14
I have the following query, where the intention is to show each record with the time until the next record
Data:
gid time name
1010883478 29/03/2016 0:00:02 John
1010883527 29/03/2016 0:00:04 John
1010883578 29/03/2016 0:00:06 John
SQL:
SELECT A.[gid]
,A.[time]
,A.[name]
,(B.[time] - A.[time]) as timeTilNext
FROM [location] A CROSS JOIN [location] B
WHERE B.[gid] IN (
SELECT MIN(C.[gid])
FROM [location] C
WHERE C.[gid] > A.[gid] AND C.[name] = A.[name] )
ORDER BY A.[gid]
Current Output:
gid time name timeTilNext
1010883478 2016-03-29 00:00:02.000 John 1900-01-01 00:00:02.000
1010883527 2016-03-29 00:00:04.000 John 1900-01-01 00:00:02.000
Expected Output:
gid time name timeTilNext
1010883478 2016-03-29 00:00:02.000 John 1900-01-01 00:00:02.000
1010883527 2016-03-29 00:00:04.000 John 1900-01-01 00:00:02.000
1010883578 2016-03-29 00:00:06.000 John -1 (or whatever)
However, it does not show a record for the highest [gid] for a given [name] (only the second highest).
I'm hoping for the highest [gid] to show -1 for timeTilNext, to indicate that there is no more events.
Any ideas about how to modify my query?
In SQL Server 2012 you can use LEAD window function to get the value of the "next" row.
DECLARE #location TABLE ([gid] int, [time] datetime, [name] varchar(50));
INSERT INTO #location ([gid], [time], [name]) VALUES
(1010883478, '2016-03-29 00:00:02', 'John'),
(1010883527, '2016-03-29 00:00:04', 'John'),
(1010883578, '2016-03-29 00:00:06', 'John');
SELECT
A.[gid]
,A.[time]
,A.[name]
,LEAD(A.[time]) OVER(PARTITION BY A.[name] ORDER BY A.[gid]) AS NextTime
,ISNULL(DATEDIFF(second, A.[time],
LEAD(A.[time]) OVER(PARTITION BY A.[name] ORDER BY A.[gid])), -1) AS SecondsTillNext
FROM #location A
ORDER BY A.[gid];
Result
+------------+-------------------------+------+-------------------------+-----------------+
| gid | time | name | NextTime | SecondsTillNext |
+------------+-------------------------+------+-------------------------+-----------------+
| 1010883478 | 2016-03-29 00:00:02.000 | John | 2016-03-29 00:00:04.000 | 2 |
| 1010883527 | 2016-03-29 00:00:04.000 | John | 2016-03-29 00:00:06.000 | 2 |
| 1010883578 | 2016-03-29 00:00:06.000 | John | NULL | -1 |
+------------+-------------------------+------+-------------------------+-----------------+
If the "next" row is not available, LEAD would return NULL. You can use ISNULL() to replace it with some non-null value if you want.
select
*,-1 as 'time until next ' from location t1
where time=(select max(time) from location t2 where t1.name=t2.name) b
SELECT A.gid,A.name,A.time,
(
(SELECT MIN(B.time) FROM [location] B WHERE B.time>A.time AND B.name=A.name)
-
A.time
) as timeTilNext
FROM [location] A
I have the following table:
OrderID | OldOrderID | Action | EntryDate | Source
1 | NULL | Insert | 2016-01-12| A
1 | NULL | Remove | 2016-01-13| A
2 | NULL | Insert | 2016-01-12| B
3 | NULL | Insert | 2016-01-12| C
4 | 3 | Insert | 2016-01-13| C
4 | NULL | Remove | 2016-01-14| C
I want to query all orders that are currently active orders - they dont have the action remove. Currently I do it with this query :
WITH Active AS
(
SELECT *, rn = ROW_NUMBER()
OVER (PARTITION BY OrderID,Source ORDER BY EntryDate DESC)
FROM Orders
)
SELECT *
FROM Active WHERE [Action] <> 'Remove' AND rn = 1;
The problem is that some orders get child orders (OrderID 3 gets a child OrderID 4) and if a child ever gets the Action Remove the query should also ignore the parent, but with the current query it dosent.
In short the current query gets me this result:
OrderID | OldOrderID | Action | EntryDate | Source
2 | NULL | Insert | 2016-01-12| B
3 | NULL | Insert | 2016-01-12| C
But I need this result:
OrderID | OldOrderID | Action | EntryDate | Source
2 | NULL | Insert | 2016-01-12| B
Is it possible to fix the query to get a result like this?
Try this:
;WITH CTE AS (
SELECT OrderID, OldOrderID, Action, EntryDate, Source,
COUNT(CASE WHEN Action = 'Remove' THEN 1 END)
OVER (PARTITION BY OrderID) AS IsRemoved,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY EntryDate) AS rn
FROM Orders
)
SELECT c1.*
FROM CTE AS c1
LEFT JOIN CTE AS c2 ON c1.OrderID = c2.OldOrderID AND c2.IsRemoved >= 1
WHERE c1.rn = 1 AND c1.IsRemoved = 0 AND c2.IsRemoved IS NULL
The above query uses COUNT() OVER() in order to count the number of occurrences of Action = 'Remove' within each OrderID partition. Hence, a value of IsRemoved that is equal to or greater than 1 identifies a 'removed' order.
I also asked the question on dba stackexchange and got the following answer, which works well.