Rank in dynamic query in SQL - sql

I am trying to build a dynamic query and create a ranking base on one of the columns
this is the code I have so far
/*Declare Variable*/
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX),
#PivotColumnNames AS NVARCHAR(MAX),
#PivotSelectColumnNames AS NVARCHAR(MAX),
#Week_Value AS NVARCHAR(MAX),
#Rank AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #PivotColumnNames= ISNULL(#PivotColumnNames + ',','')+ QUOTENAME([W])
FROM (SELECT DISTINCT [W] FROM [Server].[dbo].[Table]) AS cat
ORDER BY [W]
--Get distinct values of the PIVOT Column with isnull
SELECT #PivotSelectColumnNames = ISNULL(#PivotSelectColumnNames + ',','')+ 'ISNULL(' + QUOTENAME([W]) + ', 0) AS '+ QUOTENAME([W])
FROM (SELECT DISTINCT [W] FROM [Server].[dbo].[Table]) AS cat
ORDER BY [W]
--Get current week value
SET #Week_Value = SUBSTRING(#PivotColumnNames,81,15)
--PRINT #Week_Value --Make sure value is correct
--Get the ranking for previews week
SET #Rank = 0 -- this initialize the rank
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery = '
SELECT TOP 200 [L], [Doc_ID], '+#PivotSelectColumnNames+','+#Rank+' AS RNK
FROM
(SELECT [L],
[Doc_ID],
[W],
ISNULL(CAST([PV] AS INT),0) AS [Page_Views]
FROM [Server].[dbo].[Table]
WHERE LEN([Doc_ID]) = 8 OR LEN([Doc_ID]) = 9)Tab1
PIVOT
(
SUM([Page_Views])
FOR [Week] IN ('+#PivotColumnNames+')
) AS Tab2
ORDER BY '+#Week_Value+' DESC'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
so the problem is that the rank is static and I can't figure out how to sum +1 in each row, the rank has to be set base on #PivotColumnNames order, this is a sample from the data I have so far applying the code above
L
Doc_ID
Week 12, 2021
Week 13, 2021
Week 14, 2021
Week 15, 2021
RNK
en
c03722645m
191867
168145
188472
185189
0
fr
c03746609
55908
53467
56678
56028
0
this is what I would like
L
Doc_ID
Week 12, 2021
Week 13, 2021
Week 14, 2021
Week 15, 2021
RNK
en
c03722645m
191867
168145
188472
185189
1
fr
c03746609
55908
53467
56678
56028
2
any help or recommendation would be appreciated

Hi There are multiple Ranking options available in TSQL. The question that you have you have to determine the required ranking function as per your requirement. I will use RANK() function in this periocular solution
/*Declare Variable*/
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX),
#PivotColumnNames AS NVARCHAR(MAX),
#PivotSelectColumnNames AS NVARCHAR(MAX),
#RankingSelectColumn AS VARCHAR(MAX), --Create ranking statement (Added)
#Week_Value AS NVARCHAR(MAX),
#Rank AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #PivotColumnNames= ISNULL(#PivotColumnNames + ',','')+ QUOTENAME([W])
FROM (SELECT DISTINCT [W] FROM [Server].[dbo].[Table]) AS cat
ORDER BY [W]
--Make Ranking logic depending on the rank change ASC or DESC
SELECT #RankingSelectColumn = ISNULL(#RankingSelectColumn + ' ASC,','')+ 'ISNULL(' + QUOTENAME([W]) + ', 0) AS '+ QUOTENAME([W])
FROM (SELECT DISTINCT [W] FROM [Server].[dbo].[Table]) AS cat
ORDER BY [W]
--Get distinct values of the PIVOT Column with isnull (Added)
SELECT #PivotSelectColumnNames = ISNULL(#PivotSelectColumnNames + ',','')+ 'ISNULL(' + QUOTENAME([W]) + ', 0) AS '+ QUOTENAME([W])
FROM (SELECT DISTINCT [W] FROM [Server].[dbo].[Table]) AS cat
ORDER BY [W]
--Get current week value
SET #Week_Value = SUBSTRING(#PivotColumnNames,81,15)
--PRINT #Week_Value --Make sure value is correct
--Get the ranking for previews week
SET #Rank = 0 -- this initialize the rank
--Prepare the PIVOT query using the dynamic (Added Rank function)
SET #DynamicPivotQuery = '
SELECT TOP 200 [L], [Doc_ID], '+#PivotSelectColumnNames+',RANK() OVER('+#RankingSelectColumn+') AS RNK
FROM
(SELECT [L],
[Doc_ID],
[W],
ISNULL(CAST([PV] AS INT),0) AS [Page_Views]
FROM [Server].[dbo].[Table]
WHERE LEN([Doc_ID]) = 8 OR LEN([Doc_ID]) = 9)Tab1
PIVOT
(
SUM([Page_Views])
FOR [Week] IN ('+#PivotColumnNames+')
) AS Tab2
ORDER BY '+#Week_Value+' DESC'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This will rank according to the column logic

