Adding WHERE clause to outer query causes inner query to error - sql

The below query worked until I added a WHERE clause.
SELECT
PersonTable.FullName,
View_PersonToHead.DirectorId
FROM PersonTable
LEFT JOIN View_PersonToDirector ON PersonTable.PersonId = View_PersonToDirector.PersonId
WHERE View_PersonToDirector.DirectorId = 12345 --No error if this line is removed
The error message is:
Invalid length parameter passed to the RIGHT function.
This leads me to believe that there was an error with how I wrote the View_PersonToDirector view. Something about adding the WHERE clause causes the query to be evaluated/optimized in a different way, exposing some issue.
Internals of View_PersonToDirector:
WITH items AS (
SELECT
PersonId,
0 AS [Level],
CAST(PersonId AS VARCHAR(255)) AS [Path]
FROM PersonTable
UNION ALL
SELECT
e.PersonId,
[Level] + 1,
CAST([Path] + ' < ' + CAST(e.PersonId AS VARCHAR(255)) AS VARCHAR(255))
FROM PersonTable e
INNER JOIN items itms ON itms.PersonId = e.ManagerId
)
SELECT
A.PersonId,
CASE
WHEN A.[Level] = 1
THEN A.PersonId
ELSE CAST(LEFT(A.PathToDirector, CHARINDEX(' ', A.PathToDirector)) AS INT)
END AS DirectorId
FROM (
SELECT
items.PersonId,
items.[Level],
RIGHT(items.[Path], LEN(items.[Path])-7) AS PathToDirector
FROM items
WHERE Path LIKE '1111 < %' --Id of director
) A
I suspect that having a WITH cte in the view causes the query optimizer to work differently, applying the WHERE filtering in a different order. Is this bad practice?

