Merge multiple results to one Column - sql-server

I would like to create a select statement to merge multiple results into one rot
I was able to realized what I want via PHP but i think it should be possible to work directly with Sql.
Notes
| ID | Name |
|----|------|
| 2 | Test |
| 3 | Test |
EditorAssignment
| UserID | NoteID |
|--------|--------|
| 1 | 2 |
| 2 | 2 |
UserList
| ID | username |
|----|----------|
| 1 | testuser |
| 2 | bUser |
| 3 | cUser |
I would like to have the following Result:
| NoteID | User |
|--------|-----------------|
| 2 | testuser, bUser |
| 3 | cUser |
How can i create a select like that in SQL2012?

Before SQL Server 2017 (which has STRING_AGG aggregate function for this), you need to use the famous FOR XML PATH correlated subquery:
SetUp:
DECLARE #Notes TABLE (ID INT, Name VARCHAR(10))
INSERT INTO #Notes (ID, Name)
VALUES (2, 'Test'), (3, 'Test')
DECLARE #EditorAssignment TABLE (UserID INT, NoteID INT)
INSERT INTO #EditorAssignment (UserID, NoteID)
VALUES (1, 2), (2, 2),
(3, 3) -- Added missing row here
DECLARE #UserList TABLE (ID INT, username VARCHAR(100))
INSERT INTO #UserList (ID, username)
VALUES (1, 'testuser'), (2, 'bUser'), (3, 'cUser')
Query:
SELECT
NoteID = N.ID,
[User] = STUFF(
(
SELECT
', ' + U.username
FROM
#EditorAssignment AS E
INNER JOIN #UserList AS U ON E.UserID = U.ID
WHERE
N.ID = E.NoteID -- Link the outmost Notes' note with the inner EditorAssignment (correlated subquery)
FOR XML
PATH ('') -- FOR XML PATH('') makes the SELECT return a string value, not a result set
),
1,
2,
'')
FROM
#Notes AS N
Results:
NoteID User
2 testuser, bUser
3 cUser
The subquery generates a string with each username for the related assignment for each note.
The STUFF function is used just to replace the first ', ', since the string is build with a leading comma and a space. This is why the parameters of the STUFF are 1 (first position of the string), 2 (amount of places to remove, the comma and the space), and '' (replacement for those characters).
I'm guessing that you have a row missing for the EditorAssignment table that holds the values 3, 3 on your example.

Related

SQL Server split field and create addition rows with values from main row

