UNPIVOT Holiday Hours - sql

I have a table, that keeps track of store holiday hours:
LOCATION_ID DATE1 TIMES1 DATE2 TIMES2
123456 2020-12-12 10:00AM-09:00PM 2020-12-19 10:00AM-09:00PM
This is a highly oversimplified table. There's about 30 columns horzontially consisting of store operating hours by date - It continues (DATE3, TIMES3, DATE4, TIMES4, etc).
I need to unpivot the values vertically, ensuring the date and time values are on the same record.
(NOTE: Once I figure out to structure the UNPIVOT expression properly, I will use Dynamic SQL on my own to pivot the column names)
Desired Outcome:
LOCATION_ID DATE TIME
123456 2020-12-12 10:00AM-09:00PM
123456 2020-12-19 10:00AM-09:00PM
I tried using UNPIVOT, but I'm stuck. Any ideas?
SAMPLE DATA:
CREATE TABLE #HOURS (LOCATION_ID int, DATE1 varchar(255), TIMES1 varchar(255), DATE2
varchar(255), TIMES2 varchar(255));
INSERT INTO #HOURS VALUES ('123456', '2020-12-12', '10:00AM-09:00PM','2020-12-19','10:00AM-09:00PM' )
Code that I tried:
SELECT *
FROM (SELECT location_id,
[date1],
[times1],
[date2]
FROM #hours) AS cp
UNPIVOT ( pivotvalues
FOR pivvalues IN ([Date1],
[date2],
[times1]) ) AS up1

Gordon is 100% correct (+1).
However, if you are looking for a dynamic approach WITHOUT having to use Dynamic SQL, consider the following.
Example
Select Location_ID
,Date = max(case when [Item] like 'DATE%' then Value end)
,Time = max(case when [Item] like 'TIME%' then Value end)
From (
select A.Location_ID
,Grp = replace(replace([Item],'DATE',''),'TIMES','')
,B.*
from #hours A
Cross Apply [dbo].[tvf-XML-Unpivot-Row]( (Select A.* for XML RAW) ) B
Where [Item] not in ('LOCATION_ID')
) A
Group By Location_ID,Grp
Returns
Location_ID Date Time
123456 2020-12-12 10:00AM-09:00PM
123456 2020-12-19 10:00AM-09:00PM
The Table-Valued Function if Interested
CREATE FUNCTION [dbo].[tvf-XML-UnPivot-Row](#XML xml)
Returns Table
As
Return (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From #XML.nodes('//#*') xNode(xAttr)
)

Don't use unpivot. Use apply:
select h.location_id, v.date, v.time
from #hours h cross apply
(values (h.date1, h.times1), (h.date2, h.times2)
) v(date, time);
unpivot is non-standard syntax that does exactly one thing. APPLY is the SQL Server implementation of lateral joins. This is a very powerful join type -- using it for unpivoting is a good way to start learning the syntax.

Related

Reshape table wide to long

I'm trying to reshape a wide data table to long. My current code works but I think it's inefficient and wonder if there's a better way to do this. The original table looks like this:
Location Date1 Date2 Date3 ..... Date 80
A 1-Jan-15 3-Mar-15 7-Apr-15 4-Apr-16
B 3-Jan-15 5-Mar-15 6-Apr-15 3-Apr-16
c 2-Jan-15 7-Mar-15 8-Apr-15 2-Apr-16
And I want to reshape it like this:
Location Date
A 1-Jan-15
A 3-Mar-15
A 7-Apr-15
.
.
A 4-Apr-16
B 3-Jan-15
...
This is the code I used but since there are 80 date variables, I found it inefficient to list all 80 values in the cross apply clause. Is there a better way to get the same result?
select t.Location, Date
from my_table t
cross apply
(
values (1, Date1),
(2, Date2),
(3, Date3),
...
(80, Date80)
) x (Location, Date);
Here is an option that will dynamically unpivot your data with using dynamic sql
Example
Select A.Location
,B.*
From YourTable A
Cross Apply (
Select [Key]
,Value
From OpenJson((Select A.* For JSON Path,Without_Array_Wrapper ))
Where [Key] not in ('Location')
) B
Returns

Semivariance of variable

I'm new with sql and I struggle with such a problem. Let's suppose I have a table like this:
Date Value
2014-01-01 1248.56
2014-01-02 1247.24
2014-01-03 1245.82
2014-01-04 1252.07
...
All I want to do is count semivariance of variable 'Value'.
Semivariance only takes into account those records which are less than the average of the sample. So basically it is just a transformartion of simply variance.
Any help would be appreciated!
you can try something like this
SELECT COUNT(*)
FROM
Table1
WHERE Value < (SELECT AVG(Value) FROM Table1)
If you need avg value then you can use such code.
CREATE TABLE #test
(
date DATE,
value NUMERIC(10,2)
)
INSERT INTO #test VALUES ('2014-01-01' , 1248.56 ),
('2014-01-02' , 1247.24),
('2014-01-03' , 1245.82),
('2014-01-04' , 1252.07);
SELECT * FROM #test a
CROSS JOIN (SELECT AVG(value) avg_value FROM #test) b
WHERE a.value < b.avg_value

How to transform the table columns to vertical data in Sql server tables?

I would like to transform one Sql server table into another.
Original table
Period Date Portfolio Benchmark
Pre0Month 12/31/2014 -0.0001 -0.0025
Pre1Month 11/31/2014 0.0122 0.0269
Pre2Month 10/31/2014 0.0176 0.0244
After transformation
Returns Pre0Month Pre1Month Pre2Month
Portfolio -0.0001 0.0122 0.0176
Benchmark -0.0025 0.0269 0.0244
Considering the name of the table to be MyTable, you can pivot it the following way:
SELECT * FROM
(
SELECT Period, [Returns], value
FROM MyTable
CROSS APPLY
(
SELECT 'Portofolio', CAST(Portofolio as varchar(10))
UNION ALL
SELECT 'Benchmark', CAST(Benchmark as varchar(10))
) c([Returns], value)
) d
PIVOT
(
MAX(value)
FOR Period IN (Pre0Month, Pre1Month, Pre2Month)
) piv;
This requires a combination of PIVOT and UNPIVOT:
DECLARE #t TABLE(period VARCHAR(32),[date] DATETIME, portfolio DECIMAL(28,4), benchmark DECIMAL(28,4));
INSERT INTO #t(period,[date],portfolio,benchmark)VALUES('Pre0Month','2014-12-31',-0.0001,-0.0025);
INSERT INTO #t(period,[date],portfolio,benchmark)VALUES('Pre1Month','2014-11-30',0.0122,0.0269);
INSERT INTO #t(period,[date],portfolio,benchmark)VALUES('Pre2Month','2014-10-31',0.0176,0.0244);
SELECT
*
FROM
(
SELECT
*
FROM
(
SELECT
period,
portfolio,
benchmark
FROM
#t
) AS t
UNPIVOT(
value
FOR Returns IN (portfolio,benchmark)
) AS up
) AS t
PIVOT(
MAX(value)
FOR period IN ([Pre0Month],[Pre1Month],[Pre2Month])
) AS p;
Result is the following:
Returns Pre0Month Pre1Month Pre2Month
benchmark -0.0025 0.0269 0.0244
portfolio -0.0001 0.0122 0.0176
Because you are using SQL-Server you can use the pivot command to do what you want. check it out here: https://technet.microsoft.com/en-us/library/ms177410%28v=sql.105%29.aspx
You can use the DateDiff function to separate the dates out by month easily too. https://msdn.microsoft.com/en-us/library/ms189794.aspx

Split single column data into two columns

Name time type
--------------------------------------------
Abc 2014-01-11 10:41:37.000 In
Abc 2014-01-11 18:12:37.000 Out
def 2014-01-07 18:25:37.000 In
def 2014-01-07 20:00:02.000 Out
How to split this data by in out and by name in SQL Server? Result should look like this
Name IN Out
---------------------------------------------------------
Abc 2014-01-11 10:41:37.000 2014-01-11 18:12:37.000
def 2014-01-07 18:25:37.000 2014-01-07 20:00:02.000
Please help me
How about pair them together (in and out), if that's what you need. Then you can filter "in" and always find nearest corresponding "out" after that one. I guess the answer here can be really complicated or really easy depending on your data (what are assumptions and how dirty they can be).
In the following example [log] table contains your data.
select name,
time as [In],
(select top 1 time from [log] il where il.name=ol.name and il.time>ol.time order by il.time) as [Out]
from [log] ol
where type='in'
Hope that helps.
i would like to introduce with pivot in sql server which developer usually use in these situations :
http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
http://blogs.msdn.com/b/spike/archive/2009/03/03/pivot-tables-in-sql-server-a-simple-sample.aspx
for your example i am bulding a table and solving your problem using pivot:
Create Table #temp (Name varchar(10) , [time] datetime , type char(3)
)
puting some sample data
insert into #temp SELECT 'Abc' , '2014-01-11 10:41:37.000', 'In'
union all SELECT 'Abc' , '2014-01-11 18:12:37.000', 'Out' union
all SELECT 'def', '2014-01-07 18:25:37.000', 'In' union all
SELECT 'def' , '2014-01-07 20:00:02.000', 'Out'
and here is pivot query
SELECT Name , [IN] ,[OUT] FROM ( Select NAme , [Time] , [Type] from
#temp ) InnerTBL PIVOT
( max([Time]) FOR [Type] in ([IN] , [OUT]) )TBL

finding a mismatch while iterating rows in sql

I have this issue where date keys where just inserted into a table through SQL Server. They are populated iteratively in the fashion shown below:
20130501
20130502
20130503
...
I am currently trying to find any row where one of the dates was skipped, i.e:
20130504
20130506
20130507
I'm still a rookie in SQL Server and I have looked at CURSOR but I'm having some trouble understanding how to go about about querying this. Any help would be appreciated. Thanks.
Using some tricks from the Itzik Ben-Gan school of thought. The easiest way to find gaps is with the use of a tally table. Here is a way to create a small one into a table variable, but i would recommend creating a substantiated Numbers table because they're really handy for this kind of thing. You can find a bunch of examples on how to do that here.
First create a number table
DECLARE #Numbers TABLE ( [Number] INT );
INSERT INTO #Numbers
(
Number
)
SELECT TOP 1000
ROW_NUMBER() OVER (ORDER BY [s1].[object_id]) AS Number
FROM sys.objects s1
CROSS JOIN sys.objects s2
Next I needed to create a temp table to recreate your example
DECLARE #ExampleDates TABLE ( [RecordDateKey] INT );
INSERT INTO #ExampleDates
( [RecordDateKey] )
VALUES ( 20130501 ),
( 20130502 ),
( 20130503 ),
( 20130504 ),
( 20130506 ),
( 20130507 ),
( 20130508 ),
( 20130511 );
this syntax only works 2008-r2 and forward but since i'm just staging data it's not really a big deal. Just leaving this note for other people testing this example.
Finally we need to do some conversion work.
For larger sets, it might be beneficial to substantiate this data, but for this small example, a cte sufficed.
WITH date_convert
AS (
SELECT [RecordDateKey]
, CONVERT(DATETIME, CAST([RecordDateKey] AS VARCHAR(50)), 112) [RecordDate]
FROM #ExampleDates ed
) ,
date_range
AS (
SELECT DATEDIFF(DAY, MIN([RecordDate]), MAX([RecordDate])) [Range]
, MIN([RecordDate]) [StartDate]
FROM [date_convert]
) ,
all_dates
AS (
SELECT CONVERT(INT, CONVERT(VARCHAR(8), DATEADD(DAY, num.[Number], [StartDate]), 112)) AS [RecordDateKey]
, DATEADD(DAY, num.[Number], [StartDate]) [RecordDate]
FROM #Numbers num
CROSS JOIN [date_range] dr
WHERE num.[Number] <= dr.[Range]
)
SELECT [RecordDateKey]
, [RecordDate]
FROM all_dates ad
WHERE NOT EXISTS ( SELECT 1
FROM [date_convert] dc
WHERE ad.[RecordDate] = dc.RecordDate )
date_convert: changes the key you provided to a datetime for easy comparison and for dateadd.
date_range: finds the range of dates, and where the range starts.
all_dates: finds all of the dates that should have existed in your range.
The final select finds the records in the data that aren't in the generated set.
Using this code, this was my output. This should find gaps regardless of gap size. Which appeared to be the issue with the current accepted answer.
RecordDateKey RecordDate
------------- ----------
20130505 2013-05-05 00:00:00.000
20130509 2013-05-09 00:00:00.000
20130510 2013-05-10 00:00:00.000
SELECT *
FROM table
WHERE date - 1 NOT IN (SELECT date FROM table)
It's probably not super efficient but it should work.