SQL Server T-SQL Rows to Columns - sql-server

I am not sure If I'm doing it correctly but my requirement was to create a view display rows into columns using TSQL. Column number is fixed. Rows will never exceed the number of columns.
Limit in Col2 is 3. No limit in Col 1.
Currently my SQL is using OFFSET AND FETCH but it seems its always returning a total of 1 row.
SELECT Col1, Col2 FROM Table2 WHERE Col1 IN (SELECT Col FROM Table1) ORDER BY Col2 ASC OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY
Table 1
A
B
C
D
Table 2 Col 1
Table 2 Col 2
A
A1
A
A2
A
A3
B
B1
Output Col 1
Output Col 1
Output Col 2
Output Col 3
A
A1
A2
A3
B
B1
NULL
NULL
C
NULL
NULL
NULL
D
NULL
NULL
NULL
Thanks you.

I would, personally, use conditional aggregation for this, which would look a little like this:
SELECT T1.[Table 1] AS [Output Col 1],
MAX(CASE [Table 2 Col 2] WHEN T1.[Table 1] + '1' THEN [Table 2 Col 2] END) AS [Output Col 1],
MAX(CASE [Table 2 Col 2] WHEN T1.[Table 1] + '2' THEN [Table 2 Col 2] END) AS [Output Col 2],
MAX(CASE [Table 2 Col 2] WHEN T1.[Table 1] + '3' THEN [Table 2 Col 2] END) AS [Output Col 3]
FROM dbo.YourFirstTable T1
LEFT JOIN dbo.YourSecondTable T2 ON T1.[Table 1] = T2.[Table 2 Col 1]
GROUP BY T1.[Table 1];

