How to write an SQL Pivot Query for this scenario? - sql-server

I am using SQL Server 2014 and I have the following query that runs fine:
SELECT b.FOH_PMSCONFNUM,
a.FOC_ACCOUNT,
a.FOC_PROPERTY,
a.FOC_TCODE,
a.FOC_NETAMOUNT
FROM P5FOLIOCHARGE a
LEFT JOIN P5FOLIOHEADER b ON a.FOC_ACCOUNT = b.FOH_ACCOUNT
where b.FOH_PMSCONFNUM = '1458' AND FOC_NETAMOUNT NOT LIKE '-%'
It gives me the following output:
FOH_PMSCONFNUM FOC_ACCOUNT FOC_PROPERTY FOC_TCODE FOC_NETAMOUNT
125 52 BMA ROOMS 1,200
125 52 BMA zBev 900
125 52 BMA zTel 200
125 52 BMA ROOMS 1,200
125 52 BMA zSpa 500
125 52 BMA zTel 100
I am having a tough time writing the pivot query so that my output turns out as follows:
FOH_PMSCONFNUM FOC_ACCOUNT FOC_ PROPERTY ROOMS zBev zTel zSpa
125 52 BMA 2,400 900 300 500
Also, while running this pivot query, there are 2 things I need to consider:
(1) I must keep this statement "AND FOC_NETAMOUNT NOT LIKE '-%'", so that the pivot does not sum negative figures that are present in the FOC_NETAMOUNT column.
(2) For illustration purposes here, I have shown only a few items that exist in the FOC_TCODE column. In reality, I don't know how many items exists (may be around 30) and my aim is to output those items as column headers.
It is Note (2) above that is a hard nut to crack (for me at least!).

You need to use Dynamic pivot
In Pivot source query make the negative values as zero so that it won't be used in SUM aggregate of pivot
DECLARE #sql NVARCHAR(max),
#cols VARCHAR(max)
SET #cols = (SELECT DISTINCT a.FOC_TCODE + ','
FROM P5FOLIOCHARGE a
LEFT JOIN P5FOLIOHEADER b
ON a.FOC_ACCOUNT = b.FOH_ACCOUNT
WHERE b.FOH_PMSCONFNUM = '1458'
AND FOC_NETAMOUNT NOT LIKE '-%'
FOR xml path(''))
SELECT #cols = LEFT(#cols, Len(#cols) - 1)
SET #sql = 'SELECT *
FROM (SELECT b.FOH_PMSCONFNUM,
a.FOC_ACCOUNT,
a.FOC_PROPERTY,
a.FOC_TCODE,
CASE WHEN a.FOC_NETAMOUNT > 0 THEN a.FOC_NETAMOUNT ELSE 0 END AS FOC_NETAMOUNT
FROM P5FOLIOCHARGE a
LEFT JOIN P5FOLIOHEADER b ON a.FOC_ACCOUNT = b.FOH_ACCOUNT
where b.FOH_PMSCONFNUM = ''1458''
AND FOC_NETAMOUNT NOT LIKE ''-%'')a
PIVOT (Sum(FOC_NETAMOUNT)
FOR FOC_TCODE IN (' + #cols + ')) pv '
EXEC Sp_executesql #sql

Related

Automatically generate columns name in CTE using SQL Server

