SQL Server stored procedure looping through a comma delimited cell - sql

I am trying to figure out how to go about getting the values of a comma separated string that's present in one of my cells.
This is the query I current am trying to figure out in my stored procedure:
SELECT
uT.id,
uT.permissions
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
/*Need to loop here I think?*/
WHERE
uT.active = 'true'
AND
uT.email = 'bbarker#thepriceisright.com'
The usersPermissions table looks like this:
And so a row in the usersTbl table looks like this for permissions:
1,3
I need to find a way to loop through that cell and get each number and place the name ****, in my returned results for the usersTbl.permissions.
So instead of returning this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | 1,3 | 87 |
It needs to returns this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | Read,Upload | 87 |
Really just replacing 1,3 with Read,Upload.
Any help would be great from a SQL GURU!
Reworked query
SELECT
*
FROM
usersTbl AS uT
INNER JOIN
usersPermissionsTbl AS uPT
ON
uPT.userId = uT.id
INNER JOIN
usersPermissions AS uP
ON
uPT.permissionId = uP.id
WHERE
uT.active='true'
AND
uT.email='bBarker#thepriceisright.com'

I agree with all of the comments... but strictly trying to do what you want, here's a way with a splitter function
declare #usersTbl table ([Name] varchar(64), id int, [permissions] varchar(64), age int)
insert into #usersTbl
values
('Bbarker',5987,'1,3',87)
declare #usersTblpermissions table (id int, [type] varchar(64))
insert into #usersTblpermissions
values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
;with cte as(
select
u.[Name]
,u.id as UID
,p.id
,p.type
,u.age
from #usersTbl u
cross apply dbo.DelimitedSplit8K([permissions],',') x
inner join #usersTblpermissions p on p.id = x.Item)
select distinct
[Name]
,UID
,age
,STUFF((
SELECT ',' + t2.type
FROM cte t2
WHERE t.UID = t2.UID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from cte t
Jeff Moden Splitter
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO

First, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!
Second, you should add a table for user permissions since this is clearly a many to many relationship.
Your tables might look something like this (pseudo code):
usersTbl
(
Id int primary key
-- other user related columns
)
usersPermissionsTbl
(
UserId int, -- Foreign key to usersTbl
PermissionId int, -- Foreign key to permissionsTbl
Primary key (UserId, PermissionId)
)
permissionsTbl
(
Id int primary key,
Name varchar(20)
)
Once you have your tables correct, it's quite easy to get a list of comma separated values from the permissions table.
Adapting scsimon's sample data script to a correct many to many relationship:
declare #users table ([Name] varchar(64), id int, age int)
insert into #users values
('Bbarker',5987,87)
declare #permissions table (id int, [type] varchar(64))
insert into #permissions values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
declare #usersPermissions as table (userId int, permissionId int)
insert into #usersPermissions values (5987, 1), (5987, 3)
Now the query looks like this:
SELECT u.Name,
u.Id,
STUFF(
(
SELECT ','+ [type]
FROM #permissions p
INNER JOIN #usersPermissions up ON p.id = up.permissionId
WHERE up.userId = u.Id
FOR XML PATH('')
)
, 1, 1, '') As Permissions,
u.Age
FROM #Users As u
And the results:
Name Id Permissions Age
Bbarker 5987 Read,Upload 87
You can see a live demo on rextester.

I concur with much of the advice being presented to you in the other responses. The structure you're starting with is not going to be fun to maintain and work with. However, your situation may mean you are stuck with it so maybe some of the tools below will help you.
You can parse the delimiter with charindex() as others demonstrated here- MSSQL - How to split a string using a comma as a separator
... and even better here (several functions are provided) - Split function equivalent in T-SQL?
If you still want to do it with raw inline SQL and are committed to a loop, then pair the string manipulation with a CURSOR. Cursors have their own controversies BTW. The code below will work if your permission syntax remains consistent, which it probably doesn't.
They used charindex(',',columnName) and fed the location into the left() and right() functions along with some additional string evaluation to pull values out. You should be able to piece those together with a cursor
Your query might look like this...
--creating my temp structure
declare #userPermissions table (id int, [type] varchar(16))
insert into #userPermissions (id, [type]) values (1, 'Read')
insert into #userPermissions (id, [type]) values (2, 'Write')
insert into #userPermissions (id, [type]) values (3, 'Upload')
insert into #userPermissions (id, [type]) values (4, 'Admin')
declare #usersTbl table ([Name] varchar(16), id int, [permissions] varchar(8), age int)
insert into #usersTbl ([Name], id, [permissions], age) values ('Bbarker', 5987, '1,3', 87)
insert into #usersTbl ([Name], id, [permissions], age) values ('Mmouse', 5988, '2,4', 88)
--example query
select
ut.[Name]
, (select [type] from #userPermissions where [id] = left(ut.[permissions], charindex(',', ut.[permissions])-1) )
+ ','
+ (select [type] from #userPermissions where [id] = right(ut.[permissions], len(ut.[permissions])-charindex(',', ut.[permissions])) )
from #usersTbl ut

Related

Remove e-mail address in a String SQL

I have a table with a field "E-mailTo".
This field contains a string of e-mail addresses.
Example
user1#domain.com; user2#domain.com; user3#domain.com; user4#domain.com; user5#domain.com;
user6#domain.com; user3#domain.com;
user7#domain.com; user4#domain.com;
I have a 2nd table with a list of e-mail addresses that need to be removed from all E-mailTo strings.
Example
user3#domain.com
user4#domain.com
Please can anyone help me achieve this?
Thank you
you could try using a replace with join
select replace(a.my_string, my_table2.col_mail_to_exclude,'')
from my_table1
inner join my_table2 on my_table1.my_string like concat('%', my_table2.col_mail_to_exclude, '%')
As others have suggested, it is better to go for normalized design, instead of storing emails as multivalued data.
But, for your needs, You can use recursive CTE to achieve this.
DECLARE #table1 table(id int, Emailto VARCHAR(8000))
INSERT INTO #table1 values
(1, 'user1#domain.com; user2#domain.com; user3#domain.com; user4#domain.com; user5#domain.com')
,(2, 'user6#domain.com; user3#domain.com;')
,(3,'user7#domain.com; user4#domain.com;')
DECLARE #table2 table(EmailToremove VARCHAR(8000))
INSERT INTO #table2 values
('user3#domain.com'),('user4#domain.com');
;WITH cte_table2rank AS
(
select ROW_NUMBER() OVER(ORDER BY EmailToRemove) as rnk, EmailToRemove
FROM #table2
), CTE_Cleanser AS
(
select id, EmailTo, 1 as rnk from #table1
UNION ALL
select c2.id, REPLACE(c2.EmailTo,c1.emailtoRemove,'') as Emailto, c2.rnk+1 as rnk
FROM cte_table2rank as C1
inner join CTE_Cleanser as c2
on c2.rnk = c1.rnk
)
SELECT id,Emailto FROM cte_cleanser as oc
where rnk = (SELECT max(rnk) FROM CTE_Cleanser where id = oc.id)
order by id
id
Emailto
1
user1#domain.com; user2#domain.com; ; ; user5#domain.com
2
user6#domain.com; ;
3
user7#domain.com; ;

Join by concatenated field (in CSV format)

UPDATE: I am fully aware that this is a poor RDBMS practice, but the question is not asking whether it is and how I can re-train the DBAs who created this architecture. The question is how I can work around the situation that I have on hands. I appreciate the help of the community and must admit that this is an interesting problem indeed.
In SQL Server 2017, I have a lookup table containing codes and a transactions table with CSV-formated codes:
CREATE TABLE #t(cd VARCHAR(100))
CREATE TABLE #cd (id INT, cd VARCHAR (1000))
INSERT INTO #t SELECT 'c1'
INSERT INTO #t SELECT 'c1,c2'
INSERT INTO #t SELECT 'c1,c2,c3'
INSERT INTO #cd SELECT 10, 'c1'
INSERT INTO #cd SELECT 20, 'c2'
INSERT INTO #cd SELECT 30, 'c3'
So, the lookup is
id cd
10 c1
10 c1
20 c2
30 c3
and, the transactions table has:
cd
c1
c1,c2
c1,c2,c3
I need to replace the codes to their respective IDs, while keeping these in CSV format.
I would like to avoid the cursor because it is too slow. Is there a way to parse the codes, do the JOIN, and recombine the IDs somehow efficiently? I suppose COALESCE may be of use, but need help applying it. Perhaps, there is already a function in t-SQL that does this types of lookups.
The output needs to another column in transactions table:
id
10
10,20
10,20,30
You can first strip out comma into a list and then join and get correct ids for codes and then add them back with commas. I used a row_number upfront to get a unique thing to join back on in my query.
See live demo
CREATE TABLE #t(cd VARCHAR(100))
CREATE TABLE #cd (id INT, cd VARCHAR (1000))
INSERT INTO #t SELECT 'c1'
INSERT INTO #t SELECT 'c1,c2'
INSERT INTO #t SELECT 'c1,c2,c3'
INSERT INTO #cd SELECT 10, 'c1'
INSERT INTO #cd SELECT 20, 'c2'
INSERT INTO #cd SELECT 30, 'c3'
; WITH X AS
(
SELECT
C.id,P1.rn
FROM
(
SELECT *, row_number() over( order by (select 1)) rn,
cast('<X>'+replace(P.cd,',','</X><X>')+'</X>' as XML) AS xmlitems FROM #t P
)P1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(100)') AS splitdata
FROM P1.xmlitems.nodes('X') AS fdata(D)) O
LEFT JOIN #cd C
ON C.cd= LTRIM(RTRIM(O.splitdata ))
)
SELECT
rn,
id= STUFF((
SELECT ',' + cast(id as varchar(100)) FROM X AS x2
WHERE x2.rn = x.rn
ORDER BY rn FOR XML PATH,
TYPE).value(N'.[1]',N'varchar(max)'), 1, 1, '')
FROM
X
GROUP BY rn
Note: With SQL server 2017 you can also you SPLIT_STRING() function
and STRING_AGG() functions
SQL SERVER 2017 code:
select
id=STRING_AGG(id,',')
from
(
select V=value, rn
from
(
select
rn=row_number() over( order by (select 1)),
cd
from #T
)T
cross apply STRING_SPLIT(cd, ',')
) T
left join #cd C
on cd= v
group by rn

How to insert multiple values in SQL Server table?

I don't exactly know how to phrase the question, but an example would work. So I have this table
Users
Id Name
1 Tim
2 Jon
3 Matt
There is another table
Tags
TagId TagName
1 Test
2 Other
3 Dummy
4 More
In a temp table I have structure like this
TmpUserTags
User Tags
Tim Test
Jon Other, Test
Matt Dummy, More, Other
So, what I need to do is from this temp table, insert record in table UserTags with corresponding Ids, for the above given example, the result would be
UserTags
User TagId
1 1
2 2
2 1
3 3
3 4
3 2
So, this is the end result I want, to be inserted in UserTags. But since for each row in TmpUserTags each user can have many tags, separated by comma, I don't know what would be the best way to do it. I can probably use a while loop (or rather a cursor) to loop through all the rows in TmpUserTags, and then, for each row, split the tags by comma, find their Id, and insert those in UserTags. But that doesn't seems to be the most optimized way. Can someone please suggest some better way of doing it?
I think the simplest way would be to just join the tags column using LIKE:
CREATE TABLE #Users (ID INT, Name VARCHAR(4));
INSERT #Users (ID, Name)
VALUES (1, 'Tim'), (2, 'Jon'), (3, 'Matt');
CREATE TABLE #Tags (TagID INT, TagName VARCHAR(5));
INSERT #Tags (TagID, TagName)
VALUES (1, 'Test'), (2, 'Other'), (3, 'Dummy'), (4, 'More');
CREATE TABLE #TmpUserTags ([User] VARCHAR(4), Tags VARCHAR(100));
INSERT #tmpUserTags ([User], Tags)
VALUES ('Tim', 'Test'), ('Jon', 'Other,Test'), ('Matt', 'Dummy,More,Other');
SELECT u.ID, t.TagID
FROM #TmpUserTags AS ut
INNER JOIN #Users AS u
ON u.Name = ut.[User]
INNER JOIN #Tags AS t
ON ',' + ut.Tags + ',' LIKE '%,' + t.TagName + ',%';
You could also go down the route of creating a split function to split your comma separated list into rows:
CREATE FUNCTION [dbo].[Split](#StringToSplit NVARCHAR(MAX), #Delimiter NCHAR(1))
RETURNS TABLE
AS
RETURN
(
SELECT ID = ROW_NUMBER() OVER(ORDER BY n.Number),
Position = Number,
Value = SUBSTRING(#StringToSplit, Number, CHARINDEX(#Delimiter, #StringToSplit + #Delimiter, Number) - Number)
FROM ( SELECT TOP (LEN(#StringToSplit) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
FROM sys.all_objects a
) n
WHERE SUBSTRING(#Delimiter + #StringToSplit + #Delimiter, n.Number, 1) = #Delimiter
);
Then you can use:
SELECT u.ID, t.TagID
FROM #TmpUserTags AS ut
CROSS APPLY dbo.Split(ut.tags, ',') AS s
INNER JOIN #Users AS u
ON u.Name = ut.[User]
INNER JOIN #Tags AS t
ON t.TagName = s.Value;
Here is XML-version of answer:
SELECT Users.Id as [User],Tags.TagId
FROM
(SELECT A.[User],
LTRIM(Split.a.value('.', 'VARCHAR(100)')) AS Tagname
FROM (SELECT [User],
CAST ('<M>' + REPLACE(Tags, ',', '</M><M>') + '</M>' AS XML) AS String
FROM TmpUserTags) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) ut
LEFT JOIN Users ON Users.Name=ut.[User]
LEFT JOIN Tags ON Tags.TagName=ut.Tagname
No procedures, functions and CTEs.
[Update] If some performance issues are appeared, please read this nice article: http://beyondrelational.com/modules/2/blogs/114/posts/14617/delimited-string-tennis-anyone.aspx
Left join is used to show all rows from the table TmpUserTags even if other tables are w/o some necessary rows (E.g. new User 'Bob' with Tag 'Test2' that isn't described in table Tags)
To me the main question would be how you arrived at the temp table containing the comma delimited column. If it was an import from a file and all the data was comma delimited it would be easy enough to save the file as a csv which will save the user and each tag separately, then create a table in the your database containing the same number of columns as the file has, then bulk insert this table from the file.
drop table #TmpUserTags
GO
create table #TmpUserTags
(
[user] varchar(10),
tag1 varchar(10),
tag2 varchar(10),
tag3 varchar(10)
)
bulk insert #TmpUserTags from '<filepath>' with (fieldterminator=',')
Then union the data to create two columns which should be easy enough to reinterpret as ids.
SELECT [User],Tag1 FROM #TmpUserTags WHERE Tag1 IS NOT NULL
UNION ALL
SELECT [User],Tag2 FROM #TmpUserTags WHERE Tag2 IS NOT NULL
UNION ALL
SELECT [User],Tag3 FROM #TmpUserTags WHERE Tag3 IS NOT NULL
ORDER BY [User]
Of course all this might be conjecture but, like, how did you arrive at the table with the comma delimited values?
Just another way to do it (Fiddle: http://sqlfiddle.com/#!3/7f48f/20):
WITH cteTagMatrix
AS
(
SELECT n.ID,
CASE
WHEN CHARINDEX(',' + t.TagName + ',', REPLACE(',' + tut.Tags + ',', ' ', '')) <> 0 THEN t.TagID
ELSE NULL
END AS TagID
FROM Names n INNER JOIN TmpUserTags tut
ON n.[Name] = tut.[User]
CROSS JOIN Tags t
)
SELECT *
FROM cteTagMatrix
WHERE TagID IS NOT NULL
ORDER BY ID, TagID;)
EDIT: Oops, was a error with my comma logic. Fixed code and fiddle updated.

Is there a way to return more than 1 row in select without using existing tables

Simple question, just out of curiosity.
For example select 1,2,3 that will show table with one column and three rows.
Something like this: select values(1),(2),(3)
*with one select statement
An example for my comment in your post.
DECLARE #TABLE TABLE (ONE INT, TWO INT, THREE INT)
INSERT INTO #TABLE VALUES (1,2,3)
SELECT UP.COL, UP.VALUE
FROM #TABLE
UNPIVOT (VALUE FOR COL IN (ONE,TWO,THREE)) UP
Query:
DECLARE #t TABLE (i1 INT, i2 INT, i3 INT)
INSERT INTO #t VALUES (1, 2, 3)
SELECT t.*
FROM #t
CROSS APPLY (
VALUES(i1), (i2), (i3)
) t(value)
Output:
value
-----------
1
2
3
Additional info:
http://blog.devart.com/is-unpivot-the-best-way-for-converting-columns-into-rows.html
As it appears there is a simple code that I've been searching for:
select n from (values (1),(2),(3)) D(c);

How can I use the values Selected in a While Exists statement inside the While loop?

I'm new-ish to SQL and am trying to figure out how to use the values from the Select statement in a While Exists conditional loop. The purpose is to combine multiple occurences of an attribute for a Document into a single field, and later pivot and join those results to the Document record.
For example, three tables exist like so:
ATTRIBUTES TABLE
ID, ATTRIBUTE_NAME
---------------------------
1, Created
2, Embedded_Image
...
ATTRIBUTE_VALUES TABLE
ATTRIBUTE_ID, VALUE, DOC_ID
-------------------------------------------
1, 2010/11/01, 1
2, 'Home.png', 1
2, 'Castle.png', 1
2, 'Apartment.jpg', 1
1, 2008/06/23, 2
2, 'Ski Jump.jpg', 2
2, 'Snowboarding.png', 2
...
DOCUMENTS TABLE
ID, TEXT
---------------------------
1, 'Homes of the ...'
2, 'Winter sports ...'
...
So a final Pivot and Join of the tables would look like so:
DOC_ID, TEXT, Created, Embedded_Image
----------------------------------------------------------------------------------------
1, 'Homes of the ...', 2010/11/01, 'Home.png,Castle.png,Apartment.jpg'
2, 'Winter sports ...', 2008/06/23, 'Ski Jump.jpg, Snowboarding.png'
The SQL While Exists condition I've tried to write looks like so:
DECLARE #LOOP_DOC_ID UNIQUEIDENTIFIER
DECLARE #LOOP_ATTRIBUTE_NAME NVARCHAR(MAX)
WHILE EXISTS(
SELECT [dbo].[ATTRIBUTE_VALUES].[DOC_ID], [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME]
FROM ([dbo].[ATTRIBUTE_VALUES] INNER JOIN [dbo].[ATTRIBUTES]
ON [dbo].[ATTRIBUTE_VALUES].[ATTRIBUTE_ID] = [dbo].[ATTRIBUTES].[ID])
)
BEGIN
SET #LOOP_DOC_ID = DOC_ID
SET #LOOP_ATTRIBUTE_NAME = ATTRIBUTE_NAME
SELECT STUFF(
(
SELECT DISTINCT ',' + RTRIM(LTRIM([dbo].[ATTRIBUTE_VALUES].[VALUE]))
FROM
(
[dbo].[ATTRIBUTE_VALUES] INNER JOIN [dbo].[ATTRIBUTES]
ON [dbo].[ATTRIBUTE_VALUES].[ATTRIBUTE_ID] = [dbo].[ATTRIBUTES].[ID]
)
WHERE [dbo].[ATTRIBUTE_VALUES].[DOC_ID] = #LOOP_DOC_ID
AND [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME] = #LOOP_ATTRIBUTE_NAME
ORDER BY ',' + RTRIM(LTRIM([dbo].[ATTRIBUTE_VALUES].[VALUE]))
FOR XML PATH('')
), 1, 2, ''
) AS VALUE, #LOOP_DOC_ID AS DOC_ID, #LOOP_ATTRIBUTE_NAME AS ATTRIBUTE_NAME
END
SQL Server doesn't like the lines where I'm trying to SET the variables to the values from the Select statement in the While Exists condition.
How can I use the [dbo].[ATTRIBUTE_VALUES].[DOC_ID], [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME] values Selected in the While Exists conditional statement between the BEGIN and END statements?
Preferrably I would like to do away with the #LOOP_DOC_ID and #LOOP_ATTRIBUTE_NAME variables and deal directly with the values.
I've looked through forums that have talked about using Cursors to solve similar problems, but each one of them seem to recommend only using Cursors as a last resort due to their lack of speed. I've also seen some people use stored procedures, but I can't use those, since my boss has ruled those as off-limits. Am I in need of a Cursor, or is there a better way to do this?
Have a look at something like this (Full Example)
DECLARE #ATTRIBUTES TABLE(
ID INT,
ATTRIBUTE_NAME VARCHAR(100)
)
INSERT INTO #ATTRIBUTES SELECT 1, 'Created'
INSERT INTO #ATTRIBUTES SELECT 2, 'Embedded_Image'
DECLARE #ATTRIBUTE_VALUES TABLE(
ATTRIBUTE_ID INT,
VALUE VARCHAR(100),
DOC_ID INT
)
INSERT INTO #ATTRIBUTE_VALUES SELECT 1, '2010/11/01', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Home.png', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Castle.png', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Apartment.jpg', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 1, '2008/06/23', 2
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Ski Jump.jpg', 2
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Snowboarding.png', 2
DECLARE #DOCUMENTS TABLE(
ID INT,
[TEXT] VARCHAR(100)
)
INSERT INTO #DOCUMENTS SELECT 1, 'Homes of the ...'
INSERT INTO #DOCUMENTS SELECT 2, 'Winter sports ...'
;WITH Vals AS (
SELECT d.ID DOC_ID,
d.[TEXT] [TEXT],
a.ATTRIBUTE_NAME,
av.VALUE
FROM #DOCUMENTS d INNER JOIN
#ATTRIBUTE_VALUES av ON d.ID = av.DOC_ID INNER JOIN
#ATTRIBUTES a ON av.ATTRIBUTE_ID = a.ID
)
SELECT *
FROM (
SELECT DOC_ID,
[TEXT],
ATTRIBUTE_NAME,
stuff(
(
select ',' + t.VALUE
from Vals t
where t.DOC_ID = v.DOC_ID
AND t.ATTRIBUTE_NAME = v.ATTRIBUTE_NAME
order by t.VALUE
for xml path('')
),1,1,'') Concats
FROM Vals v
GROUP BY DOC_ID,
[TEXT],
ATTRIBUTE_NAME
) s
PIVOT ( MAX(ConCats) FOR ATTRIBUTE_NAME IN ([Created],[Embedded_Image])) pvt
Output
DOC_ID TEXT Created Embedded_Image
1 Homes of the ... 2010/11/01 Apartment.jpg,Castle.png,Home.png
2 Winter sports ... 2008/06/23 Ski Jump.jpg,Snowboarding.png
From your sample, and with support from common sense, I venture the hypothesis that
A document has a single creation date.
A document can have many embedded images.
So pivoting on creation date is straightforward:
SELECT DOC_ID
, VALUE AS Created
FROM ATTRIBUTE_VALUES
WHERE ATTRIBUTE_ID = 1
and joining this subquery to your Documents table gives you the first three columns of your desired output.
Your final column summarizes multiple embedded images for each document. I personally would use some standard reporting tool (e.g. MS Access or Crystal Reports). Alternatively, create a new empty table with your four desired columns, populate the first three columns with a SQL INSERT statement, and then have Perl (or C#, or your favorite declarative language) query for the embedded images of each document, concatenate the results with commas, and insert the concatenation into your fourth column.
But if you want to do it in SQL, the concatenate-multiple-values question has been asked here before, e.g. in How to create a SQL Server function to "join" multiple rows from a subquery into a single delimited field?.