How to get values other from a string in SQL - sql

I'm using the stuff function to get a list of ids. But there are some values that are duplicate:
----------
3180,3181,3182,3180,4180
----------
3183,3184,3184,4181
I just want to get the list of ids that are not duplicated, desired results:
----------
3181,3182,4180
----------
3183,4181
This my query:
SELECT oa.AssetId,
oal.AssetLineId,
SNIds = STUFF((SELECT ', ' + CAST(isn1.Id AS VARCHAR(MAX))
FROM dbo.InventoryOperation o1
INNER JOIN dbo.InventoryOperationAsset oa1 ON oa1.OperationId = o1.OperationId
INNER JOIN dbo.InventoryOperationAssetLine oal1 ON oal1.OperationAssetId = oa1.OperationAssetId
INNER JOIN dbo.InventoryOperationAssetLineSerialNumber ioalsn1 ON ioalsn1.OperationAssetLineId = oal1.OperationAssetLineId
INNER JOIN dbo.InventorySerialNumber isn1 ON isn1.Id = ioalsn1.SerialId
WHERE oa.AssetId = oa1.AssetId AND oal.AssetLineId = oal1.AssetLineId
FOR XML PATH('')),1,2,'')
FROM dbo.InventoryOperation o
INNER JOIN dbo.InventoryOperationAsset oa ON oa.OperationId = o.OperationId
INNER JOIN dbo.InventoryOperationAssetLine oal ON oal.OperationAssetId = oa.OperationAssetId
INNER JOIN dbo.InventoryOperationAssetLineSerialNumber ioalsn ON ioalsn.OperationAssetLineId = oal.OperationAssetLineId
GROUP BY oa.AssetId, oal.AssetLineId;
Can anyone help me with this issue? Thanks in advance if anyone could help.

You could use
SELECT DISTINCT columns FROM table;
That makes sure you only get non duplicates.

Query that provides data to STUFF function should use following query to get only unique ids (e.g. 3181,3182,4180,3183,4181) and not the ones that have duplicates (e.g. 3180,3184)
SELECT column_name
FROM TABLE_NAME
GROUP BY column_name
HAVING COUNT(*) = 1;
Updating answer with STUFF function sample ...
SELECT STUFF(', ' + REPLACE((SELECT id + ', ' AS 'data()'
FROM stackoverflow
GROUP BY id
HAVING COUNT(*) = 1
FOR XML PATH('')), '', ''), 1, 2, '')
Sample Run

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

Sql: How to combine multiple rows into a string as an expression within an update

