SQL Rows to Columns - sql

I have below table structure in MS SQL
AirQuoteID Name SalesValue
7 M 49.50
7 N 23.10
7 +45 233.20
7 +100 233.20
7 +250 2333.10
I want a query which can return
AirQuoteID M N +45 +100 +250
7 49.50 23.10 233.20 233.20 2333.10
What will be the optimum solution. The Values are dynamic

You need to look at the PIVOT operator.
;With T As
(
SELECT 7 AirQuoteID,'M' Name,49.50 SalesValue UNION ALL
SELECT 7 AirQuoteID,'N', 23.10 UNION ALL
SELECT 7 AirQuoteID,'+45',233.20 UNION ALL
SELECT 7 AirQuoteID,'+100',233.20 UNION ALL
SELECT 7 AirQuoteID,'+250',2333.10
)
SELECT AirQuoteID, [M], [N], [+45], [+100], [+250]
FROM T
PIVOT
(
MAX(SalesValue)
FOR Name IN ([M], [N], [+45], [+100], [+250])
) AS pvt;
However if the values for the columns are not fixed you will need to use dynamic SQL.

As the data is dynamic, pivot wont help
http://www.sqlteam.com/article/dynamic-cross-tabs-pivot-tables link posted by #Martin really helped.
first create the below procedure
CREATE PROCEDURE [dbo].[crosstab]
#select varchar(8000),
#sumfunc varchar(100),
#pivot varchar(100),
#table varchar(100)
AS
DECLARE #sql varchar(8000), #delim varchar(1)
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
print ('SELECT ' + #pivot + ' AS [pivot] INTO ##pivot FROM ' + #table + ' WHERE 1=2')
EXEC ('SELECT ' + #pivot + ' AS [pivot] INTO ##pivot FROM ' + #table + ' WHERE 1=2')
EXEC ('INSERT INTO ##pivot SELECT DISTINCT ' + #pivot + ' FROM ' + #table + ' WHERE '
+ #pivot + ' Is Not Null')
SELECT #sql='', #sumfunc=stuff(#sumfunc, len(#sumfunc), 1, ' END)' )
SELECT #delim=CASE Sign( CharIndex('char', data_type)+CharIndex('date', data_type) )
WHEN 0 THEN '' ELSE '''' END
FROM tempdb.information_schema.columns
WHERE table_name='##pivot' AND column_name='pivot'
SELECT #sql=#sql + '''' + convert(varchar(100), [pivot]) + ''' = ' +
stuff(#sumfunc,charindex( '(', #sumfunc )+1, 0, ' CASE ' + #pivot + ' WHEN '
+ #delim + convert(varchar(100), [pivot]) + #delim + ' THEN ' ) + ', ' FROM ##pivot
DROP TABLE ##pivot
SELECT #sql=left(#sql, len(#sql)-1)
SELECT #select=stuff(#select, charindex(' FROM ', #select)+1, 0, ', ' + #sql + ' ')
EXEC (#select)
SET ANSI_WARNINGS ON
GO
The article didnt mention to retrieve data from same table, so below is how you do it
EXECUTE crosstab 'select titles.AirQuoteID from AirSaleQuoteRateSlab titles
inner join
(select distinct AirQuoteID,[Name] from AirSaleQuoteRateSlab) sales
on (sales.AirQuoteID=titles.AirQuoteID)
group by titles.AirQuoteID', 'AVG(titles.SalesValue)','titles.Name','AirSaleQuoteRateSlab titles'

select AirQuoteID,
sum(case Name when 'M' then SalesValue else 0 end) 'M',
sum(case Name when 'N' then SalesValue else 0 end) 'N',
sum(case Name when '+45' then SalesValue else 0 end) '+45',
sum(case Name when '+100' then SalesValue else 0 end) '+100',
sum(case Name when '+250' then SalesValue else 0 end) '+250'
from Table1
group by AirQuoteID

Related

Grabbing Null and blank Columns to a table

I'm trying to find a way to grab the count of nulls and blanks of every column in a table and put the results into a table. I found this but don't know how to push it to a table? Would also like to add in a Count(distinct fieldname) as well. Any help would be appreciated!
DECLARE #t nvarchar(max)
SET #t = N'SELECT '
SELECT #t = #t + 'sum(case when ' + c.name + ' is null or ' + c.name + ' = '''' then 1 else 0 end) "' + c.name + '",
sum(case when ' + c.name + ' is null then 0 else 1 end) "Non-Null Values for ' + c.name + '",'
FROM sys.columns c
WHERE c.object_id = object_id('TableName');
SET #t = SUBSTRING(#t, 1, LEN(#t) - 1) + ' FROM TableName;'
EXEC sp_executesql #t
**Edit
This provides the results in one row. Is there a way to do it so each field gets it's own row in a table?
FieldName CountofNullsBlanks
FieldA 0
FieldB 100
Here:
DECLARE #t nvarchar(max)
SET #t = N'SELECT '+convert(nvarchar(max),count(*))+' as [Distinct fieldnames],' -- added the distinct
SELECT #t = #t + 'sum(case when ' + c.name + ' is null or ' + c.name + ' = '''' then 1 else 0 end) "' + c.name + '",
sum(case when ' + c.name + ' is null then 0 else 1 end) "Non-Null Values for ' + c.name + '",'
FROM sys.columns c
WHERE c.object_id = object_id('YOURTABLE');
SET #t = SUBSTRING(#t, 1, LEN(#t) - 1) + 'into ##a FROM YOURTABLE;' --added the into ##a
EXEC sp_executesql #t
Notice the "into ##a" towards the end; This saves the result "table" to a global temp, which will be available until all sessions that accessed it finish. Careful with the name or it might cause conflicts in the database.

Using pivot in SQL Server not returning desired output

I have two tables like this:
**tblTagDescription**
and **tblDataLog**
Now I want to show record of any specific group and in one there might be same group for multiple id in tbltagdescription. And id of tbltagdescription is foreign key for tblDataLog as TagDescID.
Here 'Group1' has 10 ID as from 1 to 10. and there might be multiple record for these ID (from 1 to 10) in tbldatalog. I want these ID from 1 to as columns. For this I used pivot:
DECLARE #COlsID NVARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
DECLARE #Group NVARCHAR(50) = 'Group1'
IF OBJECT_ID('tempdb..##MYTABLE') IS NOT NULL
DROP TABLE ##MYTABLE
SELECT
#COlsID = COALESCE(#ColsID + '],[','') + CONVERT(NVARCHAR(5), z.TagDescID)
FROM
(SELECT DISTINCT TOP 50 tblDataLog.TagDescID
FROM tblDataLog
INNER JOIN tblTagDescription ON tblDataLog.TagDescID = tblTagDescription.ID
ORDER BY tblDataLog.TagDescID) z
SET #COlsID='[' + #COlsID + ']'
SET #SQL='select [DATE],SHIFT, ' + #COlsID + ' into ##MYTABLE from ( select [Date], Value,
(CASE
WHEN ((DATEPART(hour,[DATE]))>6 and (DATEPART(hour,[DATE]))<14) THEN ''A''
WHEN ((DATEPART(hour,[DATE]))>=14 and (DATEPART(hour,[DATE]))<22) THEN ''B''
WHEN ((DATEPART(hour,[DATE]))>=22 or (DATEPART(hour,[DATE]))<6) THEN ''C''
END )AS SHIFT
from tblDataLog )d pivot(max(Value) for TagDescID in (' + #COlsID + ')) piv;'
EXEC (#SQL)
Now when I execute this statement, I get an error:
Invalid column name 'TagDescID'
but there is this column in tbldatalog. How to solve this query?
You need TagDescID column in subquery.
DECLARE #COlsID NVARCHAR(MAX) = ''
DECLARE #COlsAlias NVARCHAR(MAX) = ''
DECLARE #SQL NVARCHAR(MAX)
DECLARE #Group NVARCHAR(50) = 'Group1'
SELECT
#COlsID = #ColsID + ',' + z.TagDescID,
#COlsAlias = #COlsAlias + ',' + z.TagDescID + ' AS ' + z.ReportTag
FROM
(SELECT DISTINCT TOP 50 tblDataLog.TagDescID ID, QUOTENAME(CONVERT(NVARCHAR(5), tblDataLog.TagDescID )) TagDescID, QUOTENAME(tblTagDescription.ReportTag) ReportTag
FROM tblDataLog
INNER JOIN tblTagDescription ON tblDataLog.TagDescID = tblTagDescription.ID
ORDER BY tblDataLog.TagDescID
) z
SET #COlsID= STUFF(#COlsID,1,1,'')
SET #COlsAlias= STUFF(#COlsAlias,1,1,'')
SET #SQL='select [DATE],SHIFT, ' + #COlsAlias + ' into ##MYTABLE from ( select [Date], Value, TagDescID,
(CASE
WHEN ((DATEPART(hour,[DATE]))>6 and (DATEPART(hour,[DATE]))<14) THEN ''A''
WHEN ((DATEPART(hour,[DATE]))>=14 and (DATEPART(hour,[DATE]))<22) THEN ''B''
WHEN ((DATEPART(hour,[DATE]))>=22 or (DATEPART(hour,[DATE]))<6) THEN ''C''
END )AS SHIFT
from tblDataLog )d pivot(max(Value) for TagDescID in (' + #COlsID + ')) piv;'
EXEC (#SQL)

Dynamic pivot a 3 column table

I'm trying to use dynamic pivot to have a column containing dates to be the column names.
I want this table:
App Date Count
Excel 2018-05-01 1
Excel 2018-05-02 1
Excel 2018-05-03 2
Word 2018-05-02 3
Word 2018-05-07 5
Word 2018-05-12 2
Paint 2018-05-07 6
to look like this:
2018-05-01 2018-05-02 2018-05-03 2018-05-07 2018-05-12
Excel 1 1 2 0 0
Word 0 3 0 5 2
Paint 0 0 0 6 0
I can't use a normal pivot as I don't know how many or what the dates will actually be. Each app can have a different number of rows. This table isn't just a SELECT * FROM TABLE either, it's made up of subqueries and CTEs so is a little complicated to work with.
Any help is appreciated. Let me know if you need more information.
Using dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(App varchar(50), [Date] varchar(50), [Count] int)
truncate table dbo.test
insert into dbo.test values
('Excel', '2018-05-01', 1),
('Excel', '2018-05-02', 1),
('Excel', '2018-05-03', 2),
('Word ', '2018-05-02', 3),
('Word ', '2018-05-07', 5),
('Word ', '2018-05-12', 2),
('Paint', '2018-05-07', 6)
declare #dates nvarchar(max)='' --holds all the dates that will become column names
declare #dates_aliases nvarchar(max)='' --holds the headers without NULL values
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
select #dates = #dates + ', [' + CONVERT(char(10), [date],126)+ ']' from dbo.test
group by [date]
select #dates_aliases = #dates_aliases + ', isnull(['
+ CONVERT(char(10), [date],126)+ '], 0) as ['
+ CONVERT(char(10), [date],126)+ ']'
from dbo.test group by [date]
set #dates = RIGHT(#dates, len(#dates)-2)
set #dates_aliases = RIGHT(#dates_aliases, len(#dates_aliases)-2)
set #sql = #sql + ' select piv.[App], ' + #dates_aliases
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [App], [Date], [Count] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot '
set #sql = #sql + ' ( '
set #sql = #sql + ' max([Count]) '
set #sql = #sql + ' for [Date] in ('+#dates+') '
set #sql = #sql + ' ) piv '
exec(#sql)
Results:
Try this:
SELECT A.*
INTO #TEMP
FROM
(
SELECT 'Excel' as app,'2018-05-01' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-02' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-03' as 'Date',2 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-02' as 'Date', 3 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-07' as 'Date', 5 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-12' as 'Date', 2 as 'Count'
UNION ALL
SELECT 'Paint' as app,'2018-05-07' as 'Date', 6 as 'Count'
) as A
ANSWER:
DECLARE #SQL VARCHAR(MAX)
DECLARE #Columns VARCHAR(MAX) = ''
DECLARE #Columns2 VARCHAR(MAX) = ''
SELECT #Columns = #Columns + '[' + a.[Column] + '], '
FROM
(SELECT DISTINCT [date] as [Column]
FROM #TEMP) as a
SELECT #Columns2 = #Columns2 + 'ISNULL([' + a.[Column] + '],0) as [' + a.[column] +'], '
FROM
(
SELECT DISTINCT [date] as [Column]
FROM #TEMP
) as a
SET #Columns2 = Left(#Columns2, Len(#Columns2) - 1)
SET #Columns = Left(#Columns, Len(#Columns) - 1)
SET #SQL = 'SELECT app, ' + #Columns2
+ ' FROM #TEMP PIVOT (Avg (Count) FOR Date IN ('
+ #Columns
+ ')) AS pt '
--PRINT #Columns
EXEC( #SQL )

How to build a WHILE Loop using SQL Server 2014

I have loaded the column names from a table into the temporary table called #COLUMN_NAMES. I would like to build a while loop that passes each of the rows from the temp table through my #Data_Quality_Check table that I created below.
There are 54 rows of column_names in my table #Column_names currently. Ideally I would like to have the while loop process each of the variables and put the output into the #Data_Quality_Check table.
if object_id('tempdb..#COLUMN_NAMES') is not null
drop table #COLUMN_NAMES
SELECT COLUMN_NAME AS Column_Names
INTO #COLUMN_NAMES
FROM information_schema.columns
WHERE table_name = 'ssrs_sourcedata'
----- >>>> !!!!!! INSERT CODE TO BUILD WHILE LOOP !!!!
if object_id('tempdb..#DATA_QUALITY_CHECK') is not null
drop table #DATA_QUALITY_CHECK
SELECT
periodenddate,
'#Column_Name' AS Label,
MIN(#Column_Name) AS Min_Value,
MAX(#Column_Name) AS Max_Value,
SUM(#Column_Name) AS Sum_Value,
AVG(#Column_Name) AS Avg_Value,
SUM(Case when #Column_Name IS NULL THEN 1 ELSE 0 END) AS Null_Count,
SUM(Case when len(cast(#Column_Name AS VARCHAR)) = 0 THEN 1 ELSE 0 END) AS Space_Count,
MAX(len(cast(#Column_Name AS VARCHAR))) AS max_length,
MIN(len(cast(#Column_Name AS VARCHAR))) AS min_length
INTO
#DATA_QUALITY_CHECK
FROM
dbcrms.report.ssrs_sourcedata
WHERE
periodenddate = '2017-06-30'
GROUP BY
periodenddate
Dynamic that badboy. You need to create the empty temp table first though
declare #cnt int = 1;
declare #mcnt int;
declare #vsql varchar(2000);
declare #column_name varchar(100);
select #mcnt = count(*)
from #COLUMN_NAMES;
while #cnt <= #mcnt
begin
select #column_name = column_names
from
(
select column_names, row_number() over(order by column_names) rn
from #COLUMN_NAMES
)
where rn = #cnt;
set #vsql = 'insert into #DATA_QUALITY_CHECK (Label, min_value, max_value, sum_value, avg_value, null_count, space_count, max_length, min_length)
SELECT periodenddate, ''' + #Column_Name + ''' AS Label,
MIN(' + #Column_Name+ ') AS Min_Value,
MAX(' + #Column_Name + ') AS Max_Value,
SUM(' + #Column_Name + ') AS Sum_Value,
AVG(' + #Column_Name + ') AS Avg_Value,
SUM(Case when ' + #Column_Name + ' IS NULL THEN 1 ELSE 0 END) AS Null_Count,
SUM(Case when len(cast(' + #Column_Name + ' AS VARCHAR)) = 0 THEN 1 ELSE 0 END) AS Space_Count,
MAX(len(cast(' + #Column_Name + ' AS VARCHAR))) AS max_length,
MIN(len(cast(' + #Column_Name + ' AS VARCHAR))) AS min_length
FROM dbcrms.report.ssrs_sourcedata WHERE periodenddate = '2017-06-30'
GROUP BY periodenddate ';
execute(#vsql);
set #cnt = #cnt + 1;
end;

Query to pivot in SQL

I have data in the following format:
I need to pivot this to get the data as follows.
Please help!!!
Something like this. In this case you need to have a fixed list of names.
SELECT
SUM(CASE WHEN Student='Mike' THEN [English Mark] ELSE 0 END) as [Mike English Mark],
SUM(CASE WHEN Student='Mike' THEN [Maths Mark] ELSE 0 END) as [Mike Maths Mark],
SUM(CASE WHEN Student='Fisher' THEN [English Mark] ELSE 0 END) as [Fisher English Mark],
SUM(CASE WHEN Student='Fisher' THEN [Maths Mark] ELSE 0 END) as [Fisher Maths Mark],
SUM(CASE WHEN Student='John' THEN [English Mark] ELSE 0 END) as [John English Mark],
SUM(CASE WHEN Student='John' THEN [Maths Mark] ELSE 0 END) as [John Maths Mark],
[TestName]
FROM Table1
GROUP BY [Test Name]
The solution I got is a bit tricky but very dynamic.
You should first unpivot your table, and put the data in a temp table, after that I get the columns name for the pivoting and put the result in the #cols variable. At the end I create a dynamic sql string to pivot the the temp table that contains my data, so even if a new student gets added to the table his 2 columns will be generated in the end result.
select test, col + ' '+ Student stu_col , value
INTO
#temp
from Marks
unpivot(value for col in (english, maths)) unpiv
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(stu_col)
from #temp order by 1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT test, ' + #cols + ' from
(
select test, Value, stu_col
from #temp
) x
pivot
(
SUM(Value)
for stu_col in (' + #cols + ')
) p '
exec(#query)
DROP TABLE #temp
You can write a dynamic sql query using Pivot operator as:
DECLARE #columns NVARCHAR(MAX)
,#columnsEnglish_Mark NVARCHAR(MAX)
,#columnsMath_Mark NVARCHAR(MAX)
,#columnsFNL NVARCHAR(MAX)
,#sql NVARCHAR(MAX);
SET #columns = N'';
--Get column names for entire pivoting
SELECT #columns += N', ' + QUOTENAME(SpreadCol)
FROM (select distinct student as SpreadCol
from tblstudent
) AS T;
PRINT #columns;
--Get column names for Pivot1
SET #columnsEnglish_Mark = N'';
SELECT #columnsEnglish_Mark += N', ISNULL(' + QUOTENAME(SpreadCol) + ',0) AS [' + SpreadCol + '_English_Mark]'
FROM (select distinct student as SpreadCol
from tblstudent
) AS T
;
PRINT #columnsEnglish_Mark;
--Get column names for Pivot2
SET #columnsMath_Mark = N'';
SELECT #columnsMath_Mark += N', ISNULL(' + QUOTENAME(SpreadCol) + ',0) AS [' + SpreadCol + '_Math_Mark]'
FROM (select distinct student as SpreadCol
from tblstudent
) AS T
;
PRINT #columnsMath_Mark;
--Get final list of columns:
SET #columnsFNL = N'';
SELECT #columnsFNL += N', [' + SpreadCol + '_English_Mark], [' + SpreadCol + '_Math_Mark] '
FROM (select distinct student as SpreadCol
from tblstudent
) AS T
order by T.SpreadCol asc; -- change ordering of columns here
PRINT #columnsFNL;
SET #sql = N'
select tblEnglish_Mark.Test_Name , ' + STUFF(#columnsFNL, 1, 2, '') + ' from
'
+
'
( SELECT Test_Name, ' + STUFF(#columnsEnglish_Mark, 1, 2, '') + '
FROM
(select student as SpreadCol , English_Mark, Test_Name
from tblstudent ) as D
PIVOT
(
sum(English_Mark) FOR SpreadCol IN ('
+ STUFF(REPLACE(#columns, ', [', ',['), 1, 1, '')
+ ')
) AS Pivot1 ) tblEnglish_Mark
inner join
'
+
'
( SELECT Test_Name, ' + STUFF(#columnsMath_Mark, 1, 2, '') + '
FROM
(select student as SpreadCol , Test_Name,Math_Mark
from tblstudent ) as D
PIVOT
(
MAx(Math_Mark) FOR SpreadCol IN ('
+ STUFF(REPLACE(#columns, ', [', ',['), 1, 1, '')
+ ')
) as Pivot2 ) tblMath_Mark
on tblEnglish_Mark.Test_Name = tblMath_Mark.Test_Name ;
'
;
PRINT #sql;
EXEC sp_executesql #sql;
Hope this helps!!!
Please try:
DECLARE #pivv NVARCHAR(MAX),#Query NVARCHAR(MAX)
SELECT #pivv=COALESCE(#pivv+',','')+ QUOTENAME(Student+'_English_Mark')+','+QUOTENAME(Student+'_Maths_Mark')
FROM YourTable GROUP BY Student
IF ISNULL(#pivv, '')<>''
SET #Query='SELECT* FROM(
select English_Mark Marks, Student+''_English_Mark'' Col, Test_Name From YourTable
union all
select Maths_Mark Marks, Student+''_Maths_Mark'' Col, Test_Name From YourTable
)x pivot (sum(Marks) for Col in ('+#pivv+')) as xx'
IF ISNULL(#Query, '')<>''
EXEC (#Query)
SQL Fiddle Demo