SQL Server : query to pivot and show not set values - sql

I have a SQL Server table with 3 columns: UserID, SettingID, SettingValue.
Example
UserID | SettingID | Value
1 | 10 |0
1 | 11 |1
1 | 14 |0
2 | 10 |1
2 | 13 |1
Need to convert into columns per Setting ID
Can be that there is no row for Setting ID -> want to grab that non exisitng and Display as "not set"
Desired result:
UserID | Setting10 | Setting11 | Setting13 | Setting14
1 | Off | On | not set | off
2 | On | not set | on | not set
The list of SettingID is given, there is no need to analyze and automatically find them.
Have no idea how to approach this

SQL Fiddle
MS SQL Server 2008 Schema Setup:
DECLARE #Table TABLE (UserID INT, SettingID INT, Value INT )
INSERT INTO #Table VALUES
(1 , 10 ,0),
(1 , 11 ,1),
(1 , 14 ,0),
(2 , 10 ,1),
(2 , 13 ,1)
Query 1:
SELECT UserID
,COALESCE( Setting10 , 'not set') Setting10
,COALESCE( Setting11 , 'not set') Setting11
,COALESCE( Setting13 , 'not set') Setting13
,COALESCE( Setting14 , 'not set') Setting14
FROM (
SELECT UserID
, 'Setting' + CAST(SettingID AS VARCHAR(10)) AS Settings
,CASE Value WHEN 0 THEN 'off'
WHEN 1 THEN 'on'
END AS Value
FROM #Table
) t
PIVOT (MAX(Value)
FOR Settings
IN (Setting10 , Setting11 , Setting13 , Setting14)
)p
Results:
| UserID | Setting10 | Setting11 | Setting13 | Setting14 |
|--------|-----------|-----------|-----------|-----------|
| 1 | off | on | not set | off |
| 2 | on | not set | on | not set |

As you say the list of settingid is fixed, you can get the desired result with conditional aggregation.
select userid,
max(case when settingid = '10' then
case when value = 0 then 'Off'
when value = 1 then 'On'
else 'not set' end
end) as setting10,
--proceed similarly for other settings
from yourtable
group by userid;

