Eliminate Duplicates in Concatenated String - sql-server

I have a working solution for concatenating multiple records into one field, however I would like to eliminate the duplicates from the concatenated field, as well as order the values by another field.
Here is what I have:
CREATE VIEW vwImageDescriptions AS
SELECT i.ItemId, STUFF(ImageDescriptions.line,1,2,'') AS ImageDescriptions
FROM InventoryItems i
CROSS APPLY (
SELECT DISTINCT CAST((select CASE When p.Description = '' or p.Description is null Then '' Else '; ' + p.Description END
FROM Photos p
WHERE p.ItemId = i.ItemId
ORDER BY p.Sequence
FOR XML PATH('')) AS nvarchar(max)) line
) ImageDescriptions
go
select i.ItemName, id.ImageDescriptions
FROM InventoryItems i join vwImageDescriptions id on i.ItemId = id.ItemId
where id.ImageDescriptions like '%pla%'
This is my result:
What I want is to not have duplicated image descriptions so, for instance, I want to only see "My image" once in each row that it occurs, and not twice. As you see, I used DISTINCT in my code but that doesn't seem to be working.
Here is very simplified DDL to see the problem:
DROP TABLE IF EXISTS [InventoryItems]
DROP TABLE IF EXISTS Photos
GO
CREATE TABLE [dbo].[InventoryItems](
[ItemId] [int] NOT NULL,
[ItemName] [varchar](100) NOT NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Photos](
[ItemId] [int] NOT NULL,
[Description] [varchar](175) NULL,
[Sequence] [int] NOT NULL
) ON [PRIMARY]
GO
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(311,'11" Round Vegetable Bowl');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(312,'13" Oval Serving Platter');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(313,'19" Oval Serving Platter');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(314,'Creamer');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(315,'Gravy Boat with Attached Underplate');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(317,'Round Butter Dish and Lid');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(318,'Sugar Bowl and Lid');
INSERT INTO [InventoryItems]([ItemId],[ItemName]) VALUES(319,'Vegetable Server Bowl and Lid');
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(311,'',1)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(312,'Replacements.com image',1)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(313,'Replacements.com image',1)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(313,'Outer platter is the 19"',2)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(313,'Outer platter is the 19"',3)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(313,'Outer platter is the 19"',4)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(313,'Another image',5)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(314,'Replacements.com image',1)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(315,'From replacements.com',1)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(315,'My image',2)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(315,'My image',3)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(315,'My image',4)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(316,'My image',1)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(316,'My image',2)
INSERT INTO [Photos]([ItemId],[Description],[Sequence])VALUES(316,'From replacements.com',3)

I think this can be simplified a bit. As I understand the desired results this should produce what you are looking for. Many thanks for the tables and sample data.
select i.ItemId
, ImageDescriptions = isnull(STUFF((select ',' + isnull(p.Description, '')
from Photos p
where p.ItemId = i.ItemId
group by isnull(p.Description, '')
order by min(p.Sequence)
for xml path('')), 1, 1, ''), '')
FROM InventoryItems i
group by i.ItemId
--EDIT--
Like I said the CROSS APPLY is a layer of complexity not needed here. But this should do the same thing.
select i.ItemId
, ImageDescriptions = isnull(x.asdf, '')
FROM InventoryItems i
cross apply
(
select asdf = STUFF((select ',' + isnull(p.Description, '')
from Photos p
where p.ItemId = i.ItemId
group by isnull(p.Description, '')
order by min(p.Sequence)
for xml path('')), 1, 1, '')
) x

I usually write like below:
CREATE VIEW vwImageDescriptions AS
SELECT i.ItemId, STUFF(ImageDescriptions.line,1,1,'') AS ImageDescriptions
FROM InventoryItems i
CROSS APPLY (
SELECT DISTINCT '; ' + ISNULL(p.Description,'')
FROM Photos p
WHERE p.ItemId = i.ItemId
ORDER BY p.Sequence
FOR XML PATH('')
) ImageDescriptions(line)
GO

Related

SQL Synapse Compare data between Column(multiple values) and Column(multiple values)

I need to compare 2 columns from 2 tables.
table a:
ID|TEL
----------------
A1|1111,2222,3333
TABLE B:
ID|TEL
----------------
A1|2222,4444
Result should update in TABLE A
A1|1111,2222,3333,4444
As I know, maybe I should use select value from string_split (B.Tel,'|') to split it. However, I don't know how to loop to compare between A and B.
Please help.
Here is what I've tried but it's not working.
with split_tel as
(select id,
Value tel
from B
CROSS APPLY STRING_SPLIT(tel, ','))
,pre as (select sp.id
,sp.tel as split
,A.tel as target
from split_tel sp
inner join A
on sp.id = A.id)
select id,split,target
from pre
where split like '%' + target + '%' ;
First of all you should not really be storing your data like this, particularly if you are having to conduct set-based operations on it. However there is a simple solution using STRING_SPLIT and STRING_AGG if you are always effectively adding numbers, not taking away:
IF OBJECT_ID('tempdb..#tmpA') IS NOT NULL DROP TABLE #tmpA
IF OBJECT_ID('tempdb..#tmpB') IS NOT NULL DROP TABLE #tmpB
CREATE TABLE #tmpA (
id VARCHAR(5) NOT NULL,
tel VARCHAR(100) NOT NULL
);
CREATE TABLE #tmpB (
id VARCHAR(5) NOT NULL,
tel VARCHAR(100) NOT NULL
);
--INSERT INTO #tmpA VALUES ( 'A1', '1111,2222,3333' );
--INSERT INTO #tmpB VALUES ( 'A1', '2222,4444' );
INSERT INTO #tmpA
SELECT 'A1', '1111,2222,3333'
UNION ALL
SELECT 'B1', '2222'
INSERT INTO #tmpB
SELECT 'A1', '2222,4444'
UNION ALL
SELECT 'B1', '3333'
SELECT id, STRING_AGG( value, ',' ) tel
FROM
(
SELECT a.id, x.value
FROM #tmpA a
CROSS APPLY STRING_SPLIT( a.tel, ',' ) x
UNION
SELECT b.id, x.value
FROM #tmpB b
CROSS APPLY STRING_SPLIT( b.tel, ',' ) x
) y
GROUP BY id;

SQL-use FOR XML PATH for two TABLE

