How should I modify this SQL statement? - sql

My SQL Server view
SELECT
geo.HyperLinks.CatID, geo.Tags.Tag, geo.HyperLinks.HyperLinksID
FROM
geo.HyperLinks LEFT OUTER JOIN
geo.Tags INNER JOIN
geo.TagsList ON geo.Tags.TagID = geo.TagsList.TagID ON geo.HyperLinks.HyperLinksID = geo.TagsList.HyperLinksID WHERE HyperLinksID = 1
returns these...
HyperLinksID CatID Tags
1 2 Sport
1 2 Tennis
1 2 Golf
How should I modify the above to have results like
HyperLinksID CatID TagsInOneRowSeperatedWithSpaceCharacter
1 2 Sport Tennis Golf
UPDATE: As Brad suggested I came up here...
DECLARE #TagList varchar(100)
SELECT #TagList = COALESCE(#TagList + ', ', '') + CAST(TagID AS nvarchar(100))
FROM TagsList
WHERE HyperLinksID = 1
SELECT #TagList
Now the result looks like
HyperLinksID CatID TagsInOneRowSeperatedWithSpaceCharacter
1 2 ID_OF_Sport ID_OF_Tennis ID_OF_Golf
And of course I have to combine the contents from the#TagList variable and the original SELECT statement...
Which means that I'll have to wait for the holy SO bounty :(

If SQL, try this post:
Concatenating Row Values
If you want to try your hand at CLR code, there are examples of creating a custom aggregate function for concatenation, again, for MS SQL.
This post is pretty exhaustive with lots of ways to accomplish your goal.

Using the approach from here to avoid any issues if your tag names contain special XML characters:.
;With HyperLinks As
(
SELECT 1 AS HyperLinksID, 2 AS CatID
),
TagsList AS
(
SELECT 1 AS TagId, 1 AS HyperLinksID UNION ALL
SELECT 2 AS TagId, 1 AS HyperLinksID UNION ALL
SELECT 3 AS TagId, 1 AS HyperLinksID
)
,
Tags AS
(
SELECT 1 AS TagId, 'Sport' as Tag UNION ALL
SELECT 2 AS TagId, 'Tennis' as Tag UNION ALL
SELECT 3 AS TagId, 'Golf' as Tag
)
SELECT HyperLinksID,
CatID ,
(SELECT mydata
FROM ( SELECT Tag AS [data()]
FROM Tags t
JOIN TagsList tl
ON t.TagId = tl.TagId
WHERE tl.HyperLinksID = h.HyperLinksID
ORDER BY t.TagId
FOR XML PATH(''), TYPE
) AS d ( mydata ) FOR XML RAW,
TYPE
)
.value( '/row[1]/mydata[1]', 'varchar(max)' ) TagsInOneRowSeperatedWithSpaceCharacter
FROM HyperLinks h
Edit: As KM points out in the comments this method actually automatically adds spaces so I've removed the manually added spaces. For delimiters other than spaces such as commas Peter's answer seems more appropriate.
If you know your data will not contain any problematic characters then a simpler (probably more performant) version is
SELECT CatID ,
HyperLinksID,
stuff(
( SELECT ' ' + Tag
FROM Tags t
JOIN TagsList tl
ON t.TagId = tl.TagId
WHERE tl.HyperLinksID = h.HyperLinksID
ORDER BY t.TagId
FOR XML PATH('')
), 1, 1, '') TagsInOneRowSeperatedWithSpaceCharacter
FROM HyperLinks h

Use FOR XML in a correlated subquery. For a space-delimited list:
SELECT h.HyperLinksID, h.CatID
, TagList = (
SELECT t.Tag AS [data()]
FROM geo.TagList l
JOIN geo.Tags t ON l.TagId = t.TagId
WHERE l.HyperLinksID = h.HyperLinksID
ORDER BY t.Tag
FOR XML PATH(''), TYPE
).value('.','NVARCHAR(MAX)')
FROM geo.HyperLinks AS h
WHERE h.HyperLinksID = 1
For any other delimiter:
SELECT h.HyperLinksID, h.CatID
, TagList = STUFF((
SELECT ', '+t.Tag
FROM geo.TagList l
JOIN geo.Tags t ON l.TagId = t.TagId
WHERE l.HyperLinksID = h.HyperLinksID
ORDER BY t.Tag
FOR XML PATH(''), TYPE
).value('.','NVARCHAR(MAX)')
,1,2,'')
FROM geo.HyperLinks AS h
WHERE h.HyperLinksID = 1
The subquery creates a delimited list, and then STUFF(...,1,2,'') removes the leading ,. TYPE).value() gets around most common problems w/ special characters in XML.

