Select Times in a day by Pivot in SQL - sql-server

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

Related

Referencing the current row outer apply column within separate outer join

Recently I've been tasked with creating a report that outputs sales information by Date of Business and Hour of the Day.
Here is the query I have currently written.
WITH CTE AS
(
SELECT 0 AS Count
UNION ALL
SELECT Count + 1
FROM CTE
WHERE Count + 1 <= 23
),
ALLDATES AS
(
SELECT CONVERT(datetime, #startDate) AS [DOB]
UNION ALL
SELECT DATEADD(DAY, 1, [DOB])
FROM AllDates
WHERE [DOB] < #endDate
)
SELECT D.DOB, A.Count AS [Hour], CONCAT(A.Count, ':00') AS [DisplayHour]
, B.OrderModeName, COALESCE(B.Sales_Total, 0) AS [Sales]
, COALESCE(B.Comps, 0) AS Comps, COALESCE(B.Promos, 0) AS Promos
FROM CTE AS A
OUTER APPLY (SELECT DOB FROM ALLDATES) D
LEFT OUTER JOIN (
SELECT DATEPART(HH, ItemDetail.TransactionTime) AS [Hour]
, OrderMode.OrderModeName, SUM(ItemDetail.GrossPrice) Sales_Total
, SUM(CompAmount) AS Comps, SUM(PromoAmount) AS Promos
FROM ItemDetail
INNER JOIN OrderMode ON OrderMode.OrderModeID = ItemDetail.OrderModeID
WHERE ItemDetail.DOB = D.DOB /*NEED HELP HERE*/ AND LocationID IN (
SELECT LocationID
FROM LocationGroupMember
WHERE LocationGroupID = '#locationGroupID'
)
GROUP BY ItemDetail.DOB, DATEPART(HH, ItemDetail.TransactionTime), OrderMode.OrderModeName
) AS B
ON A.Count = B.Hour
ORDER BY D.DOB, A.Count
Where I am struggling is being able to reference the current row's DOB column that is coming from the OUTER APPLY.
I have tried WHERE ItemDetail.DOB = D.DOB, however I receive an error that the identifier can't be bound. Am I correct that in understanding that the outer applied data is not visible to the subquery within the join?
Here is an example of the output I'm expecting:
DOB | Hour | Display Hour | OrderModeName | Sales | Comps | Promos
1/8/2020 | 17 | 17:00 | Order | 163.17 | 0 | 0 <-- Sales for Hour and Order Mode present
1/8/2020 | 23 | 23:00 | | 0 | 0 | 0 <-- No sales at all for a given hour
Thanks in advance for any direction and advice!
The basic pattern here is to CROSS JOIN to define the result "grain" and then LEFT JOIN the fact table to populate the rows for which data exists. EG
WITH CTE AS
(
SELECT 0 AS Count
UNION ALL
SELECT Count + 1
FROM CTE
WHERE Count + 1 <= 23
),
ALLDATES AS
(
SELECT CONVERT(datetime, #startDate) AS [DOB]
UNION ALL
SELECT DATEADD(DAY, 1, [DOB])
FROM AllDates
WHERE [DOB] < #endDate
),
ALLHOURS as
(
SELECT D.DOB, A.Count AS [Hour], CONCAT(A.Count, ':00') AS [DisplayHour]
FROM CTE AS A
CROSS JOIN ALLDATES D
),
ITEM_SUMMARY as
(
SELECT DOB, DATEPART(HH, ItemDetail.TransactionTime) AS [Hour], OrderMode.OrderModeName, SUM(ItemDetail.GrossPrice) Sales_Total, SUM(CompAmount) AS Comps, SUM(PromoAmount) AS Promos
FROM ItemDetail
INNER JOIN OrderMode ON OrderMode.OrderModeID = ItemDetail.OrderModeID
AND LocationID IN (SELECT LocationID FROM LocationGroupMember WHERE LocationGroupID = #locationGroupID)
where DOB >= #startDate
and DOB < #endDate
GROUP BY ItemDetail.DOB, DATEPART(HH, ItemDetail.TransactionTime), OrderMode.OrderModeName
)
select ALLHOURS.DOB,
ALLHOURS.Count AS [Hour],
CONCAT(ALLHOURS.Count, ':00') AS [DisplayHour],
ITEM_SUMMARY.OrderModeName,
COALESCE(ITEM_SUMMARY.Sales_Total, 0) AS [Sales],
COALESCE(ITEM_SUMMARY.Comps, 0) AS Comps,
COALESCE(ITEM_SUMMARY.Promos, 0) AS Promos
from ALLHOURS
LEFT OUTER JOIN ITEM_SUMMARY
on ITEM_SUMMARY.DOB = ALLHOURS.DOB
and ITEM_SUMMARY.Hour = ALLHOURS.Hour

SQL Server - converting 31's to 30's on MonthEnd

I have a little problem with my code, I just want to convert all 31's end of the month to 30's. I know its a there's a little trick behind it.
DECLARE #StartYear DATE = '20170101'
DECLARE #EndYear DATE = '20171231'
;WITH n AS (SELECT n FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Dates AS (
SELECT TOP (DATEDIFF(DAY, #StartYear, #EndYear)+1)
DatesOnSelect = CONVERT(DATE,DATEADD(DAY,ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1,#StartYear))
FROM n AS deka CROSS JOIN n AS hecto CROSS JOIN n AS kilo
CROSS JOIN n AS tenK CROSS JOIN n AS hundredK
ORDER BY DatesOnSelect
)
SELECT DISTINCT MonthEnd = CONVERT(DATE, DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0)), 101) FROM dates
Using a case expression to subtract an extra day if the month end is 31.
DECLARE #StartYear DATE = '20170101'
DECLARE #EndYear DATE = '20171231'
;WITH n AS (SELECT n FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Dates AS (
SELECT TOP (DATEDIFF(DAY, #StartYear, #EndYear)+1)
DatesOnSelect = CONVERT(DATE,DATEADD(DAY,ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1,#StartYear))
FROM n AS deka CROSS JOIN n AS hecto CROSS JOIN n AS kilo
CROSS JOIN n AS tenK CROSS JOIN n AS hundredK
ORDER BY DatesOnSelect
)
SELECT DISTINCT MonthEnd
= CONVERT(varchar(10),
case when day(DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0)))=31
then DATEADD(DAY,-2,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0))
else DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0))
end , 101)
FROM dates
rextester demo: http://rextester.com/NEXST15257
returns:
+------------+
| MonthEnd |
+------------+
| 01/30/2017 |
| 02/28/2017 |
| 03/30/2017 |
| 04/30/2017 |
| 05/30/2017 |
| 06/30/2017 |
| 07/30/2017 |
| 08/30/2017 |
| 09/30/2017 |
| 10/30/2017 |
| 11/30/2017 |
| 12/30/2017 |
+------------+
A calendar/tally table would do the trick, but you can all accomplish this with an ad-hoc tally table. If you need more than 6 years just change the sub-query to From master..spt_values n1,master..spt_values n2
Declare #Date1 date = '2017-01-01'
Declare #Date2 date = '2017-12-31'
Select D=max(D)
From (Select Top (DateDiff(DD,#Date1,#Date2)+1) D=DateAdd(DD,-1+Row_Number() Over (Order By (Select Null)),#Date1) From master..spt_values) A
Where Day(D) between 28 and 30
Group By Year(D),Month(D)
Returns
D
2017-01-30
2017-02-28
2017-03-30
2017-04-30
2017-05-30
2017-06-30
2017-07-30
2017-08-30
2017-09-30
2017-10-30
2017-11-30
2017-12-30

Update table with overlap date range and change status

I have a table with following column and I would like to update it as following.
The Logic is the start date take the date will be updated if overlap with following rules: take the earliest start date and enddate of the latest row with overlapping date based on member id. And the status of the remaining overlap column will be updated to 2. Hope someone could help.
ID MemberID StartDate EndDate Status
1 2 2015-01-01 2015-02-28 1
2 2 2015-02-01 2015-02-03 1
3 2 2015-02-01 2015-03-01 1
4 1 2015-02-01 2015-02-28 1
5 3 2015-02-01 2015-02-28 1
6 2 2015-05-01 2015-05-20 1
I would like to update to
ID MemberID StartDate EndDate Status
1 2 2015-01-01 2015-03-01 1
2 2 2015-01-01 2015-03-01 2
3 2 2015-01-01 2015-03-01 2
4 1 2015-02-01 2015-02-28 1
5 3 2015-02-01 2015-02-28 1
6 2 2015-05-01 2015-05-20 1
I think this should do it :
update a set
a.startdate =
(select min(startdate) from #table where memberID = a.memberID),
a.enddate =
(select max(enddate) from #table where memberID = a.memberID),
a.status =
case when a.id =
(select min(id) from #table where memberID = a.memberID)
then status else 2
end
from #table a
Try this,
---- Creating CTE for finding overlapped dates
;WITH CTE AS (
SELECT A.ID,
B.ID AS MAPPED_ID,
A.MEMBERID,
B.STARTDATE,
B.ENDDATE,
B.STATUS
FROM #YOUR_TABLE A
JOIN #YOUR_TABLE B ON B.STARTDATE <= A.ENDDATE-- Condition for finding the overlapped dates
AND B.ENDDATE >= A.STARTDATE
AND A.MEMBERID = B.MEMBERID)-- end here
UPDATE T
SET T.STARTDATE = A.STARTDATE,
T.ENDDATE = A.ENDDATE,
T.STATUS = A.STATUS
FROM #YOUR_TABLE T
JOIN (SELECT ID,
MEMBERID,
STARTDATE,
ENDDATE,
STATUS=CASE
WHEN RN > 1 THEN 2
ELSE 1
END
FROM (SELECT T.ID,
T.MEMBERID,
CS1.STARTDATE,
CS2.ENDDATE,
ROW_NUMBER() -- ROWNUMBER FOR FINDING THE STATUS
OVER(
PARTITION BY T.MEMBERID, CS1.STARTDATE, CS2.ENDDATE
ORDER BY T.ID) AS RN
FROM #YOUR_TABLE T
CROSS APPLY (SELECT CAST(MIN(STARTDATE)AS DATETIME) AS STARTDATE --- FINDING MIN(STARTDATE) FOR THE OVERLAPPED GROUP
FROM CTE A
WHERE A.ID = T.ID) CS1
CROSS APPLY (SELECT ENDDATE -- FINDING LAST ENDDATE FOR THE OVERLAPPED GROUP (IE RN=1)
FROM (SELECT ENDDATE,--- ROW_NUMBER FOR THE OVERLAPPED GROUPS
ROW_NUMBER()
OVER(
ORDER BY B.MAPPED_ID DESC) AS RN
FROM CTE B
WHERE B.ID = T.ID)A
WHERE A.RN = 1)CS2)A)A ON A.ID = T.ID
SELECT *
FROM #YOUR_TABLE

SQL to split mutilple row by date

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.

Different order by clause for different columns

I hope my title made sense, here's a sample of my table.
My table has 1 column for a unique product barcode, and 4 date columns which is for storing what date and time a product passed an inspection area(4 of them), i removed the date in my example so just assume they're all the same dates.
Product | Time1 | Time2 | Time3 | Time4
---------------------------------------
A | 10:00 | 10:15 | 10:30 | 10:45
B | 10:05 | 10:25 | 10:35 | 10:50
C | 10:10 | 10:20 | 10:40 | 10:55
Output:
Inspect1 | Inspect2 | Inspect3 | Inspect4
-----------------------------------------
A A A A
B C B B
C B C C
In my output, I want to select the product multiple times with different order by(order by Time1, order by Time2, etc.)
I used case select to select the product multiple times, how do I put an order by so that I get something like my example above.
SELECT
CASE WHEN time1 BETWEEN '10:00' AND '11:00' THEN product END AS inspect1,
CASE WHEN time2 BETWEEN '10:00' AND '11:00' THEN product END AS inspect2,
CASE WHEN time3 BETWEEN '10:00' AND '11:00' THEN product END AS inspect3,
CASE WHEN time4 BETWEEN '10:00' AND '11:00' THEN product END AS inspect4
FROM Table
Here is a solution using ROW_NUMBER:
SQL Fiddle
WITH CteUnion(Product, TimeNum, Value) AS(
SELECT Product, 'Time1', Time1 FROM YourTable UNION ALL
SELECT Product, 'Time2', Time2 FROM YourTable UNION ALL
SELECT Product, 'Time3', Time3 FROM YourTable UNION ALL
SELECT Product, 'Time4', Time4 FROM YourTable
),
CteRN AS(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY TimeNum ORDER BY Value)
FROM CteUnion
),
CteOrd AS(
SELECT *,
Ord = ROW_NUMBER() OVER(PARTITION BY RN ORDER BY Value)
FROM CteRN
)
SELECT
Inspect1 = MAX(CASE WHEN Ord = 1 THEN Product END),
Inspect2 = MAX(CASE WHEN Ord = 2 THEN Product END),
Inspect3 = MAX(CASE WHEN Ord = 3 THEN Product END),
Inspect4 = MAX(CASE WHEN Ord = 4 THEN Product END)
FROM CteOrd
GROUP BY RN
ORDER BY RN

Resources