Here is the answer of your question
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #ColumnCaseName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(SettingID)
FROM (SELECT DISTINCT SettingID FROM TableName) AS TableName
SELECT #ColumnCaseName= ISNULL(#ColumnCaseName + ',','')
+ 'case when '+QUOTENAME(SettingID)+' = 0 then ''Off'' when '+QUOTENAME(SettingID)+' = 1 then ''On'' else ''not set'' end '
+ QUOTENAME(SettingID)
FROM (SELECT DISTINCT SettingID FROM TableName) AS TableName
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT UserID, ' + #ColumnCaseName + '
FROM TableName
PIVOT(Max(Value)
FOR SettingID IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This will work for any number of rows you add in your table.
Happy Coding

Related

How do I include an additional non-aggregated column for each of my in my PIVOT values?

I have the following code fragment which gives me the current results below. I'm attempting to add an additional column for each of my pivoted values in order to include the lastview data for each of my siteuserid / tagname combo (see expected results). Since this column isn't an aggregation, I don't believe an additional pivot would help. I've tried multiple ways of adding lastview, but it always results in additional rows rather than the desired output.
create table #taghits (userid int, email varchar(20), tagname varchar(20), hits int, lastview date)
insert into #taghits select 1, 'email1#here.com', 'tag1', 3, '2020-03-24';
insert into #taghits select 2, 'email2#here.com', 'tag1', 1, '2020-03-17';
insert into #taghits select 2, 'email2#here.com', 'tag2', 1, '2020-03-18';
insert into #taghits select 3, 'email3#here.com', 'tag1', 2, '2020-03-25';
insert into #taghits select 3, 'email3#here.com', 'tag2', 5, '2020-03-28';
select * from #taghits;
DECLARE #Columns3 as NVARCHAR(MAX)
SELECT #Columns3 = ISNULL(#Columns3 + ', ','') + QUOTENAME(TagName)
FROM (
select distinct TagName
from #taghits
) AS TagNames
ORDER BY TagNames.TagName
DECLARE #scolumns as NVARCHAR(MAX)
SELECT #scolumns = ISNULL(#Scolumns + ', ','')+ 'ISNULL(' + QUOTENAME(TagName) + ', 0) AS '+ QUOTENAME(TagName)
FROM (select distinct TagName
from #taghits) AS TagNames
ORDER BY TagNames.TagName
DECLARE #SQL as NVARCHAR(MAX)
SET #SQL = '
select userid, email, ' + #scolumns + '
from
(
select userid, email, tagname, hits
from #taghits
) as TagHits
PIVOT (
SUM(hits)
FOR TagName IN (' + #Columns3 + ')
) AS PivotTable
order by userId
'
exec sp_executesql #SQL;
Current Result
| userid | email | tag1 | tag2 |
|--------|-----------------|------|------|
| 1 | email1#here.com | 3 | 0 |
| 2 | email2#here.com | 1 | 1 |
| 3 | email3#here.com | 2 | 5 |
Desired Result
| userid | email | tag1_hits | tag1_lastview | tag2_hits | tag2_lastview |
|--------|-----------------|-----------|---------------|-----------|---------------|
| 1 | email1#here.com | 3 | 2020-03-24 | 0 | null |
| 2 | email2#here.com | 1 | 2020-03-17 | 1 | 2020-03-18 |
| 3 | email3#here.com | 2 | 2020-03-25 | 5 | 2020-03-28 |
try the following:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((select distinct ',
SUM(CASE WHEN tagname=''' + CAST(tagname as varchar(10)) + ''' THEN [hits] ELSE 0 END) AS [' + CAST(tagname as varchar(10)) + '_hits],
MAX(CASE WHEN tagname=''' + CAST(tagname as varchar(10)) + ''' THEN [lastview] ELSE NULL END) AS [' + CAST(tagname as varchar(10)) + '_lastview]'
/*---------------You can add other columns here similar to above--------------*/
FROM #taghits
FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
SET #query = 'SELECT userid, email, ' + #Cols + ' FROM #taghits group by userid, email'
print (#query)
exec(#query)
Please see db<>fiddle here.

How to get SQL query result column name as first row

I have a dynamic SQL query that gets me result sets after execution. However, the UI model that I am rendering results back from SQL server engine doesn't provide a way to render query column names.
Due to the dynamic nature of the query, I can't hard code the column names at design time. So my question is how do I get column names along with the data set returned by the query?
This Query:
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
convert(date, DATEADDED) DATEADDED
,COUNT(1) as NUMBEROFRECORDS
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
Gives me this table (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
But I want this (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
Thanks
It's doable, but not very pretty. A Stored Procedure where you pass the dynamic SQL would be much cleaner
We're essentially doing Dynamic SQL within Dynamic SQL
One caveat: I reserved the field RN
Example (Using my FRED Series Data)
-- This is Your Base/Initial Query, or the only portion you need to supply
Declare #SQL varchar(max) = 'Select Updated as Updated,Count(*) as NumberOfRecords From [dbo].[FRED-Series] Group By Updated'
Select #SQL = '
;with cte0 as ('+#SQL+')
, cte1 as (Select *,RN = Row_Number() over (Order By (Select null)) From cte0 )
, cte2 as (
Select A.RN,C.*
From cte1 A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value(''local-name(.)'',''varchar(100)'')
,Value = attr.value(''.'',''varchar(max)'')
,ColNr = Row_Number() over (Order By (Select Null))
From B.XMLData.nodes(''/row'') as A(r)
Cross Apply A.r.nodes(''./#*'') AS B(attr)
Where attr.value(''local-name(.)'',''varchar(100)'') not in (''RN'')
) C
)
Select Distinct RN=0,Item,Value=Item,ColNr Into #Temp From cte2 Union All Select * from cte2
Declare #SQL varchar(max) = Stuff((Select '','' + QuoteName(Item) From #Temp Where RN=0 Order by ColNr For XML Path('''')),1,1,'''')
Select #SQL = ''Select '' + #SQL + '' From (Select RN,Item,Value From #Temp ) A Pivot (max(Value) For [Item] in ('' + #SQL + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
Updated NumberOfRecords
Updated NumberOfRecords
2017-03-22 597
2017-03-23 40
2017-03-20 228
2017-03-21 1404
Just some Commentary
cte0 is your primary query
cte1 will take the results of your initial query and add a Row Number
cte2 will dynamically unpivot your data
The results of cte2 are dropped into a #temp table for convenience (assuming this is allowed)
Then we perform a dynamic pivot
Union a static query with the column names. You have to cast the results of the second query to varchar or nvarchar so they are the same data type as your column names.
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
''DATEADDED'' AS [DATEADDED]
,''NUMBEROFRECORDS'' AS [NUMBEROFRECORDS]
SELECT
CAST(convert(date, DATEADDED) AS NVARCHAR(MAX)
,CAST(COUNT(1) AS NVARCHAR(MAX))
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
With this said, you should be able to reference the column names via code and not have to add them to the query. This way you could keep the data types of the result set.

Count the number of matches for a prefix in a SQL query

I have a table in an SQL server that looks like the one below and I want to count the number of unique occurrences where specific prefixes are used in the data column, like "21:00:00".
Dataset:
+-------------------------+
| data |
+-------------------------+
| 21:00:00:24:ff:5e:3a:bd |
| 50:01:43:80:18:6b:2a:4c |
| 21:00:00:1b:32:0f:a7:54 |
| 10:00:00:90:fa:a8:da:2a |
+-------------------------+
Desired query output:
+----------+----------+----------+
| 21:00:00 | 50:01:43 | 10:00:00 |
+----------+----------+----------+
| 2 | 1 | 1 |
+----------+----------+----------+
I have been able to get the query to count a single prefix at a time by using this:
SELECT COUNT(DISTINCT wwpn) AS "21:00:00" FROM table WHERE wwpn LIKE '21:00:00%'
However, I want to count multiple prefixes as shown in the desired query output.
I've been waiting for someone to do a dynamic pivot (like Matt said in the comments) but no one has done it yet : (...I tried it myself and this is what I managed...
Query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + LEFT(QUOTENAME(data), 9) + ']'
FROM DataTable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select LEFT(data, 8) as data, COUNT(*) as count
from DataTable
GROUP BY LEFT(data, 8)
) x
pivot
(
max(count)
for data in (' + #cols + N')
) p '
exec sp_executesql #query;
Results:
10:00:00 | 21:00:00 | 50:01:43
---------|----------|---------
1 | 2 | 1
---------|----------|---------
Use this query:
SELECT LEFT([Data], 8) as prefix, count(*) as cnt
FROM tableName
GROUP BY LEFT([Data], 8);
If you know the prefixes in advance then you can do something simple like this;
Create test data;
CREATE TABLE #TestData (FieldName nvarchar(50))
INSERT INTO #TestData
VALUES
('21:00:00:24:ff:5e:3a:bd')
,('50:01:43:80:18:6b:2a:4c')
,('21:00:00:1b:32:0f:a7:54')
,('10:00:00:90:fa:a8:da:2a')
Query
SELECT
SUM(CASE WHEN FieldName LIKE '21:00:00%' THEN 1 ELSE 0 END) [21:00:00]
,SUM(CASE WHEN FieldName LIKE '50:01:43%' THEN 1 ELSE 0 END) [50:01:43]
,SUM(CASE WHEN FieldName LIKE '10:00:00%' THEN 1 ELSE 0 END) [10:00:00]
FROM #TestData
Result
21:00:00 50:01:43 10:00:00
2 1 1

Transforming a column in rows [duplicate]

I have the following dataset
Account Contact
1 324324324
1 674323234
2 833343432
2 433243443
3 787655455
4 754327545
4 455435435
5 543544355
5 432455553
5 432433242
5 432432432
I'd like output as follows:
Account Contact1 Contact2 Contact3 Contact4
1 324324324 674323234
2 833343432 433243443
3 787655455
4 754327545 455435435
5 543544355 432455553 432433242 432432432
The problem is also that I have an unfixed amount of Accounts & unfixed amount of Contacts
If you are going to apply the PIVOT function, you will need to use an aggregate function to get the result but you will also want to use a windowing function like row_number() to generate a unique sequence for each contact in the account.
First, you will query your data similar to:
select account, contact,
'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
See SQL Fiddle with Demo. This will create a new column with the unique sequence:
| ACCOUNT | CONTACT | SEQ |
|---------|-----------|----------|
| 1 | 324324324 | contact1 |
| 1 | 674323234 | contact2 |
If you have a limited number of columns, then you could hard-code your query:
select account,
contact1, contact2, contact3, contact4
from
(
select account, contact,
'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) d
pivot
(
max(contact)
for seq in (contact1, contact2, contact3, contact4)
) piv;
See SQL Fiddle with Demo
If you have an unknown number of columns, then you will have to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(seq)
from
(
select 'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) d
group by seq
order by seq
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT account, ' + #cols + '
from
(
select account, contact,
''contact''
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) x
pivot
(
max(contact)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both will give you a result of:
| ACCOUNT | CONTACT1 | CONTACT2 | CONTACT3 | CONTACT4 |
|---------|-----------|-----------|-----------|-----------|
| 1 | 324324324 | 674323234 | (null) | (null) |
| 2 | 433243443 | 833343432 | (null) | (null) |
| 3 | 787655455 | (null) | (null) | (null) |
| 4 | 455435435 | 754327545 | (null) | (null) |
| 5 | 432432432 | 432433242 | 432455553 | 543544355 |
Just a slightly different way to generate the dynamic PIVOT:
DECLARE #c INT;
SELECT TOP 1 #c = COUNT(*)
FROM dbo.YourTable
GROUP BY Account
ORDER BY COUNT(*) DESC;
DECLARE #dc1 NVARCHAR(MAX) = N'', #dc2 NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX);
SELECT #dc1 += ',Contact' + RTRIM(i), #dc2 += ',[Contact' + RTRIM(i) + ']'
FROM (SELECT TOP (#c) i = number + 1
FROM master.dbo.spt_values WHERE type = N'P' ORDER BY number) AS x;
SET #sql = N'SELECT Account ' + #dc1 +
' FROM (SELECT Account, Contact, rn = ''Contact''
+ RTRIM(ROW_NUMBER() OVER (PARTITION BY Account ORDER BY Contact))
FROM dbo.YourTable) AS src PIVOT (MAX(Contact) FOR rn IN ('
+ STUFF(#dc2, 1, 1, '') + ')) AS p;';
EXEC sp_executesql #sql;
SQLiddle demo

Convert Access TRANSFORM/PIVOT query to SQL Server

TRANSFORM Avg(CASE WHEN [temp].[sumUnits] > 0
THEN [temp].[SumAvgRent] / [temp].[sumUnits]
ELSE 0
END) AS Expr1
SELECT [temp].[Description]
FROM [temp]
GROUP BY [temp].[Description]
PIVOT [temp].[Period];
Need to convert this query for sql server
I have read all other posts but unable to convert this into the same
Here is the equivalent version using the PIVOT table operator:
SELECT *
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN(Period1, Period2, Period3)
) p;
SQL Fiddle Demo
For instance, this will give you:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |
Note that When using the MS SQL Server PIVOT table operator, you have to enter the values for the pivoted column. However, IN MS Access, This was the work that TRANSFORM with PIVOT do, which is getting the values of the pivoted column dynamically. In this case you have to do this dynamically with the PIVOT operator, like so:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME(Period)
FROM temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = ' SELECT Description, ' + #cols + '
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN( ' + #cols + ')
) p ';
Execute(#query);
Updated SQL Fiddle Demo
This should give you the same result:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |