I have the following table:
--------------------------------------------------------------
InspectYear Part Pos1 Pos2 Pos3 Pos4
--------------------------------------------------------------
2009 001 8 8 9 7
2009 002 9 7 8 6
2011 001 9 9 8 7
2011 002 7 8 6 8
2013 001 8 9 7 9
2013 002 7 7 8 8
2015 001 8 8 7 4
2015 002 7 6 9 8
The InspectYeardata will always add every 2 years for each Part.
I want to calculate the newest value on each pos# column with the previous year (Calc1). Also the newest value with the oldest value (Calc2). I want to have the following result:
---------------------------------------------------------------------
Part Pos 2009 2011 2013 2015 Calc1 Calc2
---------------------------------------------------------------------
001 Pos1 8 9 8 8 0 0
001 Pos2 8 9 9 8 -1 0
001 Pos3 9 8 7 7 0 -2
001 Pos4 7 7 9 4 -5 -3
For the pivot thing, I did the following code but don't know for the calculation:
declare #inspectyear as nvarchar(max), #query as nvarchar(max);
set #inspectyear = STUFF((select distinct ',' + quotename(InspectYear) from #t2 c
for XML path(''), type).value('.','NVARCHAR(MAX)'),1,1,'')
set #query =
';with data as
(
select inspectyear,
partno, Pos, number
from #t2
unpivot
(
number
for Pos in ([Pos1], [Pos2], [Pos3], [Pos4])
) unpvt
)
select * into ##temp
from data
pivot
(
sum(number)
for inspectyear in (' + #inspectyear + ')
) pvt
order by Part'
exec sp_executesql #query = #query;
select * from ##temp;
drop table ##temp;
From the table above, Calc1 is value on 2015 minus value on 2013.Calc2 is value on 2015 minus value on 2009.
The formula above will change when new records created on 2017. So the formula for Calc1 is always get the last 2 values on years. Calc2 will always get the newest value minus the oldest value.
Does anyone have an idea for this ?
Thank you.
You want to add in these 2 calculated columns as you insert into ##temp, but you need to specify what they are at the same time as you build your dynamic query. So you use the same method as you did to get the column names - you build a string.
At the top of your workings, add in another string variable, #calc:
declare #inspectyear as nvarchar(max), #calc as nvarchar(max), #query as nvarchar(max);
set #inspectyear = STUFF((select distinct ',' + quotename(InspectYear) from ##t2 c
for XML path(''), type).value('.','NVARCHAR(MAX)'),1,1,'')
select #calc = ', ' + quotename(Max(InspectYear)) + ' - ' + quotename(Max(InspectYear)-2)
+ ' as Calc1, ' + quotename(Max(InspectYear)) + ' - ' + quotename(min(InspectYear))
+ ' as Calc2' from #t2;
Then include that in your dynamic query string as follows:
set #query =
';with data as
(
select inspectyear,
partno, Pos, number
from #t2
unpivot
(
number
for Pos in ([Pos1], [Pos2], [Pos3], [Pos4])
) unpvt
)
select * ' + #calc + ' into ##temp
from data
pivot
(
sum(number)
for inspectyear in (' + #inspectyear + ')
) pvt
order by partno';
exec(#query);
This should add the extra columns to ##temp as required.
Related
I am looking to find the sum of the current + the last X Nth Rows. I am able to do this with the following query, however it is not very scalable.
SELECT [id], [amount] + LAG([amount],6) OVER(ORDER BY [id]) + LAG([amount],12) OVER(ORDER BY [id]) + LAG([amount],18) OVER(ORDER BY [id])
If this example, I'm finding the current value of "amount", plus the last 3 "amounts" split 6 apart:
X = 3
N = 6
I will be using these within dynamic queries and would prefer not to build such a complex query each time. There could be many "lags" in some of the queries. Is there another way to write this query that would be more scalable?
SOURCE DATA
ID
Amount
1
107.35
2
105.41
3
104.63
4
106.7
5
108.7
6
110.21
7
108.8
8
108.91
9
108.5
10
106.66
11
105.2
12
106.5
13
108.27
14
109.72
15
111.53
16
112.8
17
109.03
18
115.31
19
115.56
20
116.85
21
116.08
22
117.61
23
118.31
24
119.25
25
118.45
26
118.43
27
120.16
28
122.5
29
125.57
30
125.65
EXPECTED RESULTS
ID
SUM OF LAST 4
1
NULL
2
NULL
3
NULL
4
NULL
5
NULL
6
NULL
7
NULL
8
NULL
9
NULL
10
NULL
11
NULL
12
NULL
13
NULL
14
NULL
15
NULL
16
NULL
17
NULL
18
NULL
19
439.98
20
440.89
21
440.74
22
443.77
23
441.24
24
451.27
25
451.08
26
453.91
27
456.27
28
459.57
29
458.11
30
466.71
At a best guess, it seems like what you want is something like this:
DECLARE #X int = 3,
#N int = 6;
SELECT YT.ID,
YT.Amount,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY G.Grp ORDER BY ID) < #X+1 THEN NULL
ELSE SUM(Amount) OVER (PARTITION BY G.Grp ORDER BY ID
ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
END
FROM dbo.YourTable YT
CROSS APPLY (VALUES(ID % #N))G(Grp)
ORDER BY YT.ID;
You'll note, however, that the 3 is hardcoded in one place, as you cannot use a variable for the ROWS BETWEEN clause. If you need to parametrise this, you'll need to use dynamic SQL:
DECLARE #X int = 3,
#N int = 6;
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = CONCAT(N'SELECT YT.ID,', #CRLF,
N' YT.Amount,', #CRLF,
N' CASE WHEN ROW_NUMBER() OVER (PARTITION BY G.Grp ORDER BY ID) < #X+1 THEN NULL', #CRLF,
N' ELSE SUM(Amount) OVER (PARTITION BY G.Grp ORDER BY ID', #CRLF,
N' ROWS BETWEEN ',#X,N' PRECEDING AND CURRENT ROW)', #CRLF, --I don't like injecting raw values, but if #X is an int, it is "safe"
N' END', #CRLF,
N'FROM dbo.YourTable YT', #CRLF,
N' CROSS APPLY (VALUES(ID % #N))G(Grp)', #CRLF,
N'ORDER BY YT.ID;');
PRINT #SQL; --Your best debugging friend
EXEC sys.sp_executesql #SQL, N'#X int, #N int', #X, #N;
db<>fiddle
Here is my tables structure
Result
id ResultId CategoryId Total Attempted Score
----------------------------------------------------
1 8 1 30 25 20
2 8 2 30 30 19
3 8 3 30 27 21
4 7 1 20 15 10
5 7 2 20 20 15
Category
Id CategoryName
-----------------------
1 General
2 Aptitude
3 Technical
I want data in the below format
For ResultId = 8
Id General Aptitude Technical Total
--------------------------------------------------
8 20 19 21 60
For ResultId = 7
Id General Aptitude Total
-------------------------------------
7 10 15 25
I need a help to fetch the data in above format.
NOTE: The final fetched data contains score from Result table but having a column names from category table and according to CategoryId in Result table. Column name will be dynamic
Tried the below code (just for testing) but didn't work
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') + QUOTENAME(CategoryName)
FROM ( SELECT DISTINCT CategoryName
FROM [dbo].[Category] c) AS PivotExample
SET #SQLQuery =
N'SELECT DISTINCT ' + #PivotColumns + '
FROM [dbo].[Category] c
PIVOT( SUM(c.Id)
FOR CategoryName IN (' + #PivotColumns + ')) AS P'
EXEC sp_executesql #SQLQuery
My query will give you the expected output. You can check the output in SQL Fiddle
--dynamic with case
DECLARE #Sql NVARCHAR(4000) = NULL
DECLARE #ColumnHeaders NVARCHAR(4000);
SET #ColumnHeaders = STUFF((
SELECT DISTINCT ',' + 'Max(CASE WHEN rn =' + quotename(rn, '''') + ' THEN Score else null end ) as ' + CategoryName + CHAR(10) + CHAR(13)
FROM (
SELECT CategoryName, row_number() OVER (ORDER BY CategoryName) rn
FROM ( SELECT DISTINCT CategoryName FROM Category) t0
) t1
FOR XML PATH('')
,TYPE
).value('.', 'varchar(max)'), 1, 1, '');
--print #ColumnHeaders
SET #sql = N' ;with cte as (
select * , Row_number() Over(Partition by ResultId Order by ResultId ) rn from
Result)
Select ResultId, ' + #ColumnHeaders + ', SUM(Score) Total from cte Group by
ResultId ';
EXECUTE sp_executesql #sql
I have the following table:
--------------------------------------------------------------
InspectYear Part Pos1 Pos2 Pos3 Pos4
--------------------------------------------------------------
2009 001 8 8 9 7
2009 002 9 7 8 6
2011 001 9 9 8 7
2011 002 7 8 6 8
2013 001 8 9 7 9
2013 002 7 7 8 8
2015 001 10 8 7 4
2015 002 7 6 9 8
The InspectYeardata will always add every 2 years for each Part.
I want to calculate the newest value on each pos# column with the previous year (Calc1). Also the newest value with the oldest value (Calc2).
Then, I have the following code:
declare #inspectyear as nvarchar(max), #calc as nvarchar(max), #query as nvarchar(max);
set #inspectyear = STUFF((select distinct ',' + quotename(InspectYear) from ##t2 c
for XML path(''), type).value('.','NVARCHAR(MAX)'),1,1,'')
select #calc = ', ' + quotename(Max(InspectYear)) + ' - ' + quotename(Max(InspectYear)-2)
+ ' as Calc1, ' + quotename(Max(InspectYear)) + ' - ' + quotename(min(InspectYear))
+ ' as Calc2' from #t2;
set #query =
';with data as
(
select inspectyear,
partno, Pos, number
from #t2
unpivot
(
number
for Pos in ([Pos1], [Pos2], [Pos3], [Pos4])
) unpvt
)
select * ' + #calc + ' into ##temp
from data
pivot
(
sum(number)
for inspectyear in (' + #inspectyear + ')
) pvt
order by partno';
exec sp_executesqk #query = #query;
select * from ##temp;
drop table ##temp;
the result should be:
---------------------------------------------------------------------
Part Pos 2009 2011 2013 2015 Calc1 Calc2
---------------------------------------------------------------------
001 Pos1 8 9 8 10 2 2
001 Pos2 8 9 9 8 -1 0
001 Pos3 9 8 7 7 0 -2
001 Pos4 7 7 9 4 -5 -3
I want to modify the calculation for Calc1 and Calc2 which is:
Calc1 = (newest value on each pos# column - value on the previous year on each pos#) / distance from latest year until the previous year.
Calc2 = (newest value on each pos# column - value on the first year on each pos#) / distance from latest year until the first year.
For instance:
---------------------------------------------------------------------
Part Pos 2009 2011 2013 2015 Calc1 Calc2
---------------------------------------------------------------------
001 Pos1 8 9 8 10 1 0.333
Year distance on the table above are, for Calc1 2015 - 2013 = 2
for Calc2 2015 - 2009 = 6
So the question is, how can I get the distance between the newest year minus previous year and the newest year minus the oldest year...?
Does anyone have an idea for this?
Thank you.
As mentioned in the comments, the easy fix here is to change the #calc variable to look like this:
select #calc = ', (' + quotename(Max(InspectYear)) + ' - ' + quotename(Max(InspectYear)-2)
+ ') / 2.0 as Calc1, 1.0 * (' + quotename(Max(InspectYear)) + ' - ' + quotename(min(InspectYear))
+ ') / (' + cast(max(inspectyear) as char(4)) + '-' + cast(min(inspectyear) as char(4)) + ') as Calc2'
from #t2;
Which will give you output similar to this: , ([2015] - [2013]) / 2.0 as Calc1, 1.0 * ([2015] - [2009]) / (2015-2009) as Calc2
I put together a version of this but it can definitely be optimized.
To give you an idea of whats happening, run it down to (and including) the commented section. The commented section is then just pivoted to get the result.
CREATE TABLE #t2
( InspectYear int
,Part [nchar](5)
,Pos1 int
,Pos2 int
,Pos3 int
,Pos4 int
)
Insert Into #t2
Values
('2009','001','8','8','9','7'),
('2009','002','9','7','8','6'),
('2011','001','9','9','8','7'),
('2011','002','7','8','6','8'),
('2013','001','8','9','7','9'),
('2013','002','7','7','8','8'),
('2015','001','10','8','7','4'),
('2015','002','7','6','9','8');
DECLARE #cols AS NVARCHAR(MAX),
#count AS NVARCHAR(MAX),
#max AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.InspectYear)
FROM #t2 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #count = (Select COUNT(distinct InspectYear)-1 FROM #t2);
SET #max = (Select MAX(InspectYear) FROM #t2);
/* --TESTING & to see how this works
Select
inspectyear,
part,
Pos,
number,
Last_Value(Calc1)Over(Partition by Part,Pos Order by inspectyear ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as Calc1,
Last_Value(Calc2)Over(Partition by Part,Pos Order by inspectyear ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as Calc2
from (
select
inspectyear,
part,
Pos,
number,
CASE WHEN inspectyear=#max THEN
(number-LAG(number,1,0)Over(Partition by Part,Pos Order by inspectyear))
/(inspectyear-LAG(inspectyear,1,0)Over(Partition by Part,Pos Order by inspectyear)) END as Calc1,
CASE WHEN inspectyear=#max THEN
CAST((number-LAG(number,#count,0)Over(Partition by Part,Pos Order by inspectyear))as decimal(8,7))
/(inspectyear-LAG(inspectyear,#count,0)Over(Partition by Part,Pos Order by inspectyear)) END as Calc2
from #t2
unpivot
(
number
for Pos in ([Pos1], [Pos2], [Pos3], [Pos4])
) unpvt )t3
Order by Pos,Part,inspectyear
*/
set #query = 'SELECT Part,Pos, ' + #cols + ' , Calc1, Calc2 from
(
Select
inspectyear,
part,
Pos,
number,
Last_Value(Calc1)Over(Partition by Part,Pos Order by inspectyear ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as Calc1,
Last_Value(Calc2)Over(Partition by Part,Pos Order by inspectyear ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as Calc2
from (
select
inspectyear,
part,
Pos,
number,
CASE WHEN inspectyear=' + #max + ' THEN
(number-LAG(number,1,0)Over(Partition by Part,Pos Order by inspectyear))
/(inspectyear-LAG(inspectyear,1,0)Over(Partition by Part,Pos Order by inspectyear)) END as Calc1,
CASE WHEN inspectyear=' + #max + ' THEN
CAST((number-LAG(number,' + #count + ',0)Over(Partition by Part,Pos Order by inspectyear))as decimal(8,7))
/(inspectyear-LAG(inspectyear,' + #count + ',0)Over(Partition by Part,Pos Order by inspectyear)) END as Calc2
from #t2
unpivot
(
number
for Pos in ([Pos1], [Pos2], [Pos3], [Pos4])
) unpvt )t3
) x
pivot
(
SUM(number)
for inspectyear in (' + #cols + ')
) p
Order by Part,Pos'
execute(#query)
I am working in a SQL Server 2008 environment with SQL Server Management Studio 2012.
I have written 3 separate queries
QUERY 1
SQL query SUMS all the STOCK ON HAND from an Inventories table
SELECT StockCode,
Sum(QtyOnHand) AS 'SOH'
FROM InvWarehouse
WHERE StockCode NOT LIKE '%DEM%' AND StockCode NOT LIKE '%REF%' AND StockCode NOT LIKE 'Z%'
GROUP BY InvWarehouse.StockCode
QUERY 2
This query looks at future orders from a Purchase Orders Table and dynamically returns the next/following 12 months
DECLARE
#cols AS NVARCHAR(MAX),
#cols1 AS NVARCHAR(MAX),
#cols2 AS NVARCHAR(MAX),
#cols3 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(YearMonth)
FROM
-- Selecting Using the Destinct --
(SELECT DISTINCT CAST(YEAR([OrderDueDate]) AS NVARCHAR(4)) + RIGHT('00' + CAST(MONTH([OrderDueDate]) AS NVARCHAR(2)),2) AS YearMonth
FROM PorMasterHdr
JOIN PorMasterDetail
ON PorMasterDetail.PurchaseOrder = PorMasterHdr.PurchaseOrder
WHERE DATEDIFF(MONTH, OrderDueDate, DATEADD(m,12,GETDATE())) <= 12 ) sub
ORDER BY YearMonth
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,''),
#cols2 = STUFF((SELECT ',ISNULL(' + QUOTENAME(YearMonth) + ',0) AS ' + QUOTENAME(YearMonth)
FROM
-- Selecting Using the Destinct --
(SELECT DISTINCT CAST(YEAR([OrderDueDate]) AS NVARCHAR(4)) + RIGHT('00' + CAST(MONTH([OrderDueDate]) AS NVARCHAR(2)),2) AS YearMonth
FROM PorMasterHdr
JOIN PorMasterDetail
ON PorMasterDetail.PurchaseOrder = PorMasterHdr.PurchaseOrder
WHERE DATEDIFF(MONTH, OrderDueDate, DATEADD(m,12,GETDATE())) <= 12) sub
ORDER BY YearMonth
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #query = '
SELECT MStockCode, ' + #cols2 + '
FROM (
SELECT MStockCode,
MOrderQty,
CAST(YEAR([OrderDueDate]) AS NVARCHAR(4))+RIGHT(''00''+CAST(MONTH([OrderDueDate]) AS NVARCHAR(2)),2) AS YearMonth
FROM PorMasterHdr
JOIN PorMasterDetail
ON PorMasterDetail.PurchaseOrder = PorMasterHdr.PurchaseOrder
WHERE MStockCode NOT LIKE ''%DEM%'' AND MStockCode NOT LIKE ''%REF%'' AND MStockCode NOT LIKE ''Z%''
) AS X
PIVOT (
SUM(MOrderQty)
FOR YearMonth in (' + #cols + ')
) AS PT'
EXECUTE (#query)
QUERY 3
This query looks at the past 12 month of sales data from a Sales table and dynamically returns the last/previous 12 months
DECLARE
#cols AS NVARCHAR(MAX),
#cols1 AS NVARCHAR(MAX),
#cols2 AS NVARCHAR(MAX),
#cols3 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(YearMonth)
FROM
-- Selecting Using the Destinct --
(SELECT DISTINCT CAST([TrnYear] AS NVARCHAR(4)) + RIGHT('00' + CAST([TrnMonth] AS NVARCHAR(2)),2) AS YearMonth
FROM ArTrnDetail
WHERE DATEDIFF(MONTH, InvoiceDate, GETDATE()) <= 12 ) sub
ORDER BY YearMonth
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,''),
#cols2 = STUFF((SELECT ',ISNULL(' + QUOTENAME(YearMonth) + ',0) AS ' + QUOTENAME(YearMonth)
FROM
-- Selecting Using the Destinct --
(SELECT DISTINCT CAST([TrnYear] AS NVARCHAR(4)) + RIGHT('00' + CAST([TrnMonth] AS NVARCHAR(2)),2) AS YearMonth
FROM ArTrnDetail
WHERE DATEDIFF(MONTH, InvoiceDate, GETDATE()) <= 12) sub
ORDER BY YearMonth
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #query = '
SELECT StockCode, ' + #cols2 + '
FROM (
SELECT StockCode,
QtyInvoiced,
CAST([TrnYear] AS NVARCHAR(4))+RIGHT(''00''+CAST([TrnMonth] AS NVARCHAR(2)),2) AS YearMonth
FROM ArTrnDetail
WHERE StockCode NOT LIKE ''%DEM%'' AND StockCode NOT LIKE ''%REF%'' AND StockCode NOT LIKE ''Z%''
) AS X
PIVOT (
SUM(QtyInvoiced)
FOR YearMonth in (' + #cols + ')
) AS PT'
EXECUTE (#query)
The results for each query are correct. Now how do I combine them into one query. So that they return
STOCKCODE | Past 12 Month Sales Per Month | Stock On Hand | Future Purchases
Helicopters | 1 4 5 2 3 4 6 1 3 2 3 2| 15 | 2 3 5 4 6 7 8 4 3 2 8 5
Jam | 2 5 6 4 8 5 8 5 7 2 1 2| 30 | 4 5 6 5 8 7 0 1 2 1 1 4
Frogs | 2 3 2 4 8 5 4 6 8 2 1 3| 7 | 5 7 8 8 6 7 4 0 1 2 1 2
STOCK CODE for the above is the same information from the different tables eg. Helicopters in Inventory is the same as Helicopters in Purchase Orders.
I would suggest the following:
Rewrite #query2 to result in two columns: StockCode and Sales. Instead of selecting each month as a seperate column, concatenate each month in a VARCHAR. You already wrote a variable for #cols for selecting the columns seperately. Keep that for pivoting. Write a variable (#SelSales) to concatenate the results for each month in a VARCHAR and use that in your selection for the Sales column.
Rewrite #query3 to result in two columns: StockCode and Purchases (similar to 1.)
Put your #query1 in a NVARCHAR(MAX) variable (the one selecting the stock).
Write a #query to combine them all.
TSQL outline for #query:
DECLARE #query NVARCHAR(MAX);
SET #query=N'
SELECT
COALESCE(stock.StockCode,sales.StockCode,purchases.StockCode) AS StockCode,
COALESCE(sales.Sales,''0 0 0 0 0 0 0 0 0 0 0 0'') AS Sales,
COALESCE(stock.SOH,0) AS Stock,
COALESCE(purchases.Purchases,''0 0 0 0 0 0 0 0 0 0 0 0'') AS Purchases
FROM
('+#query1+') AS stock
FULL JOIN ('+#query2+') AS sales ON sales.StockCode=stock.StockCode
FULL JOIN ('+#query3+') AS purchases ON purchases.StockCode=stock.StockCode';
EXEC(#query);
If I have a table:
id tstamp x y
-----------------
1 111 1 A
2 111 2 B
3 111 3 C
4 222 1 D
5 222 2 E
6 222 3 F
7 333 1 G
8 333 2 H
9 333 3 I
... nnn ... ...
How can I split it into the following, where the number of columns is dependent on a range of tstamp?
x y111 y222 y333 ynnn
----------------------------------
1 A D G ...
2 B E H ...
3 C F I ...
It seems fundamentally simple, but I can't find any relevant example or API? There's a good 10 or so similar questions but they all deal with string handling, csv, xml???
I imagine it would be pretty straight forward to do in c# or another scripting language, but I would expect it should be an easy thing in sql?
you need to construct column values of tstamp for PIVOT and use dynamic query
DECLARE #cols NVARCHAR(MAX)
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + tstamp
FROM Table1
ORDER BY '],[' + tstamp
FOR XML PATH('')
), 1, 2, '') + ']'
DECLARE #query NVARCHAR(4000)
SET #query = N'SELECT x, '+
#cols +'
FROM
(SELECT x, y, tstamp
FROM Table1
) p
PIVOT
(
MAX(y)
FOR tstamp IN
( '+
#cols +' )
) AS pvt
'
exec sp_executesql #query