Related

How to order known values as columns in Dynamic Pivot and new ones to the end?

I am setting up a dynamic pivot where the columns (Department IDs) must be in a set order and any new values that will create a column must be at the end of the table. I set up a Sequence number for the "known" departments and any new departments get the next number in the sequence. I need the Department IDs to be the headings but I need them in the order of the Sequence number.
1) I have pivoted on the Sequence number:
FROM (SELECT DISTINCT [SEQ] FROM #TABLE) AS [SEQ]
ORDER BY [SEQ]
SET #DynamicPivotQuery =
N'SELECT [DATE], ' + #ColumnName + '
FROM #TABLE
PIVOT(SUM([COUNT])
FOR [SEQ] IN (' + #ColumnName + ')) AS PVTTable
ORDER BY [DATE]'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This comes out in the correct order but the Sequence number is the column heading
2) I also have pivoted on the Dept but the columns are in the sequence of the Dept ID:
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME([DEPT_ID])
FROM (SELECT DISTINCT [DEPT_ID] FROM #TABLE) AS [DEPT_ID]
ORDER BY [DEPT_ID]
SET #DynamicPivotQuery =
N'SELECT [DATE], ' + #ColumnName + '
FROM #TABLE
PIVOT(SUM([COUNT])
FOR [DEPT_ID] IN (' + #ColumnName + ')) AS PVTTable
ORDER BY [DATE]'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
The data/table that is ready to be pivoted is:
SEQ DEPT DATE COUNT
----------------------------------
1 8 1/1/2019 5 (Dept 8 is known Dept)
1 8 1/2/2019 7
2 3 1/1/2019 6 (Dept 3 is known Dept)
2 3 1/2/2019 4
3 1 1/1/2019 7 (Dept 1 is an unknown dept)
3 1 1/2/2019 3
The results I want to see are:
DATE 8 3 1
----------------------------------
1/1/2019 5 6 7
1/2/2019 7 4 3
Or as an image:
You can use the following solution using a dynamic query and PIVOT:
-- declare the variables.
DECLARE #ColumnName NVARCHAR(MAX)
DECLARE #DynamicQuery NVARCHAR(MAX)
-- set the columns (ORDER BY SEQ).
SET #ColumnName = STUFF((
SELECT ',' + QUOTENAME(CONVERT(VARCHAR(10), DEPT))
FROM table_name
GROUP BY SEQ, DEPT
ORDER BY SEQ
FOR XML PATH('')
) , 1 , 1 , '')
-- set the pivot query to get the result.
SET #DynamicQuery = N'
SELECT [DATE], ' + #ColumnName + '
FROM (
SELECT DEPT, DATE, COUNT
FROM table_name
) st PIVOT(
SUM([COUNT])
FOR [DEPT] IN (' + #ColumnName + ')
) pt
ORDER BY [DATE]'
-- execute the dynamic query.
EXEC sp_executesql #DynamicQuery
demo on dbfiddle.uk
In case the order of the columns doesn't matter you can use the following solution:
-- pivot query without specific order of columns.
SELECT DATE, [8], [3], [1]
FROM (
SELECT DEPT, DATE, COUNT
FROM table_name
) st PIVOT (
SUM(COUNT)
FOR DEPT IN ([8], [3], [1])
) pt
ORDER BY [DATE];

SQL Select from an array