As response to Larnu's answer, [added Table 2 Col 3] that can be used on sorting. Sorry don't know it exists.
Also after using Larnu's answer I came up with the following SQL which is I think good enough for my requirement. Big thanks to Larnu's help.
SELECT
T1.[Table 1] AS [Output Col 1],
(SELECT [Table 2 Col 2] FROM dbo.YourSecondTable WHERE dbo.YourSecondTable.[Table 2 Col 1]=T1.[Table 1] ORDER BY dbo.YourSecondTable.[Table 2 Col 3] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS [Output Col 1],
(SELECT [Table 2 Col 2] FROM dbo.YourSecondTable WHERE dbo.YourSecondTable.[Table 2 Col 1]=T1.[Table 1] ORDER BY dbo.YourSecondTable.[Table 2 Col 3] OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY) AS [Output Col 2],
(SELECT [Table 2 Col 2] FROM dbo.YourSecondTable WHERE dbo.YourSecondTable.[Table 2 Col 1]=T1.[Table 1] ORDER BY dbo.YourSecondTable.[Table 2 Col 3] OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY) AS [Output Col 3]
FROM dbo.YourFirstTable T1
LEFT JOIN dbo.YourSecondTable T2 ON T1.[Table 1] = T2.[Table 2 Col 1]
GROUP BY T1.[Table 1];

since you have an unknown number of columns (I believe that SQL Server limits to columns is 1000) I would build a dynamic "pivot" using dynamic SQL somthing like this:
DROP TABLE IF EXISTS table_1
DROP TABLE IF EXISTS table_2
CREATE TABLE table_1([Col 1] VARCHAR(2) )
CREATE TABLE table_2([Col 1] VARCHAR(2) , [col 2] VARCHAR(2))
TRUNCATE TABLE Table_1
TRUNCATE TABLE Table_2
INSERT INTO TABLE_1([col 1]) VALUES ('A'),('B'),('C'),('D')
INSERT INTO TABLE_2([col 1], [col 2])
VALUES ('A','A1'),('A','A2'),('A','A3'),('B','B1')
DROP TABLE IF EXISTS #Dict
;WITH cte1 AS (
SELECT DISTINCT [col 1] , [col 2]
FROM table_2
), cte2 AS (
SELECT [col 1], [col 2], ROW_NUMBER() OVER (PARTITION BY [Col 1] ORDER BY [Col 1],[col 2]) ColId
FROM cte1
)
SELECT DISTINCT *
INTO #Dict
FROM cte2
WHERE ColId < 999
--select * from #Dict
DROP TABLE IF EXISTS #Pivot
DECLARE #SQLCmdCreate NVARCHAR(max) = 'CREATE TABLE #Pivot ([Table_1 Col 1] VARCHAR(2), '
DECLARE #SQLCmdInsert NVARCHAR(max) = 'INSERT INTO #Pivot ( [Table_1 Col 1], '
DECLARE #SQLCmdSelect NVARCHAR(max) = 'SELECT Table_1.[Col 1], '
DECLARE #SQLCmd NVARCHAR(max)
DECLARE #q NCHAR(1) = ''''
DECLARE #crlf NCHAR(2) = CHAR(13)+ CHAR(10)
SELECT #SQLCmdCreate += STRING_AGG(CONCAT('[Output ', ColId, '] VARCHAR(2)'), ',')
, #SQLCmdInsert += STRING_AGG(CONCAT('[Output ', ColId, ']'), ',')
, #SQLCmdSelect += STRING_AGG(CONCAT(#crlf, 'MIN(CASE WHEN [ColId] = ', [ColId], ' THEN Dict.[Col 2] END) '), ',')
FROM (
SELECT DISTINCT TOP 100 PERCENT ColId FROM #Dict ORDER BY ColId
) dict
SELECT #SQLCmdCreate+= ')'
, #SQLCmdInsert += ')'
, #SQLCmdSelect += #crlf + 'FROM Table_1 '
+ 'left join #Dict dict '
+ ' on Dict.[Col 1] = Table_1.[Col 1]'
+ #crlf + 'GROUP BY table_1.[Col 1]'
SELECT #SQLCmd = #SQLCmdCreate + #crlf + #SQLCmdInsert + #crlf + #SQLCmdSelect + #crlf + 'SELECT * FROM #Pivot ORDER BY [Table_1 Col 1]'
--print #SQLCmd
EXEC (#SQLCmd)

Related

SQL query: Recode value as new variable

I am using a database that consist of answers to customer questionnaires. My problem is that while the customers has been asked several questions each, the number and specific questions vary and each question has its own record. So each questionnaire has three questions.
I have grouped the question types and want one record for each questionnaire with all the answers.
If qnumber [1,2,3],[4,5,6],[7,8,9] are the same and the info is like this
ID,qnumber,avalue
1,1,4
1,4,5
1,7,6
2,2,5
2,5,6
2,8,7
3,3,7
3,6,8
3,9,9
I want to construct the query so a get a result like this:
ID,q1,q2,q3
1,4,5,6
2,5,6,7
3,6,7,8
Is that even possible?
Try this
;With cte(ID,qnumber,avalue )
AS
(
SELECT 1,1,4 UNION ALL
SELECT 1,4,5 UNION ALL
SELECT 1,7,6 UNION ALL
SELECT 2,2,5 UNION ALL
SELECT 2,5,6 UNION ALL
SELECT 2,8,7 UNION ALL
SELECT 3,3,7 UNION ALL
SELECT 3,6,8 UNION ALL
SELECT 3,9,9
)
SELECT Cstring AS CombinedValue
,SUBSTRING(Cstring, 0, CHARINDEX(',', Cstring)) AS ID
,SUBSTRING(Cstring, CHARINDEX(',', Cstring) + 1, CHARINDEX(',', Cstring) - 1) AS Q1
,SUBSTRING(Cstring, CHARINDEX(',', Cstring) + 5, CHARINDEX(',', Cstring) - 1) AS Q2
,SUBSTRING(Cstring, CHARINDEX(',', Cstring) + 9, CHARINDEX(',', Cstring) - 1) AS Q2
FROM (
SELECT DISTINCT STUFF((
SELECT ',' + CAST(qnumber AS VARCHAR) + ',' + CAST(avalue AS VARCHAR)
FROM cte i
WHERE i.ID = o.ID
FOR XML PATH('')
), 1, 1, '') AS Cstring
FROM cte o
) DT
Result
CombinedValue ID Q1 Q2 Q2
------------------------------
1,4,4,5,7,6 1 4 5 6
2,5,5,6,8,7 2 5 6 7
3,7,6,8,9,9 3 7 8 9
CREATE TABLE #Temp
(
ID INT,
qnumber INT,
avalue INT
)
INSERT #Temp
VALUES
(1,1,4),
(1,4,5),
(1,7,6),
(2,2,5),
(2,5,6),
(2,8,7),
(3,3,7),
(3,6,8),
(3,9,9)
SELECT ID, [1] AS q1, [2] AS q2, [3] AS q3
FROM (
SELECT ID, [1], [2], [3]
FROM (
SELECT ID,
CASE WHEN qnumber IN (1, 2, 3) THEN 1
WHEN qnumber IN (4, 5, 6) THEN 2
WHEN qnumber IN (7, 8, 9) THEN 3
END AS qnumber,
avalue
FROM #Temp) AS SourceTable
PIVOT
(AVG(avalue)
FOR qnumber IN ([1], [2], [3])) AS PivotTable
) t
DROP TABLE #Temp

Stored Procedure is returning duplicate records

I have the following tables 7 tables:
1) Titles
ID Title Author
-------------------------------------------------------------------------
1 The Hidden Language of Computer Hardware and Software Charles Petzold
2 Paths, Dangers, Strategies Nick Bostrom
3 The Smart Girl's Guide to Privacy Violet Blue
4 Introduction to Algorithms Thomas H. Cormen
5 Machine Learning in Action Peter Harrington
...
2) Themes
ID Name
------------------------------------------
1 Science Fiction
2 Biography
3 Painting
...
3) Subjects
ID Name
-----------------------------------
1 Science
2 Technology
3 Music
4 Geography
...
4) Grades
ID Name
------------------------------------
1 Grade 1
2 Grade 2
3 Grade 3
4 Grade 4
5 Grade 5
...
5) TitleThemeAssociation
TitleID ThemeID
------------------------------------------
1 1
1 3
4 2
4 3
...
6) TitleSubjectAssociaton
TitleID SubjectID
---------------------------------
1 1
1 3
2 1
2 3
4 1
4 2
...
7) TitleGradeAssociaton
TitleID GradeID
1 1
1 2
1 3
2 1
2 2
...
I have a stored procudure as below:
CREATE PROCEDURE [dbo].[GetTitlesPageWise]
#PageIndex INT = 1
,#PageSize INT = 10
,#searchText NVARCHAR(250) = ''
,#PageCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET FMTONLY OFF;
select ROW_NUMBER() over
(
ORDER BY [id] ASC
) as RowNumber,
T.Id As [Title ID],
T.Title,
H.Theme,
S.Subject,
G.Grade
into #Results
From Titles T
Outer Apply
(
Select Stuff(( Select ', ' + Name
From Themes H
Join TitleThemeAssociaton TH On H.Id = TH.ThemeId
Where TH.TitleId = T.Id
For Xml Path('')), 1, 2, '') As Theme
From Themes
) H
Outer Apply
(
Select Stuff(( Select ', ' + Name
From Subjects S
Join TitleSubjectAssociation TS On S.Id = TS.SubjectId
Where TS.TitleId = T.Id
For Xml Path('')), 1, 2, '') As Subject
From Subjects
) S
Outer Apply
(
Select Stuff(( Select ', ' + Name
From Grades G
Join TitleGradeAssociation TG On G.Id = TG.GradeId
Where TG.TitleId = T.Id
For Xml Path('')), 1, 2, '') As Grade
From Grades
) G
WHERE
t.title Like #searchText + '%'
AND
(
H.Theme Is Null
Or S.Subject Is Null
Or G.Grade Is Null
)
DECLARE #RecordCount INT
SELECT #RecordCount = COUNT(*) FROM #Results
SET #PageCount = CEILING(CAST(#RecordCount AS DECIMAL(10, 2)) / CAST(#PageSize AS DECIMAL(10, 2)))
PRINT #PageCount
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
END
I want distinct title ID and output is like as below but the query is giving the duplicate result. If Theme, Subject and Grade all there values are assigned that record should be excluded form the result. In the above case Title ID 1 should be excluded because all three values are present there. I need help to fix the problem.
RowNumber Title ID Title Theme Subject Grade
1 2 Paths, Dangers, Strategies NULL Science , Music Grade 1, Grade 2
2 3 The Smart Girl's Guide to Privacy NULL NULL NULL
3 4 Introduction to Algorithms Biography, Painting Science , Technology NULL
4 5 Machine Learning in Action NULL NULL NULL
.............
SQL DEMO
SELECT
ROW_NUMBER() OVER (ORDER BY [ID]) rn,
[ID],
[Title],
Subject = STUFF ((
SELECT ',' + S.[Name]
FROM TitleSubjectAssociation TS
JOIN Subjects S
ON TS.SubjectId = S.Id
WHERE TS.[TitleID] = T.[ID]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
),
Theme = STUFF ((
SELECT ',' + TH.[Name]
FROM TitleThemeAssociation TA
JOIN Themes TH
ON TA.[ThemeID] = TH.[ID]
WHERE TA.[TitleID] = T.[ID]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
),
Grade = STUFF ((
SELECT ',' + G.[Name]
FROM TitleGradeAssociation TG
JOIN Grades G
ON TG.[GradeID] = G.[ID]
WHERE TG.[TitleID] = T.[ID]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
)
FROM Titles T;
If you need a filter you can try checking how many , are in the results. two , mean three elements.
WHERE len(Subject) - len(replace(Subject,',','')) != 2
AND len(Theme) - len(replace(Theme,',','')) != 2
AND len(Grade) - len(replace(Grade,',','')) != 2
OUTPUT

Long to wide - SQL [duplicate]

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 6 years ago.
I have a table that is long (for example)
Date Person Number
2015-01-03 A 4
2015-01-04 A 2
2015-01-05 A 3
2015-01-03 B 5
2015-01-04 B 6
2015-01-05 B 7
2015-01-03 C 1
2015-01-04 C 3
2015-01-05 C 4
2015-01-05 D 4
2015-01-04 E 1
2015-01-05 E 3
And I need it to look like (wide):
Date A B C D E
2015-01-03 4 5 1 0 0
2015-01-04 2 6 3 0 1
2015-01-05 3 7 4 4 3
Any help would be much appreciated. I am using Transact. Thanks.
Using PIVOT:
select date, isNull([A], 0) as A,
isNull([B], 0) as B,
isNull([C], 0) as C,
isNull([D], 0) as D,
isNull([E], 0) as E
from
( select date, person, number
from tbl ) AS SourceTable
PIVOT
( max(number) for
Person in ( [A], [B], [C], [D], [E]) ) AS PivotTable;
Dynamic version is:
DECLARE #columns NVARCHAR(MAX), #whereColumns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SET #whereColumns = N'';
SELECT #columns += N', isNull(p.' + QUOTENAME(Person) +', 0) as ' + Person,
#whereColumns += N', ' +QUOTENAME (Person)
FROM (select Person from tbl group by Person) AS x;
SET #sql = N'
select date, ' + STUFF(#columns, 1, 2, '') +
N'
from
( select date, person, number
from tbl ) AS SourceTable
PIVOT
( max(number) for
Person in ( '
+ stuff(#whereColumns, 1, 2, '')
+ ' )
) AS P '
print #sql;
exec (#sql);
You can use conditional aggregation:
SELECT
Date,
[A] = MAX(CASE WHEN Person = 'A' THEN Number ELSE 0 END),
[B] = MAX(CASE WHEN Person = 'B' THEN Number ELSE 0 END),
[C] = MAX(CASE WHEN Person = 'C' THEN Number ELSE 0 END),
[D] = MAX(CASE WHEN Person = 'D' THEN Number ELSE 0 END),
[E] = MAX(CASE WHEN Person = 'E' THEN Number ELSE 0 END)
FROM #tbl
GROUP BY Date
If you have unknown number of Persons, then you have to do it dynamically:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
Date' + CHAR(10) +
(
SELECT DISTINCT
' , MAX(CASE WHEN Person = ''' + Person + ''' THEN Number ELSE 0 END) AS ' + QUOTENAME(Person) + CHAR(10)
FROM #tbl
FOR XML PATH('')
) +
'FROM #tbl
GROUP BY Date;';
PRINT (#sql);
EXEC (#sql);
ONLINE DEMO

SQL combine two tables into one table based on the instruction of a third table

Simplified problem with minimal information is as follows:
I have 2 source tables:
Table A:
Col: 1 2 3 4 5
Data: 18 15 16 17 10
Table B:
Col: 1 2 3 4 5
Data: 81 51 61 71 99
And a third table that contains "instructions":
Table 3:
ID Source
1 A
2 A
3 B
4 A
5 B
Based on what Table 3 tells me, I need to pick values from table A and B, to form a result table:
Col: 1 2 3 4 5
Data: 18 15 61 17 99
Try this -
Schema
create table TableA (col int, data1 int);
create table TableB (col int, data1 int);
create table TableC (col int, Source1 varchar(100));
insert into TableA values (1, 18), (2, 15), (3, 16);
insert into TableB values (1, 81), (2, 51), (3, 61);
insert into TableC values (1, 'A'), (2, 'A'), (3, 'B');
Query
SELECT o.col
,CASE
WHEN o.Source1 = 'A'
THEN a.data1
ELSE b.data1
END data
FROM TableC o
LEFT JOIN TableA a ON o.col = a.col
AND o.Source1 = 'A'
LEFT JOIN TableB b ON o.col = b.col
AND o.Source1 = 'B'
Result
Col Data
---------
1 18
2 15
3 61
----------Updated---------
Okay, as per the discussion, you need to use dynamic query. You need to first construct columns from tablec and then use it dynamic query as shown below.
DECLARE #cols AS NVARCHAR(MAX)
,#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((
SELECT CASE
WHEN c.Source1 = 'A'
THEN ',' + 'a.[' + cast(c.Col AS VARCHAR(4)) + ']'
ELSE ',' + 'b.[' + cast(c.Col AS VARCHAR(4)) + ']'
END
FROM TableC c
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT ' + #cols + ' FROM TableA a
FULL OUTER JOIN TableB b ON 1 = 1'
EXECUTE sp_executesql #query;
Result
1 2 3 4
--------------
18 15 61 17
There is probably a better way but this works.
SELECT
CASE WHEN (SELECT Source FROM Table3 WHERE ID = 1) = 'A' THEN a.[1] ELSE b.[1] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 2) = 'A' THEN a.[2] ELSE b.[2] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 3) = 'A' THEN a.[3] ELSE b.[3] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 4) = 'A' THEN a.[4] ELSE b.[4] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 5) = 'A' THEN a.[5] ELSE b.[5] END
FROM dbo.TableA a
JOIN TableB b ON 1=1

MSSQL Group concatenation adding columns as agroup by

Table Structure
Item, Group, Min Qty, Price
A, 1, 10, 1.00
A, 2, 10, 0.75
B, 1, 20, 0.90
C, 3, 5, 5.00
Sql query I am running currently which works for all the groups into 1 column, I'm trying to work out how I add a column for each group and only put in the values from that group only.
SELECT [Item],
STUFF((
SELECT ', ' + CAST([Min Qty] AS VARCHAR(MAX)) + ':' + CAST([Price] AS VARCHAR(MAX) + ';')
FROM [Table] WHERE ([Item No_] = Results.[Item] and [Minimum Qty] > '1')
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)')
,1,2,'') as Values
FROM [Table] Results
GROUP BY [Item]
Current output
Item, Values
A, 10:0.75; 10:1.00;
B, 20:0.90;
C, 5:5.00;
Required output
Item, Group 1, Group 2, Group 3
A, 10:1.00; 10:0.75;
B, 20:0.90;
C, 5:5.00;
Thank you for input
Edit from below
We don't know all the column names as they will be added by other users to the system, so needs to auto add the columns also should be grouping all entries for that group/item into 1 column as I'm trying to produce an output file.
Table information
Item, Group, Min Qty, Price
A, 1, 10, 1.00
A, 2, 10, 0.75
B, 1, 20, 0.90
C, 3, 5, 5.00
A, 1, 20, 0.50
Item, Group 1, Group 2, Group 3
A, 10:1.00;20:0.50; 10:0.75;
B, 20:0.90;
C, 5:5.00;
declare #t table (Item Varchar(2),Groups Varchar(2),MInqty varchar(10),Price Money)
insert into #t (Item,Groups,MInqty,Price)values
('A,','1,',10,1.00),
('A,','2,',10,0.75),
('B,','1,',20,0.90),
('C,','3,',5,5.00)
select Item,[1,] AS [Groups 1],[2,] AS [Groups 2],[3,] AS [Groups 3]
from (
select Item,Groups,MInqty +':'+ CAST(price AS VARCHAR) + ';' As GRP from #t)P
PIVOT (MIN(GRP) FOR GROUPS IN ([1,],[2,],[3,]))PVT
Remodified answer check
Select Item,[1] AS [Groups 1],[2] AS [Groups 2],[3] AS [Groups 3] from (
Select P.Item,MIN(R)G,P.Value from (
SELECT [Item],SUBSTRING(Groups,0,CHARINDEX(',',Groups))R,
STUFF((
SELECT ', ' + CAST([MInqty] AS VARCHAR(MAX)) + ':' + CAST([Price] AS VARCHAR(MAX) )+';'As Grp
FROM #t WHERE ([Item] = Results.[Item] and [MInqty] > '1')
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)')
,1,2,'') as Value
FROM #t Results
GROUP BY Groups,Item)P
GROUP BY P.Item,p.Value)PP
PIVOT (MIN(Value)FOR G IN ([1],[2],[3]) )PVT

Resources