T SQL Conditional join based on parameter value - tsql

I have the need to have a inner join based on the value of a parameter I have in a stored procedure. I'm also using a function to split values out of a string of comma separated values. My code is as follows
Select *
from view_Project as vp
join inline_split_me(#earmark) as e on (vp.EarmarkId LIKE e.Value and #earmark IS NOT NULL)
If #earmark is NULL then I don't want this join to happen at all, otherwise if I have a string of '%' or '119' or '119,120,121' this join should happen and does yield the proper results. I would just like to have it not happen at all if #earmark is null, I thought that I could just use the and #earmark is not null to delineate that however it is not returning the proper results, which is discovered by commenting the join line out and running the same sproc with null as the #earmark param, which gives me all rows as a result. When I keep this join and pass null I get no rows, I've been fiddling with this for some time, any help would be appreciated.
Here is the FUNCTION:
[inline_split_me](#param nvarchar(MAX))
RETURNS TABLE AS
RETURN(SELECT ltrim(rtrim(convert(nvarchar(4000),
substring(#param, Number,
charindex(N',' COLLATE SQL_Latin1_General_CP1_CI_AS,
#param + convert(nvarchar(MAX), N','),
Number) -
Number)
))) AS Value
FROM APM_Numbers
WHERE Number <= convert(int, len(#param))
AND substring(convert(nvarchar(MAX), N',') + #param, Number, 1) =
N',' COLLATE SQL_Latin1_General_CP1_CI_AS)
Got it, thanks Cade Roux and others
if (#earmark = '%')
select *
from view_Project as vp
where vp.EarmarkId like #earmark
else
select *
from view_Project as vp
where #earmark is null or vp.EarmarkId in (select Value from inline_split_me(#earmark))

INNER JOIN is your problem. A LEFT JOIN will always return the rows on the LEFT, even though when #earmark is NULL, the join condition can never be true.
Select *
from view_Project as vp
LEFT join inline_split_me(#earmark) as e on (vp.EarmarkId LIKE e.Value and #earmark IS NOT NULL)
You could fool around with a UNION to manufacture rows to join when #earmark is NULL
Select *
from view_Project as vp
INNER join (
SELECT Value, -- columns here ...
FROM inline_split_me(#earmark) as e
UNION ALL
SELECT DISTINCT vp.EarmarkId AS Value, -- NULL, NULL, etc.
FROM view_Project
WHERE #earmark IS NULL
) AS e
ON vp.EarmarkId LIKE e.Value
But frankly, I would just do a conditional logic:
IF #earmark IS NULL
Select *
from view_Project as vp
ELSE
Select *
from view_Project as vp
INNER join inline_split_me(#earmark) as e on (vp.EarmarkId LIKE e.Value and #earmark IS NOT NULL)
If you can get away from LIKE:
Select *
from view_Project as vp
WHERE #earmark IS NULL OR vp.EarmarkId IN (
SELECT Value FROM inline_split_me(#earmark)
)

...as vp join lined_split_me(#earmark) as...
should be defaulting to an inner join, which means that the query only returns rows if matches are found between the two tables. (Double-check by explicitly saying inner join.)
Does the function call return no (zero) rows if #earmark is null? If so, then there should be no rows returned from the query.

I know this question is pretty old, but I was researching a similar issue and came across this and came up with a totally different solution that worked like a charm.
Use a LEFT JOIN, but then have a filter in you WHERE clause that if your parameter is not null, neither can your join match be null. That functionally results in a conditional INNER JOIN.
SELECT *
FROM
A
LEFT JOIN B
ON A.KEY = B.KEY
WHERE
(#JOIN_B IS NOT NULL AND B.KEY IS NOT NULL)
OR #JOIN_B IS NULL

Related

MariaDB - order by with more selects

I have this SQL:
select * from `posts`
where `posts`.`deleted_at` is null
and `expire_at` >= '2017-03-26 21:23:42.000000'
and (
select count(distinct tags.id) from `tags`
inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id`
where `post_tag`.`post_id` = `posts`.`id`
and (`tags`.`tag` like 'PHP' or `tags`.`tag` like 'pop' or `tags`.`tag` like 'UI')
) >= 1
Is it possible order the results by number of tags in posts?
Maybe add there alias?
Any information can help me.
Convert your correlated subquery into a join:
select p.*
from posts p
join (
select pt.post_id,
count(distinct t.id) as tag_count
from tags t
inner join post_tag pt on t.id = pt.tag_id
where t.tag in ('PHP', 'pop', 'UI')
group by pt.post_id
) pt on p.id = pt.post_id
where p.deleted_at is null
and p.expire_at >= '2017-03-26 21:23:42.000000'
order by pt.tag_count desc;
Also, note that I changed the bunch of like and or to single IN because you are not matching any pattern i.e. there is no % in the string. So, better using single IN instead.
Also, if you have defined your table names, column names etc keeping keywords etc in mind, you shouldn't have the need to use the backticks. They make reading a query difficult.

Filtering for NULL on a joined table

I have this query:
select t1.l_id, coalesce(t2.o_id,0) from t1 left join t2 using(l_id);
which gives me a result, with 0 in the second column. However, when I put a where on the column, it gives me no results:
select t1.l_id, coalesce(t2.o_id,0) from t1 left join t2 using(l_id)
where t2.o_id = null;
How can I select records that have no joined record?
Use:
WHERE t2.o_id IS NULL;
instead of:
WHERE t2.o_id = NULL;
If t2.o_id is NULL then the second predicate evaluates to NULL not true as one might expect. This is the so-called three-valued logic of SQL.

TSQL, join to multiple fields of which one could be NULL

I have a simple query:
SELECT * FROM Products p
LEFT JOIN SomeTable st ON st.SomeId = p.SomeId AND st.SomeOtherId = p.SomeOtherId
So far so good.
But the first join to SomeId can be NULL, In that case the check should be IS NULL, and that's where the join fails. I tried to use a CASE, but can't get that to work also.
Am I missing something simple here?
From Undocumented Query Plans: Equality Comparisons.
SELECT *
FROM Products p
LEFT JOIN SomeTable st
ON st.SomeOtherId = p.SomeOtherId
AND EXISTS (SELECT st.SomeId INTERSECT SELECT p.SomeId)

How to use parameters in a SQL query with NOT EXISTS?

How can I change following query, so that I'm able to parameterize the SparePartNames?
It returns all ID's of repairs where not all mandatory spareparts were changed, in other words where at least one part is missing.
Note that the number of spareparts might change in future not only the names. Is it possible without using a stored procedure with dynamic SQL? If not, how could this SP look like?
Edit: Note that i do not need to know how to pass a list/array as parameter, this is asked myriads of time on SO. I've also already a Split table-valued-function. I'm just wondering how i could rewrite the query to be able to join(or whatever) with a list of mandatory parts, so that i'll find all records where at least one part is missing. So is it possible to use a varchar-parameter like '1264-3212,1254-2975' instead of a list of NOT EXISTS? Sorry for the confusion if it was not clear in the first place.
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND (NOT EXISTS
(SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
WHERE (td.fiData = d.idData)
AND (sp.SparePartName = '1264-3212'))
OR (NOT EXISTS
(SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
WHERE (td.fiData = d.idData)
AND (sp.SparePartName = '1254-2975'))
)
)
Unfortunately I don't see how I could use sp.SparePartName IN/NOT IN(#sparePartNames) here.
One way to do it is to create a function to split delimited strings:
CREATE FUNCTION [dbo].[Split]
(
#Delimiter char(1),
#StringToSplit varchar(512)
)
RETURNS table
AS
RETURN
(
WITH Pieces(pieceNumber, startIndex, delimiterIndex)
AS
(
SELECT 1, 1, CHARINDEX(#Delimiter, #StringToSplit)
UNION ALL
SELECT pieceNumber + 1, delimiterIndex + 1, CHARINDEX(#Delimiter, #StringToSplit, delimiterIndex + 1)
FROM Pieces
WHERE delimiterIndex > 0
)
SELECT
SUBSTRING(#StringToSplit, startIndex, CASE WHEN delimiterIndex > 0 THEN delimiterIndex - startIndex ELSE 512 END) AS Value
FROM Pieces
)
populate a table variable with the spare part names:
DECLARE #SpareParts TABLE
(
SparePartName varchar(50) PRIMARY KEY CLUSTERED
);
INSERT INTO #SpareParts
SELECT Value FROM dbo.Split(',', '1264-3212,1254-2975');
and then join to the table variable:
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND EXISTS (
SELECT 1
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
LEFT JOIN #SpareParts AS s ON s.SparePartName = sp.SparePartName
WHERE td.fiData = d.idData
AND s.SparePartName IS NULL
)
Assuming there is (or will be) a table or view of mandatory spare parts, a list of exists can be replaced with a left join to tabDataDetail / tabSparePart pair on SparePartName; non-matches are reported back using td.fiSparePart is null.
; with mandatorySpareParts (SparePartName) as (
select '1264-3212'
union all
select '1254-2975'
)
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND exists
(
SELECT null
from mandatorySpareParts msp
left join ( tabDataDetail AS td
INNER JOIN tabSparePart AS sp
ON sp.idSparePart = td.fiSparePart
AND td.fiData = d.idData
)
ON msp.SparePartName = sp.SparePartName
WHERE td.fiSparePart is null
)
Part names should be replaced by their id's, which would simplify left join and speed the query up.
EDIT: i've errorneously left filtering of td in where clause, which invalidated left join. It is now in ON clause where it belongs.
Use a table-variable and join on that.

How to add a join based on parameter passed in stored procedure

Can I add a inner join or left join or right join based on parameter value. The only way right now I have is writing a dynamic query like
set #sql = 'select * from dbo.products PM(nolock)
'+ case when #orgunit is not null then ' join productorgunit pou on PM.ProductNumber = pou.ProductNumber '
else ''
end
+ '
Exec(#sql).
I hope there is something like
Select * from dbo.products PM(nolock)
case when #orgunit is not null then join productorgunit pou on PM.ProductNumber = pou.ProductNumber
end
Can you not just use a LEFT OUTER JOIN?
SELECT PM.*, pou.ProductNumber
FROM dbo.Products PM LEFT OUTER JOIN ProductOrgUnit pou ON
PM.ProductNumber = pou.ProductNumber
That will return all records from Products, and only return data from ProductOrgUnit if there is a matching record (otherwise the pou fields will be null in the resultset).
Alternatively you could have two separate queries in your sproc and use a T-SQL IF statement to select which one to run.