SQL query date into new columns with unique ID - sql

I have the following columns, a 5 digit ID, date and a value. The ID repeats only when a new date is present.
ID Date Value
11111 2014-12-31 45
22222 2014-12-31 435
33333 2014-12-31 11
11111 2014-12-30 5
22222 2014-12-30 2245
33333 2014-12-30 86
11111 2014-12-29 43
22222 2014-12-29 4678
33333 2014-12-29 2494
I am trying to create an SQL query that will display the following (dates are column names):
ID 2014-12-31 2014-12-30 2014-12-29
11111 45 5 43
22222 435 2245 4678
33333 11 86 2494
What is the best way of doing this using MS SQL.
Thanks

As pointed out by the comments, you need to PIVOT your data. Here is one way using a Dynamic Crosstab.
Read this article by Jeff Moden for reference: http://www.sqlservercentral.com/articles/Crosstab/65048
CREATE TABLE temp(
ID INT,
[Date] DATE,
Value INT
)
INSERT INTO temp VALUES
(11111, '2014-12-31', 45),
(22222, '2014-12-31', 435),
(33333, '2014-12-31', 11),
(11111, '2014-12-30', 5),
(22222, '2014-12-30', 2245),
(33333, '2014-12-30', 86),
(11111, '2014-12-29', 43),
(22222, '2014-12-29', 4678),
(33333, '2014-12-29', 2494);
DECLARE #sql1 VARCHAR(2000) = ''
DECLARE #sql2 VARCHAR(2000) = ''
DECLARE #sql3 VARCHAR(2000) = ''
SELECT #sql1 =
'SELECT
ID
'
SELECT #sql2 = #sql2 +
' ,MIN(CASE WHEN [Date] = CAST(''' + CONVERT(VARCHAR(10), [Date], 120) + ''' AS Date) THEN Value END) AS ['
+ CONVERT(VARCHAR(10), [Date], 120) + ']'+ CHAR(10)
FROM(
SELECT DISTINCT [Date] FROM temp
)t
ORDER BY [Date] DESC
SELECT #sql3 =
'FROM temp
GROUP BY ID
ORDER BY ID'
PRINT (#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)
DROP TABLE temp
EDIT:
If you want to use fixed name, you'll want to assign a number for each Date. This can be done using ROW_NUMBER():
SELECT #sql2 = #sql2 +
' ,MIN(CASE WHEN [Date] = CAST(''' + CONVERT(VARCHAR(10), [Date], 120) + ''' AS Date) THEN Value END) AS [Date' + CONVERT(VARCHAR, rn) + ']'+ CHAR(10)
FROM(
SELECT
[Date],
rn = ROW_NUMBER() OVER(ORDER BY [Date])
FROM (
SELECT DISTINCT [Date]FROM temp
)x
)t
ORDER BY [Date] DESC

Related

Group and expand to new columns using a SQL query?

Having this data:
Name
Date
John
2021-03-01 10:00
Paul
2021-03-01 11:00
Paul
2021-03-01 14:20
John
2021-03-01 15:00
Paul
2021-03-01 17:00
How can I obtain this result (Dates ordered ASC)
Name
Date1
Date2
Date2
John
2021-03-01 10:00
2021-03-01 15:00
NULL
Paul
2021-03-01 11:00
2021-03-01 14:20
2021-03-01 17:00
Thank you.
If you want to make your query dynamic that means no matter how many dates you have for any given name this query will generate that number of columns automatically try below query:
Schema:
create table mytable (Name varchar(50),[Date] Datetime);
insert into mytable values('John' , '2021-03-01 10:00');
insert into mytable values('Paul' , '2021-03-01 11:00');
insert into mytable values('Paul' , '2021-03-01 14:20');
insert into mytable values('John' , '2021-03-01 15:00');
insert into mytable values('Paul' , '2021-03-01 17:00');
Query:
declare #cols as varchar(max), #colsForSelect as varchar(max), #query as varchar(max);
select #colsForSelect=string_agg(concat(quotename(rn),' ', datename),',' )from(
select distinct concat('Date',rn) datename,rn from
(SELECT row_number()over(partition by name order by [date])rn from mytable)t)a
select #cols =string_agg(quotename(rn),',') from (
select distinct rn from
(SELECT row_number()over(partition by name order by [date])rn from mytable)t)a
set #query = 'Select Name, ' + #colsForSelect + ' from
(
SELECT *,row_number()over(partition by name order by [date])rn
from mytable
) x
pivot
(
max([date])
for rn in (' + #cols + ')
) p
group by Name,' + #cols
execute(#query);
Output:
Name
Date1
Date2
Date3
John
2021-03-01 10:00:00.000
2021-03-01 15:00:00.000
null
Paul
2021-03-01 11:00:00.000
2021-03-01 14:20:00.000
2021-03-01 17:00:00.000
db<>fiddle here
Based on Larnu's help, This worked:
WITH RNs AS(
SELECT [Name],
[DateTime],
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY (SELECT NULL)) AS RN
FROM dbo.Punch
WHERE Date = '2016-04-18'
)
SELECT Name,
MAX(CASE RN WHEN 1 THEN [DateTime] END) AS Result1,
MAX(CASE RN WHEN 2 THEN [DateTime] END) AS Result2,
MAX(CASE RN WHEN 3 THEN [DateTime] END) AS Result3,
MAX(CASE RN WHEN 4 THEN [DateTime] END) AS Result4
FROM RNs R
GROUP BY Name
I have tried with Stuff function instead of sting_agg which was introduced in 2017 server. If you are using below 2017 version you can use the below query.
declare #column_name varchar(5000)
declare #col_name varchar(5000)
set #column_name = (select stuff((select ','+'['+cast(rn as varchar(1000))+']' from(select distinct row_number()over(partition by name order by (select null))as rn from mytable)a
for xml path('')), 1,1,''))
set #col_name = (select stuff((select ','+'['+cast(rn as varchar(1000))+']' +' Date'+cast(rn as varchar(1000)) from(select distinct row_number()over(partition by name order by (select null))as rn from mytable)a
for xml path('')), 1,1,''))
exec('select name, '+#col_name +'
from (
select row_number()over(partition by name order by (select null))rn, year([date]) yr, *
from mytable
)a
pivot
(
max([date]) for [rn] in ('+#column_name+' )
)pv')

How to add between date filter on PIVOT SQL query

I have stored data with types and date recorded. I would like to get the total of each type per date. I have a [Transactions] table with the following pattern:
Id | Type | Date_and_Time |
----------------------------------------
1 | Bags | 2019-01-01 17:39:34.620 |
2 | Shoes | 2019-01-02 17:39:34.620 |
3 | Shoes | 2019-01-02 17:39:34.620 |
4 | Bags | 2019-01-02 17:39:34.620 |
5 | Shirts | 2019-01-02 17:39:34.620 |
6 | Shirts | 2019-01-03 17:39:34.620 |
7 | Shirts | 2019-01-03 17:39:34.620 |
...
I have working PIVOT query but without date filter:
DECLARE #cols NVARCHAR(max) = Stuff((SELECT DISTINCT ', ' + Quotename(Type)
FROM Transactions
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, NULL);
EXECUTE('SELECT * FROM (select CAST(Date_and_Time as DATE) AS Transaction_Date, Type, Count(*) n
from #Tempsa GROUP BY CAST(Date_and_Time as DATE), Type) s
PIVOT (max(n) FOR Type IN (' +#cols + ')) pvt')
Output :
Transaction_Date | Bags | Shirts | Shoes
------------------------------------------
| 2019-01-01 | 1 | NULL | NULL |
| 2019-01-02 | 1 | 1 | 2 |
| 2019-01-03 | NULL | 2 | NULL |
....
However, when I use the below query with between date filter I don't get any record:
DECLARE #STARTDATE nvarchar(100) = '01/01/2019'
DECLARE #ENDDATE nvarchar(100) = '01/03/2019'
DECLARE #cols NVARCHAR(max) = Stuff((SELECT DISTINCT ', ' + Quotename(Type)
FROM #Tempsa
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, NULL);
EXECUTE('SELECT * FROM (select CAST(Date_and_Time as DATE) AS Transaction_Date, Type, Count(*) n
from #Tempsa WHERE Date_and_Time BETWEEN ' +#STARTDATE+ ' AND ' +#ENDDATE+' GROUP BY CAST(Date_and_Time as DATE), Type) s
PIVOT (max(n) FOR Type IN (' +#cols + ')) pvt')
Output :
Transaction_Date | Bags | Shirts | Shoes
------------------------------------------
| | | | |
....
Just add quotes around the date:
DECLARE #STARTDATE nvarchar(100) = '01/01/2019'
DECLARE #ENDDATE nvarchar(100) = '01/03/2019'
DECLARE #cols NVARCHAR(max) = Stuff((SELECT DISTINCT ', ' + Quotename(Type)
FROM #Tempsa
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, NULL);
EXECUTE('SELECT * FROM (select CAST(Date_and_Time as DATE) AS Transaction_Date, Type, Count(*) n
from #Tempsa WHERE Date_and_Time BETWEEN ''' +#STARTDATE+ ''' AND ''' +#ENDDATE+''' GROUP BY CAST(Date_and_Time as DATE), Type) s
PIVOT (max(n) FOR Type IN (' +#cols + ')) pvt')
Without double quotes, you are building something like this:
BETWEEN 01/01/2011 AND 01/03/2019
which is just a calculation that is evaluated by the engine
SELECT 01/01/2011 -- 0
,01/03/2019 -- 0
and it is 0. So, you are asking to get all dates which are from 0 to 0.
And that's why adding the quotes make your filtering criteria valid (it was valid before, but SELECT CAST(0 AS DATETIME) is 1900-01-01 00:00:00.000 and as your boundaries are the same, no records are returned).
Of course, you can use CONVERT(VARCHAR(10), #STARTDATE, 121) to ensure there are no misunderstandings during the implicit conversion.
I have modified the query shared by you to get the desired result. it was not working because single quite was missing while concatenating the date for the filter. Now it is working. You can verify the same.
DECLARE #STARTDATE nvarchar(100) = '01/01/2019'
DECLARE #ENDDATE nvarchar(100) = '01/03/2019'
DECLARE #cols NVARCHAR(max) = Stuff((SELECT DISTINCT ', ' + Quotename(Type)
FROM #Tempsa
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, NULL);
EXECUTE( 'SELECT * FROM (select CAST(Date_and_Time as DATE) AS Transaction_Date, Type, Count(*) n
from #Tempsa WHERE Date_and_Time BETWEEN ''' + #STARTDATE+ ''' AND ''' +#ENDDATE+''' GROUP BY CAST(Date_and_Time as DATE), Type) s
PIVOT (max(n) FOR Type IN (' +#cols + ')) pvt')
Write your dates on ISO format so you don't get month and day mixed up.
DECLARE #STARTDATE nvarchar(100) = '2019-01-01'
DECLARE #ENDDATE nvarchar(100) = '2019-01-03'
And make sure (just in case) to explicitly convert them on your dynamic SQL. Also it's a good approach to set your SQL to a variable and preview it first before executing (with PRINT):
DECLARE #DynamicSQL NVARCHAR(MAX) = '
SELECT * FROM (select CAST(Date_and_Time as DATE) AS Transaction_Date, Type, Count(*) n
from #Tempsa WHERE Date_and_Time BETWEEN CONVERT(DATE, ''' +#STARTDATE+ ''') AND CONVERT(DATE, ''' + #ENDDATE +''')
GROUP BY CAST(Date_and_Time as DATE), Type) s
PIVOT (max(n) FOR Type IN (' +#cols + ')) pvt'
PRINT (#DynamicSQL)
-- EXECUTE(#DynamicSQL)
To simplify your query, you can use sp_executesql with parameter like following.
DECLARE #STARTDATE nvarchar(100) = '01/01/2019'
DECLARE #ENDDATE nvarchar(100) = '01/03/2019'
DECLARE #cols NVARCHAR(max) = Stuff((SELECT DISTINCT ', ' + Quotename(Type)
FROM #Tempsa
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, NULL);
declare #SQLString Nvarchar(max)= N'SELECT * FROM (select CAST(Date_and_Time as DATE) AS Transaction_Date, Type, Count(*) n
from #Tempsa WHERE Date_and_Time BETWEEN #SD AND #ED GROUP BY CAST(Date_and_Time as DATE), Type) s
PIVOT (max(n) FOR Type IN (' +#cols + ')) pvt'
exec sp_executesql #SQLString,N'#SD DATE,#ED DATE',#SD=#STARTDATE,#ED=#ENDDATE
Online Demo

SQL Dynamic Pivot Query With Multiple Subtotal

For below code, I able to create a dynamic pivot with subtotal.But I need to add one more subtotal column in this code . It has to be total of NUM_TO_EXIT values.As you can see picture 2, I used NUM_TO_ENTER within dynamic dates in my dynamic pivot but I could not able to merge just for the total values of NUM_TO_EXIT. I tried everything but could not able to do that. Appreciate for yor supports.I am using MSSQL 2008 R2.
DIRECTIONAL_METRIC_ID ZONE_ID START_TIME END_TIME RECEIVED_TS STATUS NUM_TO_ENTER NUM_TO_EXIT DAY_OF_WEEK_ID TIME_ID
436909 254 2016-02-06 10:00:00.000 2016-02-06 10:00:00.000 2016-02-06 10:00:00.000 1 195 195 7 300
436910 254 2016-02-07 10:00:00.000 2016-02-07 10:15:00.000 2016-02-07 10:15:00.000 1 195 195 7 300
436911 278 2016-02-01 10:00:00.000 2016-02-01 10:15:00.000 2016-02-01 10:15:00.000 1 95 95 7 300
436912 278 2016-02-02 10:00:00.000 2016-02-02 10:15:00.000 2016-02-02 10:15:00.000 1 95 95 7 300
436913 278 2016-02-03 10:00:00.000 2016-02-03 10:15:00.000 2016-02-03 10:15:00.000 1 95 95 7 300
What I need
Structe of DIRECTIONAL_METRIC TABLE
DECLARE #mydate DATETIME
SELECT #mydate = GETDATE()
IF OBJECT_ID('tempdb..#Dates') IS NOT NULL
drop table #Dates
SELECT DISTINCT CONVERT(VARCHAR,START_TIME,106) AS [Date]
INTO #Dates
FROM dbo.DIRECTIONAL_METRIC
WHERE CAST(START_TIME as DATE) >=CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(#mydate)-1),#mydate),101)
and CAST(START_TIME as DATE) < #mydate
AND DATEPART(HOUR,START_TIME) BETWEEN 9 AND 23
order by [Date]
DECLARE #cols VARCHAR(1000)
SELECT #cols = COALESCE (#cols + ',[' + [Date] +']','[' + [Date] + ']')
FROM #Dates
DECLARE #GrandTotalCol NVARCHAR (MAX)
SELECT #GrandTotalCol = COALESCE (#GrandTotalCol + 'ISNULL ([' + [Date] +'],0) + ', 'ISNULL([' + [Date]+ '],0) + ')
FROM #Dates
ORDER BY [Date]
SET #GrandTotalCol = LEFT (#GrandTotalCol, LEN (#GrandTotalCol)-1)
DECLARE #qry varchar(4000)
SET #qry =
'
DECLARE #mydate DATETIME
SELECT #mydate = GETDATE()
SELECT *, ('+ #GrandTotalCol + ')
AS [Grand Total] INTO #temp_MatchesTotal
FROM
(
SELECT ZO.CODE AS KOD,
convert(varchar, START_TIME, 106) AS [Date],
SUM(NUM_TO_ENTER) AS NUM_TO_ENTER
FROM dbo.DIRECTIONAL_METRIC AS z INNER JOIN ZONE AS ZO ON z.ZONE_ID=ZO.ZONE_ID
WHERE CAST(START_TIME as DATE) >=CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(#mydate)-1),#mydate),101)
and CAST(START_TIME as DATE) < #mydate
and DATEPART(HOUR,START_TIME) between 9 and 23
AND ZO.CODE IS NOT NULL
group by ZO.CODE,convert(varchar, START_TIME, 106)
) P
PIVOT
(
SUM(NUM_TO_ENTER)
FOR [Date] IN(' +#cols + ')
)
AS pvt
ORDER BY KOD
SELECT * FROM #temp_MatchesTotal
DROP TABLE #temp_MatchesTotal'
EXEC(#qry)
GO

Month wise aging report by SQL query

6 month aging report of the customers from today date.
Data in the table is like following:
Customer Date Amount
AAA 3-Sep-13 1000
BBB 4-Jan-14 4000
BBB 5-Feb-14 1000
AAA 3-Dec-13 3000
CCC 7-Nov-13 800
DDD 15-Nov-13 1000
DDD 25-Jan-13 1000
CCC 8-Nov-13 1000
I need a SQL query to get the below results and heading should be in month and year
Party name Sep-13 Oct-13 Nov-13 Dec-13 Jan-14 Feb-14 Total Amount
AAA 1000 0 0 3000 0 0 4000
BBB 0 0 0 4000 1000 0 5000
CCC 0 0 1800 0 0 0 1800
DDD 0 0 1000 0 1000 0 2000
This query will dynamically generate the output that you want. However, because you don't have values for all periods, a NULL is inserted (which is valid). If you want to make a value 0 instead of null, you'll have to modify the query and use the ISNULL function for each field.
CREATE TABLE testTable (
Party NVARCHAR(20),
[Date] SMALLDATETIME, --done like this to get a TOTAL heading
Amount DECIMAL
)
INSERT INTO testTable VALUES
('AAA','3-Sep-13',1000),
('BBB','4-Jan-14',4000),
('BBB','5-Feb-14',1000),
('AAA','3-Dec-13',3000),
('CCC','7-Nov-13',800),
('DDD','15-Nov-13',1000),
('DDD','25-Jan-13',1000)
INSERT INTO testTable
SELECT Party, '2054-12-31', SUM(Amount) [Amount]
FROM testTable
GROUP BY Party
DECLARE #columnHeadings NVARCHAR(MAX)
SELECT #columnHeadings = COALESCE(
#columnHeadings + ',[' + CONVERT(NVARCHAR, [Date], 6) + ']',
'[' + CONVERT(NVARCHAR, [Date], 6)+ ']'
)
FROM testTable
GROUP BY [Date]
ORDER BY [Date]
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'SELECT [party], ' + #columnHeadings + ' INTO ##output FROM ' +
'( SELECT [Party], [Amount], CONVERT(NVARCHAR, [Date], 6) [Date] FROM testTable ) [source]' +
' PIVOT ' +
'( SUM(Amount) FOR [Date] IN (' + #columnHeadings +')) [pvt];'
EXEC(#sql)
EXEC tempdb.sys.sp_rename '##output.[31 Dec 54]', 'Total', 'COLUMN'
SELECT * FROM ##output
DROP TABLE testTable
DROP TABLE ##output
Disclaimer: I'm not fond of this code. But it works.

data between two dates in column for each day

I have table worker
id name
----------- -------------------
5 Артур Петрович
6 Дмитрий Белов
7 Казарян Артур
and another table
id date amount id_worker
----------- ---------- ----------- -----------
27 2013-09-12 1500 5
28 2013-09-12 100 6
29 2013-09-12 500 5
30 2013-09-12 500 6
31 2013-09-14 1000 7
32 2013-09-15 100 5
33 2013-09-15 200 5
I want to write stored procedure which on input gets start and end dates
and on output I want to get this table if:
start date:2013-09-10
end date :2013-09-15
Name 2013-09-10 2013-09-11 2013-09-12 2013-09-13 2013-09-14 2013-09-15
_______________ __________ __________ __________ __________ __________ __________
Артур Петрович 0 0 2000 0 0 300
Дмитрий Белов 0 0 600 0 0 0
Казарян Артур 0 0 0 0 1000 0
The only way I konw to do this is using Dynamic SQL, IMO there is no risk of SQL Injection if the tables structures are known ahead
DECLARE #DateList VARCHAR(MAX), #DateListCoalesced VARCHAR(MAX)
SELECT #DateList = '', #DateListCoalesced = ''
;WITH DateLimits AS (
SELECT CAST('2013-9-10' AS DATE) AS dt
UNION ALL
SELECT DATEADD(dd, 1, dt)
FROM DateLimits s
WHERE DATEADD(dd, 1, dt) <= CAST('2013-9-15' AS DATE))
SELECT #DateList = #DateList + '[' + CAST(dt AS VARCHAR)+ '], ' ,
#DateListCoalesced = #DateListCoalesced + ' COALESCE( [' + CAST(dt AS VARCHAR)+ '] , 0) as [' + CAST(dt AS VARCHAR)+ '], '
FROM DateLimits
;SET #DateList = LEFT(#DateList, LEN(#DateList) - 1)
;SET #DateListCoalesced = LEFT(#DateListCoalesced, LEN(#DateListCoalesced) - 1)
DECLARE #query NVARCHAR(max)
SET #query = N'SELECT [Name], ' + #DateListCoalesced +'
FROM
(SELECT [Name], [Date], [Amount]
FROM WorkerAmount
INNER JOIN Worker ON WorkerAmount.id_worker = Worker.id
) p
PIVOT
(
Sum ([Amount] )
FOR [Date] IN ( '+ #DateList +' )
) AS pvt '
EXEC sp_executesql #Query
This answer uses a combination of few other questions
getting dates between range of dates
Pivots with dynamic columns in sql-server
replace null values in sql pivot