I have a SQL table with multiple rows that I want into one row with multiple columns - sql

I have a SQL table that has mulitiple rows of data for a user. I want to query that data and return one row for each user. So I want to take the multiple rows and combine them into one row with multiple columns. Is this possible?
Here is what I currently have
UserID Value
8111 396285
8111 812045789854
8111 Secretary
Here is what I am after
UserID Column1 Column2 Column3
8111 396285 812045789854 Secretary

You can use the PIVOT function to get the result. I used the row_number() function to generate the values that will be converted to columns.
If you know how many values you will have ahead of time, then you can hard-code the query:
select userid, Col1, Col2, Col3
from
(
select userid, value,
'Col'+cast(row_number() over(partition by userid
order by (select 1)) as varchar(10)) rn
from yt
) d
pivot
(
max(value)
for rn in (Col1, Col2, Col3)
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of values, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Col'+cast(row_number() over(partition by userid
order by (select 1)) as varchar(10)))
from yt
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT userid,' + #cols + '
from
(
select userid, value,
''Col''+cast(row_number() over(partition by userid
order by (select 1)) as varchar(10)) rn
from yt
) x
pivot
(
max(value)
for rn in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. Both give the result:
| USERID | COL1 | COL2 | COL3 |
----------------------------------------------
| 8111 | 396285 | 812045789854 | Secretary |

If you cannot or don't want to use PIVOT/UNPIVOT another option would be to join the columns one by one to the users:
DECLARE #Users TABLE(
UserID int NOT NULL
)
INSERT INTO #Users (UserID) VALUES (1)
INSERT INTO #Users (UserID) VALUES (2)
INSERT INTO #Users (UserID) VALUES (3)
DECLARE #AnyTable TABLE(
UserID int NOT NULL,
FieldNo int NOT NULL,
Value varchar(50) NULL
)
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (1, 1, 'abc')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (1, 2, 'def')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (1, 3, 'ghi')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (2, 1, '123')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (2, 3, '789')
SELECT u.UserID,
col1.Value as Column1,
col2.Value as Column2,
col3.Value as Column3
FROM #Users u
LEFT JOIN #AnyTable col1
ON col1.UserID = u.UserID
AND col1.FieldNo = 1
LEFT JOIN #AnyTable col2
ON col2.UserID = u.UserID
AND col2.FieldNo = 2
LEFT JOIN #AnyTable col3
ON col3.UserID = u.UserID
AND col3.FieldNo = 3
The result would be:
UserID Column1 Column2 Column3
1 abc def ghi
2 123 NULL 789
3 NULL NULL NULL

Related

Selecting data against numeric values saved as comma separated string

I have two sql tables and looking for a sql query to select data against each numeric value in Table1.ValueID column from Table2.ValueDescription column and save result in Table3
Table1:
ID ValueID
1 1,12,14
2 3,5,15
3 2,6,13,16
Table2:
ValueID ValueDescription
1 Motor
2 Low
3 Failed
4 New Install
5 New Item
6 Max Value
7 AC Current
8 DC Current
9 Not Reached
10 NA
11 Cutoff
12 Manual
13 Automatic
14 Device Not Found
15 Halt
16 Renew
Expected Result:
Table3:
ID ValueID Result
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Using SQL Server Management Studio
Here is the query I tried
SELECT Table1.ValueID,
Stuff((SELECT ',' + CAST(Table2.Description AS VARCHAR(100))
FROM Table2
WHERE Table1.ValueID LIKE Table2.ValueID
FOR Xml Path('')),1,1,'')
FROM Table1
what I am missing here?
If in fact you really using SQL Server 2017, you can use both the STRING_SPLIT and the STRING_AGG functions. They make for a very easy syntax.
IF OBJECT_ID('tempdb..#Table1', 'U') IS NOT NULL
DROP TABLE #Table1;
CREATE TABLE #Table1 (
ID INT NOT NULL PRIMARY KEY,
ValueID VARCHAR(50) NOT NULL
);
INSERT #Table1 (ID, ValueID) VALUES
(1, '1,12,14'),
(2, '3,5,15'),
(3, '2,6,13,16');
IF OBJECT_ID('tempdb..#Table2', 'U') IS NOT NULL
DROP TABLE #Table2;
CREATE TABLE #Table2 (
ValueID INT NOT NULL PRIMARY KEY,
ValueDescription VARCHAR(50) NOT NULL
);
INSERT #Table2(ValueID, ValueDescription) VALUES
(1, 'Motor'),
(2, 'Low'),
(3, 'Failed'),
(4, 'New Install'),
(5, 'New Item'),
(6, 'Max Value'),
(7, 'AC Current'),
(8, 'DC Current'),
(9, 'Not Reached'),
(10, 'NA'),
(11, 'Cutoff'),
(12, 'Manual'),
(13, 'Automatic'),
(14, 'Device Not Found'),
(15, 'Halt'),
(16, 'Renew');
--SELECT * FROM #Table1 t1;
--SELECT * FROM #Table2 t2;
--========================================================
SELECT
t1.ID,
t1.ValueID,
csv.Result
FROM
#Table1 t1
CROSS APPLY (
SELECT
Result = STRING_AGG(t2.ValueDescription, ',')
FROM
STRING_SPLIT(t1.ValueID, ',') ss
JOIN #Table2 t2
ON CONVERT(INT, ss.value) = t2.ValueID
) csv;
The results...
ID ValueID Result
----------- -------------- -----------------------------------
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Edit:
-
-============================================================================
-- This is an idea that I've been kicking around for a little while now.
-- It's based on the SUSPICION that, when left to it's own devices. STRING_SPLIT
-- will always retun rows in the original order and attaching a row_number()
-- to the output, right out of the gate, will effectively serve as an "ItemNumber.
--============================================================================
SELECT
t1.ID,
t1.ValueID,
csv.Result
FROM
#Table1 t1
CROSS APPLY (
SELECT
Result = STRING_AGG(t2.ValueDescription, ',') WITHIN GROUP (ORDER BY rs.rn DESC) -- sort in the descending order for no real eason...
FROM (
SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
ValueID = CONVERT(INT, ss.value)
FROM
STRING_SPLIT(t1.ValueID, ',') ss
) rs
JOIN #Table2 t2
ON rs.ValueID = t2.ValueID
) csv;
ID ValueID Result
----------- ------------- --------------------------------
1 1,12,14 Device Not Found,Manual,Motor
2 3,5,15 Halt,New Item,Failed
3 2,6,13,16 Renew,Automatic,Max Value,Low
This will keep the proper sequence
Example
Select A.*
,B.*
From Table1 A
Cross Apply (
Select Result = Stuff((Select ',' +B2.ValueDescription
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(A.ValueID,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
Join Table2 B2 on B1.RetVal=B2.ValueID
Order by RetSeq
For XML Path ('')),1,1,'')
) B
Returns
ID ValueID Result
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Oops -- Just saw you are 2017
It's not that much prettier but the new built-in functions in SQL Server 2017 do make this a little easier to follow, and can still be made to respect the order of the original list (well, I can't even tell if you intended to order by location in the list or by numerical order, since those are the same), then provided it is all integers and there are no duplicates:
;WITH explode(ID, ValueID, value, i) AS
(
SELECT t1.ID,
t1.ValueID,
TRY_CONVERT(int,f.value),
CHARINDEX(',' + f.value + ',', ',' + t1.ValueID + ',')
FROM dbo.Table1 t1
CROSS APPLY STRING_SPLIT(t1.ValueID, ',') AS f
)
SELECT x.ID, x.ValueID,
-- guarantee respect original order:
Result = STRING_AGG(t2.ValueDescription,',') WITHIN GROUP (ORDER BY x.i)
FROM explode AS x
INNER JOIN dbo.Table2 AS t2
ON x.value = t2.ValueID
GROUP BY x.ID, x.ValueID
ORDER BY x.ID;
If order doesn't matter, and you are sure there can be no duplicates or non-integers in the ValueID list in Table1, it is much simpler:
;WITH explode(ID, ValueID, value) AS
(
SELECT t1.ID, t1.ValueID, f.value
FROM dbo.Table1 t1
CROSS APPLY STRING_SPLIT(t1.ValueID, ',') AS f
)
SELECT x.ID, x.ValueID, STRING_AGG(t2.ValueDescription,',')
FROM explode AS x
INNER JOIN dbo.Table2 AS t2
ON x.value = t2.ValueID
GROUP BY x.ID, x.ValueID
ORDER BY x.ID;
You can do like
SELECT *,
STUFF(
(
SELECT ',' + ValueDescription
FROM T2
WHERE ',' + T1.ValueID + ',' LIKE '%,' + CAST(T2.ValueID AS VARCHAR) + ',%'
FOR XML PATH('')
),
1, 1, ''
) ValueDescription
FROM T1;
Returns:
+----+-----------+-------------------------------+
| ID | ValueID | ValueDescription |
+----+-----------+-------------------------------+
| 1 | 1,12,14 | Motor,Manual,Device Not Found |
| 2 | 3,5,15 | Failed,New Item,Halt |
| 3 | 2,6,13,16 | Low,Max Value,Automatic,Renew |
+----+-----------+-------------------------------+
Demo

How to convert many rows into Columns in SQL Server?

