The multi-part identifier could not be bound - stuff cmd - sql

I'm attempting a Stuff Cmd to combine multiple rows to a single entry. I keep getting "The multi-part identifier "SPCLT.CD_VAL_DESC" could not be bound." (under the first SELECT statement)
STUFF(
(SELECT
**',' + SPCLT.CD_VAL_DESC**
FROM
(
SELECT DISTINCT
SPCLT.CD_VAL_DESC SPECIALTY
FROM PIN_STATUS PS
INNER JOIN PROV_TYPE_SPCLT SPC
ON PS.PROV_ID = SPC.PROV_ID
AND SPC.VLDT_IND = 'Y'
INNER JOIN CODE_REF SPCLT
ON SPC.SPCLT_CD = SPCLT.CD_VAL
AND SPCLT.CD_REF_NM = 'SPECIALTY'
AND SPCLT.VLDT_IND = 'Y'
WHERE SPC.VLDT_IND = 'Y'
) SPCLTY
for xml
path('')
)
,1,1,'') SPECIALTIES

You need to pay attention to the format of your SQL, and then the answer would probably jump out and bite you on the nose... happens to everyone.
Your query:
STUFF(
(
SELECT
',' + SPCLT.CD_VAL_DESC
FROM
(
SELECT DISTINCT
SPCLT.CD_VAL_DESC SPECIALTY
FROM PIN_STATUS PS
INNER JOIN PROV_TYPE_SPCLT SPC
ON PS.PROV_ID = SPC.PROV_ID
AND SPC.VLDT_IND = 'Y'
INNER JOIN CODE_REF SPCLT
ON SPC.SPCLT_CD = SPCLT.CD_VAL
AND SPCLT.CD_REF_NM = 'SPECIALTY'
AND SPCLT.VLDT_IND = 'Y'
WHERE SPC.VLDT_IND = 'Y'
) SPCLTY
for xml path('')
)
,1,1,'') SPECIALTIES
...is divided into sub-queries. The STUFF() function is acting on the first SELECT beneath it.
That first SELECT is taking data FROM a sub-query, which has been aliased as SPCLTY. So, naturally, within that SELECT, you need to be referencing SPCLTY and not SPCLT.
Adding a bit of whitespace makes it a little clearer, I think.

Related

Update a column in Table B with multiple row values from Table A using XML PATH