In sql (MS sql server specifically) is it possible to combine multiple rows into a single string as an expression which is itself part of an update that is being applied to multiple rows. I have come across the approaches of using COALESCE or FOR XML PATH (e.g. How to get multiple rows into one line as a string? ) but can't get them to work in my more complex case with the extra dimension of 'listiness'.
My problem boils down to, in words:
A Project has some Launches. A Launch has a LaunchType and a date. I have a big output table of projects ProjectOutput and I want to update a column in it with a CSV string of all the launch type names for that project that happen in the same month as the first (chronologically) launch of that project.
In sql:
UPDATE ProjectOutput
SET LaunchNamesColumn = <INSERT MAGICAL SQL STRING CONCATTING ACROSS ROWS FUNCTION HERE> of Launch.name
FROM ProjectOuput
INNER JOIN Launch ON Launch.projectId = ProjectOutput.projectId
INNER JOIN LaunchType AS lt ON LaunchType.launchTypeId = Launch.launchTypeId
OUTER APPLY (
SELECT TOP 1 Launch.month, Launch.year
FROM Launch
INNER JOIN Project ON Project.projectId = Launch.projectId
WHERE Project.projectId = ProjectOutput.projectId
--In reality there's loads more JOINS and WHERE conditions here
ORDER BY Launch.date
) firstLaunch
WHERE Launch.month = firstLaunch.month AND Launch.year = firstLaunch.year
If there were only 1 Launch per Project then the stuff would not be needed and just
SET LaunchNameColumn = Launch.name
However as there can be several Launches per Project some operation is needed to join them. I tried:
SET LaunchNamesColumn = STUFF((SELECT ', ' + lt.name FROM lt FOR XML PATH('')), 1, 2, '')
However that doesn't work (error, invalid name) because it doesn't know what the alias lt is inside that SELECT. If you just say LaunchType or dbo.LaunchType then the query runs but then you are just looping over all the possible launch types rather than only those returned by the big query below. What I really want is for that FROM in the SELECT FOR XML PATH is to be the result set of the giant query below (whereas in all the examples I've found so far it's just a simple table), but copying and pasting that in seems so wrong. Maybe there is some mental block or sql feature I'm unaware of that would make this work, or is it not possible?
The problem you have is that in the SET stage of your query you only have access to one of the matching Launches as there is no grouping applied.
You can achieve want you want by moving your Launch lookup into a sub-query over the ProjectOutput rows. A simplified example:
UPDATE ProjectOutput
SET LaunchNamesColumn = STUFF((
SELECT ', ' + Launch.name
FROM Launch
-- OUTER APPLY is not required within the sub-query.
INNER JOIN (
SELECT TOP 1 Launch.month, Launch.year
FROM Launch
-- Filter results to specific project.
WHERE Launch.projectId = ProjectOutput.projectId
ORDER BY Launch.date
) firstLaunch ON Launch.month = firstLaunch.month AND Launch.year = firstLaunch.year
-- Filter results to specific project.
WHERE Launch.projectId = ProjectOutput.projectId
FOR XML PATH('')
), 1, 2, '')
FROM ProjectOutput
Logically the sub query is run once per ProjectOutput record, allowing you to filter and group by each ProjectId.
Also nice bit of syntax that may simplify your query is SELECT TOP WITH TIES,
UPDATE ProjectOutput
SET LaunchNamesColumn = STUFF((
SELECT TOP (1) WITH TIES ', ' + Launch.name
FROM Launch
WHERE Launch.projectId = ProjectOutput.projectId
ORDER BY Launch.Year, Launch.Month
FOR XML PATH('')
), 1, 2, '')
FROM ProjectOutput
This will return all the matching Launches that have the lowest Year then Month value.
It's a little bit difficult to understand your SQL without description of the tables, but what you should do is have the query with the XML path so that it returns only those items that you want to be concatenated for that single row, so my guess is that you want actually something like this:
UPDATE O
SET LaunchNamesColumn = STUFF((SELECT ', ' + lt.Name
From Launch L
INNER JOIN Launch L ON L.projectId = O.projectId
INNER JOIN LaunchType AS lt ON lt.launchTypeId = L.launchTypeId
WHERE L.month = FL.month AND L.year = FL.year
FOR XML PATH('')), 1, 2, '')
FROM ProjectOutput O
CROSS APPLY (
SELECT TOP 1 L2.month, L2.year
FROM Launch L2
WHERE L2.projectId = O.projectId
-- Removed the other tables from here. Are they really needed?
ORDER BY L2.date
) FL
Couldn't really test this, but hopefully this helps.
Can you add the Launch and LaunchType tables into your STUFF and filter it based on the Project table or Launch table in the main query?
STUFF((SELECT ', ' + lt.name
FROM Launch l
JOIN LaunchType lt2 ON lt2.launchTypeId = l.launchTypeId
WHERE
l.projectId = Launch.projectId
FOR XML PATH('')), 1, 2, '')
Or you could maybe create a CTE and select all of the launches then use your Stuff statement on the CTE
WITH cteLaunch AS (
SELECT l.projectId,
lt.NAME
FROM Launch ON Launch.projectId = ProjectOutput.projectId
INNER JOIN LaunchType AS lt ON LaunchType.launchTypeId = Launch.launchTypeId
OUTER APPLY (SELECT TOP 1
Launch.month,
Launch.year
FROM
Launch
INNER JOIN Project ON Project.projectId = Launch.projectId
WHERE
Project.projectId = ProjectOutput.projectId
ORDER BY Launch.date
) firstLaunch
WHERE Launch.month = firstLaunch.month
AND Launch.year = firstLaunch.year
)
UPDATE
ProjectOutput
SET
LaunchNamesColumn = STUFF((SELECT ', ' + lt.name
FROM cteLaunch cte
WHERE cte.projectId = ProjectOuput.projectId
FOR XML PATH('')), 1, 2, '')
FROM
ProjectOuput
INNER JOIN cteLaunch ON cteLaunch.projectId = ProjectOutput.projectId
I think you are really close; it's the alias getting in the way:
SET LaunchNamesColumn = STUFF((SELECT ', ' + lt.name
FROM LaunchType AS lt
WHERE lt.launchTypeId = Launch.launchTypeId FOR XML PATH('')), 1, 2, '')

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

ORDER BY issue in Select distinct case

