Let's say I have these values in a table
| Start Date | End date |Other Value
---------------------------------------------------------------------
| 2015-01-07 01:00:00.000 | 2015-01-08 04:00:00.000 | Yes
| 2015-01-08 10:00:00.000 | 2015-01-10 20:00:00.000 | No
I want to write a select statement that should give me results like:
|Date | Start Date | End date |Other Value
-----------------------------------------------------------
|2015-01-07 | 01:00:00.000 | | Yes
|2015-01-08 | | 04:00:00.000 | Yes
|2015-01-08 | 10:00:00.000 | | No
|2015-01-10 | | 20:00:00.000 | No
Is there a way to do it in T-SQL?
I am using SQL Server 2008 R2.
You can do something like this..
SQL Fiddle
WITH cte AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rn,startdate,enddate, othervalue FROM yourtable
)
,cte1 AS
(
SELECT rn,cast(startdate AS DATE) AS [date]
,CAST(startdate AS TIME) AS [Start Date]
,null AS [End date]
, othervalue
FROM cte
UNION all
SELECT rn,cast(Enddate AS DATE) AS [date]
,null AS [Start Date]
,CAST(Enddate AS TIME) AS [End date],
othervalue
FROM cte
)
sELECT * FROM CTE1 ORDER BY RN,[Start Date] desc
You can use UNION ALL and ROW_NUMBER() to order your result, like this:
WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL) AS rownum
FROM Your_Table
)
SELECT
rownum,
CAST([Start date] AS DATE) AS [Date],
CAST([Start date] AS DATE) AS [Start date],
NULL AS [End date],
[Other Value]
FROM CTE
UNION ALL
SELECT
rownum,
CAST([End date] AS DATE) AS [Date],
NULL AS [Start date],
CAST([End date] AS DATE) AS [End date],
[Other Value]
FROM CTE
ORDER BY rownum
Using CROSS APPLY and VALUES:
SQL Fiddle
SELECT
x.*, t.OtherValue
FROM tbl t
CROSS APPLY(VALUES
(CAST(StartDate AS DATE), CAST(StartDate AS TIME), NULL),
(CAST(EndDate AS DATE), NULL, CAST(EndDate AS TIME))
)x(Date, StartDate, EndDate)
This method scans the table only once.
Related
I am trying to duplicate rows by comparing the date of the current row with date of the next row for a user ID and row should be duplicated by incrementing the date where < date of the next row.
The last row of the id should increment till the lastworkingdate. If the lastworkingdate is null, should increment the date till current date.
Input:
Output expected
Please suggest if we can implement this logic using SQL Server.
I have tried the below code
WITH cte (User_ID, Start_DateMonth, Start_DateDAY, Last_working_date_text, lead_start_datemonth) AS
(SELECT User_ID,
CONVERT(date, CAST(Start_DateMonth AS varchar(50)) + '01') AS Start_DateMonth,
Start_DateDAY,
Last_working_date_text,
LEAD(CONVERT(datetime, CAST(Start_DateMonth AS varchar(MAX)) + '01')) OVER (PARTITION BY User_ID
ORDER BY CONVERT(date, CAST(Start_DateMonth AS varchar(50)) + '01')) AS lead_start_datemonth
FROM [dbo].[Historic_Headcount3] --mytable
UNION ALL
SELECT User_ID,
CONVERT(date, DATEADD(MONTH, 1, ISNULL(Start_DateMonth, GETDATE()))),
Start_DateDAY,
Last_working_date_text,
CONVERT(datetime, CAST(lead_start_datemonth AS varchar(MAX)) + '01') AS lead_start_datemonth
FROM cte
WHERE DATEADD(MONTH, 1, Start_DateMonth) < ISNULL(lead_start_datemonth,
CASE
WHEN ISDATE(Last_working_date_text) = 1
AND Last_working_date_text != '#' THEN CONVERT(date, Last_working_date_text)
ELSE GETDATE()
END))
SELECT User_ID,
Start_DateMonth,
Start_DateDAY,
Last_working_date_text
FROM cte
ORDER BY User_ID,
Start_DateMonth;
I am getting error
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
This script will hopefully give you enough understanding of how to utilise a numbers table to increment your months, so that you can apply it once you get your data cleaning and transformation working as required:
-- Define test data
declare #t table(UserID int, StartDate date, EndDate date);
insert into #t values(1,'20190901','20200217'),(2,'20200202','20200205'),(3,'20200108',null);
-- Find maximum possible number of Month interations required
declare #Months int = (select datediff(month,min(StartDate),getdate())+1 from #t);
-- Query the data
with t(t) as(select t from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t(t)) -- Create a table with 10 rows in
,n(n) as(select top(#Months) row_number() over (order by (select null))-1 from t t1,t t2,t t3,t t4) -- Cross Join the table to itself to return a row_number() up to a possible 10*10*10*10 = 10,000 rows. Use TOP to limit this to what is actually required.
select t.UserID
,dateadd(month,n.n,t.StartDate) as StartDateMonth
,isnull(t.EndDate,getdate()) as EndDate
from #t as t
join n
on dateadd(month,n.n,t.StartDate) <= isnull(t.EndDate,getdate()) -- JOIN the dates to the row_number, incrementing the months as required
order by UserID
,StartDateMonth;
Output:
+--------+----------------+------------+
| UserID | StartDateMonth | EndDate |
+--------+----------------+------------+
| 1 | 2019-09-01 | 2020-02-17 |
| 1 | 2019-10-01 | 2020-02-17 |
| 1 | 2019-11-01 | 2020-02-17 |
| 1 | 2019-12-01 | 2020-02-17 |
| 1 | 2020-01-01 | 2020-02-17 |
| 1 | 2020-02-01 | 2020-02-17 |
| 2 | 2020-02-02 | 2020-02-05 |
| 3 | 2020-01-08 | 2020-02-26 |
| 3 | 2020-02-08 | 2020-02-26 |
+--------+----------------+------------+
The code worked for me
with cte ([CountryId],[Is_EO_EmployeeId],[DepartmentId],[FunctionId],[Employee_StatusId],[Event_ReasonId],User_ID, Start_DateMonth, Start_DateDAY, Last_working_date_text, lead_start_datemonth) as (
SELECT [CountryId],[Is_EO_EmployeeId],[DepartmentId],[FunctionId],[Employee_StatusId],[Event_ReasonId], User_ID, Start_DateMonth,Start_DateDAY,Last_working_date_text, CASE WHEN lead_start_datemonth IS NULL THEN NULL ELSE Convert(datetime, CAST(lead_start_datemonth AS Nvarchar(max))+'01')END AS lead_start_datemonth FROM (
select
[CountryId],
[Is_EO_EmployeeId],
[DepartmentId],
[FunctionId],
[Employee_StatusId],
[Event_ReasonId],
User_ID,
CONVERT(datetime, CAST(Start_DateMonth AS varchar(50)) + '01') AS Start_DateMonth,
Start_DateDAY,
Last_working_date_text,
lead(Start_DateMonth) over(partition by User_ID order by CONVERT(datetime, CAST(Start_DateMonth AS varchar(50)) + '01')) lead_start_datemonth
from [dbo].[Historic_Headcount3]) T--mytable
union all
select
[CountryId],
[Is_EO_EmployeeId],
[DepartmentId],
[FunctionId],
[Employee_StatusId],
[Event_ReasonId],
User_ID,
Convert(datetime,DateAdd(month,1, ISNULL(Start_DateMonth,GetDate()))),
Start_DateDAY,
Last_working_date_text,
lead_start_datemonth
from cte
where DateAdd(month,1, Start_DateMonth) < ISNULL(lead_start_datemonth,CASE WHEN ISDATE(Last_working_date_text)=1 AND Last_working_date_text != '#' THEN CONVERT(datetime,Last_working_date_text) ELSE GETDATE() END)
)
select [CountryId],[Is_EO_EmployeeId],[DepartmentId],[FunctionId],[Employee_StatusId],[Event_ReasonId],User_ID, LEFT(CONVERT(varchar, Start_DateMonth,112),6) AS Start_DateMonth, Start_DateDAY ,Last_working_date_text from cte order by User_ID, Start_DateMonth OPTion (maxrecursion 0)
I need to combine the data from two hospital activity reports. What happens is this: Patients get admitted to a spinal department. Some of whom then get referred to put on ventilation. After a while patient is discharged. Later, the same patient may or may not get re-referred back to the spinal department and may or may not be re-referred for ventilation. I am sent activity data in two reports:
Monthly Activity Report:
[MRN] [NHS Number] [Admission Date] [DoB] [Blah] [Blah]
Ventilation Report
[MRN] [Admission Date] [Ventilation Days] [Ventilation Type] [blah] [blah]
N.B. The Admission Date on the Ventilation Report is the date they are referred for ventilation. This may be the same day, or some date after they are referred into spinal dept.
What I need to achieve is this: join each row to the most immediate entry prior to the patient being referred to ventilation. I need to avoid duplicating rows, but I cannot join it to the most recent row in the Monthly Activity Report as this could easily be a subsequent referral and the other information will not be applicable.
By following the answer to a similar question on Stackoverflow, I came up with this code:
SELECT [Year], [Month], MRN, [NHS Number], [Admission Date] AS [VD
Admission Date],
[Admit date] AS [MAR Admit Date], Days,
[Ventilation Type], [Ventilation Route], [Ventilation Time], [Package of
care class],
[Para/Tetra/No deficit], [Social charge date commenced ] AS [Social charge
date], [Discharge date]
FROM Spinal_Costing.Vented_Days VD
LEFT JOIN (SELECT *, ROW_NUMBER() OVER(PARTITION BY [Patient MRN] ORDER BY
[Admit Date] DESC) AS row
FROM Spinal_Costing.MAR
) MAR ON VD.MRN = MAR.[Patient MRN]
WHERE MAR.row = 1;
But this returns the most recent entry in MAR for each patient.
This can also be achieved with an apply that references the values in Vented_Days and simply returns a top 1 for each row. cross apply won't return null values whereas outer apply will:
declare #vd table(MRN int,AdmissionDate date);
declare #mar table(MRN int,AdmissionDate date);
insert into #vd values
(1,'20190102')
,(1,'20190106')
,(2,'20190104')
,(3,'20190101');
insert into #mar values
(1,'20190101')
,(1,'20190105')
,(2,'20190102');
select v.MRN
,v.AdmissionDate
,m.AdmissionDate
from #vd as v
outer apply (select top 1 m.AdmissionDate
from #mar as m
where v.MRN = m.MRN
and v.AdmissionDate >= m.AdmissionDate
order by m.AdmissionDate desc
) as m
order by v.MRN
,v.AdmissionDate;
Output
+-----+---------------+---------------+
| MRN | AdmissionDate | AdmissionDate |
+-----+---------------+---------------+
| 1 | 2019-01-02 | 2019-01-01 |
| 1 | 2019-01-06 | 2019-01-05 |
| 2 | 2019-01-04 | 2019-01-02 |
| 3 | 2019-01-01 | NULL |
+-----+---------------+---------------+
You were on the right track, you just need to add a JOIN to that derived table to limit the rows in the Spinal_Costing.MAR table to those that came at, or before discharge.
SELECT
[Year],
[Month],
MRN,
[NHS Number],
[Admission Date] AS [VD Admission Date],
[Admit date] AS [MAR Admit Date],
Days,
[Ventilation Type],
[Ventilation Route],
[Ventilation Time],
[Package of care class],
[Para/Tetra/No deficit],
[Social charge date commenced ] AS [Social charge date],
[Discharge date]
FROM
Spinal_Costing.Vented_Days VD
LEFT JOIN
(SELECT
*,
ROW_NUMBER() OVER(PARTITION BY [Patient MRN] ORDER BY [Admit Date] DESC) AS row
FROM Spinal_Costing.MAR
--added the JOIN and WHERE clause here
INNER JOIN Spinal_Costing.Vented_Days
ON Spinal_Costing.Vented_Days.MRN = Spinal_Costing.MAR.[Patient MRN]
WHERE Spinal_Costing.MAR.[Admit Date] <= Spinal_Costing.Vented_Days.[Discharge date]
) MAR ON VD.MRN = MAR.[Patient MRN]
WHERE MAR.row = 1;
I have a table that looks like the below
Date | ID | Period | ArchivedBy | ArchivedFlag | Value
2018-01-20 12:23 |23344 | Q1 | NULL | NULL | 200
2018-01-20 12:20 |23344 | NULL | P.Tills | 1 | NULL
2018-01-20 12:19 |23344 | NULL | NULL | 1 | NULL
This table represents all edits made to an agreement (each new edit gets it's own row). If a value hasn't been changed at all, it will say NULL.
so ideally the above would look like the following
Date | ID | Period | ArchivedBy | ArchivedFlag | Value
2018-01-20 |23344 | Q1 | P.Tills | 1 | 200
This returned row should show the latest state of the agreement based on the date. So for the date in my example (2018-01-20) this one row would be returned, combining all changes that were made throughout the day into 1 row which shows how it looks following all the changes throughout the day.
I hope this makes sense?
Thank you!
Here is one way using Row_Number and Group by
SELECT [Date] = Cast([Date] AS DATE),
ID,
Max(period),
Max(ArchivedBy),
Max(ArchivedFlag),
Max(CASE WHEN rn = 1 THEN [Value] END)
FROM (SELECT *,
Rn = Row_number()OVER(partition BY Cast([Date] AS DATE), ID ORDER BY [Date] DESC)
FROM Yourtable)a
GROUP BY Cast([Date] AS DATE),
ID
I would propose 2 solutions.
Simple
For each day select top 1 NOT NULL value:
SELECT G.ID, G.GD Date, Period.*, ArchivedBy.*, Value.* FROM
(SELECT DISTINCT ID, CAST(Date AS Date) GD FROM T) G
CROSS APPLY (SELECT TOP 1 Period FROM T WHERE Period IS NOT NULL AND CAST(Date AS Date)=GD ORDER BY Date DESC) Period
CROSS APPLY (SELECT TOP 1 ArchivedBy FROM T WHERE ArchivedBy IS NOT NULL AND CAST(Date AS Date)=GD ORDER BY Date DESC) ArchivedBy
CROSS APPLY (SELECT TOP 1 Value FROM T WHERE Value IS NOT NULL AND CAST(Date AS Date)=GD ORDER BY Date DESC) Value
Optimized (intuitively, not tested*)
Use varbinary sorting rules and aggregation, manually order NULLs:
SELECT CAST(Date AS Date), ID,
CAST(SUBSTRING(MAX(Arch),9, LEN(MAX(Arch))) AS varchar(10)) ArchivedBy --unbox
--other columns
FROM
(
SELECT Date, ID,
CAST(CASE WHEN ArchivedBy IS NOT NULL THEN ROW_NUMBER() OVER (PARTITION BY CAST(Date AS Date) ORDER BY Date) ELSE 0 END AS varbinary(MAX))+CAST(ArchivedBy AS varbinary(MAX)) Arch --box
--other columns
FROM T
) Tab
GROUP BY ID, CAST(Date AS Date)
I have a table with the following format (which I cannot change)
ClientID | RefAd1 | Cluster Start Date | Cluster End Date
100001 | R1234 | 2014-11-01 |
100001 | R1234 | 2014-11-10 |
100001 | R1234 | 2014-11-20 |
What I would like to come out with is:
ClientID | RefAd1 | Cluster Start Date | Cluster End Date
100001 | R1234 | 2014-11-01 | 2014-11-10
100001 | R1234 | 2014-11-10 | 2014-11-20
100001 | R1234 | 2014-11-20 | NULL
I've searched on here, and had many attempts myself, but just can't get it working.
I can't update the source table (or add another table into the database) so I'm going to do this in a view (which I can save)
Any help would be gratefully appreciated, been going round in circles with this for a day and a bit now!
Use Self join to get next record
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER(ORDER BY [Cluster Start Date])RNO,*
FROM YOURTABLE
)
SELECT C1.ClientID,C1.RefAd1,C1.[Cluster Start Date],C2.[Cluster Start Date] [Cluster End Date]
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
Click here to view result
EDIT :
To update the table, you can use the below query
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER(ORDER BY [Cluster Start Date])RNO,*
FROM #TEMP
)
UPDATE #TEMP SET [Cluster End Date] = TAB.[Cluster End Date]
FROM
(
SELECT C1.ClientID,C1.RefAd1,C1.[Cluster Start Date],C2.[Cluster Start Date] [Cluster End Date]
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
)TAB
WHERE TAB.[Cluster Start Date]=#TEMP.[Cluster Start Date]
Click here to view result
EDIT 2 :
If you want this to be done for ClientId and RefAd1.
;WITH CTE AS
(
-- Get current date and next date for each type of ClientId and RefAd1
SELECT ROW_NUMBER() OVER(PARTITION BY ClientID,RefAd1 ORDER BY [Cluster Start Date])RNO,*
FROM #TEMP
)
UPDATE #TEMP SET [Cluster End Date] = TAB.[Cluster End Date]
FROM
(
SELECT C1.ClientID,C1.RefAd1,C1.[Cluster Start Date],C2.[Cluster Start Date] [Cluster End Date]
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1 AND C1.ClientID=C2.ClientID AND C1.RefAd1=C2.RefAd1
)TAB
WHERE TAB.[Cluster Start Date]=#TEMP.[Cluster Start Date] AND TAB.ClientID=#TEMP.ClientID AND TAB.RefAd1=#TEMP.RefAd1
Click here to view result
If you want to do it only for ClientId, remove the conditions for RefAd1
Here is the script if you just want the view you described:
CREATE VIEW v_name as
SELECT
ClientId,
RefAd1,
[Cluster Start Date],
( SELECT
min([Cluster Start Date])
FROM yourTable
WHERE
t.[Cluster Start Date] < [Cluster Start Date]
) as [Cluster End Date]
FROM yourtable t
I Have a table as:
CREATE TABLE [PersonelTraffic](
[CardNo] [int] ,
[CardDateTime] [smalldatetime] )
and rows as:
CardNo CardDateTime
1048 2014-06-02 16:30:00
1414 2014-06-02 13:11:00
1414 2014-06-02 13:59:00
1414 2014-06-02 16:43:00
How can I select times showing them in one row by days:
CardNo Date Time0 Time1 Time2 Time3
1414 2014-06-02 13:11 13:59 16:43 Null
1048 2014-06-02 16:30 Null Null Null
using Pivot operator or any other solution.
MayBe Something Like this
;with cte as
(
SELECT CardNo,CardDateTime,Cast(CardDateTime As Date)AS Date,Row_Number() over(Partition By CARDNO Order By CARDNO) AS RN
FROM personeltraffic
)
SELECT CardNo,Date,
MAX(CASE WHEN RN = 1 Then Convert(char(5), CardDateTime, 108) ELSE NULL End)AS Time1,
MAX(CASE WHEN RN = 2 Then Convert(char(5), CardDateTime, 108) ELSE NULL End)AS Time2,
MAX(CASE WHEN RN = 3 Then Convert(char(5), CardDateTime, 108) ELSE NULL End)AS Time3,
MAX(CASE WHEN RN = 4 Then Convert(char(5), CardDateTime, 108) ELSE NULL End)AS Time4
From cte
Group By CardNo,Date
SQL FIDDLE DEMO
Output:
+--------+------------+---------+---------+---------+--------+
| CardNo | Date | Time0 | Time1 | Time2 | Time3 |
+--------+------------+---------+---------+---------+--------+
| 1414 | 2014-06-02 | 13:11 | 13:59 | 16:43 | Null |
| 1048 | 2014-06-02 | 16:30 | Null | Null | Null |
+--------+------------+---------+---------+---------+--------+
You can do something like this:
;with cte as
(select cardno, convert(time,carddatetime) cardtime, row_number() over (partition by cardno order by carddatetime) rn
from personeltraffic)
select distinct p.cardno [CardNo],
convert(date,p.carddatetime) [Date],
c1.cardtime [Time0],
c2.cardtime [Time1],
c3.cardtime [Time2],
c4.cardtime [Time3]
from personeltraffic p
left join cte c1 on p.cardno = c1.cardno and c1.rn = 1
left join cte c2 on p.cardno = c2.cardno and c2.rn = 2
left join cte c3 on p.cardno = c3.cardno and c3.rn = 3
left join cte c4 on p.cardno = c4.cardno and c4.rn = 4
order by p.cardno
Basically, you use row_number() to assign a sequence number to each time within a day, and then join multiple times to get the time at a specific number in the sequence. However, considering the number of joins in this query, I would really advise you to look for an alternative for performance reasons.
Alternatively to Vignesh Kumar's answer, a PIVOT-based answer uses essentially the same execution plan:
SELECT
CardNo,
[Date],
CONVERT(CHAR(5), [1], 108) AS [Time0],
CONVERT(CHAR(5), [2], 108) AS [Time1],
CONVERT(CHAR(5), [3], 108) AS [Time2],
CONVERT(CHAR(5), [4], 108) AS [Time3]
FROM (
SELECT
CardNo,
CONVERT(DATE, CardDateTime) AS [Date],
CardDateTime,
ROW_NUMBER() OVER (PARTITION BY CardNo ORDER BY CardDateTime) AS TimeOrdinal
FROM
PersonelTraffic
) pt
PIVOT (
MAX(CardDateTime) FOR [TimeOrdinal] IN (
[1],
[2],
[3],
[4]
)
) pvt