I have 4 columns in Table A viz., Inv_Num1, Inv_Date1, Inv_Amt1, Inv_DocNum1
I have 4 columns in Table B viz., Inv_Num2, Inv_Date2, Inv_Amt2, Inv_Status2
I would like to match the rows between Table A and Table B by using an inner join where condition on is
Invoice_Num1=Invoice_Num2 AND Invoice_Date1=Invoice_Date2 AND
Invoice_Amt1=Invoice_Amt2
When I do this matching I may get more than 1 row as a result in Table
A (Invoice_DocNum1 column)
I tried XML Path code but I dont know how to implement in Update statement
update cis2
set cis2.Inv_Status2 =
(SELECT
TypeName = STUFF((
SELECT '; ' + imd1.Inv_DocNum1
FROM [VRS].[Table_B] cis1
INNER JOIN [Table_A] imd1
ON cis1.Inv_Num1 = imd1.Inv_Num2
WHERE cis1.Inv_Num1 = imd1.Inv_Num2
AND cis1.Inv_Date1 = imd1.Inv_Date2
AND cis1.Inv_Amt1 = imd1.Inv_Amt2
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
) FROM Table_B cis2
Doing this to your database is against good practices since it violates 1NF. But you could still this if you are deadset on doing it. Something along these lines should work.
with myCte as
(
SELECT Inv_Num1
, TypeName = STUFF((
SELECT '; ' + imd1.Inv_DocNum1
FROM [VRS].[Table_B] cis1
INNER JOIN [Table_A] imd1
ON cis1.Inv_Num1 = imd1.Inv_Num2
WHERE cis1.Inv_Num1 = imd1.Inv_Num2
AND cis1.Inv_Date1 = imd1.Inv_Date2
AND cis1.Inv_Amt1 = imd1.Inv_Amt2
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from Table_A
group by Inv_Num1
)
update tb
set Inv_Status2 = c.TypeName
from Table_B tb
join myCte c on c.Inv_Num1 = tb.Inv_Num2
The answer has two parts. First, you need to produce your comma-separated list per row. The best way to do it is STRING_AGG (https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017)
You will need to use it with the group by, like select ..., STRING_AGG(Inv_DocNum1, ',') group by ... where ... stands for your three fields forming unique key.
Second, you need to use update ... from syntax, see https://learn.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql?view=sql-server-2017#l-specifying-a-table-alias-as-the-target-object. In your case, it will be from your target table joining the resultset you computed at step one.

Why doesn't IN operator work on the result returned by subquery ?

Why doesn't operator work i.e. returns nothing?
Select * from complaints where Code
IN
(select
distinct
stuff((
select ',' + QUOTENAME(cs.Code,'''')
from MC_Complaints.dbo.Complaints cs where cs.OverDue= 1
order by cs.Code asc
for xml path('')
),1,1,'') as codeslist
from MC_Complaints.dbo.Complaints cs)
This query
select
distinct
stuff((
select ',' + QUOTENAME(cs.Code,'''')
from MC_Complaints.dbo.Complaints cs where cs.OverDue= 1
order by cs.Code asc
for xml path('')
),1,1,'') as codeslist
from MC_Complaints.dbo.Complaints cs
returns
'LG/17/05/0','LG/17/05/2','LG/17/05/3','Local Council Board/17/05/1','Local Council Board/17/05/10','Local Council Board/17/05/11'
but IN doesn't pick any record but when I put these codes in IN operator i.e.
Select * from complaints where Code in
(
'LG/17/05/0','LG/17/05/2','LG/17/05/3','Local Council Board/17/05/1','Local Council Board/17/05/10','Local Council Board/17/05/11'
)
then it works surprisingly but not when returned but the subquery. Why ?
The result from the stuff() is a string. Just because the string happens to contain commas does not mean that it is a list of values. The string 'A,B,C' is quite different from the list 'A', 'B', 'C'.
The good news is that this can greatly simplify your logic:
Select c.*
from complaints c
where c.code in (select cs.Code
from MC_Complaints.dbo.Complaints cs
where cs.OverDue = 1
);
Two more points. First, your in expression is followed by a column alias (codelist). That is not permitted syntax.
Second, the relevant comparison would be:
where cCode in (
'''LG/17/05/0'',''LG/17/05/2'',''LG/17/05/3'',''Local Council Board/17/05/1'',''Local Council Board/17/05/10'',''Local Council Board/17/05/11'''
)
That is an in list that has a single string value.

how to omit null values using SQL query

I am trying to only display the rows in which there is date for Researchers.
I cannot manage to omit the rows with Null Values. I even tried this solution How to remove null rows from sql query result?..
This is my Query:
SELECT Submission.Title AS [Submission_Title], CA.Surname AS [Researchers], Submission.Status AS [Status]
FROM Submission
CROSS APPLY (SELECT STUFF((SELECT DISTINCT ', ' + r.Surname
FROM ResearcherSubmission rs INNER JOIN Researcher r
ON r.ResearcherID = rs.ResearcherID
WHERE CONCAT (DATENAME(MONTH,[Submission].[SubmissionDate]), ' ',DATEPART (YEAR,[Submission].[SubmissionDate])) = 'October 2015'
AND Submission.SubmissionID = rs.SubmissionID
FOR XML PATH (''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ')) AS CA (Surname)
GROUP BY convert(varchar(10),datename(month,Submission.SubmissionDate)), Submission.Title, CA.Surname, Submission.Status;
This is my Current output:
any suggestion. Thank you
Quickfix, without reading query:
WITH cte AS
(
SELECT Submission.Title AS [Submission_Title], CA.Surname AS [Researchers], Submission.Status AS [Status]
FROM Submission
CROSS APPLY (SELECT STUFF((SELECT DISTINCT ', ' + r.Surname
FROM ResearcherSubmission rs INNER JOIN Researcher r
ON r.ResearcherID = rs.ResearcherID
WHERE CONCAT (DATENAME(MONTH,[Submission].[SubmissionDate]), ' ',DATEPART (YEAR,[Submission].[SubmissionDate])) = 'October 2015'
AND Submission.SubmissionID = rs.SubmissionID
FOR XML PATH (''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ')) AS CA (Surname)
GROUP BY convert(varchar(10),datename(month,Submission.SubmissionDate)), Submission.Title, CA.Surname, Submission.Status
)
SELECT *
FROM cte
WHERE Researchers IS NOT NULL;
There is probably more elegant solution, but you need to share sample data and structures.
This part may cause problems:
SELECT DISTINCT ', ' + r.Surname
try with CONCAT instead or :
SELECT DISTINCT ', ' + ISNULL(r.Surname, '')
You should filter out the researchers before the group by rather than afterwards. When possible, it is better (performance-wise) to put conditions before aggregation.
SELECT s.Title AS Submission_Title, CA.Surname AS Researchers, s.Status
FROM Submission s CROSS APPLY
(SELECT STUFF((SELECT DISTINCT ', ' + r.Surname
FROM ResearcherSubmission rs INNER JOIN
Researcher r
ON r.ResearcherID = rs.ResearcherID
WHERE s.SubmissionID = rs.SubmissionID
FOR XML PATH (''), TYPE).value('.', 'NVARCHAR(MAX)'
), 1, 2, ' '))
) AS CA(Surname)
WHERE s.SubmissionDate >= '2015-10-01' AND s.SubmissionDate < '2015-11-01' AND
ca.Surname IS NULL
GROUP BY YEAR(s.SubmissionDate), MONTH(s.SubmissionDate), s.Title, CA.Surname, s.Status;
Note the changes made:
Table aliases make the query easier to write and to read.
I changed the date comparison to have no functions on the date itself. This would allow SQL Server to use an index, if appropriate.
I also moved the date comparison from the CROSS APPLY subquery to the outer query. This could be a big gain in efficiency. Why do the extra work for rows that will be filtered out anyway?
I added the NOT NULL condition to the WHERE clause.
The date key in the outer GROUP BY is redundant because the query is only using one month of data. I simplified the logic but left it.

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, '')

carriage return in sql server 2012

Hey I am using the following query to display the problem list separated by commas.
SELECT tt.VrNo, STUFF((select ','+ Er1.ErrorDesc
from ( select * from CallRegErrors )as Main
left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode
WHERE (main.VrNo = tt.VrNo)
FOR XML PATH('')) ,1,1,'') AS Problemlist
query is giving the output like a,b,c,d etc
But my actual requirement is I want to display each error description in a new line like,
a
b
c
d
etc
I tried the following query for it:
SELECT tt.VrNo, STUFF((select char(13)+char(10)+ Er1.ErrorDesc
from ( select * from CallRegErrors )as Main
left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode
WHERE (main.VrNo = tt.VrNo)
FOR XML PATH('')) ,1,1,'') AS Problemlist
and also i have used
SELECT tt.VrNo,Replace(STUFF((select ','+ Er1.ErrorDesc as [text()] from ( select * from CallRegErrors )as Main left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode
WHERE (main.VrNo = tt.VrNo)
FOR XML PATH('')),1,1,''),',',char(13)+char(10)) AS Problemlist
from (select main.VrNo, Er1.ErrorDesc from ( select * from CallRegErrors )as Main left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode )as tt
group by tt.VrNo
but now get the problem list seperated by spaces instead of commas after using the above query
but its does not give the output that i want.
please help..
Thanks in advance
I think we need more information before we can help you.
I think you are trying to format the information at the child level in a parent child relationship into a list. You probably saw something like this blog on the web.
However, your query is not correctly formatted.
Is the ErrorMaster (Production.ProductCategory) the parent and CallRegErrors (SUB.ProductCategoryID) the child?
If so just change the query to those table name field names for it to work.
I used the REPLACE function on the overall result to change COMMAS to CR + LF.
-- Sample database
USE AdventureWorks2012
GO
-- Change SQL from www.sqlandme.com for this users problem
SELECT
CAT.Name AS [Category],
REPLACE(STUFF((
SELECT ',' + SUB.Name AS [text()]
FROM Production.ProductSubcategory SUB
WHERE SUB.ProductCategoryID = CAT.ProductCategoryID
FOR XML PATH('')
), 1, 1, '' ), ',', CHAR(13) + CHAR(10))
AS [Sub Categories]
FROM Production.ProductCategory CAT
You can only see carriage returns in the output window when the type is set to TEXT in SSMS.
I hope this solves your problem. If not, please write back with more information!!