Related

display more than one value using a SQL query

I am trying to display multiple authors per title in a single column. At the moment there a repeating rows, due to the fact that some Titles have more than 1 FirstName. Is there a form of concatenation that can be used to resolve this and display all the authors in a single filed and perhaps separated by a comma.
This is my current query:
SELECT
Submission.Title, Researcher.FirstName, Submission.Type
FROM
Submission
INNER JOIN
((Faculty
INNER JOIN
School ON Faculty.FacultyID = School.[FacultyID])
INNER JOIN
(Researcher
INNER JOIN
ResearcherSubmission ON Researcher.ResearcherID = ResearcherSubmission.ResearcherID)
ON School.SchoolID = Researcher.SchoolID)
ON Submission.SubmissionID = ResearcherSubmission.SubmissionID
GROUP BY
Submission.Title, Researcher.FirstName, Submission.Type;
This the output it generates:
[
this is the output I am trying to generate:
Title FirstName Type
---------------------------------------------------------------------------
21st Century Business Matthew, Teshar Book Chapter
A Family Tree... Keshant, Lawrence Book Chapter
Benefits of BPM... Jafta Journal Article
Business Innovation Matthew, Morna, Teshar Book Chapter
You may inclde the concantenation logic within a CROSS APPLY
SELECT
Submission.Title
, CA.FirstNames
, Submission.Type
FROM Submission
CROSS APPLY (
SELECT
STUFF((
SELECT /* DISTINCT ??? */
', ' + r.FirstName
FROM ResearcherSubmission rs
INNER JOIN Researcher r ON r.ResearcherID = rs.ResearcherID
WHERE Submission.SubmissionID = rs.SubmissionID
FOR XML PATH (''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ')
) AS CA (FirstNames)
GROUP BY
Submission.Title
, CA.FirstNames
, Submission.Type
;
NB: I'm not sure if you need to include DISTINCT into the subquery when concatenating the names, e.g. if these was 'Jane' (Smith) and 'Jane' (Jones) do you want the final list as: 'Jane' or 'Jane, Jane'?
You can do this in your application logic as well.
But if you want to do this with a query. You should be able do something like this:
SELECT DISTINCT
sm.Title,
STUFF(
(SELECT ', ' + r.FirstName
FROM ResearcherSubmission rs
INNER JOIN Researcher r ON r.ResearcherID = rs.ResearcherID
WHERE sm.SubmissionID = rs.SubmissionID
FOR XML PATH('')), 1, 2, '') AS FirstNames,
sm.Type
FROM Submission sm
You can use the below query to generate the o/p as you want from the o/p that you have got.
CREATE TABLE #temptable(Title VARCHAR(200), FirstName VARCHAR(200), Type VARCHAR(200))
INSERT INTO #temptable
SELECT 'Book1','Matt','Chapter' UNION
SELECT 'Book1','Tesh','Chapter' UNION
SELECT 'BPM','Jafta','Article' UNION
SELECT 'Ethics','William','Journal' UNION
SELECT 'Ethics','Lawrence','Journal' UNION
SELECT 'Ethics','Vincent','Journal' UNION
SELECT 'Cellular','Jane','Conference'
SELECT Title
,STUFF((SELECT ', ' + CAST(FirstName AS VARCHAR(10)) [text()]
FROM #temptable
WHERE Title = t.Title
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') List_Output
,Type
FROM #temptable t
GROUP BY Title,Type

trying to concatenate a column into a comma delimited list

i have 3 tables, 1 for products and one for categories the products are assigned to. what IM trying to do is concatenate the column called stCategoryName to a single column in a comma delimited list.
Basically I have the products table containing the primary key for each product and im trying to figure out how to concatenate all the stcategoryName column next to each product so i can have a simplified return
what im trying to get is the following.
stProductID stCategoryName
123 category1,category2,category3
SELECT
dbo.StoreItemTracking.StCategoryID,
dbo.StoreItemTracking.StProductID,
dbo.StoreItemTracking.viewOrder,
dbo.StoreCategories.StCategoryName,
dbo.Store_Products.PartNumber
FROM
dbo.StoreItemTracking
INNER JOIN dbo.StoreCategories
ON dbo.StoreItemTracking.StCategoryID = dbo.StoreCategories.StCategoryID
INNER JOIN dbo.Store_Products
ON dbo.StoreItemTracking.StProductID = dbo.Store_Products.ID
Im stuck as to how to concatenate a column where the query contains 3 tables to select from.
any help greatly appreciated
Look at using coalesce to turn category into a CSV:
See example:
DECLARE #EmployeeList varchar(100)
SELECT #EmployeeList = COALESCE(#EmployeeList + ', ', '')
+ CAST(Emp_UniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1
SELECT #EmployeeList
You can also use CTE's or Subqueries. See:
http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=createacommadelimitedlist
Another nice and easy example:
http://www.codeproject.com/Articles/21082/Concatenate-Field-Values-in-One-String-Using-CTE-i
This:
FId FName
--- ----
2 A
4 B
5 C
6 D
8 E
with:
;WITH ABC (FId, FName) AS
(
SELECT 1, CAST('' AS VARCHAR(8000))
UNION ALL
SELECT B.FId + 1, B.FName + A.FName + ', '
FROM (And the above query will return
SELECT Row_Number() OVER (ORDER BY FId) AS RN, FName FROM tblTest) A
INNER JOIN ABC B ON A.RN = B.FId
)
SELECT TOP 1 FName FROM ABC ORDER BY FId DESC
becomes:
FName
----------------------------
A, B, C, D, E,
Don't understand how your products and categories are connected but in general I do like this to create comma separated lists.
SELECT table1.Id
,Csv
FROM table1
CROSS APPLY (
-- Double select so we can have an alias for the csv column
SELECT (SELECT ',' + table2.Name
FROM table2
WHERE table2.Id = table1.Id
FOR XML PATH('')
) AS RawCsv
) AS CA1
CROSS APPLY (
-- Trim the first comma
SELECT RIGHT(RawCsv, LEN(RawCsv) - 1) AS Csv
) AS CA2

How to concatenate distinct columns of otherwise duplicate rows into one row without using FOR XML PATH('')?

I have this query:
SELECT DISTINCT
f1.CourseEventKey,
STUFF
(
(
SELECT '; ' + Title
FROM (
SELECT DISTINCT
ces.CourseEventKey,
f.Title
FROM CourseEventSchedule ces
INNER JOIN Facility f ON f.FacilityKey = ces.FacilityKey
WHERE ces.CourseEventKey IN
(
SELECT CourseEventKey
FROM #CourseEvents
)
) f2
WHERE f2.CourseEventKey = f1.CourseEventKey
FOR XML PATH('')
), 1, 2, ''
)
FROM (
SELECT DISTINCT
ces.CourseEventKey,
f.Title
FROM CourseEventSchedule ces
INNER JOIN Facility f ON f.FacilityKey = ces.FacilityKey
WHERE ces.CourseEventKey IN
(
SELECT CourseEventKey
FROM #CourseEvents
)
) f1
It produces this result set:
CourseEventKey Titles
-------------- ----------------------------------
29 Test Facility 1
30 Memphis Training Room
32 Drury Inn & Suites Creve Coeur
The data is accurate, but I can't have FOR XML PATH('') because it escapes certain special characters.
To be clear, I'm using FOR XML PATH('') because it is possible for records with the same CourseEventKey to have multiple Facility titles associated with them.
How can I retain the data returned by this query without using FOR XML PATH('')?
Change the "for xml path('') )" section to "for xml path(''), root('root'), type).query('root').value ('.', 'varchar(max)')" this will unescape the characters correctly.
Sorry for the poor formatting but not at my computer right now. I can give a full example later if you need.

Using XML PATH to concatenate multiple fields

I'm having a bit of trouble implementing the XML PATH method of concatenating multiple rows. So, given the following table, Test.
ID Type
1 boy
2 boy
3 boy
1 girl
2 girl
3 man
3 woman
The query is:
SELECT DISTINCT a.ID,
(
SELECT b.Type + ','
FROM Test as b
WHERE a.Type = b.Type
for XML PATH ('')
)
FROM Test as a
but instead of returning:
ID Type
1 boy,girl,man,
2 boy,girl,
3 boy,girl,woman
it instead returns this:
ID Type
1 boy,boy,boy,
1 girl,girl,
2 boy,boy,boy,
2 girl,girl,
3 boy,boy,boy,
3 man,
3 woman,
What's going on?
You're joining on the wrong field.
Try
SELECT DISTINCT a.ID,
(
SELECT b.Type + ','
FROM Test as b
WHERE a.ID = b.ID
for XML PATH ('')
)
FROM Test as a
Instead of using DISTINCT prefer using GROUP BY something like this...
SELECT a.ID,
STUFF((SELECT ', ' + [Type] [text()]
FROM Test
WHERE ID = a.[ID]
for XML PATH (''),TYPE).
value('.','NVARCHAR(MAX)'),1,2,'') AS [Type]
FROM Test as a
GROUP BY a.ID

Flattening multiple rows

I'm trying to combine the results of multiple rows into one. I would like to flatten the first two rows below, and perhaps combine the keywords so that they are in the same column. How can I do that simply using a select statement (i.e. no functions)?
I'm currently getting:
documentid documentname keyword
1 doc1 politics politics italy
2 doc2 politics politics italy
I would like to get:
documentid documentname keyword
1 doc1 politics italy
2 doc2 politics
This is part of my query:
SELECT d.DocumentId AS documentid ,
m.Title AS documentname ,
STUFF(( SELECT N' ' + k.Word
FROM [arabicarchive].[dbo].[Keywords] k
JOIN [arabicarchive].[dbo].DocumentKeywords dk ON k.KeywordId = dk.Keyword_KeywordId
JOIN [arabicarchive].[dbo].Documents d ON dk.Document_DocumentId = d.DocumentId
FOR
XML PATH('')
), 1, 1, '') AS Keyword
FROM [arabicarchive].[dbo].[Metadatas] m
JOIN [arabicarchive].[dbo].[Documents] d ON d.DocumentId = m.DocumentId
WHERE d.Status = 1
EDIT: I have updated the query and the results that I am currently getting. I haven't used STUFF or XML PATH before so please bear with me.
EDIT 2: I have managed to get rid of the duplicate row, but the result in the keyword column is not correct.
EDIT 3: Adding DISTINCT to the query still doesn't produce a keyword column with the correct values.
In an article I once wrote, towards the end, I showed how to do this
Database Migration Scripts: Getting from place A to place B
If you look near the end, you'll see some code that is similar to what you want. I've modified to fit what you want a bit more closely.
SELECT title_ID, title, ltrim(
(SELECT distinct ' '+tagname.tag FROM titles thisTitle
INNER JOIN TagTitle ON titles.title_ID=TagTitle.Title_ID
INNER JOIN Tagname ON Tagname.TagName_ID=TagTitle.TagName_ID
WHERE ThisTitle.title_id=titles.title_ID
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'))
FROM titles
ORDER BY title_ID
I reckon that something like this would work but I've no means of testing it!...
SELECT d.DocumentId AS documentid , m.Title AS documentname ,
ltrim(
( SELECT distinct N' ' + k.Word
FROM [arabicarchive].[dbo].[Keywords] k
JOIN [arabicarchive].[dbo].DocumentKeywords dk ON k.KeywordId = dk.Keyword_KeywordId
Where dk.Document_DocumentId=d.DocumentId
FOR
XML PATH(''), TYPE).value('.', 'nvarchar(max)')
) AS Keyword
FROM [arabicarchive].[dbo].[Metadatas] m
JOIN [arabicarchive].[dbo].[Documents] d ON d.DocumentId = m.DocumentId
WHERE d.Status = 1