How to pivot the given structure to expected one? - sql

I have queries results as in the image, similarly for 90 date's, so how to group object and make date's as columns and count to respective date's.
Thanks in advance!!

You can do it using a dynamic crosstab:
SQL Fiddle
DECLARE #sql1 VARCHAR(4000) = ''
DECLARE #sql2 VARCHAR(4000) = ''
DECLARE #sql3 VARCHAR(4000) = ''
SELECT #sql1 =
'SELECT
[Object]' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN [Date] = CAST(''' + CONVERT(VARCHAR(8), [Date], 112) + ''' AS DATE) THEN [count] END) AS ' + QUOTENAME([Date]) + CHAR(10)
FROM(
SELECT DISTINCT [Date] FROM tbl
)t
ORDER BY [Date]
SELECT #sql3 =
'FROM tbl
GROUP BY [Object]
ORDER BY [Object]'
PRINT(#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)
RESULT
| Object | 2015-01-01 | 2015-01-02 |
|--------|------------|------------|
| 1 | 10 | 34 |
| 2 | 20 | 46 |
| 3 | 130 | 78 |
| 4 | 40 | 89 |
| 5 | 55 | 45 |
This is the output of the PRINT command:
SELECT
[Object]
, MAX(CASE WHEN [Date] = CAST('20150101' AS DATE) THEN [count] END) AS [2015-01-01]
, MAX(CASE WHEN [Date] = CAST('20150102' AS DATE) THEN [count] END) AS [2015-01-02]
FROM tbl
GROUP BY [Object]
ORDER BY [Object]

You can use SQL Server PIVOT relational operator
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
--Get unique values of pivot column
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') + QUOTENAME([Date])
FROM (SELECT DISTINCT [Date] FROM [dbo].[PivotExample]) AS PivotExample
--Create the dynamic query with all the values for
--pivot column at runtime
SET #SQLQuery =
N'SELECT ObjectId, ' + #PivotColumns + '
FROM [dbo].[PivotExample]
PIVOT( SUM(COUNT)
FOR [Date] IN (' + #PivotColumns + ')) AS P'
EXEC sp_executesql #SQLQuery

Related

how to convert Row to column dynamically in sql server

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

How to transform rows into column in SQL Server?

Using MS SQL Server and I have below table:
+-----------+------------------+------------------+---------------------+-------------------------+
| SrNo | ApprCode | ApprName | ApprStatus | ApprDate |
+-----------+------------------+------------------+---------------------+-------------------------+
| SR_176 | X001 | James | APR | 2019-10-03 |
| SR_176 | X002 | Sam | APR | 2019-10-03 |
+-----------+------------------+------------------+---------------------+-------------------------+
Tried with PIVOT but its showing james and X001 as a column heading:
Expected Result:
+-----------+-------------- +---------------+---------------------+-------------------------+------------------+
SrNo | ApprCode_1 | ApprName_1 | ApprDate_2 ApprCode_2 ApprName_2 ApprDate_2
+-----------+---------------+---------------+---------------------+-------------------------+------------------+
SR_176 X001 James 2019-10-03 X002 Sam 2019-10-03
+-----------+---------------+---------------+---------------------+---- ---------------------+------------------+
Query to generate data:
CREATE TABLE #Temp
(
SrNo NVARCHAR(200),
ApprCode NVARCHAR(200),
ApprName NVARCHAR(200),
ApprDate Date
)
INSERT INTO #Temp VALUES ('SR_176','X001','James', '2019-10-03')
INSERT INTO #Temp VALUES ('SR_176','X002','Sam', '2019-10-03')
Query that I tried:
declare #sql nvarchar(max)
declare #name_concat nvarchar(max)
declare #name1_concat nvarchar(max)
declare #select_aggs nvarchar(max)
select #name_concat = STUFF((select distinct ',' + quotename(ApprCode) from #Temp order by 1 for xml path('')), 1, 1, '')
select #name1_concat = STUFF((select distinct ',' + quotename(ApprName) from #Temp order by 1 for xml path('')), 1, 1, '')
select #sql = '
;with cte2 as
(
SELECT SrNo,' + #name_concat + ',' + #name1_concat + '
FROM #Temp
PIVOT(MAX(ApprCode)
FOR ApprCode IN (' + #name_concat + ')) AS PVTTable PIVOT
(
MAX(ApprName)
FOR ApprName IN (' + #name1_concat + ')) AS PVTTable1
)
select * from cte2
'
exec sp_executesql #sql
In fact, This is not a SQL Pivot situation and the sample data for question is not enough to test it completely but you can find the main idea :
Select
SrNo,
MAX(IIF(ApprCode = 'X001', ApprCode, null)) as ApprCode_1,
MAX(IIF(ApprCode = 'X001', ApprName, null)) as ApprName_1,
MAX(IIF(ApprCode = 'X001', ApprDate, null)) as ApprDate_1,
MAX(IIF(ApprCode = 'X002', ApprCode, null)) as ApprCode_2,
MAX(IIF(ApprCode = 'X002', ApprName, null)) as ApprName_2,
MAX(IIF(ApprCode = 'X002', ApprDate, null)) as ApprDate_2
From #Temp
Group by SrNo
This code works if ApprCode was the key to make two separate columns & SrNo is for the group by between rows.
I solved it by using ROW_NUMBER() and Case Expression:
Here is the query:
SELECT *,
Row_number()
OVER(
partition BY srno
ORDER BY apprdate) AS RN
INTO #temptable
FROM #temp
SELECT srno,
CASE rn
WHEN 1 THEN Max(apprname)
END AS [1 Approver],
CASE rn
WHEN 2 THEN Max(apprname)
END AS [2 Approver],
CASE rn
WHEN 1 THEN Max(apprcode)
END AS [1 ApproverCode],
CASE rn
WHEN 2 THEN Max(apprcode)
END AS [2 ApproverCode],
CASE rn
WHEN 1 THEN Max(apprdate)
END AS [1 Date],
CASE rn
WHEN 2 THEN Max(apprdate)
END AS [2 Date]
INTO #james
FROM #temptable
GROUP BY srno,
rn
SELECT srno,
Max([1 approver]) AS ApproverName_1,
Max([1 approvercode]) AS ApproverCode_1,
Max([1 date]) AS ApproverDate_1,
Max([2 approver]) AS ApproverName_2,
Max([2 approvercode]) AS ApproverCode_2,
Max([2 date]) AS ApproverDate_2
FROM #james
GROUP BY srno

SQL - Getting Percent Difference from dynamic SQL

I currently have a dynamic stored procedure that takes every month in my database and averages out the values per day.
What I currently have (values are the overall average for that month):
+-------------------------------------------------------+
| ID | CustName | 201501 | 201502 | 201503 | 201504 | ..|
+-------------------------------------------------------+
| 32 | CustOne | 5852.25| 5847.50| 6542.98| 7585.25| ..|
| 56 | CustTwo | 5452.45| 7852.50| 6985.41| 1245.21| ..|
| 89 | CustThree| 8520.25| 7410.01| 9630.36| 1245.32| ..|
| .. | ... | .. | .. | .. | .. | ..|
+-------------------------------------------------------+
This is the stored procedure I'm using to create the above table:
DECLARE #Dates NVARCHAR(MAX);
SELECT #Dates = CONCAT(#Dates + ', ', QUOTENAME(BalMonth))
FROM vAvgMonBal
GROUP BY BalMonth
ORDER BY BalMonth;
DECLARE #DynSQL NVARCHAR(MAX),
#months NVARCHAR(MAX);
SET #months = 'CONCAT( CONVERT(nvarchar(15), YEAR(BalDate)) , IIF(LEN(MONTH(BalDate)) > 1, CONVERT(nvarchar(15), MONTH(BalDate)), ''0'' + CONVERT(nvarchar(15), MONTH(BalDate)))) AS BalMonth'
SET #DynSQL = 'SELECT *
FROM
(SELECT
a1.IDNbr,
a2.CustName, ' + #months + ',
AVG(a1.Balance) as Balance
FROM tblID a1
INNER JOIN tblCust a2 ON (a1.IDNbr = a2.IDNbr)
WHERE a2.CustType != ''Inactive''
GROUP BY
a1.IDNbr, a2.CustName, CONCAT( CONVERT(nvarchar(15), YEAR(BalDate)) , IIF(LEN(MONTH(BalDate)) > 1, CONVERT(nvarchar(15), MONTH(BalDate)), ''0'' + CONVERT(nvarchar(15), MONTH(BalDate))))) as d1
PIVOT (
AVG(Balance)
FOR BalMonth IN (' + #Dates + ')
) piv';
EXECUTE sp_executesql #DynSQL
Question: how can I take the data from the previous stored procedure and get the percent difference, (Month1/Month2) * 100, from it like shown below? I'm expecting I'll need a new stored procedure or add on to the one I currently have.
What I'm needing (where each "PerDiff" is the percent difference of the previous month from the before table example):
+---------------------------------------------------------------+
| ID | CustName | PerDiff1 | PerDiff2 | PerDiff3 | PerDiff4 | ..|
+---------------------------------------------------------------+
| 32 | CustOne | 100.00 | 68.12 | 654.25 | 483.36 | ..|
| 56 | CustTwo | 58.21 | 154.54 | 932.45 | 58.45 | ..|
| 89 | CustThree| 965.25 | 951.58 | 689.12 | 32.50 | ..|
| .. | ... | .... | .... | .... | .... | ..|
+---------------------------------------------------------------+
I've attempted to use something like:
DECLARE #PerDiff nvarchar(max);
SET #PerDiff = 'SELECT *
FROM (' + #DynSQL + ')'
EXECUTE sp_executesql #PerDiff
To at least try and get the data moving.
I'm no longer getting any error messages-- I'm generally just stuck on how to proceed with getting the math to be applied dynamically.
Any help or advice would be greatly appreciated!
EDIT1: Here is the result of finalized #DynSQL
SELECT *
FROM
(
SELECT a1.DDANbr,
a2.CustName,
CONCAT( CONVERT(nvarchar(15), YEAR(BalDate)) , IIF(LEN(MONTH(BalDate)) > 1, CONVERT(nvarchar(15), MONTH(BalDate)), '0' + CONVERT(nvarchar(15), MONTH(BalDate))))AS BalMonth,
a1.Balance
FROM tblID a1 INNER JOIN tblCust a2 ON (a1.IDNbr = a2.IDNbr)
WHERE a2.CustType != 'Inactive'
GROUP BY a1.IDNbr, a2.CustName, BalDate, a1.Balance
) as d1
PIVOT (
AVG(Balance)
FOR BalMonth IN ([201501], [201502], [201503], [201504], [201505], [201506], [201507], [201508], [201509], [201510], [201511], [201512], [201601], [201602], [201603], [201604], [201605], [201606], [201607], [201608], [201609], [201610], [201611], [201612], [201701], [201702], [201703], [201704], [201705], [201706], [201707], [201708], [201709], [201710], [201711], [201712], [201801], [201802], [201803], [201804], [201805], [201806], [201807], [201808], [201809])
) piv
When you take a query and make it a derived table, you have to give that table an alias.
Like this:
DECLARE #PerDiff nvarchar(max);
SET #PerDiff = 'SELECT IDNbr, CustName, ' + #months + '
FROM (' + #DynSQL + ') t1'
EXECUTE sp_executesql #DynSQL, #PerDiff
DECLARE #Dates NVARCHAR(MAX);
SELECT #Dates = CONCAT(#Dates + ', ', QUOTENAME(BalMonth))
FROM vAvgMonBal
GROUP BY BalMonth
ORDER BY BalMonth;
DECLARE #DynSQL NVARCHAR(MAX),
#months NVARCHAR(MAX);
SET #months = 'CONCAT( CONVERT(nvarchar(15), YEAR(BalDate)) , IIF(LEN(MONTH(BalDate)) > 1, CONVERT(nvarchar(15), MONTH(BalDate)), ''0'' + CONVERT(nvarchar(15), MONTH(BalDate)))) AS BalMonth'
SET #DynSQL = 'SELECT *
FROM
(SELECT
a1.IDNbr,
a2.CustName, ' + #months + ',
AVG(a1.Balance) as Balance
FROM tlbID a1
INNER JOIN tblCust a2 ON (a1.IDNbr = a2.IDNbr)
WHERE a1.Balance != 0.00
AND a2.CustType != ''Inactive''
GROUP BY
a1.IDNbr, a2.CustName, CONCAT( CONVERT(nvarchar(15), YEAR(BalDate)) , IIF(LEN(MONTH(BalDate)) > 1, CONVERT(nvarchar(15), MONTH(BalDate)), ''0'' + CONVERT(nvarchar(15), MONTH(BalDate))))) as d1
PIVOT (
AVG(Balance)
FOR BalMonth IN (' + #Dates + ')
) piv';
EXECUTE sp_executesql #DynSQL

Is this possible with SQL Server

I have a table which has events and the day they occurred:
Table 'Events':
Name Day
-----------
A 1
B 2
A 2
B 3
I need output columns to be a date range based on query input with rows being which event happened on the day so:
Desired output:
Day-1 Day-2 Day-3
-----------------
A A -
- B B
If this is possible can anyone give me a sample query that could generate this output based on a date range. There are all sorts of I have no clue how to even approach this issues here like an unknown number of columns.
You can use conditional aggregation:
select max(case when day = 1 then name end) as day_1,
max(case when day = 2 then name end) as day_2,
max(case when day = 3 then name end) as day_3
from t
group by name;
Note: This returns NULL rather than -. I think NULL makes more sense.
Try this one...
Table Script and Sample data
CREATE TABLE [TableName](
[Name] [nvarchar](50) NULL,
[Day] [int] NULL
)
INSERT [TableName] ([Name], [Day]) VALUES (N'A', 1)
INSERT [TableName] ([Name], [Day]) VALUES (N'B', 2)
INSERT [TableName] ([Name], [Day]) VALUES (N'A', 2)
INSERT [TableName] ([Name], [Day]) VALUES (N'B', 3)
Query (dynamic PIVOT)
DECLARE #startDay AS INT;
DECLARE #endDay AS INT;
SET #startDay = 1;
SET #endDay = 3;
DECLARE #cols AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([day])
FROM TableName
WHERE [Day] >= #startDay AND [Day] <= #endDay
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #query AS NVARCHAR(max) = 'SELECT '+ #cols +'
FROM (SELECT *,
Dense_rank() OVER (ORDER BY NAME) AS dr
FROM TableName) sq
PIVOT(Max([name])
FOR [day] IN ('+ #cols +') ) pvt ';
EXECUTE(#query)
Output
+--------+---+--------+
| 1 | 2 | 3 |
+--------+---+--------+
| A | A | (null) |
| (null) | B | B |
+--------+---+--------+
Online Demo: http://www.sqlfiddle.com/#!18/c688b/8/0
If you also want to use custom column name, try this...
DECLARE #startDay AS INT;
DECLARE #endDay AS INT;
SET #startDay = 1;
SET #endDay = 3;
DECLARE #cols AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([day])
FROM TableName
WHERE [Day] >= #startDay AND [Day] <= #endDay
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #colNames AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([day]) + ' AS Days' + CONVERT(NVARCHAR(MAX), [day])
FROM TableName
WHERE [Day] >= #startDay AND [Day] <= #endDay
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #query AS NVARCHAR(max) = 'SELECT '+ #colNames +'
FROM (SELECT *,
Dense_rank() OVER (ORDER BY NAME) AS dr
FROM TableName) sq
PIVOT(Max([name])
FOR [day] IN ('+ #cols +') ) pvt ';
EXECUTE(#query)
Output
+-------+-------+-------+
| Days1 | Days2 | Days3 |
+-------+-------+-------+
| A | A | NULL |
| NULL | B | B |
+-------+-------+-------+
Online Demo: http://www.sqlfiddle.com/#!18/c688b/9/0

Dynamic Pivot Not displaying right output

I have this table:
Year Month| User| Player | Manager
1996-06 | 1256| 2 | 1
1997-07 | 1243| 5 | 2
and was trying to pivot it so it returns:
| 1997-06|1996-07
User | 1256 | 1243
Player | 2 | 5
Manager| 1 | 2
however I'm not to sure why my script below is returning something different:
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME([User])
FROM (SELECT p.[User] FROM dbo.practise AS p
GROUP BY p.[User]) AS x;
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT [year month], [User]
FROM dbo.practise AS p
) AS j
PIVOT
(
SUM([User]) FOR [year month] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;
It returns:
1256|1243
1 Null|Null
Not sure what I'm doing wrong :/
Your could simplify this a bit
Example
Declare #SQL varchar(max) = '
Select *
From (
Select [Year Month]
,B.*
From practise
Cross Apply (values (''User'' ,cast([User] as varchar(max)))
,(''Player'' ,cast(Player as varchar(max)))
,(''Manager'',cast(Manager as varchar(max)))
) B (Item,Value)
) A
Pivot (max([Value]) For [year month] in (' + Stuff((Select Distinct ','+QuoteName([year month])
From practise
Order By 1
For XML Path('')),1,1,'') + ') ) p
Order by 1 Desc'
Exec(#SQL);
--Print #SQL
Returns
Item 1996-06 1997-07
User 1256 1243
Player 2 5
Manager 1 2
The Generated SQL Looks Like This
Select *
From (
Select [Year Month]
,B.*
From practise
Cross Apply (values ('User' ,cast([User] as varchar(max)))
,('Player' ,cast(Player as varchar(max)))
,('Manager',cast(Manager as varchar(max)))
) B (Item,Value)
) A
Pivot (max([Value]) For [year month] in ([1996-06],[1997-07]) ) p
Order by 1 Desc