I have a table with rows and in one field there are values like this A,B,C
Table 'Mytable':
|ID | Date | MyValue | SplitID |
|1 | 2019-12-17 | A | |
|2 | 2019-12-15 | A,B | |
|3 | 2019-12-16 | B,C | |
Result should be:
|1 | 2019-12-17 | A | 1 |
|2 | 2019-12-15 | A | 2 |
|4 | 2019-12-15 | B | 2 |
|3 | 2019-12-16 | B | 3 |
|5 | 2019-12-16 | C | 3 |
(Sorry, I could not find HOW to format a table in the Stackoverflow help)
I tried a inline table function which splits the Field Myvalue into more lines but could not pass my rows with
charindex(',',[MyValue])>0
from MyTable as input lines.
The code is this:
ALTER function [dbo].[fncSplitString](#input Varchar(max), #Splitter Varchar(99), #ID int)
returns table as
Return
with tmp (DataItem, ix, ID) as
( select LTRIM(#input) , CHARINDEX('',#Input), #ID --Recu. start, ignored val to get the types right
union all
select LTRIM(Substring(#input, ix+1,ix2-ix-1)), ix2, #ID
from (Select *, CHARINDEX(#Splitter,#Input+#Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem,ID from tmp where ix<>0
Thanks for help
Michael
You can try the following query.
Create table #Temp(
Id int,
DateField Date,
MyValue Varchar(10),
SplitID int
)
CREATE FUNCTION [dbo].[SplitPra] (#Value VARCHAR(MAX), #delimiter CHAR)
RETURNS #DataResult TABLE([Position] TINYINT IDENTITY(1,1),[Value] NVARCHAR(128))
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#Value, #delimiter, ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #DataResult ([Value])
SELECT RTRIM(LTRIM(T.c.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') T(c)
RETURN
END
insert into #Temp Values(1, '2019-12-17', 'A', NULL),(2, '2019-12-15', 'A,B', NULL), (3, '2019-12-16', 'B,C', NULL)
Select
#Temp.Id, DateField, b.Value as MyValue, b.Id as SplitValue
from #Temp inner join (
select
Id, f.*
from
#Temp u
cross apply [dbo].[SplitPra](u.MyValue, ',') f
)b on #Temp.Id = b.Id
Drop table #Temp
This will give an output as shown below.
Id DateField MyValue SplitValue
---------------------------------
1 2019-12-17 A 1
2 2019-12-15 A 2
2 2019-12-15 B 2
3 2019-12-16 B 3
3 2019-12-16 C 3
You can find the live demo here.
I found this solution, i hope it will work for you. But i didn't use your function to solve this problem. Instead of that, i used cross apply function.You can find the query below:
-- Creating Test Table
CREATE TABLE #Test
(
ID int,
Date date,
MyValue nvarchar(max),
SplitID int
);
GO
-- Inserting data into test table
INSERT INTO #Test VALUES (1, '2019-12-17', 'A', NULL);
INSERT INTO #Test VALUES (2, '2019-12-15', 'A,B', NULL);
INSERT INTO #Test VALUES (3, '2019-12-16', 'B,C', NULL);
GO
-- Select query
SELECT
*,
(SELECT ID FROM test t1 WHERE t.Date = t1.date) AS SplitID
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY ID) AS ID,
Date,
substring(A.value,1,
CASE WHEN charindex(',',rtrim(ltrim(A.value))) = 0 then LEN(A.value)
ELSE charindex(',',rtrim(ltrim(A.value))) -1 end) as MyValue
FROM Test
CROSS APPLY string_split (MyValue, ',') A) AS T
ORDER BY MyValue ASC;
And the result must be like that:
ID Date MyValue SplitID
1 2019-12-17 A 1
2 2019-12-15 A 2
3 2019-12-15 B 2
4 2019-12-16 B 3
5 2019-12-16 C 3

How to generate XML path within CASE

Edit: Trying to query a XML Path list that has been narrowed down by a case statement. Column 'displayname' contains over 700 unique values throughout the database. However, based on other criteria including the AccountID and if RenderedValue is = '', the remaining results will most likely be less than 5. The variables in my query is I cannot explicitly declare an Account Id or DisplayName.
I have a successful CASE statement on it's own. But trying to also have the XML PATH statement pulls all the data from the table and comma separates it instead of just the results from the previous CASE statement. Can't figure out how to nest them together. Besides the GUID in column 1, values are nvarchar.
Query w/o CASE
select tb1.AccountID,
tb3.DisplayName,
tb4.RenderedValue
from Accounts tb1
join Display tb2 on tb2.AccountID = tb1.AccountID
inner join ExtractDetail tb3 on tb3.ExtractID = tb2.ExtractID
left join ExtractDetailData tb4 on tb4.ExtractDetailID = tb3.ExtractDetailID
result:
+-----------+---------------+-----------------------+
| AccountID | DisplayName | RenderedValue |
+-----------+---------------+-----------------------+
| E8175 | FirstName | John |
| E8175 | LastName | Smith |
| E8175 | StreetAddress | 123 Washington Street |
| E8175 | City | |
| E8175 | State | NY |
| E8175 | ZipCode | |
| E8175 | PhoneNumber | 555-555-5555 |
| E8175 | Email | JohnSmith#aol.com |
+-----------+---------------+-----------------------+
Query w/ CASE
select tb1.AccountID,
CASE When tb4.RenderedValue = ''
Then tb3.DisplayName
Else ''
End As MissingField
from Accounts tb1
join Display tb2 on tb2.AccountID = tb1.AccountID
inner join ExtractDetail tb3 on tb3.ExtractID = tb2.ExtractID
left join ExtractDetailData tb4 on tb4.ExtractDetailID = tb3.ExtractDetailID
Where tb4.RenderedValue =''
Result:
+-----------+--------------+
| AccountID | MissingField |
+-----------+--------------+
| E8175 | City |
| E8175 | ZipCode |
+-----------+--------------+
Expected Output:
+-----------+--------------+
| AccountID | MissingField |
+-----------+--------------+
| E8175 | City,ZipCode |
+-----------+--------------+
i think this code will help you
create table #temp (AccountID varchar(20),DisplayName varchar(20),RenderedValue varchar(255))
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','FirstName','John')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','LastName','Smith')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','StreetAddress','123 Washington Street')
insert into #temp (AccountID,DisplayName,RenderedValue) values ('E8175','City','')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','State','NY')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','ZipCode','')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','PhoneNumber','555-555-5555 ')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','Email','JohnSmith#aol.com')
SELECT distinct
P.AccountID,
STUFF
(
(
SELECT ',' + case when RenderedValue = '' Then DisplayName Else '' End
FROM #temp M
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, ''
) AS Temp
FROM
#temp P
Drop table #temp

SQL JOIN with COALESCE in condition is duplicating rows

I am trying to join two tables together. The first table contains data records that I do not want to duplicate. The second table I am joining to the first table to lookup a [value] by a distinct [profileId] and [role]. The [profileId], [role] column in the second table has a unique constraint on the combination, but [role] can sometimes be NULL, in which case I treat that value as the default for that profile.
How can I join these tables together without duplicating the rows, and without using multiple left joins? My actual query is more complex than the example.
See example below.
DECLARE #temp TABLE ([profileId] int, [role] int)
DECLARE #temp2 TABLE ([profileId] int, [role] int, [value] nvarchar(50))
INSERT INTO #temp ([profileId], [role]) VALUES (1, 1)
INSERT INTO #temp ([profileId], [role]) VALUES (1, 2)
INSERT INTO #temp ([profileId], [role]) VALUES (2, 1)
INSERT INTO #temp ([profileId], [role]) VALUES (2, 2)
INSERT INTO #temp2 ([profileId], [role], [value]) VALUES (1, 1, 'MATCH')
INSERT INTO #temp2 ([profileId], [role], [value]) VALUES (1, NULL, 'DEFAULT1')
INSERT INTO #temp2 ([profileId], [role], [value]) VALUES (2, NULL, 'DEFAULT2')
SELECT
T1.[profileId],
T1.[role],
T2.value
FROM
#temp T1
JOIN #temp2 T2 ON T1.profileId = T2.profileId AND COALESCE(T2.[role], T1.[role]) = T1.[role]
This gives me (and I understand why)
================================
| profileId | role | value |
================================
| 1 | 1 | MATCH |
--------------------------------
| 1 | 1 | DEFAULT1 |
--------------------------------
| 1 | 2 | DEFAULT1 |
--------------------------------
| 2 | 1 | DEFAULT2 |
--------------------------------
| 2 | 2 | DEFAULT2 |
================================
While I want
================================
| profileId | role | value |
================================
| 1 | 1 | MATCH |
--------------------------------
| 1 | 2 | DEFAULT1 |
--------------------------------
| 2 | 1 | DEFAULT2 |
--------------------------------
| 2 | 2 | DEFAULT2 |
================================
This SQL works fine:
SELECT
T1.[role],
Value = coalesce(max(nullif(T2.value,'DEFAULT')),'DEFAULT')
FROM
#temp T1
JOIN #temp2 T2 ON COALESCE(T2.[role], T1.[role]) = T1.[role]
group by
T1.[role]
;
You can use APPLY and TOP:
SELECT
t.profileId,
t.role,
x.value
FROM #temp t
OUTER APPLY(
SELECT TOP 1 value
FROM #temp2
WHERE
profileId = t.profileId
AND (role = t.role OR role IS NULL)
ORDER BY
CASE WHEN role IS NOT NULL THEN 0 ELSE 1 END
)x
if you know the DEFAULT value use left join.
SQL Fiddle Demo
SELECT
T1.[role],
COALESCE(T2.value, 'DEFAULT') as value
FROM
temp T1
LEFT JOIN temp2 T2
ON T1.[role] = T2.[role];
Otherwise
SELECT
T1.[role],
COALESCE(T2.value, (SELECT value
FROM temp2
WHERE role is NULL and temp2.profileID = T1.profileID)) as value
FROM
temp T1
LEFT JOIN temp2 T2
ON T1.[role] = T2.[role]
AND T1.[profileID] = T2.[profileID]
;

Split column with delimited values for an inner join

I need to perform an inner join to a column containing delimited values like:
123;124;125;12;3433;35343;
Now what I am currently doing is this:
ALTER procedure [dbo].[GetFruitDetails]
(
#CrateID int
)
AS
SELECT Fruits.*, Fruits_Crates.CrateID
FROM Fruits_Crates INNER JOIN Fruits
ON Fruits_Crates.FruitID = Fruits.ID
WHERE Fruits_Crates.CrateID = #CrateID
Now the issue is I am saving the data this way:
FruitCrateID FruitID
1 1;
2 1;2;3;4
3 3;
How can I inner join FruitsIDs to the fruit table to get fruit details as well?
Using the method posted in this answer, you can convert the delimited string into rows of a temp table and then join to that:
SQL Fiddle
Schema Setup:
CREATE TABLE Fruits_Crates
([FruitCrateID] int, [FruitID] varchar(10))
;
INSERT INTO Fruits_Crates
([FruitCrateID], [FruitID])
VALUES
(1, '1;'),
(2, '1;2;3;4;'),
(3, '3;')
;
CREATE TABLE Fruits
([FruitID] int, [FruitName] varchar(10))
;
INSERT INTO Fruits
([FruitID], [FruitName])
VALUES
(1, 'Apple'),
(2, 'Banana'),
(3, 'Orange'),
(4, 'Pear')
;
Insert to temp table:
SELECT A.[FruitCrateID],
Split.a.value('.', 'VARCHAR(100)') AS FruitId
INTO #fruits
FROM (SELECT [FruitCrateID],
CAST ('<M>' + REPLACE([FruitID], ';', '</M><M>') + '</M>' AS XML) AS String
FROM Fruits_Crates) AS A CROSS APPLY String.nodes ('/M') AS Split(a)
Join temp table to lookup:
SELECT t1.*, t2.FruitName
FROM #Fruits t1
INNER JOIN Fruits t2 on t1.FruitId = t2.FruitId
Results:
| FRUITCRATEID | FRUITID | FRUITNAME |
|--------------|---------|-----------|
| 1 | 1 | Apple |
| 2 | 1 | Apple |
| 2 | 2 | Banana |
| 2 | 3 | Orange |
| 2 | 4 | Pear |
| 3 | 3 | Orange |

Need help pivoting some data

I'm hoping someone can help me. I'm trying to pivot some data on SQL Server 2005 and can't quite get the results I'm looking for.
This is my current table schema:
| ProductCode | AttributeName | AttributeValue |
| 1 | AttributeA | 10 |
| 1 | AttributeB | 20 |
| 2 | AttributeA | 30 |
| 2 | AttributeB | 40 |
| 3 | AttributeA | 50 |
This is the results I'm trying to achieve:
| ProductCode | AttributeA | AttributeB |
| 1 | 10 | 20 |
| 2 | 30 | 40 |
| 3 | 50 | NULL |
I know that I can achieve this result with the following SQL:
SELECT DISTINCT ProductCode,
(SELECT AttributeValue
FROM attributes
WHERE ProductName = 'AttributeA' AND ProductCode=a.ProductCode) AttributeA,
(SELECT AttributeValue
FROM attributes
WHERE ProductName = 'AttributeB' AND ProductCode=a.ProductCode) AttributeB,
FROM attributes a
Although that SQL does produce the result I'm looking for, it's obviously not dynamic (in reality, I not only have more Attribute Types, but different products have different sets of attributes) and it also scans the table 3 times. It's also a maintenance nightmare.
I tried using the PIVOT functionality of SQL Server, but with no luck.
Can anyone help?
create table #attributes (ProductCode int,
AttributeName varchar(20),
AttributeValue int)
insert into #attributes values (1, 'AttributeA', 10)
insert into #attributes values (1, 'AttributeB', 20)
insert into #attributes values (2, 'AttributeA', 30)
insert into #attributes values (2, 'AttributeB', 40)
insert into #attributes values (3, 'AttributeA', 50)
declare #attributes_columns nvarchar(max)
set #attributes_columns
= (
select ', [' + AttributeName + ']'
from
(
select distinct AttributeName as AttributeName
from #attributes
) t
order by t.AttributeName
for xml path('')
)
set #attributes_columns = stuff(#attributes_columns,1,2,'')
declare #sql nvarchar(max)
set #sql = N'
select ProductCode, <attributes_columns>
from
(select ProductCode, AttributeName, AttributeValue
from #attributes )p
pivot
(
sum(AttributeValue) for AttributeName in (<attributes_columns>)
) as pvt
'
set #sql = replace(#sql, '<attributes_columns>', #attributes_columns)
print #sql
exec sp_executesql #sql
drop table #attributes

Resources