SELECT DISTINCT
CAST(dpt.TrackId AS varchar(20)) + ' , ' + CONVERT(VARCHAR(19), dpt.CreatedDate) AS TrackId
FROM
ExcelUtilityTemplate
INNER JOIN
DataImportProcessTracker dpt ON ExcelUtilityTemplate.TemplateId = dpt.TemplateId
INNER JOIN
TA_JDSU..Employee_Temp et ON et.TrackId = dpt.TrackId
WHERE
(ExcelUtilityTemplate.ClientId = 'e0a94231-3265-4277-9cc3-236d9a2ead49'
AND et.RowStatus = 1)
ORDER BY
dpt.CreatedDate desc
My above query throws the following error:
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
I want to sort my records on the basis of CreatedDate.
Hoping for quick reply. Thanks
Modify your query as follows:
SELECT distinct Cast(dpt.TrackId as varchar(20)) +' , '+ CONVERT(VARCHAR(19),dpt.CreatedDate) as TrackId ,dpt.CreatedDate
FROM ExcelUtilityTemplate INNER JOIN
DataImportProcessTracker dpt ON ExcelUtilityTemplate.TemplateId = dpt.TemplateId
inner join TA_JDSU..Employee_Temp et on et.TrackId=dpt.TrackId
WHERE (ExcelUtilityTemplate.ClientId = 'e0a94231-3265-4277-9cc3-236d9a2ead49' and et.RowStatus=1) order by dpt.CreatedDate desc
Explanation:
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
When ever you are using distinct with select and ordering by some field. Then , That field should be included in your select clause
Hope this helps..
You'll need to order by the same value as you're doing DISTINCT on, since otherwise you may end up with more than one value to order by for each row;
SELECT distinct CAST(dpt.TrackId as varchar(20)) +' , '+
CONVERT(VARCHAR(19),dpt.CreatedDate) as TrackId
FROM ExcelUtilityTemplate
INNER JOIN DataImportProcessTracker dpt
ON ExcelUtilityTemplate.TemplateId = dpt.TemplateId
INNER JOIN TA_JDSU..Employee_Temp et
ON et.TrackId=dpt.TrackId
WHERE (ExcelUtilityTemplate.ClientId = 'e0a94231-3265-4277-9cc3-236d9a2ead49'
AND et.RowStatus=1)
ORDER BY CAST(dpt.TrackId as varchar(20)) +' , '+
CONVERT(VARCHAR(19),dpt.CreatedDate) DESC

Grouping the results of multiple rows in one column [duplicate]

This question already has answers here:
Can I Comma Delimit Multiple Rows Into One Column? [duplicate]
(5 answers)
Closed 10 years ago.
The query below gives me two results where everything is same except the last column v2_roles.id. Is there a way to group them in one column (may be a comma seprated list) and get just one result back
SELECT v2_admin.adminid,
v2_admin.adminname,
v2_admin.login,
v2_admin.email,
v2_roles.id
FROM v2_admin
INNER JOIN v2_admin_roles
ON v2_admin.adminid = v2_admin_roles.adminid
INNER JOIN v2_roles
ON v2_admin_roles.roleid = v2_roles.id
WHERE v2_admin.adminid = 2
This should also work,
;WITH mytable AS (
SELECT v2_admin.adminid,
v2_admin.adminname,
v2_admin.login,
v2_admin.email,
v2_roles.id
FROM v2_admin
INNER JOIN v2_admin_roles
ON v2_admin.adminid = v2_admin_roles.adminid
INNER JOIN v2_roles
ON v2_admin_roles.roleid = v2_roles.id
WHERE v2_admin.adminid = 2
)
SELECT t1.adminid,t1.adminname, t1.login, t1.email,
roleIds =REPLACE( (SELECT convert(varchar,id) AS [data()]
FROM mytable t2
WHERE t2.adminid = t1.adminid
--ORDER BY t2.adminid
FOR XML PATH('')
), ' ', ' , ')
FROM mytable t1
GROUP BY t1.adminid,t1.adminname, t1.login, t1.email ;
This is a little trick using FOR XML to convert the field to a list and STUFF to remove the beginning comma -- this should be close (untested):
SELECT v2_admin.adminid,
v2_admin.adminname,
v2_admin.login,
v2_admin.email,
STUFF(
(
SELECT ',' + CAST(v2_roles.id as varchar) AS [text()]
FROM v2_roles
WHERE v2_admin_roles.roleid = v2_roles.id
ORDER BY v2_roles.id
FOR XML PATH('')
), 1, 1, '') AS Roles
FROM v2_admin
INNER JOIN v2_admin_roles
ON v2_admin.adminid = v2_admin_roles.adminid
WHERE v2_admin.adminid = 2
GROUP BY v2_admin.adminid,
v2_admin.adminname,
v2_admin.login,
v2_admin.email