How to prevent not in results from appending to union clause - sql

I am trying to union two result sets and omit the results from the first set when returning the second unioned set.
However, when the second set is selected it is appending the data from the sub-query.
declare #bool nvarchar(1) = 'Y'
select [name[
from table_1
where #bool = 'N'
union
select [name]
from table_2 t2
where #bool = 'Y'
and [name] not in (
select [name]
from table_1
)
How can I select the second part when #bool = 'Y' without the not in sub-query appending its results to the dataset?

Related

How can I create a conditional WHERE clause in SQL Server that returns multiple values?

I have the following SQL code for a SSRS report. I simplified the code because the original query is much longer.
There is a parameter #ARTICLE which a user can input. What I want to do is create a conditional WHERE statement. If a user enters an article number (#ARTICLE) the query should filter ID's from Table1 that match with ID's for which the entered article number (#ARTICLE) have a match with a 'detailcode' from another table. If there is no article number given, do not filter (or skip the whole WHERE statement)
With the code below I get the following error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.'
Logically it works perfectly fine without the CASE statement, so when only the subquery is used to check for matching ID's. However, I only want to return matching IDs if the #ARTICLE parameter has a value. If it is NULL or an empty string I want to return all IDs (or just skip the entire WHERE statement). How can I include a condition in the WHERE clause that allows multiple rows to return given the example below?
I feel like my approach is way to complicated, any help is much appreciated!
DECLARE #ARTICLE AS VARCHAR(50) = '1234567'
SELECT * FROM Table1
WHERE
Table1.ID IN (
CASE
WHEN ISNULL(#ARTICLE,'')<>'' THEN
(
SELECT ID
FROM Table2
WHERE detailcode IN (#ARTICLE)
)
ELSE Table1.ID
END
)
You're right, you're overcomplicating it a bit - if you look at the LIKE operator you can do something like:
DECLARE #filter NVARCHAR(50) = '123456';
DECLARE #f NVARCHAR(100) = '%' + #filter + '%';
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField]
AND [t2].[detailCode] LIKE #f;
Where #filter is a parameter to the stored procedure.
Or to account for detailCode being null:
DECLARE #filter NVARCHAR(50) = '123456';
DECLARE #f NVARCHAR(100) = '%' + #filter + '%';
IF #filter != NULL
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField]
AND [t2].[detailCode] LIKE #f;
ELSE
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField];
I would check wether #ARTICLE is NULL or if it is NOT NULL and your subquery is fulfilled, like so:
WHERE
ISNULL(#ARTICLE, '') = ''
OR
(
ISNULL(#ARTICLE, '') <> ''
AND ID IN
(
SELECT ID FROM Table2
WHERE detailcode = #ARTICLE
)
)
Maybe you can do it entire different, with an exists for example.
So you return all rows when #ARTICLE is null or '' OR exists at least one row in table2 with this article
The OR will have the effect that no filtering is done when the variable is null or ''
DECLARE #ARTICLE AS VARCHAR(50) = '1234567'
select t1.*
from table1 t1
where ( isnull(#ARTICLE, '') = ''
or
exists ( select 1 from table2 t2 where t2.detailcode = #ARTICLE )
)

Select dummy values if where clause returns zero rows

I am using SQL Server 2016. I have a SQL and it has where clause which returns zero or non-zero rows depending on data present in the table at a specific time. For example the SQL is:
select step_id,
step_name
from msdb.dbo.sysjobhistory
where step_id = 9999
What I want is when the SQL returns zero rows, then it should return one row with dummy values that I can specify.
What I tried: I can do this by using this SQL:
DECLARE #Condition int
SET #Condition = 0;
IF (
SELECT count(*) FROM
(
select step_id,
step_name
from msdb.dbo.sysjobhistory
where step_id = 9999
) t1
) > 0
BEGIN
SET #Condition = 1;
END;
IF #Condition = 1
BEGIN
select step_id,
step_name
from msdb.dbo.sysjobhistory
where step_id = 9999
END
ELSE
BEGIN
SELECT 123456 AS [step_id], 'There are no step_id matching the WHERE clause' AS [step_name]
END
Question: But here I am running the same SQL twice. I want to know if there is any better way to do this.
The problem is, that if the WHERE clause eliminates all rows, you end up with an empty set.
One way around it, is to use an aggregate function to force one row in the result.
Then, with COALESCE (or ISNULL), you can assign your default values.
This solution is only feasible if:
your basic query always returns exactly 0 or 1 row
your columns do not contain NULL
the selected columns can be aggregated (e.g. it will not work for text columns)
your default values have the same data type as the columns
For example:
select COALESCE(MAX(step_id),123456) AS step_id,
COALESCE(MAX(step_name),'There are no step_id matching the WHERE clause') AS step_name
from msdb.dbo.sysjobhistory
where step_id = 9999
Here is a simpler way to accomplish the same thing. No need for variables and such.
select step_id,
step_name
from msdb.dbo.sysjobhistory
where step_id = 9999
if ##ROWCOUNT = 0
SELECT 123456 AS [step_id], 'There are no step_id matching the WHERE clause' AS [step_name]
Here's another option, just for comparison (it returns your dummy row in the same table as your main results, if you don't have main results).
DECLARE #search_ID INT = 999;
WITH data
AS (SELECT step_id, step_name
FROM msdb.dbo.sysjobhistory
UNION ALL
SELECT 12345, 'dummy') --Insert your dummy data here
SELECT *
FROM data
WHERE step_id = #search_ID
OR
( step_id = 12345
AND NOT EXISTS (SELECT * FROM data WHERE step_id = #search_ID)
);

Apply and/or in SQL query based on flags passed to stored procedure

I need a query which can apply and/or clauses dynamically basing on the flags. I am using SQL Server 2014.
Consider the following example:
declare #UGFlag char(1)
declare #PGFlag char(1)
declare #DoctrateFlag char(1)
create table #FiltQual(Candidate_ID int) -- Final Output
create table #FiltUG(Candidate_ID int)
create table #FiltPG(Candidate_ID int)
create table #FiltDOC(Candidate_ID int)
insert into #FiltUG
select '1' union
select '2' union
select '3'
insert into #FiltPG
select '1' union
select '2'
insert into #FiltPG
select '2' union
select '3' union
select '4' union
select '5'
--Case 1
set #UGFlag='Y'
set #PGFlag='Y'
set #DoctrateFlag = 'Y'
--Desired Output
Candidate_ID
2
Case 2
set #UGFlag='N'
set #PGFlag='N'
set #DoctrateFlag = 'N'
Desired Output
Candidate_ID
1
2
3
4
5
Case 3:
set #UGFlag='N'
set #PGFlag='Y'
set #DoctrateFlag = 'N'
Desired Output
Candidate_ID
1
2
Case 4:
set #UGFlag='Y'
set #PGFlag='Y'
set #DoctrateFlag = 'N'
Desired Output
Candidate_ID
1
2
I want to populate data into #FiltQual basing on the trhee flags. Flags can contain either 'Y' or 'N', if it is 'Y' then 'And' should be apllied else 'or' should be applied.
Something like this should work:
SELECT COALESCE(UG.Candidate_ID, PG.Candidate_ID, DOC.Candidate_ID) AS Candidate_ID
FROM #FiltUG AS UG
FULL OUTER JOIN #FiltPG AS PG ON UG.Candidate_ID = PG.Candidate_ID
FULL OUTER JOIN #FiltDOC AS DOC ON PG.Candidate_ID = DOC.Candidate_ID
WHERE ((#UGFlag='Y' AND UG.Candidate_ID IS NOT NULL) OR (#UGFlag='N'))
AND
((#PGFlag='Y' AND PG.Candidate_ID IS NOT NULL) OR (#PGFlag='N'))
AND
((#DoctrateFlag='Y' AND DOC.Candidate_ID IS NOT NULL) OR (#DoctrateFlag='N'))
The key idea is to use a FULL JOIN between all tables. Then using, for example:
(#UGFlag='Y' AND UG.Candidate_ID IS NOT NULL) OR (#UGFlag='N')
we apply the NOT NULL condition to a table (#FiltUG) if the corresponding table filter (#UGFlag) is equal to 'Y.
Demo here
Not a complete answer, but the following abstract pattern can work. What you do is stack a check for the condition indicator in with the actual check.
SELECT *
FROM Table
WHERE (#ConditionIndicator = 'Y' AND (Column = #Value))
A slightly more concrete example, that does not address your sample data:
DECLARE #Filter NCHAR(1);
SET #Filter = 'Y'; -- Change to 'N' (or anything other than 'Y') to see the results change
SELECT *
FROM sysobjects
WHERE (#Filter = 'Y' AND name = 'sysrscols')
When #Filter == 'Y', it returns 1 row. When #Filter is anything other than 'Y', it returns no rows.
Hope this helps.
Edited to add:
Sorry, just re-read your question. You need something more like the below, but it effectively uses the same pattern:
DECLARE #Filter NCHAR(1);
SET #Filter = 'Y';
SELECT *
FROM sysobjects
WHERE (#Filter = 'Y' AND (name = 'sysrscols' AND xtype = 'S'))
OR (#Filter != 'Y' AND (name = 'sysrscols' OR xtype = 'S'))

Select with IN and Like

I have a very interesting problem. I have an SSRS report with a multiple select drop down.
The drop down allows to select more than one value, or all values.
All values is not the problem.
The problem is 1 or the combination of more than 1 option
When I select in the drop down 'AAA' it should return 3 values: 'AAA','AAA 1','AAA 2'
Right now is only returning 1 value.
QUESTION:
How can make the IN statement work like a LIKE?
The Drop down select
SELECT '(All)' AS team, '(All)' AS Descr
UNION ALL
SELECT 'AAA' , 'AAA'
UNION ALL
SELECT 'BBB' , 'BBB'
Table Mytable
ColumnA Varchar(5)
Values for ColumnA
'AAA'
'AAA 1'
'AAA 2'
'BBB'
'BBB 1'
'BBB 2'
SELECT * FROM Mytable
WHERE ColumnA IN (SELECT * FROM SplitListString(#Team, ',')))
Split function
CREATE FUNCTION [dbo].[SplitListString]
(#InputString NVARCHAR(max), #SplitChar CHAR(1))
RETURNS #ValuesList TABLE
(
param NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #ListValue NVARCHAR(max)
DECLARE #TmpString NVARCHAR(max)
DECLARE #PosSeparator INT
DECLARE #EndValues BIT
SET #TmpString = LTRIM(RTRIM(#InputString));
SET #EndValues = 0
WHILE (#EndValues = 0) BEGIN
SET #PosSeparator = CHARINDEX(#SplitChar, #TmpString)
IF (#PosSeparator) > 1 BEGIN
SELECT #ListValue = LTRIM(RTRIM(SUBSTRING(#TmpString, 1, #PosSeparator -1 )))
END
ELSE BEGIN
SELECT #ListValue = LTRIM(RTRIM(#TmpString))
SET #EndValues = 1
END
IF LEN(#ListValue) > 0 BEGIN
INSERT INTO #ValuesList
SELECT #ListValue
END
SET #TmpString = LTRIM(RTRIM(SUBSTRING(#TmpString, #PosSeparator + 1, LEN(#TmpString) - #PosSeparator)))
END
RETURN
END
You can't. But, you can make the like work like the like:
select *
from mytable t join
SplitListString(#Team, ',') s
on t.ColumnA like '%'+s.param+'%'
That is, move the split list to an explicit join. Replace with the actual column name returned by the function, and use the like function.
Or, if you prefer:
select *
from mytable t cross join
SplitListString(#Team, ',') s
where t.ColumnA like '%'+s.param+'%'
The two versions are equivalent and should produce the same execution plan.
Better approach would be to have a TeamsTable (teamID, teamName, ...) and teamMembersTable (teamMemberID, teamID, teamMemberDetails, ...).
Then you an build your dropdown list as
SELECT ... FROM TeamsTable ...;
and
SELECT ... FROM teamMembersTable WHERE teamID IN (valueFromYourDropDown);
Or you can just store your teamID or teamName (or both) in your (equivalent of) teamMembersTable
You're not going to get IN to work the same as LIKE without a lot of work. You could do something like this though (and it would be nice to see some of your actual data though so we could give better solutions):
SELECT *
FROM table
WHERE LEFT(field,3) IN #Parameter
If you'd like better performance, create a code field on your table and update it like this:
UPDATE table
SET codeField = LEFT(field,3)
Then just add an index on that field and run this query to get your results:
SELECT *
FROM table
WHERE codeField IN #Parameter

Execute queries until non-empty result

I have three queries, looking like these:
SELECT * FROM Table1 WHERE Column1 = 'a'
SELECT * FROM Table2 WHERE Column2 = 'b'
SELECT * FROM Table1 A, Table2 B WHERE A.Column1 <> B.Column1
Now all logic is implemented on the client side as following. Execute the first query, if HasRows, set a flag to 1 and return the rows. Otherwise execute the second query, if HasRows, set the flag to 2 and return the rows. Otherwise execute the third query, set the flag to 3 and return the rows.
How to do this with a single query? Flag stuff, I guess, should be solved adding Flag to the queries:
SELECT Flag = 1, * FROM Table1 WHERE Column1 = 'a'
SELECT Flag = 2, * FROM Table2 WHERE Column2 = 'b'
SELECT Flag = 3, * FROM Table1 A, Table2 B WHERE A.Column1 <> B.Column1
But now what? How to check, if a query returns non-empty result?
Also, I'd like to cache the results, in other words, to avoid executing the same query twice - once for checking and the second time - for returning data.
Regards,
You could use a table variable to store the result and only return it at the end of the SQL block. Checking ##rowcount would tell you if the previous insert added any rows; if it's zero, you can run further queries:
declare #result table (flag int, col1 int, col2 varchar(50))
insert #result select 1, col1, col2 from Table1 where Column1 = 'a'
if ##rowcount = 0
begin
insert #result select 2, col1, col2 from Table2 where Column1 = 'b'
end
if ##rowcount = 0
begin
insert #result select 3, col1, col2 from Table1 A, Table2 B
where A.Column1 <> B.Column1
end
select * from #result
This approach only works if each select has the same column definition.