One of your problems is definitely in this code:
RIGHT(items.[Path], LEN(items.[Path])-7) AS PathToDirector
I appreciate that you think that this WHERE clause fixes the problem:
WHERE Path LIKE '1111 < %'
But it does not. The problem is that SQL Server does not guarantee the order of evaluation of expressions. This true even for subqueries, CTEs, and views. These can be evaluated in any order. In fact, SQL Server sometimes pushes the evaluation to the node that reads from the table -- which is why you get an error.
Personally, I think this is a bug in SQL Server. Those powers that be do not agree. Here are two solutions:
(CASE WHEN Path LIKE '1111 < %' THEN RIGHT(items.[Path], LEN(items.[Path])-7) END) AS PathToDirector
Or:
(CASE WHEN Path LIKE '1111 < %' THEN RIGHT(items.[Path], LEN(items.[Path] + '1234567')-7) END AS PathToDirector
You may have the same problem with the CHARINDEX().

The use of a where condition related to column from left join table generate and implicit inner join
In this case add the condition to the ON clause for let the left join work
SELECT
PersonTable.FullName,
View_PersonToHead.DirectorId
FROM PersonTable
LEFT JOIN View_PersonToDirector ON PersonTable.PersonId = View_PersonToDirector.PersonId
AND View_PersonToDirector.DirectorId = 12345
for the length problem try use conditional eg using case
SELECT
items.PersonId,
items.[Level],
case when LEN(items.[Path]) > 7 then RIGHT(items.[Path], LEN(items.[Path])-7)
ELSE items.[Path] END AS PathToDirector
FROM items

The base part of your rCTE has:
CAST(PersonId AS VARCHAR(255)) AS [Path]
Which means base rows of the rCTE would contain values such as 1234 which are shorter than 7 characters and cause RIGHT(x, LEN(x) - 7) to fail. The condition:
RIGHT(items.[Path], LEN(items.[Path])-7) AS PathToDirector
Could be safely written as:
SUBSTRING(items.[Path], NULLIF(CHARINDEX(' < ', items.[Path]), 0) + 3, LEN(items.[Path]))

Related

Single Query To Select Based On Parameters If Or not supplied

I have used SqlDataSource and have a select query based on District & Zone as below
SELECT a.[committee_id] memberid, a.[membername], a.[memberemail], a.[memberdesignation], a.[membercreatedby],b.districtname AS district,b.districtid,c.zone_name AS zone,c.zoneid
FROM [committee_details] a
LEFT JOIN district_master b on b.districtid=a.districtid
LEFT JOIN zone_master c on c.districtid=a.districtid and c.zoneid = a.zoneid
WHERE (a.[membercreatedby] = 'director') AND ((convert(varchar,a.districtid) LIKE '%2%') AND (convert(varchar,a.zoneid) LIKE '%25%')) ORDER BY a.[committee_id] DESC
It's an inline query. I have tried above query but not able to figure out how to Select condition based.
I want if district supplied then Select according to District, if both District & Zone supplied then Select according to both & If nothing supplied then Select All. But should be in single query. How should I do this?
First, fix your query so you are not using meaningless table aliases. Use table abbreviations! I would also drop all the square braces; they just make the query harder to write and to read.
Basically, you want comparisons with NULL in the WHERE clause. I have no idea why your sample code uses LIKE, particularly columns that appear to be numbers. Nothing in the question explains why LIKE is used for the comparison, so the idea is:
SELECT cd.committee_id as memberid, cd.membername,
cd.memberemail, cd.memberdesignation, cd.membercreatedby,
dm.districtname AS district, dm.districtid,
zm.zone_name AS zone, zm.zoneid
FROM committee_details cd LEFT JOIN
district_master dm
ON cd.districtid = dm.districtid LEFT JOIN
zone_master zm
ON zm.districtid = cd.districtid AND
zm.zoneid = cd.zoneid
WHERE cd.membercreatedby = 'director') AND
(cd.districtid = #district or #district is null) AND
(cd.zoneid = #zone or #zone is null)
ORDER BY cd.[committee_id] DESC;
If you were using LIKE, then I would phrase the logic like:
WHERE cd.membercreatedby = 'director') AND
(cast(cd.districtid as varchar(255)) like #district) AND
(cast(cd.zoneid as varchar(255)) like #zone)
And pass in the patterns as '%' when you want all values to match. This assumes that the columns in cd are not NULL. If they can be NULL, then you want an explicit comparison, as in the first example.
If I got the question right then you can use parameters and compare to the column itself if the values are not supplied or not present.
try the following:
SELECT a.[committee_id] memberid, a.[membername], a.[memberemail], a.[memberdesignation], a.[membercreatedby],b.districtname AS district,b.districtid,c.zone_name AS zone,c.zoneid
FROM [committee_details] a
LEFT JOIN district_master b on b.districtid=a.districtid
LEFT JOIN zone_master c on c.districtid=a.districtid and c.zoneid = a.zoneid
WHERE (a.[membercreatedby] = 'director')
AND b.districtname = isnull(nullif(#districtname, ''), b.districtname)
AND c.zone_name = isnull(nullif(#zone_name, ''), c.zone_name)
ORDER BY a.[committee_id] DESC

Adding an aggregate column causing GROUP BY error

I'm adding an aggregate column min(finumber) to my query. Once I add this number I want to be able to use it in the ORDER BY. When I remove the min(finumber) the query works fine. When I add min(finumber) it gives me the error:
Column 'soitem.fsono' is invalid in the select list because it is not
contained in either an aggregate function or the GROUP BY clause.
My question is: How am I able to add min(finumber) and use it to ORDER BY my data?
SELECT A.fsono,
MAX(A.Attention) AS Attention,
MAX(A.Address1) AS Address1,
MAX(A.Address2) AS Address2,
A.fcustno,
A.fcontact,
A.min_fin
FROM (SELECT soitem.fsono,
somast.fcustno,
somast.fcontact,
min(finumber)as min_fin,
CASE
WHEN (CONVERT(varchar(MAX), soship.fmstreet)) LIKE 'ATTN%' THEN LEFT(CONVERT(varchar(MAX), soship.fmstreet), CHARINDEX(CHAR(10), CONVERT(varchar(MAX), soship.fmstreet)))
ELSE NULL
END AS Attention,
CASE WHEN [id] = 2 THEN S.val ELSE NULL END AS Address1,
CASE WHEN [id] = 3 THEN S.val ELSE NULL END AS Address2
FROM soitem
INNER JOIN somast ON soitem.fsono = somast.fsono
LEFT OUTER JOIN soship ON somast.fsono = soship.fcsono
AND soship.fcenumber = ''
CROSS APPLY [dbo].[split3](soship.fmstreet, CHAR(13) + CHAR(10)) S
WHERE (somast.fstatus <> 'Cancelled')
AND (somast.fsocoord = 'IFP'
OR somast.fsocoord = '711')
AND somast.fsono >= '034023') A
GROUP BY A.fsono,
A.fcustno,
A.fcontact
Order by A.fsono, A.min_fin
;
The error is telling you the problem here.
min(finumber) is in your subquery, not your main query. Your sub query doesn't have a GROUP BY, hence the error. You'll need to use an OVER clause. This is pseudo SQL, however:
MIN(finumber) OVER (PARTITION BY {Partition Columns}) AS min_fin
I would guess those columns would be soitem.fsono, somast.fcustno, somast.fcontact.

Complex Query Incorrect Syntax Error

I have tried to search for a solution to this but I have not been able to find one that fits this situation.
First I must say that my SQL is a lot rusty. The following query is the most complex one I have ever done to date.
Here is the query:
Declare #root varchar(Max)
set #root = ''
Select
ib.irrnum, ib.status, pu.probtype,
ib.submitby, ta.[Task Action], ib.area, co.cost,
rc.rootcause, ib.jobnum
From
tbl_irrbase ib
Left Join
tbl_cost co On ib.irrnum = co.irrnum
Left join
(Select Distinct probtype, irrnum
From tbl_probtype) pu On ib.irrnum = pu.irrnum
Left Join
(Select Distinct rootcause, irrnum
From tbl_rtcause) rc On ib.irrnum = rc.irrnum
Left Join
(Select TOP 1
(owner + Space(1) + Convert(varchar(10), senddate, 101) + Space(1) + taskitem) As 'Task Action',
irrnum
From
(select * From tbl_taskaction) ta
Order by
senddate Desc, sendtime Desc) ta On ib.irrnum = ta.irrnum
left Join
(Select [#root] = #root + rs.rootsource + Space(3), irrnum
From tbl_rtsource rs
Where rs.entrydate Between '10/04/2016' And '10/06/2016'
Select #root As 'Root Source') sr On ib.irrnum = sr.irrnum
Where
ib.submitedate between '09/28/2016' And '10/05/2016'
My problem is with the last Left Join line. If I take the entire Select statement out and run it in SSMS it runs fine, no errors. But when I try and run it in this query I get an error, red squiggly line under 'Select #root As' telling me the following:
Incorrect Syntax near 'Select'.
Expecting ')', EXCEPT, or UNION
I do not know how to fix this. If I remove this last 'Left Join' line the query runs fine.
Any ideas?
Instead of the last LEFT JOIN, try something like this:
CROSS APPLY (
SELECT (
SELECT rs.rootsource + Space(3)
From tbl_rtsource rs
Where rs.entrydate Between '10/04/2016' And '10/06/2016'
AND rs.irrnum=ib.irrnum
FOR XML PATH('')
) AS rootsource
) sr
Then include sr.rootsource in the columns of the first SELECT.

How to join two table with partial match

I have two table with the following data:
TableA.name
R4.23-Core-2
R4.23-Core-2
LA#213 CGHPBXsw01 127.213 0024-737e-e341
LA#252 CGHRack1sw01 127.252 0022-57ab-d781
SOC-01A-SW01
to - R4-DISTR-9512
to-R2-DISTR-5900-1
to-R3.25-EDGE
TableB.caption
R4.23-Core-2.ehd.ca
R4.23-Core-2.nhd.ca
CGHPBXsw01
CGHRack1sw01
SOC-01A-SW01
R4-DISTR-9512
R2-DISTR-5900-1.phsnc.
R3.25-EDGE.phsne.edjc.ca
I've tried using the following join statement but it doesn't seem to work for any row with a . in it.
dbo.TableA.Name
INNER JOIN dbo.TableB.Caption
ON dbo.TableA.Name LIKE '%' + dbo.TableB.Caption + '%'
I also try using replace function, which work but there are too much variant to include with replace.
I could try using the RIGHT or LEFT function to normalize the data but for row that doesn't have '.' it would throw an error. And I don't know how to skip row that doesn't have '.'
What is the most efficient way to join these two table?
In some situations in your example the caption is longer, and other times the name is longer, if you wanted to join on any value where name is in the caption or caption is in the name you could use:
dbo.TableA.Name
INNER JOIN dbo.TableB.Caption
ON dbo.TableA.Name LIKE '%' + dbo.TableB.Caption + '%'
OR dbo.TableB.Caption LIKE '%' + dbo.TableA.Name + '%'
That could explain why your query isn't working as expected.
As far as the most efficient way to do this, you'd want to have a standardized field in your table that you could use to JOIN on via equality (ex. a.col1 = b.col1), so that would entail stripping out the heart of each field that makes it join-worthy.
Update: If the important part is everything before the first period, then you want to use a combination of LEFT() and CHARINDEX() (and a CASE statement since not all strings contain a period):
SELECT NewField = CASE WHEN CHARINDEX('.',Name) > 0 THEN LEFT(Name,CHARINDEX('.',Name)-1)
ELSE Name
END
FROM YourTable
You could use the above in your JOIN too:
dbo.TableA.Name
INNER JOIN dbo.TableB.Caption
ON CASE WHEN CHARINDEX('.',TableA.Name) > 0 THEN LEFT(TableA.Name,CHARINDEX('.',TableA.Name)-1)
ELSE TableA.Name
END
= CASE WHEN CHARINDEX('.',TableB.Caption) > 0 THEN LEFT(TableB.Caption,CHARINDEX('.',TableB.Caption)-1)
ELSE TableB.Caption
END
How about this ( Not Tested)
dbo.TableA
INNER JOIN dbo.TableB
ON CHARINDEX(dbo.TableB.Caption, dbo.TableA.Name) > 0
Test it and don't forget Upvote OR accept.

Update table with date conversion failing on Multi-Part Identifier

Thanks to the guys with suggestions on date conversion yesterday, I now have a working SELECT script.
Unfortunately, when I try to turn it into an UPDATE script, I get the dreaded
Msg 4104, Level 16, State 1, Line 25
The multi-part identifier "mbrProject.ID" could not be bound.
The entire script should insert the date from mbrProject into ProjectDates, for each matching ID (mbrProject.[ID] = ProjectDates.[Project_ID]), whilst resolving mixed US & UK format dates in mbrProject, due to users having incorrect country settings when updating the tables (not changeable from my end).
Update ProjectDates
SET ProjectDates.[Start_Date] =
(
select right([Start_Date],4) +
case
when len([Start_Date])=10 then
substring([Start_Date],4,2) + left([Start_Date],2)
else right('0' + left([Start_Date],firstIndex-1),2) +
right('0' + substring([Start_Date],firstIndex+1,secondIndex - firstIndex-1),2)
end
from
(
select mp.[Start_Date], charindex('/',mp.[Start_Date],1) firstIndex,
charindex('/',mp.[Start_Date],charindex('/',mp.[Start_Date],1)+1) secondIndex
from mbrProject mp
join ProjectDates pd
on mp.ID = pd.Project_ID
)A
where mbrProject.[ID] = ProjectDates.Project_ID
)
If I run ONLY the SELECT statement, it works fine, returning the correct values.
i.e Everything from
select right([Start_Date],4) +
case
down to
from mbrProject mp
join ProjectDates pd
on mp.ID = pd.Project_ID
)A
However, as soon as I try to wrap it up in the UPDATE statement, I get the error.
I know that this is down to syntax, but wherever I try to move stuff, I can't quite figure it out.
Any ideas please?
Thanks
Craig
Try this:
UPDATE PR
SET PR.[Start_Date] = A.[Corrected_Start_Date]
FROM ProjectDates PR
JOIN
(
SELECT
[Id],
RIGHT([Start_Date],4) +
CASE
WHEN len([Start_Date])=10
THEN substring([Start_Date],4,2) + LEFT([Start_Date],2)
ELSE RIGHT('0' + LEFT([Start_Date],firstIndex-1),2) +
RIGHT('0' + substring([Start_Date],firstIndex+1,secondIndex - firstIndex-1),2)
END [Corrected_Start_Date]
FROM
(
SELECT
[Id],
[Start_Date],
charindex('/',[Start_Date],1) firstIndex,
charindex('/',[Start_Date],charindex('/',[Start_Date],1)+1) secondIndex
FROM mbrProject
) S
) A
ON A.[ID] = PR.[Project_ID]
(
select mp.[Start_Date], charindex('/',mp.[Start_Date],1) firstIndex,
charindex('/',mp.[Start_Date],charindex('/',mp.[Start_Date],1)+1) secondIndex
from mbrProject mp
join ProjectDates pd
on mp.ID = pd.Project_ID
)A
At the end of the last line here, the names mp, pd, mbrProject and (the inner ProjectDates) no longer exist as table names or aliases. The only name applicable to this row set from here on is A, the alias you've provided.
So you need to include mp.ID in your select list, and then use A.ID in your outer comparison.
Also, not knowing what you're trying to do, I'd just want to make sure you're aware that ProductDates (the table being updated) and pd are referring to two separate instances of that table, an inner one and an outer one. I'm hoping that the final WHERE should be:
where A.[ID] = ProjectDates.Project_ID
The problem is that doing
Update ProjectDates
SET ProjectDates.[Start_Date] = (select ...)
you are trying to set one field of one row to the results of the sub-select statement, which, I presume, is returning multiple values.