I have two TABLE (SQL Server 2016)
--TABLE
CREATE TABLE TEST (
Num VARCHAR(50),
OID VARCHAR(50)
);
INSERT INTO TEST
VALUES ('1', 'Lisa'),
('2', 'Fanny'),
('3', 'Doris'),
('4', 'Tom'),
('5', 'Johnson'),
('6', 'Seam'),
('7', 'Matt');
CREATE TABLE TEST2 (
Num VARCHAR(50),
OID VARCHAR(50),
TAB2C VARCHAR(50)
);
INSERT INTO TEST2
VALUES ('1', 'Fanny','A'),
('2', 'Johnson','B'),
('3', 'Seam','C');
Usually I only use FOR XML PATH for the second TABLE
SELECT ',' + TAB2C
FROM TEST2
FOR XML PATH('')
output:
,A,B,C
Now I need to connect with the first TABLE
I can't get the result I want
SELECT ',' + T2.TAB2C
FROM TEST2 T2
left join TEST1 T1 on T1.OID= T2.OID
FOR XML PATH('')
output:
,A,B,C
hope output:
0,A,0,0,B,C,0
Hope to use OID to judge, if no data is found, add 0
Or is there any way I can do it?
Thank you
Your LEFT JOIN is the wrong way round. It also references an invalid object (it's TEST not TEST1). Finally, you need to wrap T2.TAB2C in an ISNULL. I also add in a STUFF to remove the first delimiter:
SELECT STUFF((SELECT ',' + ISNULL(T2.TAB2C,0)
FROM dbo.TEST T
LEFT JOIN dbo.TEST2 T2 ON T.OID= T2.OID
ORDER BY T.Num
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'');
db<>fiddle

Using Dynamic SQL to concatenate two columns into a table header with their respective values

I have a two tables, GuestList and CustomerList. I joined them and used dynamic SQL pivot table to convert the 'city' column from GuestList table into rows or table headers and the average population would be displayed under each city. So after executing the query at the bottom, my table header looks like this and the average population is displayed under each city.
Time| Atlanta| Los Angeles | New York | Denver| Minneapolis
But I want my table header to look like this. Basically 'Id' has four values, 1, 2,3,4 and each city has all these four ID's. I could not add all the cities but rest of the cities will also be like this.
Time| Atlanta_1|| Atlanta_2|| Atlanta_3|| Atlanta_4|
Could someone help me on this by writing the rest of the query on how to concatenate the two columns in the GuestList table and put their respective population underneath it.
declare #ColumnNames nvarchar(max) = ''
declare #SQL nvarchar(max) = ''
select #ColumnNames += QUOTENAME(a.address) + ','
from GuestList as a
inner join CustomerList as b
on a.Id = b.Id
group by a.address
order by a.address
set #ColumnNames = left(#ColumnNames, LEN(#ColumnNames)-1 )
set #SQL= N'
select Time,' + #ColumnNames + '
from
(
select a.Time, a.city, a.population, b.Gender
from GuestList as a
inner join CustomerList as b
on a.Id = b.Id
inner join Split(#city, '','') as c
on a.city = c.Data
where a.city = c.Data
) as SourceTable
pivot
(avg(population)
for city
in ('
+ #ColumnNames +
')) as PivotTable
order by Time'
execute sp_executesql #SQL,
N'#city nvarchar(max)'
,#city = 'Atlanta,Los Angeles,New York'
"FOR XML PATH" to the rescue. Here's the basic idea...
CREATE TABLE #ids(id NVARCHAR(20));
INSERT #ids VALUES ('1'), ('2'), ('3'), ('4');
CREATE TABLE #cities(city NVARCHAR(20));
INSERT #cities VALUES ('Atlanta'), ('Los Angeles'), ('New York'), ('Denver'), ('Minneapolis');
SELECT 'Time' + (
SELECT '|' + city + '_' + id
FROM #cities
CROSS JOIN #ids
ORDER BY city, id
FOR XML PATH('')
) ;
... yields the result ...
Time|Atlanta_1|Atlanta_2|Atlanta_3|Atlanta_4|Denver_1|Denver_2|Denver_3|Denver_4|Los Angeles_1|Los Angeles_2|Los Angeles_3|Los Angeles_4|Minneapolis_1|Minneapolis_2|Minneapolis_3|Minneapolis_4|New York_1|New York_2|New York_3|New York_4

Comparing two tables and displaying the result as a separate output

I have two tables and the values like this, `
CREATE TABLE Location (ID int ,Location Varchar(500))
INSERT INTO Location values (1,'Loc3'),(2,'Loc4'),(3,'Loc5'),(4,'Loc7')
CREATE TABLE InputLocation (ID int ,Location Varchar(500))
Insert into InputLocation values(1,'Loc1,Loc2,Loc3,Loc4,Loc5,Loc6')
I need to get the output by matching each values from table Location with table InputLocation and need to display the output whichever not matched with 2nd table, i.e Loc1,Loc2,Loc6 , I have tried some code like this and it worked But i need even simpler code, Any help would be greatly appreciated
My code :
SELECT STUFF((select ','+ Data.C1
FROM
(select
n.r.value('.', 'varchar(50)') AS C1
from InputLocation as T
cross apply (select cast('<r>'+replace(replace(Location,'&','&'), ',', '</r><r>')+'</r>' as xml)) as S(XMLCol)
cross apply S.XMLCol.nodes('r') as n(r)) DATA
WHERE data.C1 NOT IN (SELECT Location
FROM Location) for xml path('')),1,1,'') As Output
your script is ok.
Another method will be to use SPLIT String as describe here.
http://www.sqlservercentral.com/articles/Tally+Table/72993/
use [dbo].[DelimitedSplit8K]
Suppose my comma seperated string won't be longer than 500 then in my custom UDF i make it 500 varchar instead of varchar(8000) in order to improve performance.
SELECT STUFF((
SELECT ',' + Data.item
FROM (
SELECT il.ID
,fn.item
FROM #InputLocation IL
CROSS APPLY (
SELECT *
FROM dbo.DelimitedSplit2K(il.Location, ',')
) fn
WHERE NOT EXISTS (
SELECT *
FROM #Location L
WHERE l.Location = fn.Item
)
) data
FOR XML path('')
), 1, 1, '') AS
OUTPUT
Use recursion to avoid using slow XML Reader:
;with tmp(DataItem, Location) as (
select cast(LEFT(Location, CHARINDEX(',',Location+',')-1) as nvarchar(50)),
cast(STUFF(Location, 1, CHARINDEX(',',Location+','), '') as nvarchar(50))
from [InputLocation]
union all
select cast(LEFT(Location, CHARINDEX(',',Location+',')-1) as nvarchar(50)),
cast(STUFF(Location, 1, CHARINDEX(',',Location+','), '') as nvarchar(50))
from tmp
where Location > ''
)
select STUFF((SELECT ',' + x.Location
from (
select DataItem as Location from tmp
except Select Location from [Location]) x
FOR XML path('')), 1, 1, '') AS OUTPUT

Sql select to string

I have a query that returns a result set of one column, but I want it to be converted into one long string for use as a subquery. I.E. I want to be able to do:
SELECT user.*, (SELECT group_name FROM user_groups WHERE userID = user.ID) AS Groups FROM user
However, that will fail because a user can belong to more than one group. For example if the user belong to {"writer", "editor"} I want it to return a string like this: "writer, editor".
How can I do this?
You can use FOR XML to do this pivoting action. Here is a working sample.
set nocount on
declare #Users Table
(
UserId int,
UserName varchar (20)
)
declare #UserGroups Table
(
GroupId int,
UserId int,
GroupName varchar (20)
)
Insert #Users Values (1, 'Tim')
Insert #Users Values (2, 'Jane')
Insert #Users Values (3, 'Hal')
Insert #UserGroups Values (1, 1, 'Admin')
Insert #UserGroups Values (2, 1, 'Power-users')
Insert #UserGroups Values (3, 2, 'Noobs')
Insert #UserGroups Values (4, 2, 'Users')
Insert #UserGroups Values (5, 3, 'Noobs')
/* How this works */
SELECT 'FirstStep : Users table'
SELECT * FROM #Users
SELECT 'NextStep : User Groups table'
SELECT * FROM #UserGroups
SELECT 'NextStep : Users & UserGroups table'
SELECT *
FROM #Users U
INNER JOIN #UserGroups UG ON U.UserId = UG.UserId
SELECT 'NextStep : Just get the groups for one user (UserId = 2)'
SELECT GroupName
FROM #UserGroups UG
WHERE UG.userID = 2
ORDER BY GroupName
SELECT 'NextStep : When you use XML Path the output comes out in XML format'
SELECT GroupName
FROM #UserGroups UG
WHERE UG.userID = 2
ORDER BY GroupName
FOR XML PATH('') -- XML Path added
SELECT 'NextStep : When you remove the column name the XML tags go away,
but it looks ugly because there is no separator'
SELECT GroupName + '' -- Added an empty string to remove the column name
FROM #UserGroups UG
WHERE UG.userID = 2
ORDER BY GroupName
FOR XML PATH('')
SELECT 'NextStep : Add a separator
We add it to the beginning instead of the end so that we can STUFF later on.'
SELECT ', ' + GroupName -- Added an empty string to remove the column name
FROM #UserGroups UG
WHERE UG.userID = 2
ORDER BY GroupName
FOR XML PATH('')
SELECT 'NextStep : I don''t like that ugly XML column name. Let me give it my own name'
SELECT
(
SELECT ', ' + GroupName
FROM #UserGroups UG
WHERE UG.userID = 2
ORDER BY GroupName
FOR XML PATH('')
) as UserGroups
SELECT 'NextStep : STUFF the extra comma'
SELECT STUFF
(
(
SELECT ', ' + GroupName
FROM #UserGroups UG
WHERE UG.userID = 2
ORDER BY GroupName
FOR XML PATH('')
),
1, 2, ''
) as UserGroups
SELECT 'NextStep : Now join it with the Users table by the UserId and get the UserName'
SELECT
U.UserName,
STUFF
(
(
SELECT ', ' + GroupName
FROM #UserGroups UG
WHERE UG.userID = U.userID
ORDER BY GroupName
FOR XML PATH('')
),
1, 2, ''
) as UserGroups
FROM #Users U

Resources