How would you convert a field that is stored as multiple rows into columns?
I listed the code below as well. Below is an example of what is needed but it can really go up to 20 columns. Thanks!
COL1 COL2 COL3
----------------
TEST 30 NY
TEST 30 CA
TEST2 10 TN
TEST2 10 TX
I would like the output to be :
COL1 COL2 COL3 COL4
------------------------
TEST 30 NY CA
TEST2 10 TN TX
select * from (
select
ID,
Name,
STORE,
Group,
Type,
Date,
State,
row_number() over(partition by ID, state order by Date desc) as rn
from
#test
) t
where t.rn = 1
declare #Table AS TABLE
(
Col1 VARCHAR(100) ,
Col2 INT ,
Col3 VARCHAR(100)
)
INSERT #Table
( Col1, Col2, Col3 )
VALUES
( 'TEST', 30 ,'NY' ),
( 'TEST', 30 ,'CA' ),
( 'TEST2', 10 ,'TN' ),
( 'TEST2', 10 ,'TX' )
SELECT
xQ.Col1,
xQ.Col2,
MAX(CASE WHEN xQ.RowNumber = 1 THEN xQ.Col3 ELSE NULL END) AS Col3,
MAX(CASE WHEN xQ.RowNumber = 2 THEN xQ.Col3 ELSE NULL END) AS Col4
FROM
(
SELECT * , RANK() OVER(PARTITION BY T.Col1,T.Col2 ORDER BY T.Col1,T.Col2,T.Col3) AS RowNumber
FROM #Table AS T
)AS xQ
GROUP BY
xQ.Col1,
xQ.Col2
There are multiple options to convert data from rows into columns. In SQL, you can use PIVOT to transform data from rows into columns.
CREATE table #tablename
(Id int, Value varchar(10), ColumnName varchar(15);
INSERT INTO #tablename
(ID, Value, ColumnName)
VALUES
(1, ‘Lucy’, 'FirstName'),
(2, ‘James’, ‘LastName’),
(3, ‘ABCDXX’, ‘Adress’),
(4, ’New York’, ‘City’),
(5, '8572685', ‘PhoneNo’);
select FirstName, LastName, Address, City, PhoneNo
from
(
select Value, ColumnName
from #tablename
) d
pivot
(
max(Value)
for ColumnName in (FirstName, LastName, Address, City, PhoneNo)
) piv;
Refer the below link for other options of transforming data from rows to columns:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

SQL inserted value of a table as column of another table

Table1 Table2
Id Name Id Table1Id Value
1 Some 1 1 value1
2 Some1 2 2 value2
3 Some2 3 3 value3
4 Some3
.
.
I want to result this:
Some Some1 Some2 Some3
value1 value2 value3 NULL
When I entered value into the Table1 I want to look like the table2's column, how can I do this?
I guess I'm looking for pivot query.
Thanks for posting the additional information. Based on what you said, you're looking for a PIVOT statement that will turn each name in Table1 into a column header, containing the corresponding value from Table2.
Have a look at the following SQL, which produces the output your sample data indicates.
CREATE TABLE #Table1 (ID INT, Name varchar(5))
CREATE TABLE #Table2 (ID INT, Table1ID INT, Value varchar(6))
INSERT INTO #Table1 (ID, Name) SELECT 1, 'Some'
INSERT INTO #Table1 (ID, Name) SELECT 2, 'Some1'
INSERT INTO #Table1 (ID, Name) SELECT 3, 'Some2'
INSERT INTO #Table1 (ID, Name) SELECT 4, 'Some3'
INSERT INTO #Table2 (ID, Table1ID, Value) SELECT 1, 1, 'Value1'
INSERT INTO #Table2 (ID, Table1ID, Value) SELECT 2, 2, 'Value2'
INSERT INTO #Table2 (ID, Table1ID, Value) SELECT 3, 3, 'Value3'
-- List of values to be columns
declare #cols nvarchar(max)
select #cols = coalesce(#cols+N',', N'') + quotename(Name) from #Table1 order by ID
PRINT #Cols
declare #query varchar(MAX)
SET #query = '
SELECT *
FROM
(
SELECT T1.Name, T2.Value
FROM
#Table1 T1
INNER JOIN
#Table2 T2 ON
T1.ID = T2.Table1ID
) s
PIVOT
(
MAX(Value) FOR Name IN ('+#Cols+')
) p'
PRINT #query
EXEC (#query)
DROP TABLE #Table1
DROP TABLE #Table2

SQL Cross Tab Function

Hi Dear All My friends,
I want to ask one thing about sql cross tab function.Currently, I am using sql 2008 express version and my table structure is like below.
UserID Str_Value
1 A
1 B
1 C
2 A
2 B
3 D
3 E
I want to get like this .
UserID Str_Value
1 A,B,C
2 A,B
3 D,E
I don't want to use cursor.Is there any function for that one?
Please give me the right way.I really appreciate it.
Thanks.
Best Regards,
Chong
Hope this helps. You can comment ORDER BY T1.Str_Value if not needed and set the nvarchar(500) size as required
SELECT DISTINCT T1.UserId,
Stuff(
(SELECT N', ' + T2.Str_Value
FROM t T2
WHERE T2.userId = T1.userid
ORDER BY T2.Str_Value
FOR XML PATH(''),TYPE).value('text()[1]','nvarchar(500)'),1,2,N'')
AS Str_Value
FROM t T1
SELECT UserId, LEFT(Str_Value, LEN(Str_Value) - 1) AS Str_Value
FROM YourTable AS extern
CROSS APPLY
(
SELECT Str_Value + ','
FROM YourTable AS intern
WHERE extern.UserId = intern.UserId
FOR XML PATH('')
) pre_trimmed (Str_Value)
GROUP BY UserId, Str_Value
Try this:
SELECT DISTINCT
t1.UserID,
Values = SUBSTRING((SELECT ( ', ' + t2.Str_Value)
FROM dbo.Users t2
ORDER BY
t2.Str_Value
FOR XML PATH( '' )
), 3, 4000 )FROM dbo.Users t1
GROUP BY t1.UserID
create table #temp
(
userid int,
str_value varchar(1)
)
insert into #temp values (1, 'A')
insert into #temp values (1, 'B')
insert into #temp values (1, 'C')
insert into #temp values (2, 'A')
insert into #temp values (2, 'B')
insert into #temp values (3, 'D')
insert into #temp values (3, 'E')
select userid, left(x.str_value, len(x.str_value) -1) as str_value
from #temp t
cross apply
(
select str_value + ','
FROM #temp t1
where t.userid = t1.userid
for xml path('')
) x (str_value)
group by userid, x.str_value
drop table #temp

Stored Procedure

I have a dataTable which has the column Device Type,DeviceName and label.
There can be multiple Devices for each deviceType.
So i want to write the stored procedure which will populate label column as 01,02,03,04....
for each DeviceName and DeviceType Combination.
For Example:-
DeviceName DeviceType **Label**
Probe1 1 01
Probe2 1 02
Probe3 1 03
Tank1 2 01
Tank2 2 02
Pump1 3 01
Pump2 3 02
Have a look at using ROW_NUMBER
SELECT *,
ROW_NUMBER() OVER(PARTITION BY DeviceType ORDER BY DeviceName) Label
FROM Table
EDIT
Here is a little UPDATE example then:
DECLARE #Table TABLE(
DeviceName VARCHAR(20),
DeviceType INT,
Label VARCHAR(20)
)
INSERT INTO #Table SELECT 'Probe1',1,''
INSERT INTO #Table SELECT 'Probe2',1,''
INSERT INTO #Table SELECT 'Probe3',1,''
INSERT INTO #Table SELECT 'Tank1',2,''
INSERT INTO #Table SELECT 'Tank2',2,''
INSERT INTO #Table SELECT 'Pump1',3,''
INSERT INTO #Table SELECT 'Pump2',3,''
;WITH Vals AS (
SELECT DeviceName,
DeviceType,
ROW_NUMBER() OVER(PARTITION BY DeviceType ORDER BY DeviceName) Label
FROM #Table
)
UPDATE #Table
SET Label = Vals.Label
FROM #Table t INNER JOIN
Vals ON t.DeviceName = Vals.DeviceName
AND t.DeviceType = Vals.DeviceType
SELECT *
FROM #Table
With the assumption that DeviceName is unique, you can use ROW_NUMBER like this:
DECLARE #Data TABLE (DeviceName VARCHAR(50), DeviceType INTEGER, Label CHAR(2))
INSERT #Data VALUES ('Probe1', 1, '')
INSERT #Data VALUES ('Probe2', 1, '')
INSERT #Data VALUES ('Probe3', 1, '')
INSERT #Data VALUES ('Tank1', 2, '')
INSERT #Data VALUES ('Tank2', 2, '')
INSERT #Data VALUES ('Pump1', 3, '')
INSERT #Data VALUES ('Pump2', 3, '')
UPDATE d
SET d.Label = RIGHT('0' + CAST(x.RowNo AS VARCHAR(2)), 2)
FROM #Data d
JOIN (
SELECT DeviceName, ROW_NUMBER() OVER (PARTITION BY DeviceType ORDER BY DeviceName) AS RowNo
FROM #Data
) x ON d.DeviceName = x.DeviceName
SELECT * FROM #Data