SQL Pivot Dynamic Columns - sql

I have a query that generates results for learners on a specific course showing whether or not they have completed a module. Learners can take different modules from each other.
SELECT LearnerID,
UnitID,
CASE WHEN (SUM(Total - [Total Achieved])) = 0 THEN 'Yes' ELSE 'No' END AS Completed
FROM dbo.LMS_Achieved_Standards_Report
GROUP BY CourseID, LearnerID, UnitID
The results look like this
LearnerID UnitID Completed
15 15 Yes
15 28 No
28 28 Yes
116 150 Yes
79 12 No
69 34 Yes
69 15 No
I need it to look like this:
LearnerID Unit 15 Unit 28 Unit 150 Unit 12 Unit 34
15 Yes No
28 Yes
116 Yes
79 Yes
69 No Yes
The other factor as already stated is that they can all take different units so I can't create a PIVOT with set column headings.
Any ideas?

try this;
select * into #tbl from (select 'cust1' as key1, 'Red'as Type
union select 'cust1' as key1, 'Blue'as Type
union select 'cust1' as key1, 'Yellow'as Type
union select 'cust2' as key1, 'Red'as Type
union select 'cust2' as key1, 'Blue'as Type
union select 'cust2' as key1, 'Green'as Type) as dd
declare #strsql as nvarchar(max)
declare #columns as nvarchar(max)
set #columns = (select stuff((select distinct ',['+Type+']' from #tbl FOR XML PATH('')),1,1,''))
set #strsql = 'select key1,'+#columns + 'from (select * from #tbl) as p pivot (count(p.Type) For p.Type in ('+#columns+'))as pivottable'
--set #strsql = 'select
EXECUTE sp_executesql #strsql

Related

Sum of Current + Previous X Nth Rows

