SQL Dynamic Pivot Query With Multiple Subtotal - sql

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

Related

SQL Determining if Time Stamps Overlap Into Multiple Months and Calculating the Relative Time in Each Month

I'm quite new at SQL so bear with me.
I have a data set with datetime stamps of various activities.
For example, I have a list of projects with the following fields
start_project [datetime]
finish_project [datetime]
verify_project [datetime]
I want to determine if at any point in the lifespan of the project the work spanned multiple months.
For example start_project could begin at 9 am on Jan 1, finish_project occurs at 12 pm on Feb 3, and verify_project at 3 pm on Feb 12th.
I want to determine how many hours were spent in each month for the project so I can bin these time periods by month. I'm just sure how to implement the logic.
You should create a dimDate table that has business days in it.
For example
Date isBusinessDay
1/1/2017 0
1/2/2017 1
...
Then query it:
select Month(Date) Month,YEAR(Date) Year, WorkingHours = Count(*)*8 --Assuming 8 bus hours in a day
from DimDate
where Date between StartDate and EndDate
and isBusinessDay=1
group by Month(Date),YEAR(Date)
To add it to your base query you need to cross apply:
select BaseTable.*, a.Month,a.Year, a.WorkingHours
from BaseTable
cross apply(
select Month(Date) Month,YEAR(Date) Year, WorkingHours = Count(*)*8 --Assuming 8 bus hours in a day
from DimDate
where Date between BaseTable.StartDate and BaseTable.EndDate
and isBusinessDay=1
group by Month(Date),YEAR(Date)) a
With a numbers table this is relatively straight forward...
https://www.mssqltips.com/sqlservertip/4176/the-sql-server-numbers-table-explained--part-1/
The following query works out...
- the month each project started in
- how many months the whole project spans
- returns one row per spanned month
- appends the date the month started as a new field
SELECT
yourTable.*,
CASE
WHEN months_since_start.id = 0
THEN yourTable.start_project
ELSE DATEADD(month, months_since_start.id, first_month.start)
END
AS partial_month_start
FROM
yourTable
CROSS APPLY
(
VALUES( DATEADD(month, DATEDIFF(month, 0, yourTable.start_project)) )
)
first_month(start)
INNER JOIN
dbo.numbers AS months_since_start
ON months_since_start.id >= 0
AND months_since_start.id <= DATEDIFF(month, yourTable.start_project, yourTable.verify_project)
hen you can use DATEDIFF() to your hearts content on the new rows/field.
I should have used a numbers table maybe but this works too.
CREATE TABLE #myTable(ProjName VARCHAR(100), start_project DATETIME, finish_project DATETIME, verify_project DATETIME)
INSERT INTO #myTable SELECT 'proj1', '2017-01-01', '2017-04-15', '2017-04-15'
INSERT INTO #myTable SELECT 'proj2', '2017-01-02', '2017-01-11', '2017-01-11'
INSERT INTO #myTable SELECT 'proj3', '2017-06-06', '2017-06-06', '2017-06-06'
INSERT INTO #myTable SELECT 'proj4', '2017-03-01', '2017-08-15', '2017-08-15'
;with cte1 AS (
SELECT CASE
WHEN CAST(YEAR(t.start_project) AS VARCHAR) + CAST(MONTH(t.start_project) AS VARCHAR) <> CAST(YEAR(t.finish_project) AS VARCHAR) + CAST(MONTH(t.finish_project) AS VARCHAR)
THEN (DATEDIFF(DAY, start_project,EOMONTH(start_project, 0)) * 8) + 8
WHEN start_project = finish_project
THEN 8
ELSE (DATEDIFF(DAY, start_project,finish_project) * 8) + 8
END AS hours_this_month
, DATENAME(MONTH,start_project) AS month_name
, 0 AS month_level, start_project, finish_project, ProjName, verify_project
FROM #myTable t
UNION ALL
SELECT CASE
WHEN t.finish_project > EOMONTH(DATEADD(MONTH, t.month_level + 1, t.start_project))
AND ( CAST(YEAR(t.start_project) AS VARCHAR) + CAST(MONTH(t.start_project) AS VARCHAR) <> CAST(YEAR(t.finish_project) AS VARCHAR) + CAST(MONTH(t.finish_project) AS VARCHAR))
THEN
(DATEDIFF(DAY, CAST(CAST(YEAR(DATEADD(MONTH, t.month_level + 1, t.start_project)) AS VARCHAR) + '-' +
CAST(MONTH(DATEADD(MONTH, t.month_level + 1, t.start_project)) AS VARCHAR) + '-01' AS DATETIME)
, EOMONTH(DATEADD(MONTH, t.month_level + 1, t.start_project))) * 8) + 8
ELSE
(DATEDIFF(DAY, CAST(CAST(YEAR(DATEADD(MONTH, t.month_level + 1, t.start_project)) AS VARCHAR) + '-' +
CAST(MONTH(DATEADD(MONTH, t.month_level + 1, t.start_project)) AS VARCHAR) + '-01' AS DATETIME)
, t.finish_project) * 8) + 8
END
, DATENAME(MONTH,DATEADD(MONTH, t.month_level + 1, t.start_project))
, t.month_level + 1
, t.start_project
, t.finish_project, t.ProjName, t.verify_project
FROM cte1 t INNER JOIN
#myTable myT ON myT.ProjName = t.ProjName
WHERE CAST(CAST(YEAR(DATEADD(MONTH, t.month_level + 1, t.start_project)) AS VARCHAR) + '-' +
CAST(MONTH(DATEADD(MONTH, t.month_level + 1, t.start_project)) AS VARCHAR) + '-01' AS DATETIME)
<= t.finish_project )
SELECT ProjName
, month_name
, hours_this_month
, start_project
, finish_project
, verify_project
, month_level
FROM cte1
ORDER BY ProjName, month_level ASC
output...
proj1 January 248 2017-01-01 00:00:00.000 2017-04-15 00:00:00.000 2017-04-15 00:00:00.000 0
proj1 February 224 2017-01-01 00:00:00.000 2017-04-15 00:00:00.000 2017-04-15 00:00:00.000 1
proj1 March 248 2017-01-01 00:00:00.000 2017-04-15 00:00:00.000 2017-04-15 00:00:00.000 2
proj1 April 120 2017-01-01 00:00:00.000 2017-04-15 00:00:00.000 2017-04-15 00:00:00.000 3
proj2 January 80 2017-01-02 00:00:00.000 2017-01-11 00:00:00.000 2017-01-11 00:00:00.000 0
proj3 June 8 2017-06-06 00:00:00.000 2017-06-06 00:00:00.000 2017-06-06 00:00:00.000 0
proj4 March 248 2017-03-01 00:00:00.000 2017-08-15 00:00:00.000 2017-08-15 00:00:00.000 0
proj4 April 240 2017-03-01 00:00:00.000 2017-08-15 00:00:00.000 2017-08-15 00:00:00.000 1
proj4 May 248 2017-03-01 00:00:00.000 2017-08-15 00:00:00.000 2017-08-15 00:00:00.000 2
proj4 June 240 2017-03-01 00:00:00.000 2017-08-15 00:00:00.000 2017-08-15 00:00:00.000 3
proj4 July 248 2017-03-01 00:00:00.000 2017-08-15 00:00:00.000 2017-08-15 00:00:00.000 4
proj4 August 120 2017-03-01 00:00:00.000 2017-08-15 00:00:00.000 2017-08-15 00:00:00.000 5

