Is it possible to Sum columns if columns name is like '2020%' - sql

I have a table on my server where they have already pivoted the data so now I am sitting with a table that looks something like this.
Client_No
20200201
20200401
20220101
20220201
20220301
20220401
20220501
123456789
3
1
0
0
0
0
0
321654987
4
4
0
4
2
1
0
this table gets updated monthly so to automate the script I want to be able to do something like this
Select Client_No
, Sum(column like '2022%')
From [table_name]
Is this possible? Basically I want the script to sum all the columns that start with 2022, and yes I am currently running this in SSMS

Since you mentioned that you already have table which is pivoted you will have to unpivot it first.
SELECT CLIENT_NO, SUM(CAST(ORDERS AS BIGINT)) OrdersNumber FROM (
SELECT Client_No, Years, Orders
FROM
(SELECT Client_No,[20200201], [20220101], [20200401], [20220201]
FROM myTable) p
UNPIVOT
(Orders FOR Years IN
([20200201], [20200401], [20220101], [20220201])
)AS unpvt
) SRC
WHERE LEFT(SRC.Years,4) = '2022'
GROUP BY CLIENT_NO
Or if you don't want to put down all columns you can use dynamic unpivot
DECLARE #Pivot_Column [nvarchar](max);
DECLARE #Query [nvarchar](max);
set #Pivot_Column = (SELECT STRING_AGG('[' + cName + ']', ',') FROM
(select c.Name cName from sys.all_columns c
left join sys.objects o on c.object_id = o.object_id
where o.name = 'MyTable' and c.name <> 'Client_No' )Tab
)
SELECT #Query='
SELECT CLIENT_NO, SUM(CAST(ORDERS AS BIGINT)) OrdersNumber FROM (
SELECT Client_No, Years, Orders
FROM
(SELECT Client_No,' + #Pivot_Column + '
FROM myTable) p
UNPIVOT
(Orders FOR Years IN
(' + #Pivot_Column + ')
)AS unpvt
) SRC
WHERE LEFT(SRC.Years,4) = ''2022''
GROUP BY CLIENT_NO
'
EXEC sp_executesql #Query
Note: You can use STRING_AGG starting from SQL Server 2017 (14.x) and later

To do a Sum, you usually need a GROUP BY clause. In this case you might be better-off using a sub-query to create a calculated column for grouping. Try this:
SELECT Client_No, Yr, Sum(ItemValue)
FROM (
Select Client_No,
Left(datemask_or_whatever, 4) as Yr, --column like '2022%'
ItemValue
From [table_name]
) SubQuery
GROUP BY Client_No, Yr

Related

Restructure table by removing NULL values

