SQL Server SORT Dynamic Pivot Column Names - sql

I have an dynamic SQL query which is returning the result as desired but the only problem is the resultant pivot columns are not getting selected in correct order.
I am using SQL Server 2012.
Here is the query:
DECLARE #columns NVARCHAR(MAX), #columns_pivot NVARCHAR(MAX), #sql NVARCHAR(MAX);
SELECT #columns_pivot = COALESCE(#columns_pivot + ', ', '') + QUOTENAME(Week_No)
,#columns = COALESCE(#columns + ', ', '') + 'ISNULL(' + QUOTENAME(Week_No) + ',0) AS ' + QUOTENAME(Week_No) + ''
FROM (SELECT DISTINCT TOP 100 PERCENT DATEPART(wk,T_Date) As Week_No
FROM [VISIT].[dbo].[Report]
WHERE DATEPART(m,T_Date) = 5
ORDER BY Week_No DESC) x; // This query returns Column 'Week_No' in order 22,21,20,19,18 (As Desired)
Edit: //But when Order By Clause is removed it returns 21,18,19,22,20 which is the actual output( Not Desired)
SET #sql = '
SELECT ABC, ' + #columns + '
FROM
(Select TOP 100 PERCENT
ABC
,SUM(CASE WHEN Type = A THEN Sum ELSE 0 END) AS Revenue
,DATEPART(wk,T_Date) As Week_No
FROM [VISIT].[dbo].[Report]
GROUP BY ABC
,DATEPART(wk,T_Date)
ORDER BY Week_No DESC) As j
PIVOT(
max(Revenue)
FOR Week_No in (' + #columns_pivot + ')) As p '
Final result returns columns in the order ABC, 21, 18, 19, 22, 20
But I want the result As ABC, 22, 21, 20, 19, 18.
I saw few posts but was not able to figure out what is wrong with my query. Can someone please point what I have to change in the Query to get the desired output. Where exactly do I have to put the ORDER BY clause
In short when the Select Distinct statement returns the column Week_No in Desc Order why the variables #columns and #columns_pivot don't get it in Desc order
Thanks.

I have solved the issue by storing Week_No in a temp table and apply clustered indexing to it.
Here is the code I added:
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
SET NOCOUNT ON;
DECLARE #params nvarchar(max) = :month
SELECT DISTINCT TOP 100 PERCENT DATEPART(wk,Tour_Datum) As Week_No into #temp
FROM [VISITOUR].[dbo].[BI_001_ADM_Report]
WHERE DATEPART(m,Tour_Datum) = #params
--Clustered index is created on week_no in acending order, so that data is physically stored in that way
CREATE CLUSTERED INDEX cix_wekno ON #temp(Week_No ASC)

Related

Dynamic pivot in SQL Server not working as expected