I am looking to find the sum of the current + the last X Nth Rows. I am able to do this with the following query, however it is not very scalable.
SELECT [id], [amount] + LAG([amount],6) OVER(ORDER BY [id]) + LAG([amount],12) OVER(ORDER BY [id]) + LAG([amount],18) OVER(ORDER BY [id])
If this example, I'm finding the current value of "amount", plus the last 3 "amounts" split 6 apart:
X = 3
N = 6
I will be using these within dynamic queries and would prefer not to build such a complex query each time. There could be many "lags" in some of the queries. Is there another way to write this query that would be more scalable?
SOURCE DATA
ID
Amount
1
107.35
2
105.41
3
104.63
4
106.7
5
108.7
6
110.21
7
108.8
8
108.91
9
108.5
10
106.66
11
105.2
12
106.5
13
108.27
14
109.72
15
111.53
16
112.8
17
109.03
18
115.31
19
115.56
20
116.85
21
116.08
22
117.61
23
118.31
24
119.25
25
118.45
26
118.43
27
120.16
28
122.5
29
125.57
30
125.65
EXPECTED RESULTS
ID
SUM OF LAST 4
1
NULL
2
NULL
3
NULL
4
NULL
5
NULL
6
NULL
7
NULL
8
NULL
9
NULL
10
NULL
11
NULL
12
NULL
13
NULL
14
NULL
15
NULL
16
NULL
17
NULL
18
NULL
19
439.98
20
440.89
21
440.74
22
443.77
23
441.24
24
451.27
25
451.08
26
453.91
27
456.27
28
459.57
29
458.11
30
466.71
At a best guess, it seems like what you want is something like this:
DECLARE #X int = 3,
#N int = 6;
SELECT YT.ID,
YT.Amount,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY G.Grp ORDER BY ID) < #X+1 THEN NULL
ELSE SUM(Amount) OVER (PARTITION BY G.Grp ORDER BY ID
ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
END
FROM dbo.YourTable YT
CROSS APPLY (VALUES(ID % #N))G(Grp)
ORDER BY YT.ID;
You'll note, however, that the 3 is hardcoded in one place, as you cannot use a variable for the ROWS BETWEEN clause. If you need to parametrise this, you'll need to use dynamic SQL:
DECLARE #X int = 3,
#N int = 6;
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = CONCAT(N'SELECT YT.ID,', #CRLF,
N' YT.Amount,', #CRLF,
N' CASE WHEN ROW_NUMBER() OVER (PARTITION BY G.Grp ORDER BY ID) < #X+1 THEN NULL', #CRLF,
N' ELSE SUM(Amount) OVER (PARTITION BY G.Grp ORDER BY ID', #CRLF,
N' ROWS BETWEEN ',#X,N' PRECEDING AND CURRENT ROW)', #CRLF, --I don't like injecting raw values, but if #X is an int, it is "safe"
N' END', #CRLF,
N'FROM dbo.YourTable YT', #CRLF,
N' CROSS APPLY (VALUES(ID % #N))G(Grp)', #CRLF,
N'ORDER BY YT.ID;');
PRINT #SQL; --Your best debugging friend
EXEC sys.sp_executesql #SQL, N'#X int, #N int', #X, #N;
db<>fiddle

How do I change repeated values in a column to rows and then populate the respective row values with the respective values

I have a table this way:
ID ATTR_NAME SUB_ATTR_NAME VALUE
1 ATTR-1 SUB-ATTR-1 23
2 ATTR-1 SUB-ATTR-2 32
3 ATTR-1 SUB-ATTR-3 25
4 ATTR-1 SUB-ATTR-4 28
5 ATTR-2 SUB-ATTR-1 78
6 ATTR-2 SUB-ATTR-2 45
7 ATTR-2 SUB-ATTR-3 48
8 ATTR-2 SUB-ATTR-4 41
9 ATTR-3 SUB-ATTR-1 47
10 ATTR-3 SUB-ATTR-2 12
11 ATTR-3 SUB-ATTR-3 16
12 ATTR-3 SUB-ATTR-4 18
But using SQL, I want a table this way:
SUB-ATTR-1 SUB-ATTR-2 SUB-ATTR-3 SUB-ATTR-4
ATTR-1 23 32 25 28
ATTR-2 78 45 48 41
ATTR-3 47 12 16 18
Please help I am a newbie to SQL
Sample Data
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp(ID INT, ATTR_NAME VARCHAR(20), SUB_ATTR_NAME VARCHAR(20),VALUE INT)
INSERT INTO #Temp
SELECT 1 ,'ATTR-1','SUB-ATTR-1', 23 UNION ALL
SELECT 2 ,'ATTR-1','SUB-ATTR-2', 32 UNION ALL
SELECT 3 ,'ATTR-1','SUB-ATTR-3', 25 UNION ALL
SELECT 4 ,'ATTR-1','SUB-ATTR-4', 28 UNION ALL
SELECT 5 ,'ATTR-2','SUB-ATTR-1', 78 UNION ALL
SELECT 6 ,'ATTR-2','SUB-ATTR-2', 45 UNION ALL
SELECT 7 ,'ATTR-2','SUB-ATTR-3', 48 UNION ALL
SELECT 8 ,'ATTR-2','SUB-ATTR-4', 41 UNION ALL
SELECT 9 ,'ATTR-3','SUB-ATTR-1', 47 UNION ALL
SELECT 10,'ATTR-3','SUB-ATTR-2', 12 UNION ALL
SELECT 11,'ATTR-3','SUB-ATTR-3', 16 UNION ALL
SELECT 12,'ATTR-3','SUB-ATTR-4', 18
SELECT * FROM #Temp
Using Dynamic Sql
DECLARE #Sql nvarchar(max),
#Col nvarchar(max),
#Col2 nvarchar(max)
SELECT #Col=STUFF((SELECT DISTINCT ', '+QUOTENAME(SUB_ATTR_NAME) FROM #Temp
FOR XML PATH ('')),1,1,'')
SELECT #Col2=STUFF((SELECT DISTINCT ', '+'MAX( '+QUOTENAME(SUB_ATTR_NAME)+' ) AS'+QUOTENAME(SUB_ATTR_NAME) FROM #Temp
FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT ATTR_NAME,'+#Col2+' FROM
(
SELECT * FROM #Temp
)AS SRC
PIVOT
(
SUM(VALUE) FOR SUB_ATTR_NAME IN ('+#Col+')
)AS PVT
GROUP BY ATTR_NAME'
PRINT #Sql
EXEC (#Sql)
Result
ATTR_NAME SUB-ATTR-1 SUB-ATTR-2 SUB-ATTR-3 SUB-ATTR-4
----------------------------------------------------------
ATTR-1 23 32 25 28
ATTR-2 78 45 48 41
ATTR-3 47 12 16 18
Demo :http://rextester.com/SURJ9296
You need to group your table by ATTR_NAME, then you can get sum of the SUB_ATTR
select attr_name ,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-1' THEN VALUE
else 0 end) SUB-ATTR-1,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-2' THEN VALUE
else 0 end) SUB-ATTR-2,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-3' THEN VALUE
else 0 end) SUB-ATTR-3,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-4' THEN VALUE
else 0 end) SUB-ATTR-4
END sum(value)
from TABLE
group by attr_name