I am building a pivot query inside a CTE. I have a table Table_1:
Store Week xCount
------- ---- ------
101 1 138
105 1 37
109 1 59
101 2 282
109 2 97
105 3 60
109 3 87
This is the query I used to pivot Table_1:
with CTE as
(
select
*
from
(select
store, week, xCount
from
table_1) src
pivot
(sum(xcount)
for week in ([1], [2], [3])
) piv;
)
Select *
From CTE
And this is the result I got:
| STORE | 1 | 2 | 3 |
+-------+-----+-----+-----+
| 101 | 138 | 282 | null|
| 105 | 37 | null| 60 |
| 109 | 59 | 97 | 87 |
The result is fine, but now there is one more WEEK added.
I want to develop a CTE with pivot query that will automatically generate distinct weeks and create a column on that basis.
I did some research and found a recursive CTE can be used to do this. I am new to recursive CTE, so please anyone can help me to solve this issue.
I also tried dynamic pivot query but CTE does not allow dynamic query.
Please help.
dynamic pivot doesn't work inside CTE
No, but a CTE works inside a dynamic query:
{assuming you have declared the variables used below}
SELECT #Cols = {query to get the column names in a comma-separated string}
SET #sql='
with CTE as
(
select
*
from
(select
store, week, xCount
from
table_1) src
pivot
(sum(xcount)
for week in ('+#Cols+')
) piv;
)
Select *
From CTE
'
EXEC (#sql)
Can i use recursive CTE?
No this isn't an appropriate use-case for a recursive CTE.
/* Variable to hold unique Week to be used in PIVOT clause */
DECLARE #Weeks NVARCHAR(MAX) = N''
/* Extract unique Week names with pivot formattings */
SELECT #Weeks = #Weeks + ', [' + COALESCE(week, '') + ']'
FROM (SELECT DISTINCT week FROM table_1) DT
/* Remove first comma and space */
SELECT #Weeks = LTRIM(STUFF(#Weeks , 1, 1, ''))
/* Variable to hold t-sql query */
DECLARE #CTEStatement NVARCHAR(MAX) = N''
/* Generate dynamic PIVOT query here */
SET #CTEStatement=N'
;WITH CTE as
( SELECT *
FROM
(SELECT
store
,week
,xCount
FROM
table_1) SRC
PIVOT
(SUM(xcount)
FOR week in ('+ #Weeks +')
) PIV;
)
SELECT *
FROM CTE
'
EXEC (#CTEStatement)

Create dynamic query to report number of products per month per company group with totals per product and per company

Should create a dynamic pivot table to report products sold monthly by company group.
There are several products and each company group might have a different combination of them.
The product status are Active and Cancelled.
The Active products are counted using the date where they were billed (dbilldate) and the cancelled should be counted using dtContractCancelledDate.
There are some cases where the company group does not sell or cancel a product during the month so all the column should reflect that (look on Prod2 Cancelled).
A calculation column should exist to calculate the net per product (Prod Active – Prod Cancelled).
We need help
• Creating different calculation to count the product active and cancelled using the corresponding dates (dbilled and dtcancelledbilled)
• Creating a column with 0s when there are not sold/cancelled products
• Creating the net columns for each product
• Creating the subtotals at the end of each column
This is the result desired:
Pd1Actv Pd1Cancd Pd1Net Prd2Actv Prd2Cancd Prd2Net Total
Comp1 6 5 1 15 0 15 16
Comp2 20 6 14 39 0 39 53
Comp3 63 14 49 82 0 82 131
Total 89 25 64 136 0 136 200
This is the main table from where data is extracted
SELECT [iCompId],[sContractStatusCode],[sContractStatusDesc],[dBillDate] ,dtContractCancelledBilled
FROM [Contract_Header]
This is what I have until now:
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#Compgroupnumber as varchar(20),
#startdate as date,
#enddate as date
set #startdate=cast(DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0) as
date)
set #enddate=DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE()),0))
set #sCompGroupNumber ='DG10000174'
select #cols =
STUFF((SELECT distinct ',' + QUOTENAME(h.sProductCode+'
'+sContractStatusDesc)
from [Contract_Header] h
inner join [Comp_Header] d on d.iid=h.icompId
inner join [Comp_CompGroupLink] dgl on d.iId=dgl.CompId
inner join [Comp_DealerGroups] dg on dgl.iCompGroupId=dg.iId
where (h.sContractStatusCode='A' or h.sContractStatusCode='C') and
(h.dBillDate between #startdate and #enddate) and
sCompGroupNumber='DG10000174' and
h.sProductCode in
(
select distinct t.sProductCode
from [Contract_Header] t
inner join [Comp_Header] d on d.iid=t.iDealerId
inner join [Comp_CompGroupLink] dgl on d.iId=dgl.iCompId
inner join [Comp_CompGroups] dg on dgl.iDealerGroupId=dg.iId
where (t.sContractStatusCode='A' or t.sContractStatusCode='C') and
(t.dBillDate between #startdate and #enddate) and
sCompGroupNumber='DG10000174'
)FOR XML PATH('')) ,1,1,'')
set #query=
'SELECT sCompName,' + #cols + ' , +Total
from
(
select d.sCompName,
col = c.sProductCode+'' ''+c.sContractStatusDesc,c.sContractStatusCode,
from [Contract_Header] c
inner join [Comp_Header] d on d.iid=c.iDealerId
inner join [Comp_CompGroupLink] dgl on d.iId=dgl.iCompId
inner join [Comp_CompGroups] dg on dgl.iCompGroupId=dg.iId
where (dBillDate between '''+CAST(#startdate AS varchar)+''' and
'''+CAST(#enddate AS VARCHAR)+''') and
sCompGroupNumber='''+'DG10000174'+''' and
(c.sContractStatusCode='''+'A'+''' or
c.sContractStatusCode='''+'C'+''' ) and
sproductcode in
(select distinct sProductCode
from [Contract_Header] t
inner join [Comp_Header] d on d.iid=t.iCompId
inner join [Comp_CompGroupLink] dgl on d.iId=dgl.iCompId
inner join [Comp_CompGroups] dg on dgl.iCompGroupId=dg.iId
where (dBillDate between '''+CAST(#startdate AS varchar)+'''
and '''+CAST(#enddate AS VARCHAR)+''') and
(t.sContractStatusCode='''+'A'+''' or
t.sContractStatusCode='''+'C'+''' ) and
sCompGroupNumber='''+'DG10000174'+'''
)
) as DataSource
pivot
(
Count(sContractStatusCode)
for col in (' + #cols + ')
) p order by sCompName'
execute sp_executesql #query
This is the result of the query above
Pd1Actv Pd1Cancd Pd2Actv Pd3Actv
Comp 1 59 0 59 118
Comp 2 26 1 25 65
there are missing the columns when there are not products cancelled for Pd2 and Pd3, the net columns (pdAct-PdCanc), sub total and totals..
Thanks in advance for all the help...
mary

Sqlserver PIVOT to turn a "reconstruct" a flat table into columns - why does this not work?

The system we are using allows a data entry form to be created from multiple user defined fields to satisfy information required on a particular group of different "ORDES". The fields are then stored in a database as such from what is entered:
GUID OrderGUID UserDataCode Value
1 100 OrderName Breakfast
2 100 OrderDesc Food you eat before Lunch
3 100 CerealYN Y
4 100 ToastYN Y
5 100 ToastDesc White Bread
6 100 PaperYN Y
7 100 PaperDesc The Newsroom
8 101 OrderName Lunch
9 101 OrderDesc Food you eat before Dinner
10 101 CerealYN N
11 101 ToastYN Y
12 101 ToastDesc Brown Bread
13 101 PaperYN Y
14 101 PaperDesc The MiddayNews
(etc)
(in fact this is an Enterprise Hospital software but I have used simpler examples here)
I would like using SQL to return this table PIVOTed like below
OrderGUID OrderName OrderDESC CerealYN ToastYN ToastDesc ....
101 Breakfast Food you.. Y Y White Bread ....
102 Lunch Food you.. N Y Brown Bread ....
I wrote the following SQL based on examples found on the net:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME([UserDataCode])
FROM (
SELECT
[UserDataCode]
FROM
[XXX].[dbo].[CV3OrderUserData]
WHERE OrderGUID = 3000680
) AS Codes;
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery = N'SELECT OrderGUID, ' + #ColumnName + '
FROM
[XXX].[dbo].[CV3OrderUserData]
PIVOT(Max(Value)
FOR UserDataCode IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
--SELECT #DynamicPivotQuery
EXEC sp_executesql #DynamicPivotQuery
However while it does the pivot as requested.. and puts the values in the correct new "dynamic" columns, if returns a row for each OrderGUID + Value,
ie:
OrderGUID OrderName OrderDesc CerealYN ToastYN
100 Breakfast null null null ...
100 null Food you.. null null ...
101 null null Y null ...
etc.etc
What am i doing wrong :( ?
The problem in your query is the pivot source query has GUID column which makes the pivot operator to consider GUID column.
To get the expected output you need to remove GUID column from the pivot source query.
Here is a static version you can convert it to dynamic version as you already did.
select * from
(
SELECT OrderGUID,UserDataCode,Value
FROM
tst) A
PIVOT(Max(Value)
FOR UserDataCode IN ([OrderName],[OrderDesc],
[CerealYN],[ToastYN],
[ToastDesc],[PaperYN],
[PaperDesc])) AS PVTTable
SQLFIDDLE DEMO

Find the n highest consecutive values in a set of rows

I have some data in a table as follows:
FileDate SumAmount
20150401 90.99
20150401 313
20150403 481.2
20150404 321.27
20150405 103
20150406 25
20150407 180.5
20150408 319.91
20150409 688
20150411 69
20150412 65
20150413 322
20150414 100
20150415 111.97
20150416 979.15
20150417 655.4
20150418 124
20150419 30
20150420 457
20150421 192.6
20150422 191.96
20150423 220
20150424 252.5
20150425 109.1
20150426 135.25
20150427 648.08
20150428 692
20150429 410.99
20150430 170
20150501 166.19
20150502 92
20150503 100
20150504 59
20150505 124.01
20150506 44.5
20150507 331.64
20150508 299.8
I am trying to devise a query that will find the highest 4 consecutive days values in the data.
Essentially, I think I need to partition by date and perform a row numbering over it but I can't seem to get the syntax right to evaluate the values.
So I use -3 in the join conditions since the day itself counts as one. Let me know what you think. Also I use day of year(DY) to ensure that it's only consecutive days and so I don't have to rank the dates manually. Hope this helps!
DECLARE #yourTable TABLE(FileDate DATE ,SumAmount FLOAT);
INSERT INTO #yourTable
VALUES ('20150401',90.99),
('20150402',313),
('20150403',481.2),
('20150404',321.27),
('20150405',103),
('20150406',25),
('20150407',180.5),
('20150408',319.91),
('20150409',688),
('20150411',69),
('20150412',65),
('20150413',322),
('20150414',100),
('20150415',111.97),
('20150416',979.15),
('20150417',655.4),
('20150418',124),
('20150419',30),
('20150420',457),
('20150421',192.6),
('20150422',191.96),
('20150423',220),
('20150424',252.5),
('20150425',109.1),
('20150426',135.25),
('20150427',648.08),
('20150428',692),
('20150429',410.99),
('20150430',170),
('20150501',166.19),
('20150502',92),
('20150503',100),
('20150504',59),
('20150505',124.01),
('20150506',44.5),
('20150507',331.64),
('20150508',299.8);
WITH CTE
AS
(
SELECT YEAR(FileDate) yr,DATEPART(DY,FileDate) dy,fileDate,SumAmount
FROM #yourTable
),
CTE_Max_Sum
AS
(
SELECT TOP 1 A.yr,A.dy,A.FileDate,SUM(B.SumAmount) consec4DaySum
FROM CTE A
INNER JOIN CTE B
ON B.dy BETWEEN A.dy - 3 AND A.dy
AND A.yr = B.yr
GROUP BY A.yr,A.dy,A.FileDate
ORDER BY SUM(B.SumAmount) DESC
)
SELECT A.*,B.consec4DaySum
FROM CTE A
INNER JOIN CTE_Max_Sum B
ON A.dy BETWEEN B.dy - 3 AND B.dy
AND A.yr = B.yr
Results:
yr dy fileDate SumAmount consec4DaySum
----------- ----------- ---------- ---------------------- ----------------------
2015 117 2015-04-27 648.08 1921.07
2015 118 2015-04-28 692 1921.07
2015 119 2015-04-29 410.99 1921.07
2015 120 2015-04-30 170 1921.07
You can use a CTE for that, joining every row with its three following rows (day-wise) and summing up. This Fiddle sadly does not work for me, it runs on my sql server and work for you. Watch out for recursion depth, without WHERE cte.Consecutive < 4 you quickly run into an error.
WITH cte (StartDate, EndDate, Consecutive, SumAmount)
AS (
SELECT t.FileDate, t.FileDate, 1, t.SumAmount FROM dbo.table30194903 t
UNION ALL
SELECT cte.StartDate, t.FileDate, cte.Consecutive + 1, cte.SumAmount + t.SumAmount
FROM dbo.table30194903 t INNER JOIN cte ON DATEADD(DAY, 1, cte.EndDate) = t.FileDate
WHERE cte.Consecutive < 5
)
SELECT *
FROM cte
WHERE cte.Consecutive = 4
ORDER BY cte.SumAmount DESC
EDIT: Had two errors in my query, it summed up wrong rows and showd the last day in the series.
I would like to add an answer using a subquery, however it does take more time compared to my cte...
SELECT t.FileDate, SUM(s.SumAmount)
FROM dbo.table30194903 t
LEFT JOIN dbo.table30194903 s ON t.FileDate <= s.FileDate AND DATEDIFF(DAY, t.FileDate, s.FileDate) < 4
GROUP BY t.FileDate
HAVING COUNT(s.SumAmount) = 4
ORDER BY SUM(s.SumAmount) DESC
I think the simplest way to get this is to use an APPLY to get the number of records in the n days following each row, and then limit this to where there are n dates, this ensures you have consecutive days. You can then just order by the sum and select the top 1:
DECLARE #n INT = 4;
SELECT TOP 1
FirstDate = t.FileDate,
FourDaySum = t2.Amount
FROM dbo.T
CROSS APPLY
( SELECT Amount = SUM(t2.SumAmount),
Dates = COUNT(DISTINCT t2.FileDate)
FROM dbo.T AS t2
WHERE t2.FileDate >= t.FileDate
AND t2.FileDate < DATEADD(DAY, #n, t.FileDate)
) AS t2
WHERE t2.Dates = #n
ORDER BY t2.Amount DESC;
Example on SQL Fiddle
How about a simply while block and sum the values of a range of dates?
DECLARE #startingDate DATETIME, #searchDate DATETIME;
DECLARE #maxSoFar INT, #sum INT, #daysRange INT;
SET #startingDate = convert(datetime, '20150401', 110)
SET #searchDate = #startingDate;
SET #daysRange = 3;
SET #maxSoFar = 0;
WHILE GETDATE()> #searchDate
BEGIN
--PRINT #searchDate
--PRINT DATEADD(DAY,#daysRange,#searchDate)
SELECT #sum = SUM(SumAmount) FROM MyTable WHERE FileDate >= #searchDate AND FileDate <= DATEADD(DAY,#daysRange,#searchDate)
IF #sum > #maxSoFar
BEGIN
SET #maxSoFar = #sum;
END
SET #searchDate = DATEADD(DAY,1,#searchDate)
END

Add functionality to query

I have some data in SQL Server as:
att1 att2 att3 att4 att5 ... att205
---------------------------------------
438 498 3625 3645 5000 ... 5000
438 498 3625 3648 5000 ... 5040
438 498 3625 3629 5000 ... 5330
437 501 3625 3626 5000 ... 5040
438 498 3626 3629 5000 ... 5050
I want to know the square root of the sum for each column of data, to do so I do:
CREATE VIEW VIEW_myTable (ID, Value) AS (
SELECT 1, SQRT(SUM(att1)) FROM myTABLE
UNION ALL SELECT 2, SQRT(SUM(att2)) FROM myTABLE
UNION ALL SELECT 3, SQRT(SUM(att3)) FROM myTABLE
UNION ALL SELECT 4, SQRT(SUM(att4)) FROM myTABLE
...
UNION ALL SELECT 205, SQRT(SUM(att205)) FROM myTABLE
) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'myTABLE'
Also would like to ADD a extra result, the number of rows it sumed...
So in the example above there are 5 rows and 205 columns.
How can I take advantage of the scan sql did to the table while suming the elements?
so I do not do something like another SELECT aka.
SELECT COUNT(*) FROM [myTABLE]
In fewer words I want to take advantage of scanning the table in that query...
I was thinking to put Something like SUM(1) somewhere but do not know where....
I used the FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'myTABLE' because I am doing dynamic sql... but the query looks like that...
----------------EDIT----------------------
As answered by #Neil Moss:
if I would have lots of columns, more than 1024... SQL will not support this... Is there a way to overcome this situation?
If you are prepared to accept the query returning a single row, with a SqrtSumAttrX column for each AttrX column, then dynamically constructing the following works just fine:
select
sqrt(sum(attr1)) as SqrtSumAttr1,
sqrt(sum(attr2)) as SqrtSumAttr2,
sqrt(sum(attr3)) as SqrtSumAttr3,
...
sqrt(sum(attr205)) as SqrtSumAttr205,
sum(1) as RowsScanned
from
MyTable
This has the bonus of only scanning the table once, whereas the sample in your question scans it 205 times - once for each union.
Input:
Attr1 Attr2 Attr3
1 2 3
4 5 6
7 8 9
10 11 12
Output:
SqrtSumAttr1 SqrtSumAttr2 SqrtSumAttr3 RowsScanned
4.69041575982343 5.09901951359278 5.47722557505166 4
EDIT post-acceptance
For dynamic construction, try this:
declare #columns nvarchar(max)
declare #sql nvarchar(max)
set #columns = ''
select
#columns = #columns + 'sqrt(sum([' + [COLUMN_NAME] + '])) as SumSqrt' + [COLUMN_NAME] + ','
from
[INFORMATION_SCHEMA].[COLUMNS]
where
TABLE_NAME = 'MyTable'
and
DATA_TYPE in ('int', 'decimal', 'float') -- numerics only (add other datatypes as needed)
order by
ORDINAL_POSITION
set #sql = 'select ' + #columns + 'count(*) as RowsScanned from Results'
exec (#sql)
I've used count(*) rather than sum(1) as suggested by Andriy M as this returns 0 if no rows exist, rather than null.

Resources