I am using SQL Server 13.0 Developer Edition.
I can't make the correct structure for the SQL code with Pivot and Group by clauses.
I have data like;
Id
OperationType
Date
ResultCode
1
BeginTransaction
2022-12-01 16:54:30
-28
2
BeginTransaction
2022-12-02 18:54:30
-30
3
BeginTransaction
2022-12-02 18:54:30
-30
4
BeginTransaction
2022-12-03 14:54:30
-10
5
BeginTransaction
2022-12-03 11:54:30
-5
6
BeginTransaction
2022-12-05 10:54:30
-3
and I want to see total number of ResultCodes per day but I want to generate ResultCode columns dynamicly because I have so much different result codes. Query result should be like;
Day
-3
-5
-10
-28
-30
Total
2022-12-01
0
0
0
1
0
1
2022-12-02
0
0
0
0
2
2
2022-12-03
0
1
1
0
0
2
2022-12-05
1
0
0
0
0
1
I wrote this query but it says The incorrect value "ResultCode" is supplied in the PIVOT operator.
Select * from (SELECT CAST(Date as date),
COUNT(ResultCode) as Result,
COUNT(*) AS Totals
FROM OperationLogs
WHERE OperationType = 'Begin'
GROUP BY CAST(StartTime as date)
) As Source
PIVOT (
COUNT(Result) FOR Result IN ([ResultCode])
) AS PivotTable
ORDER BY ForDate
Can anyone help me with how can I group by date and also have counts for ResultCodes as colums and a Total by day?
CREATE TABLE #ResultCodes (
Id INT,
OperationType VARCHAR(50),
[Date] DateTime,
ResultCode INT
)
INSERT INTO #ResultCodes(Id,OperationType,[Date],ResultCode) VALUES
(1,'BeginTransaction','2022-12-01 16:54:30',-28),
(2,'BeginTransaction','2022-12-02 18:54:30',-30),
(3,'BeginTransaction','2022-12-02 18:54:30',-30),
(4,'BeginTransaction','2022-12-03 14:54:30',-10),
(5,'BeginTransaction','2022-12-03 11:54:30',-5),
(6,'BeginTransaction','2022-12-05 10:54:30',-3)
DECLARE #COLUMNS AS NVARCHAR(MAX)
DECLARE #QUERY AS NVARCHAR(MAX)
SET #COLUMNS = STUFF((SELECT ',' + QUOTENAME(ResultCode)
FROM #ResultCodes GROUP BY ResultCode ORDER BY ResultCode DESC
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #QUERY = N'
SELECT [Date],'+#COLUMNS+', tc AS "Total"
FROM (
SELECT
[tc] = COUNT(CAST([Date] AS date)) over(partition by CAST([Date] AS date)),
CAST([Date] AS date) AS "Date",
[ResultCode]
FROM #ResultCodes
) AS tb
PIVOT (
COUNT([ResultCode])
FOR [ResultCode]
IN (
'+#COLUMNS+'
)
) AS P';
EXEC(#QUERY)
DROP TABLE IF EXISTS #ResultCodes
You can find the answer here : https://www.sqlshack.com/dynamic-pivot-tables-in-sql-server/
and below
USE [tempdb]
IF OBJECT_ID ('[Date]') IS NOT NULL DROP TABLE [Date]
CREATE TABLE [Date] ([Id] int , [OperationType] varchar(50) , [Date] datetime , [ResultCode] int)
INSERT INTO [Date] VALUES (1, 'BeginTransaction', '2022-12-01 16:54:30', -28) , (2, 'BeginTransaction', '2022-12-02 18:54:30', -30) , (3, 'BeginTransaction', '2022-12-02 18:54:30', -30) , (4, 'BeginTransaction', '2022-12-03 14:54:30', -10) , (5, 'BeginTransaction', '2022-12-03 11:54:30', -5) , (6, 'BeginTransaction', '2022-12-05 10:54:30', -3)
DECLARE #pivotcolumns varchar(500) = STUFF((SELECT ',' + QUOTENAME(CAST([ResultCode] AS varchar(20))) FROM [Date] GROUP BY [ResultCode] ORDER BY [ResultCode] FOR XML PATH('')),1,1,'')
DECLARE #SqlStatement NVARCHAR(MAX)
SET #SqlStatement = N'
SELECT * , SUM('+REPLACE(#pivotcolumns, ',', '+')+') OVER (PARTITION BY CAST([Date] AS date)) AS "Total" FROM (
SELECT
CAST([Date] AS date) AS "Date", [ResultCode]
FROM [Date]
) AS t1
PIVOT (
COUNT([ResultCode])
FOR [ResultCode]
IN (
'+#pivotcolumns+'
)
) AS PivotTable
';
EXEC(#SqlStatement)
Related
I have below SQL which gives me count of files received in particular country according to date. But here dates are hard coded. I want them dynamically. I want it in such a way that whenever I run this query, I get result for last 30 days. Below is the SQL:
with t (Country ,Date,total)
as
(
select b.country as Market, CAST(a.ProcessDate AS Date) AS DATE, count(a.ProcessDate) AS total from Log a LEFT JOIN File b ON a.FileID = b.FileID where a.ProcessDate BETWEEN '2022-11-01' AND '2022-11-07' GROUP BY b.country, CAST(a.ProcessDate AS DATE)
)
Select
*
from
(
Select
Date,
Total,
Country
from t
) x
Pivot(
sum(total)
for Date in (
[2022-11-01],
[2022-11-02],
[2022-11-03],
[2022-11-04]
)
) as pivottable
Below is Result of the query with dummy data:
Country
2022-11-01
2022-11-02
2022-11-03
2022-11-04
Brazil
2
1
Chile
1
1
Switzerland
1
Below is the structure of MasterFile and FileProcessLog with dummy data:
MasterFile:
FileID
Country
1
Brazil
2
Brazil
3
Chile
4
Chile
5
Switzerland
FileProcessLog:
FileID
ProcessDate
1
2022-11-01T15:31:53.0000000
2
2022-11-01T15:32:28.0000000
3
2022-11-02T15:33:34.0000000
4
2022-11-03T15:33:34.0000000
5
2022-11-04T15:37:10.0000000
Create function as below to return last 30 day dates:
CREATE FUNCTION [dbo].[RETURNDATE]()
RETURNS
#ParsedList table
(
DATEINFO DATE
)
AS
BEGIN
DECLARE #Counter Int
SET #Counter=1
WHILE ( #Counter <= 30)
BEGIN
--PRINT 'The counter value is = ' + CONVERT(VARCHAR,Convert(Date,DATEADD(DAY, -(#Counter), getdate())))
INSERT INTO #ParsedList (DATEINFO)
VALUES (CONVERT(VARCHAR,Convert(Date,DATEADD(DAY, -(#Counter), getdate()))))
SET #Counter = #Counter + 1
END
RETURN
END
now use inside your code as below:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME([DateInfo])
from [DBO].RETURNDATE()
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'WITH t (Country ,Date,total) AS (
SELECT b.country as Market,
CAST(a.ProcessDate AS Date) AS DATE,
COUNT(a.ProcessDate) AS total
FROM [dbo].[FileProcessLog] a
LEFT JOIN [dbo].[MasterFile] b ON a.FileID = b.FileID where a.ProcessDate BETWEEN ''2022-11-01'' AND ''2022-11-07''
GROUP BY b.country, CAST(a.ProcessDate AS DATE)
)
SELECT * FROM (SELECT Date,Total,Country from t) x
PIVOT(SUM(total)
FOR Date IN ('
+ #cols +
')
) as PIVOTTABLE
'
execute(#query)
I think your full answer is ready now. Happy Coding.
I am working with a view I'm trying to process and present in a more usable way using SQL Server.
The data:
select *
from vReportData
where date between '01.01.2020' and '31.01.2020'
which looks like this:
persnr date abw target attendance
----------------------------------------------------
000001 2020-01-01 5.00 4.45
000001 2020-01-02 0.00 8.00
000001 2020-01-04 6.00 7.00
000001 2020-01-30 6.00 6.00
000001 2020-01-31 6.00 6.50
[...]
999999 2020-01-02 U 5.00 0.00
999999 2020-01-30 4.00 5.00
999999 2020-01-31 5.00 5.00
The desired output:
persnr 01.01.2020 02.01.2020 [...] 30.01.2020 31.01.2020 sum
-----------------------------------------------------------------------
000001 4.45/5.00 8.00/0.00 [...] 6.00/6.00 6.50/6.00 xxx
999999 U [...] 5.00/4.00 0.00/5.00 xxx
Basically I would like to transform the table to have one line per person and have the dates as column. As for the abw, target and attendance I want to get them to be displayed by the date it happened.
The idea
I'm very likely need to use pivot function to get the table shape I want. Since I don't have all dates of a given month in my vReportData I thought about generating them first using something along the lines of:
declare #start datetime = '2020-05-01'
declare #ende datetime = '2020-05-31'
;with calender AS
(
select #start as Datum
union all
select Datum + 1 from Calender
where Datum + 1 <= #ende
)
select [Date] = convert(varchar, Datum, 104), 0.00 as value
from calender
to get the dates of the given month as rows.
Adding the persnr I ended up with:
select distinct
#vReportData.persnr,
[Date] = convert(varchar, Datum, 104)
from
calender
cross join
#vReportData
I don't how how to get the pivot working using my approach.
My complete SQL + attempt can be found here - for testing purpose I ended up changing the end date at some point.
https://data.stackexchange.com/stackoverflow/query/1240742/reportdata
additon & followup:
I was trying a few other attemts. Here's another issue I encountered:
I ended up using a dynamic pivot where I was first selecting the dates and then adding them to the pivot. I have the desired table form, but not one line per persnr.
declare #dynQuery as nvarchar(MAX)
declare #cols as nvarchar(MAX)
select #cols= isnull(#cols + ',','')
+ quotename(convert(varchar, date, 104))
from (select distinct date from app_hours
where date between '01.01.2020' and '31.01.2020'
) AS Dates
SET #dynQuery =
N'select distinct persnr, ' + #cols + '
from app_hours
pivot (
sum(attendancetime)
for date in (' + #cols + '))
AS pivot where persnr = 000001'
EXEC sp_executesql #dynQuery
the table looks like this:
persnr 01.01.2020 02.01.2020 03.01.2020 [...]
----------------------------------------------------------------
000001 NULL NULL 5.00 ...
000001 NULL 5.00 NULL ...
000001 5.00 NULL NULL ...
Here is a way to add a floating subtotal placeholder to be offset from the last column of real data. This does not solve the column headers problem that was solved above, however, you could either create a SP to produce your headers laid out similarly or convert the pivot part to dynamic sql.
DECLARE #vReportData TABLE (
[persnr] int,
[date] datetime,
[abw] varchar(5),
[target] float,
[attendance] float
);
INSERT INTO #vReportData
([persnr], [date], [abw], [target], [attendance])
VALUES
(000001, '2020-01-01', '', 5.00, 4.45),
(000001, '2020-01-02', '', 0.00, 8.00),
(000001, '2020-01-04', '', 6.00, 7.00),
(000001, '2020-01-30', '', 6.00, 6.00),
(000001, '2020-01-31', '', 6.00, 6.50),
(999999, '2020-01-02', 'U', 6.00, 0.00),
(999999, '2020-01-30', '', 4.00, 5.00),
(999999, '2020-01-31', '', 5.00, 5.00)
declare #start datetime = '2020-01-01'
declare #ende datetime = '2020-01-04'
DECLARE #SubtotalPosition INT = DATEDIFF(DAY, #start,#ende) + 2
;with calender AS
(
select #start as Datum
union all
select Datum + 1 from Calender
where Datum + 1 <= #ende
)
,DistinctUsers AS
(
SELECT DISTINCT persnr FROM #vReportData
)
,MakeSubTotalPlaceholders AS
(
--Add placeholder to place at the end of the real data as subtotal buckets
select IsPlaceHolder=1, persnr, date = #ende, abw=null, target=0, attendance=null FROM DistinctUsers
UNION ALL
SELECT IsPlaceHolder=0, v.persnr, date, abw, target, attendance FROM #vReportData v
WHERE
v.date BETWEEN #start AND #ende
)
,ReadyForPivotWithTotal AS
(
SELECT
persnr, date, abw, target, attendance,
DayOffset = CASE WHEN IsPlaceHolder = 1 THEN #SubtotalPosition ELSE DENSE_RANK() OVER (ORDER BY DATEPART(DAY,date)) END,
total = CASE WHEN IsPlaceHolder = 1 THEN SUM(target) OVER (PARTITION BY persnr) ELSE target END
FROM
MakeSubTotalPlaceholders
)
SELECT persnr,
P1=MAX([1]),P2=MAX([2]),P3=MAX([3]),P4=MAX([4]),P5=MAX([5]),P6=MAX([6]),P7=MAX([7]),P8=MAX([8]),P9=MAX([9]),P10=MAX([10]),P11=MAX([11]),P12=MAX([12]),
P13=MAX([13]),P14=MAX([14]),P15=MAX([15]),P16=MAX([16]),P17=MAX([17]),P18=MAX([18]),P19=MAX([19]),P20=MAX([20]),P21=MAX([21]),P22=MAX([22]),P23=MAX([23]),P24=MAX([24]),
P25=MAX([25]),P26=MAX([26]),P27=MAX([27]),P28=MAX([28]),P29=MAX([29]),P30=MAX([30]),P31=MAX([31]),P32=MAX([32])
FROM
ReadyForPivotWithTotal A
PIVOT
(
MAX(total)
FOR DayOffSet IN
(
[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],
[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],
[25],[26],[27],[28],[29],[30],[31],[32]
)
)AS B
GROUP BY
persnr
Yes, the pain with pivots is having to name all the columns in your build statement, especially when that list is big or prone to change -- which, as you've pointed out, is best done with dynamic SQL.
I've used the EOMONTH function, a recursive CTE and a temp table to populate the list of pivot columns. Then used dynamic SQL to build and execute the PIVOT.
I need a little more clarity around what the [sum] column is actually adding, and your expected output for your example data set, before I can incorporate that.
But without the [sum] column...
-- declare vars
DECLARE #columns_select AS VARCHAR(MAX), #columns_pivot AS VARCHAR(MAX) ;
DECLARE #start_date AS DATE, #end_date AS DATE ;
DECLARE #sqlCmd AS NVARCHAR(MAX) ;
-- determine start date
SET #start_date = ( SELECT TOP(1) DATEADD ( DD, 1, EOMONTH ( [date], -1 ) )
FROM #vReportData ORDER BY [date] ASC ) ;
-- determine end date
SET #end_date = ( SELECT TOP(1) EOMONTH ( [date], 0 )
FROM #vReportData ORDER BY [date] DESC ) ;
-- generate date range to temp table
WITH cte_DateCols1 AS
(
SELECT #start_date AS [date]
UNION ALL
SELECT DATEADD ( DD, 1, [date] )
FROM cte_DateCols1
WHERE [date] < #end_date
)
SELECT [date]
INTO #tbl_Dates
FROM cte_DateCols1 ;
-- populate list of columns for SELECT statement
SELECT #columns_select = CONCAT ( #columns_select, ', ISNULL ( ', QUOTENAME ( [date] ), ', '''' ) AS '
, QUOTENAME ( CONVERT ( VARCHAR(10), [date], 104 ) ) )
FROM #tbl_Dates ;
-- populate list of columns for PIVOT statement
SELECT #columns_pivot = CONCAT ( #columns_pivot, ', ', QUOTENAME ( [date] ) )
FROM #tbl_Dates ;
SET #columns_pivot = RIGHT ( #columns_pivot, LEN ( #columns_pivot ) - 2 ) ;
-- drop temp table
DROP TABLE #tbl_Dates ;
-- build dynamic SQL PIVOT statement
SET #sqlCmd = N'
WITH cte_Data AS
(
SELECT [persnr]
, CAST ( [date] AS DATE ) AS [date]
, CASE [abw]
WHEN ''U'' THEN ''U''
WHEN '''' THEN CONCAT ( [attendance], ''/'', [target] )
ELSE NULL
END AS [result]
FROM #vReportData
)
SELECT RIGHT ( ''00000'' + CAST ( [persnr] AS VARCHAR(6) ), 6 ) AS [persnr]
' + #columns_select + '
FROM cte_Data
PIVOT (
MAX ( [result] )
FOR [date]
IN ( ' + #columns_pivot + ' )
) AS pvt
ORDER BY [persnr] ASC ;' ;
-- execute dynamic SQL PIVOT statement
PRINT #sqlCmd ;
EXEC sp_executesql #statement = #sqlCmd ;
GO
Updated for Attendance Totals
You should be able to grab the [attendance] totals using a SUM function on the CTE output, before applying the pivot, and tack it onto the end of your pivot output, like this...
-- declare vars
DECLARE #columns_select AS VARCHAR(MAX), #columns_pivot AS VARCHAR(MAX) ;
DECLARE #start_date AS DATE, #end_date AS DATE ;
DECLARE #sqlCmd AS NVARCHAR(MAX) ;
-- determine start date
SET #start_date = ( SELECT TOP(1) DATEADD ( DD, 1, EOMONTH ( [date], -1 ) )
FROM #vReportData ORDER BY [date] ASC ) ;
-- determine end date
SET #end_date = ( SELECT TOP(1) EOMONTH ( [date], 0 )
FROM #vReportData ORDER BY [date] DESC ) ;
-- generate date range to temp table
WITH cte_DateCols AS
(
SELECT #start_date AS [date]
UNION ALL
SELECT DATEADD ( DD, 1, [date] )
FROM cte_DateCols
WHERE [date] < #end_date
)
SELECT [date]
INTO #tbl_Dates
FROM cte_DateCols ;
-- populate list of columns for SELECT statement
SELECT #columns_select = CONCAT ( #columns_select, ', ISNULL ( ', QUOTENAME ( [date] ), ', '''' ) AS '
, QUOTENAME ( CONVERT ( VARCHAR(10), [date], 104 ) ) )
FROM #tbl_Dates ;
-- populate list of columns for PIVOT statement
SELECT #columns_pivot = CONCAT ( #columns_pivot, ', ', QUOTENAME ( [date] ) )
FROM #tbl_Dates ;
SET #columns_pivot = RIGHT ( #columns_pivot, LEN ( #columns_pivot ) - 2 ) ;
-- drop temp table
DROP TABLE #tbl_Dates ;
-- build dynamic SQL PIVOT statement
SET #sqlCmd = N'
WITH cte_Data AS
(
SELECT [persnr]
, CAST ( [date] AS DATE ) AS [date]
, CASE [abw]
WHEN ''U'' THEN ''U''
WHEN '''' THEN CONCAT ( [attendance], ''/'', [target] )
ELSE NULL
END AS [result]
FROM #vReportData
)
, cte_DataWithTotals AS
(
SELECT r.[persnr]
, SUM ( CAST ( r.[attendance] AS DECIMAL (5,2) ) ) AS [total_attendance]
, d.[date]
, d.[result]
FROM #vReportData AS r
INNER JOIN cte_Data AS d
ON r.[persnr] = d.[persnr]
GROUP BY r.[persnr], d.[date], d.[result]
)
SELECT RIGHT ( ''00000'' + CAST ( [persnr] AS VARCHAR(6) ), 6 ) AS [persnr]
' + #columns_select + '
, [total_attendance] AS [attendance_sum]
FROM cte_DataWithTotals
PIVOT (
MAX ( [result] )
FOR [date]
IN ( ' + #columns_pivot + ' )
) AS pvt
ORDER BY [persnr] ASC ;' ;
-- execute dynamic SQL PIVOT statement
PRINT #sqlCmd ;
EXEC sp_executesql #statement = #sqlCmd ;
GO
I am trying to create pivot where row is Brand and SKU, sum is qty for column YYMM, but I'm not sure why it is throwing an error. Please help.
Code:
DECLARE #TABLE TABLE
(
SKU VARCHAR(10),
YYMM VARCHAR(50),
BRAND VARCHAR(50),
QTY INT
)
INSERT INTO #TABLE
SELECT '104591168', '2015-January', 'abott', 2 UNION ALL
SELECT '104580709', '2016-January', 'GSK', 2 UNION ALL
SELECT '104720038', '2017-January', 'RANBAXCY', 2 UNION ALL
SELECT '10467011A', '2018-January', 'abott', 2 UNION ALL
SELECT '104590691', '2019-January', 'abott', 10
Pivot code:
select *
from
(select
BRAND, sku, QTY, YYMM
from #TABLE) src
pivot
(sum(QTY)
for [Year Month]
You forgot to finish your query.
Please see the bottom of MSDN article for PIVOT examples.
I would guess that you wanted your query to look something like this:
SELECT *
FROM(
SELECT BRAND, sku, QTY, YYMM
FROM #TABLE
) AS src
PIVOT(
sum(QTY)
for [YYMM] IN( [2015-January], [2016-January], [2017-January] /* add other moneths here */ )
) AS Pivoted
We can make it as Dynamic sql
DECLARE #Column NVARCHAR(max),
#Column2 NVARCHAR(max),
#Sql NVARCHAR(max)
SELECT #Column = Stuff((SELECT DISTINCT ', '
+ Quotename(Cast(yymm AS VARCHAR(20)))
FROM #table
FOR xml path ('')), 1, 1, '')
SELECT #Column2 = Stuff((SELECT DISTINCT ', ' + 'ISNULL('
+ Quotename(Cast(yymm AS VARCHAR(20)))
+ ','
+ '''0''' + ') AS '
+ Quotename(Cast(yymm AS VARCHAR(20)))
FROM #table
FOR xml path ('')), 1, 1, '')
SET #Sql='
SELECT BRAND ,sku,'+#Column2+'
FROM(
SELECT *
FROM #TABLE
) AS src
PIVOT(
sum(QTY)
for [YYMM] IN('+#Column+')
) AS Pivoted
'
PRINT #Sql
EXEC(#Sql)
Result
BRAND sku 2015-January 2016-January 2017-January 2018-January 2019-January
--------------------------------------------------------------------------------------------------------
abott 104590691 0 0 0 0 10
abott 104591168 2 0 0 0 0
abott 10467011A 0 0 0 2 0
GSK 104580709 0 2 0 0 0
RANBAXCY 104720038 0 0 2 0 0
I have a table named "TABLE1" Need output in below format. How can I use pivot in this case or any Inner joins to get output
You can use dynamic SQL with pivoting like below:
CREATE TABLE TABLE1 (
Code nvarchar(1),
Ind nvarchar(3),
Region nvarchar(10),
Amt decimal(17,7),
Currency nvarchar(3),
Aging nvarchar(6),
[Count] int
)
--create table with data you provided
INSERT INTO TABLE1 VALUES
('X','XYZ','Region1', 16882.96585, 'INR','0-30', 3),
('X','XYZ','Region2', 30831.0445, 'INR','31-60', 3),
('X','XYZ','Region3', 8759.319245, 'INR','61-90', 1),
('X','XYZ','Region4', 39070.18077, 'INR','91-180', 1)
DECLARE #sql nvarchar(max),
#col nvarchar(max)
--here we join all column names we need with aging values
SELECT #col = (
SELECT ','+QUOTENAME([name]+' ('+Aging+')')
FROM TABLE1 t
CROSS JOIN sys.columns c
WHERE [object_id] = OBJECT_ID(N'TABLE1') AND column_id > 2 AND [name] != 'Aging'
FOR XML PATH('')
)
--construction of dynamic query
SELECT #sql = N'
SELECT *
FROM (
SELECT Code,
Ind,
[Columns]+'' (''+Aging+'')'' as [Columns],
[Values]
FROM (
SELECT Code,
Ind,
CAST(Region as nvarchar(max)) Region,
CAST(Amt as nvarchar(max)) Amt,
CAST(Currency as nvarchar(max)) Currency,
CAST(Aging as nvarchar(max)) Aging,
CAST([Count] as nvarchar(max)) [Count],
ROW_NUMBER() OVER (PARTITION BY Code ORDER BY Aging) as RowNum
FROM TABLE1
) as t
UNPIVOT (
[Values] FOR [Columns] IN (Region, Amt, Currency, [Count])
) as unp
) as d
PIVOT (
MAX([Values]) FOR [Columns] IN ('+STUFF(#col,1,1,'')+')
) as pvt'
EXEC sp_executesql #sql
DROP TABLE TABLE1
Output:
Code Ind Region (0-30) Amt (0-30) Currency (0-30) Count (0-30) Region (31-60) Amt (31-60) Currency (31-60) Count (31-60) Region (61-90) Amt (61-90) Currency (61-90) Count (61-90) Region (91-180) Amt (91-180) Currency (91-180) Count (91-180)
X XYZ Region1 16882.9658500 INR 3 Region2 30831.0445000 INR 3 Region3 8759.3192450 INR 1 Region4 39070.1807700 INR 1
Hope this helps.
If the structure of the query won't vary, you can PRINT #sql and then use the query for your needs, without dynamic SQL.
I have a table UTENSILS with 3 columns like this:
CLASS_NAME RANGE COUNT
---------------------------
pens 0-0.5 200
pencil 0-0.5 50
pens 0.5-1.0 300
pencil 0.5-1.0 40
pens 1.0-1.5 150
pencil 1.0-1.5 45
I want a query that displays the above table result as below:
RANGE Pens Pencils
------------------------------
0-0.5 200 50
0.5-1.0 300 40
1.0-1.5 150 45
Any ideas about this? Thanks in advance!
What you are trying to do is known as a PIVOT. This is when you transform data from rows into columns. Some databases have a PIVOT function that you can take advantage of but you did not specify which RDBMS.
If you do not have a PIVOT function then you can replicate the functionality using an aggregate function along with a CASE statement:
select `range`,
sum(case when class_name = 'pens' then `count` end) pens,
sum(case when class_name = 'pencil' then `count` end) pencils
from yourtable
group by `range`
See SQL Fiddle with Demo
Note: the backticks are for MySQL, if SQL Server then use a square bracket around range and count. These are used to escape the reserved words.
If you are working in an RDBMS that has a PIVOT function, then you can use the following:
select *
from
(
select class_name, [range], [count]
from yourtable
) src
pivot
(
sum([count])
for class_name in ([pens], [pencil])
) piv
See SQL Fiddle with Demo
Both will produce the same result:
| RANGE | PENS | PENCIL |
---------------------------
| 0-0.5 | 200 | 50 |
| 0.5-1.0 | 300 | 40 |
| 1.0-1.5 | 150 | 45 |
The above will work great if you have a known number of values for class_name, if you do not then, depending on your RDBMS there are ways to generate a dynamic version of this query.
In SQL Server a dynamic version will be similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(CLASS_NAME)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [range], ' + #cols + ' from
(
select CLASS_NAME, [RANGE], [COUNT]
from yourtable
) x
pivot
(
sum([COUNT])
for CLASS_NAME in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
This pivot query can be used in all major DBMS. The trick is getting the bad column names 'range' and 'count' quoted property.
SQL Server (below) uses [], MySQL uses backticks (`), Oracle uses double quotes, SQLite can use any of the preceding.
select [range],
sum(case when class_name='pens' then [count] else 0 end) Pens,
sum(case when class_name='pencil' then [count] else 0 end) Pencils
from tbl
group by [range]
order by [range];
here is the dynamic version of the pivot:
IF OBJECT_ID('tempdb..#T') IS NOT NULL
DROP TABLE #T
CREATE TABLE #T(
[CLASS_NAME] VARCHAR(20) NOT NULL
, [range] VARCHAR(10) NOT NULL
, [count] INT NOT NULL
)
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pens', '0-0.5', '200')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pencil', '0-0.5', '50')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pens', '0.5-1.0', '300')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pencil', '0.5-1.0', '40')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pens', '1.0-1.5', '150')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pencil', '1.0-1.5', '45')
DECLARE #PivotColumnHeaders VARCHAR(MAX)
SELECT #PivotColumnHeaders = STUFF((
--SELECT DISTINCT TOP 100 PERCENT
SELECT DISTINCT '],[' + [CLASS_NAME]
FROM #T
ORDER BY '],[' + [CLASS_NAME]
FOR XML PATH('')
), 1, 2, '') + ']';
DECLARE #PivotTableSQL NVARCHAR(MAX)
SET #PivotTableSQL = N'
SELECT *
FROM (
SELECT *
FROM #T
) AS PivotData
PIVOT (
SUM([count])
FOR [CLASS_NAME] IN (
' + #PivotColumnHeaders + '
)
) AS PivotTable
'
EXECUTE(#PivotTableSQL)