Is there an easy way to select from a (long) array?
I want something like this
SELECT citizenship, count(1)
FROM table
WHERE year(date) = 2018
GROUP BY citizenship
But I want all citizenship as column and all dates (from 2000 until as 2018) as rows so I can delete the where clause.
Sounds like you want to use PIVOT columns.
Heres what I think you want... ( in SQL Server)
See this article: https://www.databasejournal.com/features/mssql/converting-rows-to-columns-pivot-and-columns-to-rows-unpivot-in-sql-server.html
Applied to you that could be...
--Declare necessary variables
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
--Get unique values of pivot column
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') + QUOTENAME([citizenship])
FROM (SELECT DISTINCT [citizenship] FROM [dbo].[table]) AS PivotExample
--Create the dynamic query with all the values for
--pivot column at runtime
SET #SQLQuery =
N'SELECT [Year], ' + #PivotColumns + '
FROM (SELECT [citizenship], YEAR([Date]) AS [Year], COUNT(1) As Counter
FROM [Table] GROUP BY [citizenship], YEAR([Date])) AS A
PIVOT( SUM(Counter)
FOR [citizenship] IN (' + #PivotColumns + ')) AS P'
SELECT #SQLQuery
--Execute dynamic query
EXEC sp_executesql #SQLQuery
Adding distinct will give you list of unique citizens
SELECT distinct citizenship
FROM table
WHERE year(date) >= 2000 and year(date) <= 2018

Select transformation to table