I have the following table:
enter image description here
I'm doing a dynamic PIVOT in SQL_Server and I got the following query:
declare #colunas_pivot as nvarchar(max), #comando_sql as nvarchar(max)
set #colunas_pivot =
stuff((
select
distinct ',' + quotename(datename(year,PLD_Date) + '' + datename(month, PLD_Date))
from TB_Planned
/* where PLD_Date > getdate() */
order by 1
for xml path('')
), 1, 1, '')
print #colunas_pivot
set #comando_sql = '
SELECT * FROM (
SELECT
[PLD_ProjectSapCode],
[PLD_Date],
[PLD_Value]
FROM TB_Planned
) result_pivot
pivot (max(PLD_Value) for PLD_Date in (' + #colunas_pivot + ')) result_pivot
'
print #comando_sql
execute(#comando_sql)
This query results in the following table:
enter image description here
As you can see, I want to PIVOT the column "PLD_Date" and group my columns by month/year, and put the sum of the values ​​corresponding to each month, from the column "PLD_Value.
However, in this result above it is only returning the value of the first day of each month.
How would I group and correctly add the values ​​of the entire month?

Dynamically create table columns with values from Pivot Table

I have a dynamic query that utilizes a pivot function and the following is an example of data in my table.
Status 1 | Week 1 |25
Status 1 | Week 1 |25
Status 1 | Week 2 |25
Status 2 | Week 1 | 2
Status 2 | Week 1 | 8
Status 2 | Week 1 | 10
Status 2 | Week 1 | 10
and this is an example of how the data is returned.
Week 1 Week 2
Status 1 | 50 25
Status 2 10 20
For my query I am passing in a week and I want to pivot on the following 5 weeks, so example, if I pass in 1, I expect to have columns from week 1 to week 6.
To help facilitate that I have written the following query.
--EXEC usp_weekReport #weeks=1, #year='2019'
ALTER PROC usp_weekReport
(
#weeks INT,
#year NVARCHAR(4)
)
AS
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX), #csql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME([week])
FROM (
SELECT p.[week]
FROM [Housing_support_DB].[dbo].[Invoices] P
WHERE DATEPART(YEAR,P.date)='2019'--#year
AND
([week] IN (1)
OR
[week] IN (1+1)
OR
[week] IN (1+2)
OR
[week] IN (1+3)
OR
[week] IN (1+4)
OR
[week] IN (1+5)
)
GROUP BY P.[week]
) AS x;
SET #sql = N'
SELECT p.[statusName],' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT
SUM(CAST(REPLACE(REPLACE(A.amount,'','',''''),''$'','''') AS FLOAT)) as sumInvoice,
A.invoiceStatusID_FK,
B.statusName,
-- C.programme,
[week]
FROM [dbo].[Invoices] A
INNER JOIN invoiceStatus B
ON A.invoiceStatusID_FK=B.invoiceStatusID
-- INNER JOIN CapitalAccountBalances C
-- ON C.accountBalanceID=A.accountBalanceID_FK
-- WHERE A.accountBalanceID_FK=5
GROUP BY invoiceStatusID_FK,B.statusName,[week]--,C.programme
) AS j
PIVOT
(
SUM(sumInvoice) FOR [week] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
--PRINT #sql;
EXEC sp_executesql #sql;
--SET #csql = N'
--CREATE TABLE ##reportResult
--(
--statusName nvarchar(50),'+
CREATE TABLE ##reportResult
(
statusName nvarchar(50),
weekA INT DEFAULT 0,
weekB int DEFAULT 0--,
--weekC int DEFAULT 0,
--weekD int DEFAULT 0,
--weekE int DEFAULT 0,
--weekF int DEFAULT 0
)
INSERT into ##reportResult Exec(#sql)
--INSERT ##reportResult Exec(#sql)
--SELECT statusName, weekA,weekB,weekC,weekD,weekE,weekF -- here you have "static SELECT with field names"
--FROM ##reportResult
--DROP TABLE ##reportResult
Problem
The huge problem that I have here is that, I need to send the result of this query to a tempTable...#reportResult. As a result, I need to create the table. However, if I attempt to create the table with the max amount of columns anticipated (6) I will get an invalid number of columns error. For example, in my database I only have two weeks, that's why I can only create the table with columns weekA and weekB. I also cannot do a select into.
Presently, I am trying to find a way to either create the table dynamically depending on the amount of weeks from the first part of the pivot table. Or, to manipulate the first part of the pivot to select week,week+1 etc as columns when run so that way , I can create the column with all fields.
Appreciate any help that could be provided.
You required dynamic SQL in your case as the column name is need to generate based on th Input week number. Below I have give you the script I created with your sample data using CTE. You just need to updated the script based on your table and requirement.
You can test the code changing the value of Week_No
For your final query, just use the SELECT part after removing the CTE code
DECLARE #Week_No INT = 2
DECLARE #Loop_Count INT = 1
DECLARE #Column_List VARCHAR(MAX) = '[Week '+CAST(#Week_No AS VARCHAR) +']'
WHILE #Loop_Count < 5
BEGIN
SET #Column_List = #Column_List +',[Week '+CAST(#Week_No+#Loop_Count AS VARCHAR) +']'
SET #Loop_Count = #Loop_Count + 1
END
--SELECT #Column_List
EXEC
('
WITH your_table(Status,Week_No,Val)
AS
(
SELECT ''Status 1'',''Week 1'',25 UNION ALL
SELECT ''Status 1'',''Week 1'',25 UNION ALL
SELECT ''Status 1'',''Week 2'',25 UNION ALL
SELECT ''Status 2'',''Week 1'',2 UNION ALL
SELECT ''Status 2'',''Week 1'',8 UNION ALL
SELECT ''Status 2'',''Week 1'',10 UNION ALL
SELECT ''Status 2'',''Week 1'',10
)
SELECT * FROM
(
SELECT * FROM your_table
) AS P
PIVOT
(
SUM(val)
FOR Week_No IN ('+#Column_List+')
)PVT
')

How to get columns with specific string

I am working in SQL Server 2014 and below is my database with which I am working on and need some analysis done on it.
Upon inspecting the database sample carefully we can notice a number 8777 in L9 and in L13 column.
Now I want to get only those columns which have 8777 in them and in the end a column named "count" which shows how many times the number appeared means I need in output something like this as shown below:
So far I have written this query which is giving the category and subcategory correct. But it is showing all the columns. I have no idea how to count the occurrences of a number and show its count in a count column.
select *
from Sheet2$
where '8777' IN ([L1],[L2],[L3],[L4],[L5],[L6],[L7],[L8],[L9],[L10],[L11],[L12],[L13]
To dynamically limit the columns, you would need Dynamic SQL
Example
Select *
Into #Temp
From YourTable A
Unpivot ( Value for Item in ([L1], [L2],[ L3], [L4], [L5], [L6], [L7], [L8], [L9], [L10], [L11], [L12], [L13]) ) u
Where Value = 8777
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Item) From #Temp Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select *,[Count] = sum(1) over()
From #Temp A
Pivot (max(Value) For [Item] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Category SubCategory L13 L9 Count
C1 SC1 NULL 8777 2
C1 SC3 8777 NULL 2
Hmmm. I think you want the original rows with the count. I think this is:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
set #cols = (select distinct ', ' + v.colname
from t cross apply
(values ('l1', l1),
('l2', l2),
('l3', l3),
('l4', l4),
('l5', l5),
('l6', l6),
('l7', l7),
('l8', l8),
('l9', l9),
('l10', l10),
('l11', l11),
('l12', l12),
('l13', l13)
) v(colname, val)
where v.val = '8777'
for xml path ('')
);
set #sql = '
select category, subcategory' + #cols + ',
count(*) over () as cnt
from t
';
exec sp_executesql #sql;
The only difference from your result is that the count is on every row. That can easily be adjusted using a case expression, but I'm not sure it is necessary.
If you want the count in only one row, then:
set #sql = '
select category, subcategory' + #cols + ',
(case when row_number() over (order by category, subcategory) = 1
then count(*) over ()
end) as cnt
from t
order by category, subcategory
';
You can try this part as a replacement of John's answer's second query to get the proper count, it does not achieve the exact thing you want but can be a work around.
Declare #sql varchar(max) = Stuff((Select Distinct ',' + QuoteName(Item)
From #Temp Order by 1 For XML Path('')),1,1,'')
print #sql;
Select #SQL = '
Select *,value=8777
From #Temp A
Pivot (Count(Value) For [Item] in (' + #sql + ') ) p'
print #sql;
Exec(#SQL);
I just used count function in pivot in place of sum.

Check to see if any combination of results when added will equal a variable

I need a select statement which will interrogate a list of totals and work out whether an adding combination exists within the result set that is equal to a local variable.
Example:
create table #mytemptable
(
totals Decimal (19,2)
)
insert into #mytemptable (totals)
values (57.83),
(244.18),
(239.23),
(227.79),
(563.12)
select *
from #mytemptable
I would now like to check if any combination(s) within the result when added will equal
285.62
Also, it would be nice if there were multiple instances where totals could be added to equal my variable then this would be handled and displayed in an appropriate fashion.
A bit convoluted but here it goes:
Basically my aim is to generate a dynamic query where one column will identify the value (first value on column A, second on column B, etc) and finally a column with the total.
After that we can do a group by with cube, which will sum all the permutations of values grouping them by the different columns. view example
The final result will show something like:
Total A B C D E
285.62 NULL NULL NA NA NA
This will indicate that 285.62 is the sum of the first and 2nd values, sorted by value
DECLARE #columns varchar(max) = ''
DECLARE #allcolumns varchar(max) = ''
DECLARE #columnName varchar(1) = 'A'
DECLARE #select varchar(max) = ''
SELECT
#columns = #columns + ',''NA'' AS ' + #columnName,
#allcolumns = #allcolumns + ',' + #columnName,
#columnName = CHAR(ASCII(#columnName)+1)
FROM
#mytemptable
SET #columnName = 'A'
SELECT
#select = #select + CHAR(13) + 'UNION SELECT ' + CONVERT(varchar(100),totals) + ' AS totals' + STUFF(#columns,2+10*(ord-1),4,'''' + #columnName + ''''), #columnName = CHAR(ASCII(#columnName)+1)
FROM
(SELECT totals, ROW_NUMBER() OVER(ORDER BY totals) ord from #mytemptable)
A
SET #select = STUFF(#select,1,6,'')
SET #allcolumns = STUFF(#allcolumns, 1,1,'')
--PRINT (#select)
EXEC ( 'SELECT * FROM (
SELECT SUM(totals) AS Total, ' + #allcolumns + '
FROM (' + #select + ') A GROUP BY ' + #allcolumns + ' WITH CUBE
) sub WHERE Total = 285.62 ')
If you are willing to add an identity column to your table the following CTE solution will work for you:
WITH SumOfPermutations AS
(
SELECT
CONVERT(decimal(15,2), 0) SummedTotals,
0 id
UNION ALL
SELECT
CONVERT(decimal(15,2), A.SummedTotals + B.totals),
B.ID
FROM
SumOfPermutations A
INNER JOIN myTempTable B ON A.ID < B.ID AND A.SummedTotals + B.Totals <= 285.62
WHERE
A.SummedTotals + B.totals <= 285.62
)
SELECT
COUNT(*)
FROM
SumOfPermutations
WHERE
SummedTotals = 285.62
However be advised if you have a large number of small values the performance will degrade massively. This is because of the fact that once a permutation's sum is above 285.62 it is not included any more. If you have lots of small small values then you will have lots of permutations that have a large number of values before they reach the 285.62 threshold. If your real data is distibuted similarly to the example data you gave, this should work well and quickly.
If you expect that the most numbers from your table that can be summed to a value below your 285.62 is of the order of 10, you should be OK. However if you have 20 values in your table below 30.0 you will probably have issues with this.

How can I create a month name as a column name for a given date range in sql?

I have a data as below:
Table
country date value
------------------------------------------------------
test1 5/1/2008 500
test1 5/7/2008 200
test1 5/8/2008 300
test1 7/1/2008 100
test1 7/2/2008 100
test2 6/1/2008 100
And I want a result as below:
Result
-----------
countryName May-08 Jun-08 July-08
test1 1000 - 200
test2 - 100
This is adapted from T-SQL Pivot? Possibility of creating table columns from row values
You can see it working here: http://sqlfiddle.com/#!3/7b8c0/28
I think you might need to fiddle around with the column ordering
-- Static PIVOT
SELECT *
FROM (SELECT country,
CONVERT(char(3), date, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(date)), 2) AS date,
value
FROM country) AS D
PIVOT(SUM(value) FOR date IN([May-08],[Jun-08],[Jul-08])) AS P;
GO
-- Dynamic PIVOT
DECLARE #T AS TABLE(y INT NOT NULL PRIMARY KEY);
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX)
SELECT #cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT DISTINCT CONVERT(char(3), date, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(date)), 2) AS y
FROM Country
) AS Y
ORDER BY y desc
FOR XML PATH('')),
1, 1, N'')
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT country, CONVERT(char(3), date, 0) + ''-'' +
RIGHT(CONVERT(varchar, YEAR(date)), 2) AS date, value
FROM Country) AS D
PIVOT(SUM(value) FOR date IN(' + #cols + N')) AS P;'
EXEC sp_executesql #sql
You have to use a rather complex query for that, using a LOOP it think.
For creating dynamic column names look at this post: https://stackoverflow.com/a/10926106/1321564
With sql server you have some advantages: https://stackoverflow.com/a/5638042/1321564