I have a table in SQL that looks like this:
Customer Product 1999 2000 2001 2002 2003
Smith 51 NULL NULL 15 14 NULL
Jones 14 11 7 NULL NULL NULL
Jackson 13 NULL NULL NULL 3 9
The figures under each year column are amounts, in dollars. Each customer has two consecutive years of amounts, and the rest of the years are zero. I would like to re-structure this table so that instead of wide list of years, it just has two columns Amount-Year1 and Amount-Year2. So it selects the two non-zero years and puts them in those columns, in the correct order. This would greatly reduce the size of my table.
So far I've been able to re-structure it so that there is one amount column and one year column, but I then get multiple rows per customer, which unfortunately I can't have (due to downstream analysis). Can anyone think of a way to get the two Amount-Year columns?
I would like the final table to look like this:
Customer Product Amount_Y1 Amount_Y2
Smith 51 15 14
Jones 14 11 7
Jackson 13 3 9
I don't mind that I lose the information about the specific years, as I can get that from another source. The actual table has data for all years between 1999 and 2018, and there will be further years in the future.
Thanks
Thankfully UNPIVOT removes NULLs anyway, so we can do this with UNPIVOT/ROWNUMBER(),PIVOT:
declare #t table (Customer varchar(15),Product int,[1999] int,
[2000] int,[2001] int,[2002] int,[2003] int)
insert into #T(CUstomer,Product,[1999],[2000],[2001],[2002],[2003]) values
('Smith' ,51,NULL,NULL, 15, 14,NULL),
('Jones' ,14, 11, 7,NULL,NULL,NULL),
('Jackson',13,NULL,NULL,NULL, 3, 9)
;With Numbered as (
select
Customer,Product,Value,
ROW_NUMBER() OVER (PARTITION BY Customer,Product
ORDER BY Year) rn
from
#t t
unpivot
(Value for Year in ([1999],[2000],[2001],[2002],[2003])) u
)
select
*
from
Numbered n
pivot
(SUM(Value) for rn in ([1],[2])) w
Results:
Customer Product 1 2
--------------- ----------- ----------- -----------
Jackson 13 3 9
Jones 14 11 7
Smith 51 15 14
Use COALESCE that will do the job for you. The query is dynamics so that if tomorrow year columns are changed, i.e. removed or added you do not have to change anything.
Sample query: (Assuming table to be table1 and column names to be same as year).
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM Table1'
EXEC(#sql)
If you're dealing with a temporary table, then the code will change slightly:
Test it here: http://rextester.com/MRVR48808
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM #Table1' --Changes here
EXEC(#sql)
Try using COALESCE as follows : For one field from beginning to end and for the second in the reverse manner.
SELECT Customer,Product, COALESCE([1999],[2000],[2001],[2002],[2003]) as Y1,
COALESCE([2003],[2002],[2001],[2000],[1999]) as Y2
FROM #TEMPDATA
I would do this using cross apply:
select t.customer, t.product, v.Amount_Y1, v.Amount_Y2
from t cross apply
(select max(case when which = 1 then val end) as Amount_Y1,
max(case when which = 2 then val end) as Amount_Y2
from (select val, yr, row_number() over (order by yr) as which
from (values (t.[1999], 1999), (t.[2000], 2000), (t.[2001], 2001),
(t.[2002], 2002), (t.[2003], 2003)
) v(val, yr)
where val is not null
) v

Split/separate column into multiple columns

I'm completely stuck and I cannot find any answers for this problem even though problem seems to be quite simple. Can I separate that 'description' column without making a new table?
For now I just wrote this simplest code.
select item_id, description
from data
where item_id = '123'
With that code it looks like this:
item_id description
123 A
123 B
123 C
But I'd like to make it look like this:
item_id desc_1 desc_1 desc_2
123 A B C
Use conditional aggregation with the help of case expression
select item_id,
max(case when description= 'A' then description end) [desc_1],
max(case when description= 'B' then description end) [desc_2],
max(case when description= 'C' then description end) [desc_3],
from table
group by item_id
EDIT : So, the dynamic pivot way will look like as for SQL Server
declare #col varchar(max), #q varchar(max)
set #col = stuff(
(select distinct ','+quotename('desc_'+cast(row_number() over(partition by Item_id order by description) as varchar))
from table for xml path('')),
1,1,'')
set #q = 'select * from
(
select *,
''desc_''+cast(row_number() over(partition by Item_id order by description) as varchar) rn
from table
)a
PIVOT
(
max(description) for rn in ('+#col+')
)p'
EXEC (#Q)
Result :
item_id desc_1 desc_2 desc_3
123 A B C
234 B C d
first Declare Distinct column names
Like ABC, DEF,GHI
and values
then write dynamic pivot
DECLARE #COLS AS NVARCHAR(MAX)
DECLARE #query AS NVARCHAR(MAX)
SET #COLS=STUFF((select ',' + QUOTENAME(Course) from cst_coursedetails where programid=1 FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
SET #query =' SELECT * FROM(SELECT B.COLLCODE, B.COLLNAME,C.Course AS COURSE,D.ROLLNAME, E.ExamType, COUNT (A.HTNO) AS PRECOUNT FROM TableOne AS A
INNER JOIN TableTwo AS B ON A.COLLCODE=B.COLLCODE AND A.PROGRAMID=B.PROGRAMID
INNER JOIN TableThreee AS C ON C.CourseId=A.CourseId
INNER JOIN TableFour AS D ON D.ROLLID=A.ROLLID
INNER JOIN CST_EXAMTYPE AS E ON E.ExamTypeId=A.ExamTypeId
WHERE A.STATUSID !=17 AND a.ProgramId=1 AND A.ROLLID IN(1,2,3) AND A.EXAMTYPEID IN(1) GROUP BY B.COLLNAME,B.COLLCODE,C.Course,d.ROLLNAME ,E.ExamType
)SRC
PIVOT(
SUM(PRECOUNT) FOR COURSE IN('+#COLS+')
)AS PIV'
afs -- with clause name
giga -- alias name for listagg
with afs as
(
select item_id,LISTAGG(description, ',') WITHIN GROUP (ORDER BY item_id) AS
giga from test_jk group by item_id
)
select item_id,REGEXP_SUBSTR (giga, '[^,]+', 1, 1) AS
desc_1,REGEXP_SUBSTR (giga, '[^,]+', 1, 2) as desc_2 from afs;
output

SQL Pivot Convert Null to 0 [duplicate]

I tried to convert the (null) values with 0 (zeros) output in PIVOT function but have no success.
Below is the table and the syntax I've tried:
SELECT
CLASS,
[AZ],
[CA],
[TX]
FROM #TEMP
PIVOT (SUM(DATA)
FOR STATE IN ([AZ], [CA], [TX])) AS PVT
ORDER BY CLASS
CLASS AZ CA TX
RICE 10 4 (null)
COIN 30 3 2
VEGIE (null) (null) 9
I tried to use the ISNULL but did not work.
PIVOT SUM(ISNULL(DATA,0)) AS QTY
What syntax do I need to use?
SELECT CLASS,
isnull([AZ],0),
isnull([CA],0),
isnull([TX],0)
FROM #TEMP
PIVOT (SUM(DATA)
FOR STATE IN ([AZ], [CA], [TX])) AS PVT
ORDER BY CLASS
If you have a situation where you are using dynamic columns in your pivot statement you could use the following:
DECLARE #cols NVARCHAR(MAX)
DECLARE #colsWithNoNulls NVARCHAR(MAX)
DECLARE #query NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(Name)
FROM Hospital
WHERE Active = 1 AND StateId IS NOT NULL
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #colsWithNoNulls = STUFF(
(
SELECT distinct ',ISNULL(' + QUOTENAME(Name) + ', ''No'') ' + QUOTENAME(Name)
FROM Hospital
WHERE Active = 1 AND StateId IS NOT NULL
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
EXEC ('
SELECT Clinician, ' + #colsWithNoNulls + '
FROM
(
SELECT DISTINCT p.FullName AS Clinician, h.Name, CASE WHEN phl.personhospitalloginid IS NOT NULL THEN ''Yes'' ELSE ''No'' END AS HasLogin
FROM Person p
INNER JOIN personlicense pl ON pl.personid = p.personid
INNER JOIN LicenseType lt on lt.licensetypeid = pl.licensetypeid
INNER JOIN licensetypegroup ltg ON ltg.licensetypegroupid = lt.licensetypegroupid
INNER JOIN Hospital h ON h.StateId = pl.StateId
LEFT JOIN PersonHospitalLogin phl ON phl.personid = p.personid AND phl.HospitalId = h.hospitalid
WHERE ltg.Name = ''RN'' AND
pl.licenseactivestatusid = 2 AND
h.Active = 1 AND
h.StateId IS NOT NULL
) AS Results
PIVOT
(
MAX(HasLogin)
FOR Name IN (' + #cols + ')
) p
')
You cannot place the IsNull() until after the data is selected so you will place the IsNull() around the final value in the SELECT:
SELECT CLASS,
IsNull([AZ], 0) as [AZ],
IsNull([CA], 0) as [CA],
IsNull([TX], 0) as [TX]
FROM #TEMP
PIVOT
(
SUM(DATA)
FOR STATE IN ([AZ], [CA], [TX])
) AS PVT
ORDER BY CLASS
Sometimes it's better to think like a parser, like T-SQL parser. While executing the statement, parser does not have any value in Pivot section and you can't have any check expression in that section. By the way, you can simply use this:
SELECT CLASS
, IsNull([AZ], 0)
, IsNull([CA], 0)
, IsNull([TX], 0)
FROM #TEMP
PIVOT (
SUM(DATA)
FOR STATE IN (
[AZ]
, [CA]
, [TX]
)
) AS PVT
ORDER BY CLASS
You have to account for all values in the pivot set. you can accomplish this using a cartesian product.
select pivoted.*
from (
select cartesian.key1, cartesian.key2, isnull(relationship.[value],'nullvalue') as [value]
from (
select k1.key1, k2.key2
from ( select distinct key1 from relationship) k1
,( select distinct key2 from relationship) k2
) cartesian
left outer join relationship on relationship.key1 = cartesian.key1 and relationship.key2 = carterisan.key2
) data
pivot (
max(data.value) for ([key2_v1], [key2_v2], [key2_v3], ...)
) pivoted
To modify the results under pivot, you can put the columns in the selected fields and then modify them accordingly. May be you can use DECODE for the columns you have built using pivot function.
Kranti A
I have encountered a similar problem. The root cause is that (use your scenario for my case), in the #temp table, there is no record for:
a. CLASS=RICE and STATE=TX
b. CLASS=VEGIE and (STATE=AZ or STATE=CA)
So, when MSSQL does pivot for no record, MSSQL always shows NULL for MAX, SUM, ... (aggregate functions).
None of above solutions (IsNull([AZ], 0)) works for me, but I do get ideas from these solutions.
Sorry, it really depends on the #TEMP table. I can only provide some suggestions.
Make sure #TEMP table have records for below condition, even Data is null.
a. CLASS=RICE and STATE=TX
b. CLASS=VEGIE and (STATE=AZ or STATE=CA)
You may need to use cartesian product: select A.*, B.* from A, B
In the select query for #temp, if you need to join any table with WHERE, then would better put where inside another sub select query. (Goal is 1.)
Use isnull(DATA, 0) in #TEMP table.
Before pivot, make sure you have achieved Goal 1.
I can't give an answer to the original question, since there is no enough info for #temp table. I have pasted my code as example here.
SELECT * FROM (
SELECT eeee.id as enterprise_id
, eeee.name AS enterprise_name
, eeee.indicator_name
, CONVERT(varchar(12) , isnull(eid.[date],'2019-12-01') , 23) AS data_date
, isnull(eid.value,0) AS indicator_value
FROM (select ei.id as indicator_id, ei.name as indicator_name, e.* FROM tbl_enterprise_indicator ei, tbl_enterprise e) eeee
LEFT JOIN (select * from tbl_enterprise_indicator_data WHERE [date]='2020-01-01') eid
ON eeee.id = eid.enterprise_id and eeee.indicator_id = enterprise_indicator_id
) AS P
PIVOT
(
SUM(P.indicator_value) FOR P.indicator_name IN(TX,CA)
) AS T

Dynamic SELECT statement, generate columns based on present and future values

Currently building a SELECT statement in SQL Server 2008 but would like to make this SELECT statement dynamic, so the columns can be defined based on values in a table. I heard about pivot table and cursors, but seems kind of hard to understand at my current level, here is the code;
DECLARE #date DATE = null
IF #date is null
set # date = GETDATE() as DATE
SELECT
Name,
value1,
value2,
value3,
value4
FROM ref_Table a
FULL OUTER JOIN (
SELECT
PK_ID ID,
sum(case when FK_ContainerType_ID = 1 then 1 else null) Box,
sum(case when FK_ContainerType_ID = 2 then 1 else null) Pallet,
sum(case when FK_ContainerType_ID = 3 then 1 else null) Bag,
sum(case when FK_ContainerType_ID = 4 then 1 else null) Drum
from
Packages
WHERE
#date between PackageStart AND PackageEnd
group by PK_ID ) b on a.Name = b.ID
where
Group = 0
The following works great for me , but PK_Type_ID and the name of the column(PackageNameX,..) are hard coded, I need to be dynamic and it can build itself based on present or futures values in the Package table.
Any help or guidance on the right direction would be greatly appreciated...,
As requested
ref_Table (PK_ID, Name)
1, John
2, Mary
3, Albert
4, Jane
Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
1 , 1, 4, 1JAN2014, 30JAN2014
2 , 2, 3, 1JAN2014, 30JAN2014
3 , 3, 2, 1JAN2014, 30JAN2014
4 , 4, 1, 1JAN2014, 30JAN2014
ContainerType (PK_ID, Type)
1, Box
2, Pallet
3, Bag
4, Drum
and the result should look like this;
Name Box Pallet Bag Drum
---------------------------------------
John 1
Mary 1
Albert 1
Jane 1
The following code like I said works great, the issue is the Container table is going to grow and I need to replicated the same report without hard coding the columns.
What you need to build is called a dynamic pivot. There are plenty of good references on Stack if you search out that term.
Here is a solution to your scenario:
IF OBJECT_ID('tempdb..##ref_Table') IS NOT NULL
DROP TABLE ##ref_Table
IF OBJECT_ID('tempdb..##Packages') IS NOT NULL
DROP TABLE ##Packages
IF OBJECT_ID('tempdb..##ContainerType') IS NOT NULL
DROP TABLE ##ContainerType
SET NOCOUNT ON
CREATE TABLE ##ref_Table (PK_ID INT, NAME NVARCHAR(50))
CREATE TABLE ##Packages (PK_ID INT, FK_ref_Table_ID INT, FK_ContainerType_ID INT, PackageStartDate DATE, PackageEndDate DATE)
CREATE TABLE ##ContainerType (PK_ID INT, [Type] NVARCHAR(50))
INSERT INTO ##ref_Table (PK_ID,NAME)
SELECT 1,'John' UNION
SELECT 2,'Mary' UNION
SELECT 3,'Albert' UNION
SELECT 4,'Jane'
INSERT INTO ##Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
SELECT 1,1,4,'2014-01-01','2014-01-30' UNION
SELECT 2,2,3,'2014-01-01','2014-01-30' UNION
SELECT 3,3,2,'2014-01-01','2014-01-30' UNION
SELECT 4,4,1,'2014-01-01','2014-01-30'
INSERT INTO ##ContainerType (PK_ID, [Type])
SELECT 1,'Box' UNION
SELECT 2,'Pallet' UNION
SELECT 3,'Bag' UNION
SELECT 4,'Drum'
DECLARE #DATE DATE, #PARAMDEF NVARCHAR(MAX), #COLS NVARCHAR(MAX), #SQL NVARCHAR(MAX)
SET #DATE = '2014-01-15'
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(T.[Type])
FROM ##ContainerType T
FOR XML PATH, TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #SQL = 'SELECT [Name], ' + #COLS + '
FROM (SELECT [Name], [Type], 1 AS Value
FROM ##ref_Table R
JOIN ##Packages P ON R.PK_ID = P.FK_ref_Table_ID
JOIN ##ContainerType T ON P.FK_ContainerType_ID = T.PK_ID
WHERE #DATE BETWEEN P.PackageStartDate AND P.PackageEndDate) X
PIVOT (COUNT(Value) FOR [Type] IN (' + #COLS + ')) P
'
PRINT #COLS
PRINT #SQL
SET #PARAMDEF = '#DATE DATE'
EXEC SP_EXECUTESQL #SQL, #PARAMDEF, #DATE=#DATE
Output:
Name Bag Box Drum Pallet
Albert 0 0 0 1
Jane 0 1 0 0
John 0 0 1 0
Mary 1 0 0 0
Static Query:
SELECT [Name],[Box],[Pallet],[Bag],[Drum] FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( [Box],[Pallet],[Bag],[Drum])
) AS PivotTable
) AS Main
ORDER BY RFID
Dynamic Query:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + [Type] + ']'
FROM ContanerType
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT [Name],' + #columnList + ' FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( ' + #columnList + ')
) AS PivotTable
) AS Main
ORDER BY RFID;'
EXEC sp_executesql #pivotsql
Following my tutorial below will help you to understand the PIVOT functionality:
We write sql queries in order to get different result sets like full, partial, calculated, grouped, sorted etc from the database tables. However sometimes we have requirements that we have to rotate our tables. Sounds confusing?
Let's keep it simple and consider the following two screen grabs.
SQL Table:
Expected Results:
Wow, that's look like a lot of work! That is a combination of tricky sql, temporary tables, loops, aggregation......, blah blah blah
Don't worry let's keep it simple, stupid(KISS).
MS SQL Server 2005 and above has a function called PIVOT. It s very simple to use and powerful. With the help of this function we will be able to rotate sql tables and result sets.
Simple steps to make it happen:
Identify all the columns those will be part of the desired result set.
Find the column on which we will apply aggregation(sum,ave,max,min etc)
Identify the column which values will be the column header.
Specify the column values mentioned in step3 with comma separated and surrounded by square brackets.
So, if we now follow above four steps and extract information from the above sales table, it will be as below:
Year, Month, SalesAmount
SalesAmount
Month
[Jan],[Feb] ,[Mar] .... etc
We are nearly there if all the above steps made sense to you so far.
Now we have all the information we need. All we have to do now is to fill the below template with required information.
Template:
Our SQL query should look like below:
SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( [Jan],[Feb] ,[Mar],
[Apr],[May],[Jun] ,[Jul],
[Aug],[Sep] ,[Oct],[Nov] ,[Dec])
) AS PivotTable;
In the above query we have hard coded the column names. Well it's not fun when you have to specify a number of columns.
However, there is a work arround as follows:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + SalesMonth + ']'
FROM Sales
GROUP BY SalesMonth
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( ' + #columnList +' )
) AS PivotTable;'
EXEC sp_executesql #pivotsql
Hopefully this tutorial will be a help to someone somewhere.
Enjoy coding.