I have data from select query which looks this way:
Id EventName Quantity
A930FF06-B9F2-4D06-A28E-00037E82DDB1 DiscountClick 40
A930FF06-B9F2-4D06-A28E-00037E82DDB1 DiscountLike 1
A930FF06-B9F2-4D06-A28E-00037E82DDB1 DiscountSave 11
A930FF06-B9F2-4D06-A28E-00037E82DDB1 DiscountView 2579
28D64EEB-97FB-45A9-AA4C-00359FF6FF42 DiscountClick 22
28D64EEB-97FB-45A9-AA4C-00359FF6FF42 DiscountSave 1
28D64EEB-97FB-45A9-AA4C-00359FF6FF42 DiscountView 971
I want transform it to table which looks this:
Id DiscountView Discount...
A930FF06-B9F2-4D06-A28E-00037E82DDB1 2579
28D64EEB-97FB-45A9-AA4C-00359FF6FF42 971
How I can do that?
This is the dynamic pivot query for your problem.
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX),
#PivotColumnNames AS NVARCHAR(MAX),
#PivotSelectColumnNames AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #PivotColumnNames= ISNULL(#PivotColumnNames + ',','')
+ QUOTENAME(EventName)
FROM (SELECT DISTINCT EventName FROM test_table) AS Courses
--Get distinct values of the PIVOT Column with isnull
SELECT #PivotSelectColumnNames
= ISNULL(#PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(EventName) + ', 0) AS '
+ QUOTENAME(EventName)
FROM (SELECT DISTINCT EventName FROM test_table) AS Courses
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT Id, ' + #PivotSelectColumnNames + '
FROM test_table
pivot(sum(quantity) for EventName in (' + #PivotColumnNames + ')) as pvt';
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This is the traditional cross tab / conditional aggregation version of a pivot():
select
Id
, DiscountView = sum(case when EventName = 'DiscountView' then Quantity end)
, DiscountSave = sum(case when EventName = 'DiscountSave' then Quantity end)
, DiscountLike = sum(case when EventName = 'DiscountLike' then Quantity end)
from t
group by Id
pivot() version:
select
Id
, DiscountView
, DiscountSave
, DiscountLike
from t
pivot (sum(Quantity) for EventName in (DiscountView, DiscountSave, DiscountLike)) pvt

T-SQL Pivot - Total Row and Dynamic Columns

Let's jump straight into it. Here's the code
SELECT [prov], [201304], [201305], [201306], [201307]
FROM (
SELECT [prov], [arrival], [Amount]
FROM [tblSource]) up
PIVOT (SUM([Amount]) FOR [arrival] IN ([201304], [201305], [201306], [201307])) AS pvt
GO
It brings me back an ever so lovely table. I was wondering how I would get the totals for each "date" column to show in an appended last row?
In addition, the underlying table will have more data added, specifically more dates. This means that 201308 will be added next, then 201309 etc
This will mean that currently I will have to amend the code above each month to reflect the addition. Is there anyway around this?
You can dynamically create the columns using dynamic SQL, however, I would really recommend handling dynamic pivots in a layer designed for it, such as SSRS or excel.
DECLARE #SQL NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '',
#SQL3 NVARCHAR(MAX) = '';
-- COMPILE THE UNIQUE VALUES FOR ARRIVAL THAT NEED TO BE PIVOTED
SELECT #SQL = #SQL + ',' + QUOTENAME(Arrival),
#SQL2 = #SQL2 + '+ISNULL(' + QUOTENAME(Arrival) + ', 0)',
#SQL3 = #SQL3 + ',' + QUOTENAME(Arrival) + ' = ISNULL(' + QUOTENAME(Arrival) + ', 0)'
FROM (SELECT DISTINCT Arrival FROM tblSource) s;
-- COMBINE THEM INTO A SINGLE QUERY
SET #SQL = 'SELECT [Prov]' + #SQL3 + ', [Total] = ' + STUFF(#SQL2, 1, 1, '') + '
FROM ( SELECT Arrival, Prov, Amount
FROM [tblSource]
UNION ALL
SELECT Arrival, ''Total'', SUM(Amount)
FROM [tblSource]
GROUP BY Arrival
) up
PIVOT
( SUM(Amount)
FOR Arrival IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt;';
-- EXECUTE THE QUERY
EXECUTE SP_EXECUTESQL #SQL;
This creates and executes the following SQL:
SELECT [Prov],
[2013-01-01] = ISNULL([2013-01-01], 0),
[2013-02-01] = ISNULL([2013-02-01], 0),
[Total] = ISNULL([2013-01-01], 0) + ISNULL([2013-02-01], 0)
FROM ( SELECT Arrival, Prov, Amount
FROM [tblSource]
UNION ALL
SELECT Arrival, 'Total', SUM(Amount)
FROM [tblSource]
GROUP BY Arrival
) up
PIVOT
( SUM(Amount)
FOR Arrival IN ([2013-01-01],[2013-02-01])
) pvt;
It is the query below union in the subquery up that adds the total row at the bottom, and the row total is simply created by adding all the columns in the row.
Example on SQL Fiddle
I will stress again though, I really recommend handling manipulation of data like this outside of SQL.
EDIT
An alternative to using the UNION to get the the total row is to use GROUPING SETS as follows:
DECLARE #SQL NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '',
#SQL3 NVARCHAR(MAX) = '';
-- COMPILE THE UNIQUE VALUES FOR ARRIVAL THAT NEED TO BE PIVOTED
SELECT #SQL = #SQL + ',' + QUOTENAME(Arrival),
#SQL2 = #SQL2 + '+ISNULL(' + QUOTENAME(Arrival) + ', 0)',
#SQL3 = #SQL3 + ',' + QUOTENAME(Arrival) + ' = ISNULL(' + QUOTENAME(Arrival) + ', 0)'
FROM (SELECT DISTINCT Arrival FROM tblSource) s;
-- COMBINE THEM INTO A SINGLE QUERY
SET #SQL = 'SELECT [Prov]' + #SQL3 + ', [Total] = ' + STUFF(#SQL2, 1, 1, '') + '
FROM ( SELECT Arrival, Prov = ISNULL(Prov, 'Total'), Amount = SUM(Amount)
FROM [tblSource]
GROUP BY GROUPING SETS((Prov, arrival), (arrival))
) up
PIVOT
( SUM(Amount)
FOR Arrival IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt;';
-- EXECUTE THE QUERY
EXECUTE SP_EXECUTESQL #SQL;
SAMPLE TABLE
CREATE TABLE #TEMP([prov] VARCHAR(100),[arrival] INT, AMOUNT NUMERIC(12,2))
INSERT INTO #TEMP
SELECT 'A' [prov],'201304' [arrival],100 AMOUNT
UNION ALL
SELECT 'A' ,'201305' ,124
UNION ALL
SELECT 'A' ,'201306' ,156
UNION ALL
SELECT 'B' ,'201304' ,67
UNION ALL
SELECT 'B' ,'201305' ,211
UNION ALL
SELECT 'B' ,'201306' ,176
UNION ALL
SELECT 'C' ,'201304' ,43
UNION ALL
SELECT 'C' ,'201305' ,56
UNION ALL
SELECT 'C' ,'201306' ,158
QUERY
You can use ROLLUP to get the row total. More about ROLLUP here
-- Get the columns for dynamic pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + CAST([arrival] AS VARCHAR(50)) + ']',
'[' + CAST([arrival] AS VARCHAR(50)) + ']')
FROM (SELECT DISTINCT [arrival] FROM #TEMP) PV
ORDER BY [arrival]
-- Replace NULL value with zero
DECLARE #NulltoZeroCols NVARCHAR (MAX)
SELECT #NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+[arrival]+'],0) AS ['+[arrival]+']'
FROM (SELECT DISTINCT CAST([arrival] AS VARCHAR(50)) [arrival] FROM #TEMP)TAB
ORDER BY CAST([arrival]AS INT) FOR XML PATH('')),2,8000)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT [prov],' + #NullToZeroCols + ' FROM
(
SELECT
ISNULL([prov],''Total'')[prov],
SUM(AMOUNT)AMOUNT ,
ISNULL(CAST([arrival] AS VARCHAR(50)),''Total'')[arrival]
FROM #TEMP
GROUP BY [arrival],[prov]
WITH ROLLUP
) x
PIVOT
(
MIN(AMOUNT)
FOR [arrival] IN (' + #cols + ')
) p
ORDER BY CASE WHEN ([prov]=''Total'') THEN 1 ELSE 0 END,[prov]'
EXEC SP_EXECUTESQL #query
Note : If you do not want to replace NULL with zero, just replace #NullToZeroCols with #cols in outer query of dynamic pivot

Including default values on dynamic pivot

I am pivoting data in a table based on date, but need to return NULL for dates with no data.
Data in [dbo].[Metrics]:
The dynamic pivot SQL I am executing:
DECLARE #cols NVARCHAR(1000);
SELECT #cols =
STUFF((SELECT N'],[' + month_day
FROM (SELECT DISTINCT RIGHT(CONVERT(NCHAR(10), [Date], 126), 2)
FROM [Metrics]) AS O(month_day)
ORDER BY month_day
FOR XML PATH('')
), 1, 2, '') + N']';
DECLARE #sql NVARCHAR(2000);
SET #sql =
N'SELECT [Key], ' + #cols +
N' FROM (SELECT [Key], ' +
N' RIGHT(CONVERT(NCHAR(10), [Date], 126), 2) AS month_day, ' +
N' [Value] ' +
N' FROM [Metrics]) AS O ' +
N'PIVOT ' +
N'(SUM([Value]) FOR month_day IN (' + #cols + N')) AS P;';
EXECUTE(#sql);
...and the result set that dynamic SQL returns:
As you can see, since I do not have data for every day of the month it is simply not returning columns for those days. How can I have it return columns 10-23 and 28-31 with NULL for each cell? It should return columns 1-31 regardless of the actual month (e.g., 31 columns even when there are less than 31 days in a given month).
Any help is appreciated.
Then you don't really need that columns to be generated dynamically, since you always want them from 1 to 31. The easier way would be hardcoding those values on your pivot, really. Anyway, here is the way you can define your columns dynamically and still get all days:
SELECT #cols =
STUFF((SELECT N'],[' + CAST(number AS VARCHAR(2))
FROM (SELECT DISTINCT number
FROM master..[spt_values]
WHERE number BETWEEN 1 AND 31) AS O(number)
ORDER BY number
FOR XML PATH('')
), 1, 2, '') + N']';
And the version you really should be using is this:
SELECT *
FROM (SELECT [Key],
RIGHT(CONVERT(NCHAR(10), [Date], 126), 2) AS month_day,
[Value]
FROM [Metrics]) AS O
PIVOT (SUM([Value]) FOR month_day IN ([01],[02],[03],[04],[05],[06],[07],[08],[09],
[10],[11],[12],[13],[14],[15],[16],[17],[18],
[19],[20],[21],[22],[23],[24],[25],[26],[27],
[28],[29],[30],[31])) AS P;