SQL query date into new columns with unique ID

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

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

How to get column value into row header

ID Amount Date
------------------------------
1 300 02-02-2010 00:00
2 400 02-02-2009 00:00
3 200 02-02-2011 00:00
4 300 22-02-2010 00:00
5 400 12-02-2009 00:00
6 500 22-02-2009 00:00
7 600 02-02-2006 00:00
8 700 02-07-2012 00:00
9 500 08-02-2012 00:00
10 800 09-02-2011 00:00
11 500 06-02-2010 00:00
12 600 01-02-2011 00:00
13 300 02-02-2019 00:00
Desired output:
Y1 Y2 Y3 ...........
sum(amount) sum(amount) sum(amount)
What is an approach, where Y1 is the year part of the date, such that the result column would be the following?
2006 2009 2010 2011 2012
---------------------------------
600 1300 800 1900 1200
Database system: SQL Server 2008
You need to use a dynamic PIVOT Table
DECLARE #Years nvarchar(max)
SELECT #Years =
STUFF(
(
select distinct ',[' + cast(Year([date]) as nvarchar(4)) + ']'
from YOUR_TABLE_NAME_HERE
for xml path('')
),
1,1,'')
DECLARE #SQL nvarchar(max)
SELECT #SQL = N'
select
*
from (
select
amount, year([date]) as [y]
from YOUR_TABLE_NAME_HERE
) Data
PIVOT (
sum(amount)
FOR [y]
IN (
' + #Years + '
)
) PivotTable
'
EXECUTE (#SQL)
Reference: PIVOT Docs

Displaying weeks as columns

Below is the script to display the number of weeks between the given dates.
SET DATEFIRST 1
SELECT ta.account, ta.customer, SUM(amount), DATEPART(ww,ta.dt) WeekNumber
FROM tablename ta
WHERE dt >= '12/01/2011 00:00:00'
and dt < '12/29/2011 00:00:00'
GROUP BY ta.account, ta.customer, DATEPART(ww,ta.dt)
How do I display the diff weeks as diff columns in my result. Any suggestion would be helpful.
Sample O/P for the above is:
Date Account Customer TotalSeconds Amount WeekNumber
2011-11-01 xx0918252 198303792R 394 2.99 45
2011-11-08 xx1006979 200100567G 92 0.16 46
2011-11-15 xx1005385 A6863744I 492 1.275 47
2011-11-21 xx1012872 D7874694G 770 0.52 48
2011-11-28 xx1006419 C7112151H 1904 2.64 49
2011-11-28 xx1006420 G7378945A 77 0.3 49
I want the O/P like:
Date Account Customer TotalSeconds Amount WeekNumber45 WeekNumber46 WeekNumber47 WeekNumber8 WeekNumber49
and their corresponding data. Hope u understand my question. Thanks in advance.
Hi All, Thanks for the suggestions n help. Finally, I got the results that i wanted for time being. I still believe that it is hard coding. Is there a better solution for this. Thanks in advance. My code is as follows:
SELECT ta.account, ta.customer,
isnull(SUM(CASE WHEN DATEPART(ww,ta.dt) = '49' THEN amount END),0) AS "Week49",
isnull(SUM(CASE WHEN DATEPART(ww,ta.dt) = '50' THEN amount END),0) AS "Week50",
isnull(SUM(CASE WHEN DATEPART(ww,ta.dt) = '51' THEN amount END),0) AS "Week51",
isnull(SUM(CASE WHEN DATEPART(ww,ta.dt) = '52' THEN amount END),0) AS "Week52",
isnull(SUM(CASE WHEN DATEPART(ww,ta.dt) = '53' THEN amount END),0) AS "Week53",
FROM (
select * from tablename
where dt >= '12/01/2011 00:00:00' and dt <= '12/31/2011 00:00:00'
) ta
group by ta.account, ta.customer
First of all I would put your result in temporary table for later calculations. Let's imagine that following CTE is your result:
if object_id('tempdb..#tab') is not null drop table #tab
;with cte (Date,Account,Customer,TotalSeconds,Amount,WeekNumber) as (
select cast('20111101' as datetime),'xx0918252','198303792R',394,2.99,45 union all
select '20111108','xx1006979','200100567G',92,0.16,46 union all
select '20111115','xx1005385','A6863744I',492,1.275,47 union all
select '20111121','xx1012872','D7874694G',770,0.52,48 union all
select '20111128','xx1006419','C7112151H',1904,2.64,49 union all
select '20111128','xx1006420','G7378945A',77,0.3,49
)
select * into #tab from cte
Now your computed data is in #tab table and the following query returns pivoted table for those weeknumbers:
select date, account, customer, totalSeconds, amount, [45], [46], [47], [48], [49] from
(
select date, account, customer, totalSeconds, amount, weeknumber as weeknumber from #tab
) src
pivot
(
max(weeknumber) for weekNumber in ([45], [46], [47], [48], [49])
) pvt
Dynamic version of this query might look like this:
declare #sql nvarchar(max), #cols varchar(max)
select #cols = coalesce(#cols + ',', '') + '[' + cast(weeknumber as varchar) + ']'
from (select distinct weeknumber from #tab) t
order by weeknumber
set #sql = N'
select date, account, customer, totalSeconds, amount, ' + #cols + ' from
(
select date, account, customer, totalSeconds, amount, weeknumber as weeknumber from #tab
) src
pivot
(
max(weeknumber) for weekNumber in (' + #cols + ')
) pvt
'
exec sp_executesql #sql
The result (in both cases):
date account customer totalSeconds amount 45 46 47 48 49
----------------------- --------- ---------- ------------ ---------- ------ ------ ------ ------ ------
2011-11-01 00:00:00.000 xx0918252 198303792R 394 2.990 45 NULL NULL NULL NULL
2011-11-08 00:00:00.000 xx1006979 200100567G 92 0.160 NULL 46 NULL NULL NULL
2011-11-15 00:00:00.000 xx1005385 A6863744I 492 1.275 NULL NULL 47 NULL NULL
2011-11-21 00:00:00.000 xx1012872 D7874694G 770 0.520 NULL NULL NULL 48 NULL
2011-11-28 00:00:00.000 xx1006419 C7112151H 1904 2.640 NULL NULL NULL NULL 49
2011-11-28 00:00:00.000 xx1006420 G7378945A 77 0.300 NULL NULL NULL NULL 49
Take a look at the PIVOT function.
Tsql pivot command
T-SQL Pivot function combined with dynamic SQL. Examples:
SQL Server 2005 Pivot on Unknown Number of Columns.
Pivots with Dynamic Columns in SQL Server 2005/2008.