Iterating a SELECT statement in SQL Server 2008 using a WHILE LOOP

I have a database that gives an employeeID, a job, an effectiveDate, and a dept. If an employee has worked more than one job they will have an additional row of data. My goal is to compress the rows corresponding to each employee into one. Basically I need a query to that pulls from a db that looks like this:
EmpID Job EffDate Dept
001 QB 01-01-2001 OFF
001 LB 01-01-2010 DEF
001 K 01-01-2005 SPEC
002 HC 01-01-2007 STAFF
003 P 01-01-2001 SPEC
003 CB 01-01-2002 DEF
To output like this:
EmpID Job1 EffDate1 Dept1 Job2 EffDate2 Dept2 Job3 EffDate3 Dept3
001 QB 01-01-2001 OFF K 01-01-2005 SPEC LB 01-01-2010 DEF
002 HC 01-01-2007 STAFF
003 P 01-01-2001 SPEC CB 01-01-2002 DEF
So far I have done this:
SELECT
EmpNo
, Job
, EffDate
, Dept
, ROW_NUMBER() OVER (PARTITION BY EmpNo ORDER BY EffDate) AS RowNum
INTO #temp1
FROM JobHist
ORDER BY EffDate DESC
SELECT
JobHist.EmpNo
, JobHist.Job AS Job1
, JobHist.EjhJobDesc AS JobDesc1
, JobHist.EffDate AS EffDate1
, JobHist.Dept AS Dept1
, temp2.Job AS Job2
, temp2.EffDate AS EffDate2
, temp2.Dept AS Dept2
FROM #temp1 AS JobHist LEFT JOIN #temp1 AS temp2 ON JobHist.EmpNo = temp2.EmpNo AND temp2.RowNum = 2
WHERE JobHist.RowNum = 1
And that works just fine. The problem is that I need to make many columns, and I do not want to write all that code 20 times. So I want to iterate through using a WHILE command. Here is what I tried in that second SELECT statement:
DECLARE #Flag INT
DECLARE #FlagPlus INT
SET #Flag = 1
SET #FlagPlus = (#Flag + 1)
WHILE(#Flag < 20)
BEGIN
SELECT
temp#Flag.EmpNo
, temp#Flag.Job AS Job#Flag
, temp#Flag.EjhJobDesc AS JobDesc#Flag
, temp#Flag.EffDate AS EffDate#Flag
, temp#Flag.Dept AS Dept#Flag
FROM #temp1 AS temp#Flag
LEFT JOIN #temp#Flag AS temp#FlagPlus
ON temp#Flag.EmpNo = temp#FlagPlus.EmpNo AND temp#FlagPlus.RowNum = #FlagPlus
WHERE JobHist.RowNum = 1
SET #Flag = (#Flag + 1)
SET #FlagPlus = (#FlagPlus + 1)
END
I knew this probably wouldn't work because SQL will not understand the naming conventions I am trying to call each table and field. Is there a way using a cast or a concat command that I can automate the process so it just increments the numbers where I am asking it to?
First, let me maske clear that this is not directly an answer to the question. However, due to the large code block it is not suitable for a comment either, and I feel that it does add value to the question. So here it goes...
Having a dynamic number of columns is rarely a good solution. I'd opt for a different solution if using XML is an option:
SELECT
e.EmpNo,
(SELECT
h.Job,
h.EffDate,
h.Dept
FROM JobHist h
WHERE e.EmpNo = h.EmpNo
ORDER BY EffDate DESC
FOR XML PATH('job'), ROOT('jobs'), TYPE
) Jobs
FROM (SELECT DISTINCT EmpNo FROM JobHist) e
You can do an UNPIVOT and then a PIVOT of the data. this can be done either statically or dynamically:
Static Version:
select *
from
(
select empid, col + cast(rn as varchar(10)) colname, value
from
(
select Top 20 empid,
job,
convert(varchar(10), effdate, 101) effdate,
dept,
row_number() over(partition by empid order by effdate) rn
from yourtable
order by empid
) x
unpivot
(
value
for col in (Job, Effdate, Dept)
) u
) x1
pivot
(
min(value)
for colname in([Job1], [EffDate1], [Dept1],
[Job2], [EffDate2], [Dept2],
[Job3], [EffDate3], [Dept3])
)p
see SQL Fiddle with Demo
Dynamic Version:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#colsPivotName as NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+ quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('empid')
for xml path('')), 1, 1, '')
select #colsPivot
= STUFF((SELECT ','
+ quotename(c.name + cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by empid order by effdate) rn
from yourtable
) t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('empid')
group by c.name, t.rn
order by t.rn, c.name desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select empid, col + cast(rn as varchar(10)) colname, value
from
(
select Top 20 empid,
job,
convert(varchar(10), effdate, 101) effdate,
dept,
row_number() over(partition by empid order by effdate) rn
from yourtable
order by empid
) x
unpivot
(
value
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
min(value)
for colname in ('+ #colspivot +')
) p'
exec(#query)
see SQL Fiddle with Demo
Here is the solution.No matter how many job changes for the Emp it will pivot all of them
If you want to Pivot only 20 then set #MAXCol =20
edit: forget parentheses around #SQL in last line
SELECT
EmpNo
, Job
, EffDate
, Dept
, ROW_NUMBER() OVER (PARTITION BY EmpNo ORDER BY EffDate) AS RowNum
INTO #temp1
FROM JobHist
ORDER BY EffDate DESC
DECLARE #MAXCol INT = (SELECT MAX(RowNum)FROM #temp1)
,#index INT =1
,#ColNames varchar(4000)=''
,#SQL VARCHAR(MAX)=''
WHILE (#index<=#MAXCol)
BEGIN
SET #ColNames =#ColNames +'MAX(CASE WHEN RowNum = '+LTRIM(STR(#index))+' THEN Job END) as Job'+LTRIM(STR(#index))+','
+'MAX(CASE WHEN RowNum = '+LTRIM(STR(#index))+' THEN EffDate END) as EffDate'+LTRIM(STR(#index))+','
+'MAX(CASE WHEN RowNum = '+LTRIM(STR(#index))+' THEN Dept END) as Dept'+LTRIM(STR(#index))+','
SET #Index=#Index +1
END
SET #ColNames = LEFT(#ColNames,LEN(#ColNames)-1) -- Remove Last Comma
SET #SQL = 'SELECT EmpNo ,'+#ColNames+' FROM #temp1 GROUP BY EmpNo'
EXECUTE (#SQL)
Here is SQL Fiddle Demo working
http://sqlfiddle.com/#!3/99cea/1
Here's one way using a series of dynamically-created MAX/CASE expressions. You could also do this with PIVOT but this is quicker for me:
DECLARE #sql NVARCHAR(MAX) = N'SELECT EmpID';
SELECT TOP (20) #sql += N',
Job' + rn + ' = MAX(CASE WHEN rn = ' + rn + ' THEN Job END),
EffDate' + rn + ' = MAX(CASE WHEN rn = ' + rn + ' THEN EffDate END),
Dept' + rn + ' = MAX(CASE WHEN rn = ' + rn + ' THEN Dept END)'
FROM
(
SELECT rn = RTRIM(ROW_NUMBER() OVER (ORDER BY name))
FROM sys.all_objects
) AS x;
SET #sql += ' FROM (SELECT *, rn = ROW_NUMBER() OVER
(PARTITION BY EmpID ORDER BY EffDate) FROM dbo.your_table) AS y
GROUP BY EmpID;';
EXEC sp_executesql #sql;
You can probably tune this so that it determines the maximum number of job changes for any employee, rather than just defaulting to 20. You might also consider ordering the opposite way - surely an employee's last 20 job changes are more relevant than their first 20, if they've had more than 20.