Create a sparse matrix from observations

I am new to SQL server, I am a R user but R can't be used with my huge dataset (not enough memory).
What I want to do:
I want to create a sparse matrix from a table with only 2 columns, I dont have any value column, it seems easy but I didn't find the right way to do it.
My data:
ID_patient | ID_product
-----------------------
123 A
123 B
111 C
222 A
333 D
333 E
Ouput wanted:
ID_patient | A | B | C | D | E |
----------------------------------------------------
123 1 1
111 1
222 1
333 1 1
I have read that I can use the GROUP BY function or Pivot feature but what I have tried so far failed.
Edit
I don't know all the products, so the right way to do that is by using dynamic pivot ?
You can try something like PIVOT
See demo
Select * from
(select *, copy=Id_product from t)t
pivot
(
count(copy) for ID_product in ([A],[B],[C],[D],[E]))p
If you don't know A, B, C, D, .. before hand then you should go for dynamic pivot
update:
updated dynamic piv demo
declare #cols nvarchar(max)
declare #query nvarchar(max)
select #cols= Stuff((select ','+ quotename( ID_product) from
(select distinct id_product from t) t for xml path ('')),1,1,'')
select #query='Select * from
( select *, copy=Id_product from t ) t
pivot
(count(copy) for ID_product in ( '+#cols+' ))p '
exec(#query)
Try this
IF OBJECT_ID('tempdb..#Temp')IS NOT NULL
DROP TABLE #Temp
DECLARE #temp AS TABLE (ID_patient INT, ID_product varchar(10))
INSERT INTO #temp
SELECT 123,'A' UNION ALL
SELECT 123,'B' UNION ALL
SELECT 111,'C' UNION ALL
SELECT 222,'A' UNION ALL
SELECT 333,'D' UNION ALL
SELECT 333,'E'
SELECT * INTO #Temp
FROM #temp
DECLARE #Sql nvarchar(max),
#Col nvarchar(max),
#Col2 nvarchar(max)
SELECT #Col=STUFF((SELECT DISTINCT ', '+QUOTENAME(ID_product) FROM #Temp FOR XML PATH ('')),1,1,'')
SELECT #Col2=STUFF((SELECT DISTINCT ', '+'ISNULL('+QUOTENAME(ID_product)+','' '') AS '+QUOTENAME(ID_product) FROM #Temp FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT ID_patient,'+#Col2+'
FROM
(
SELECT *, DENSE_RANK()OVER( PARTITION BY ID_patient ORDER By ID_patient) AS [Val] FROM #Temp
)AS Src
PIVOT
(MAX(Val) FOR ID_product IN ('+#Col+')
)AS PVT '
PRINT #Sql
EXEC (#Sql)
Result
ID_patient A B C D E
------------------------------
111 0 0 1 0 0
123 1 1 0 0 0
222 1 0 0 0 0
333 0 0 0 1 1

Create different tables from 1 source table using Dynamic SQL

I have a sourcetable, from which I want to create different tables, using dynamic SQL.
Sourcetable:
ACCP_AcceptantID TransactionID Descrption Period AcceptantNumber
1 12 A 201801 16
1 13 AA 201801 16
2 21 B 201801 22
2 46 BB 201801 22
3 31 C 201801 54
3 38 CC 201801 54
4 94 D 201801 62
4 96 DD 201801 62
To be able to use a WHILE Loop to select each acceptant, I created a Table Acceptants:
SELECT DISTINCT ACCP_AcceptantNr
INTO Acceptants
FROM Sourcetable
ALTER TABLE #TussentabelAcceptanten ADD ID INT IDENTITY
ACCP_AcceptantNr ID
16 1
22 2
54 3
62 4
Desired Result (for each acceptantID, own table with content):
ACCP_AcceptantID TransactionID Descrption Period AcceptantNumber
1 12 A 201801 16
1 13 AA 201801 16
My query attempt:
DECLARE #SQL NVARCHAR(MAX),
#Acceptant NVARCHAR(40),
#Row INT = 1
WHILE #Row <= ( SELECT MAX(ID) FROM Acceptants)
BEGIN
SELECT #Acceptant = ACCP_AcceptantNr FROM Acceptants
SET #SQL = 'SELECT *
INTO MyDatabase.dbo.'+ Period + #Acceptant'
FROM #SourceTable
WHERE ACCP_AcceptantNr = '+ #Acceptant''
EXEC (#SQL)
SET #Row = #Row + 1
SET #SQL = ''
END
Error message: Incorrect syntax near '
Dynamic SQL always gets the better of me. I just don't get where to place the quotes. Suggestions would be great. Thanks.
You are missing the + symbols
SET #SQL = 'SELECT *
INTO MyDatabase.dbo.'+ Period + #Acceptant+'--Added +
FROM #SourceTable
WHERE ACCP_AcceptantNr = '+ #Acceptant -- Removed ''
Since you are Giving Period without an # symbol, I assume it is a column from some table, so add a selet from that table otherwise this will throw an error.
Like this
SELECT #SQL = 'SELECT *
INTO MyDatabase.dbo.'+ Period + #Acceptant+'--Added +
FROM #SourceTable
WHERE ACCP_AcceptantNr = '+ #Acceptant -- Removed ''
FROM YourTableName

PIVOT in SQL Server 2012 - new to SQL [duplicate]

This question already has answers here:
Efficiently convert rows to columns in sql server
(5 answers)
Closed 6 years ago.
I am having some trouble with the next situation: I need to select 4 position for a statement and I need this positions to be pivoted as columns. The database is structured as: StatementId 1, PositionId 2, RepCurrValue 3. For example for StatementId = 55 and for the positions = 58 OR 62 OR 67 OR 82 the result is:
1 2 3
-----------------
55 58 146,8000
55 62 59,9800
55 67 800,0500
55 82 136,7600
and I want it to be
1 58 62 67 82
---------------------------------------
55 146,8000 59,9800 800,0500 136,7600
Thank you very much for your support.
I did this exercise with PIVOT table that could be help you, considering the column headings equal to the contents of the field [2] and NOT providing repeated values ​​in column [2]:
DECLARE #cols AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT ',[' + convert(nvarchar,(t.[2])) + ']' AS ID
FROM TB_1 t
FOR XML PATH(''), TYPE).value('.', 'varchar(max)'),1,1, '')
DECLARE #query AS NVARCHAR(MAX);
SET #query = N'SELECT p.[1],' + #cols + N' from
(
SELECT [1],[2],[3] FROM TB_1
) x
pivot
(
max([3])
for [2] in (' + #cols + N')
) p
'
exec sp_executesql #query;
below the result obtained by adding an additional StatementId (56) and new different positions (90,91) associated with StatementId (56)
1 58 62 67 82 90 91
55 146,8000 59,9800 800,0500 136,7600 NULL NULL
56 NULL NULL NULL NULL 185,74 185,74
a non-dynamic solution can be:
select *
from
(
SELECT [1],[2],[3] FROM TB_1
) x
pivot
(
max([3])
for [2] in ([58] , [62] , [67] , [82] )
) p