IF in Where with multiple Conditions - sql

I am trying to write a query where I can have either of the 2 variables entered and the query needs to execute on the basis of which data was entered. For example
DECLARE #A NVARCHAR(15) = '' -- Input A here
-------------------OR---------------------
DECLARE #B NVARCHAR(15) = '' -- Input B here
SELECT
[DOC].[ety_ts] as [CreatedDate],
[DOC].[ord_int_id],
[DOC].[doc_int_id],
[pnt_cln_doc_int_id] as [ParentDoc],
CDTL_STATUS2.cod_dtl_ds as DocumentName,
CDTL_STATUS.[cod_dtl_ds] as [DocumentStatus],
.................................
..................................
.....................................
WHERE
IF ( #A IS NULL OR #A = '' )
BEGIN
DOC.asn_int_id = (Select asn_int_id from TPM(nolock) where rec_no=#B)
END
ELSE
BEGIN
DOC.ord_int_id IN
(
SELECT DOC.ord_int_id
FROM TAD_DOCUMENT WITH (NOLOCK)
WHERE DOC.ord_int_id IN
(
SELECT TPM.ord_int_id FROM TPM_VISIT TPM WITH (NOLOCK)
WHERE TPM300.vst_no = #A
)
END
It throws an error for the first IF Statement
Msg 156, Level 15, State 1, Line 59
Incorrect syntax near the keyword 'IF'.
Not sure what I am doing wrong. Can anyone please guide on how to fix this query.
Thanks in advance.

IF is a procedural statement, not a query clause. From the documentation:
An IF...ELSE construct can be used in batches, in stored procedures, and in ad hoc queries.
You just want regular AND/OR logic e.g.
WHERE (
NULLIF(#A,'') IS NULL
AND DOC.asn_int_id = (
SELECT asn_int_id
FROM TPM
WHERE rec_no = #B
)
)
OR (
NULLIF(#A,'') IS NOT NULL
AND DOC.ord_int_id IN (
SELECT DOC.ord_int_id
FROM TAD_DOCUMENT
WHERE DOC.ord_int_id IN (
SELECT TPM.ord_int_id
FROM TPM_VISIT TPM
WHERE TPM300.vst_no = #A
)
)
)
Note: Unless you are very sure you understand and accept the consequences of using NOLOCK I highly recommend not using it. Its not a "go faster for free" hint.

Related

Complex Conditional Query in Where Clause

In my company, each department has their own statuses for purchase orders. I'm trying to load only the pertinent POs for a specific user's department. I keep getting errors. It seems like it should be correct, though. I initially attempted to use a Case statement, but it appears that SQL can only do a simple return from a case, so I'm now attempting If statements. Current error is Incorrect Syntax near the keyword IF. It looks right to me. It's as if the incorrect syntax is that it is in the IN parentheses.
declare #Dept nvarchar(12);
set #Dept = 'IT'
SELECT *
FROM TBL_ORDERS
WHERE ORD_STATUS IN
(
IF #Dept = 'PURCH'
BEGIN
SELECT distinct * FROM (VALUES ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4')) AS X(a)
END
ELSE
IF #Dept = 'ADMIN'
BEGIN
SELECT distinct * FROM (VALUES ('ADStat1')) AS X(a)
END
ELSE
IF #Dept = 'IT'
BEGIN
SELECT distinct * FROM (VALUES ('ADStat1'), ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4'), ('ITStat1'), ('ITStat2')) AS X(a)
END
ELSE END
)
There's a conceptual problem to address here first, and then we can look at how to actually do what you want.
select is starting a statement here. Statements end with a semicolon. You can't put statements inside other statements. What you are doing is the same as trying to do something like this in c#:
int i = if (true) 1; else 2;;
You can of course use expressions inside statements:
int i = true ? 1 : 2;
Moreover, select is a declarative statement. You can't do imperative flow of control inside a declarative language construct. You're mixing metaphors, as it were. To understand the declarative/imperative distinction see this question and in particular (in my opinion) this answer.
So the first thing to do is wrap your head around the declarative nature of SQL statements like select. Yes, T-SQL also includes imperative constructs like if and while, but you can't do imperative inside declarative.
You can use conditional expressions (and other expressions) inside a declarative statement:
select name,
case
when name = 'date' then 'this is the date row'
else 'this is not the date row'
end
from sys.types;
In this example the declarative select says what to do with all of the rows returned by the from clause. I don't write a while loop or a for loop in order to instruct the computer to loop over each row and provide instructions inside the loop. The from returns all the rows, and the select declares what I want to do with each of them. The case expression will be evaluated against every row in sys.types.
OK, so what about your specific question? There's many ways to write the code. Here is one way that is very similar to your current structure. First I conditionally (imperatively!) populate a temp table with the statuses I want. Then I declaratively use that temp table as my filter:
create table #statuses
(
statusname varchar(32)
);
declare #dept nvarchar(12) = 'IT';
if (#dept = 'IT')
begin
insert #statuses (statusname) values
('ADStat1'), ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4'), ('ITStat1'), ('ITStat2');
end
else if (#dept = 'PURCH')
begin
insert #statuses (statusname) values
('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4');
end
else if (#dept = 'ADMIN')
begin
insert #statuses (statusname) values
('ADStat1');
end
select *
from tbl_orders
where ord_status in (select statusName from #statuses);
Can I do it without the temp table? Sure. Here's one way:
declare #dept nvarchar(12) = 'IT';
select *
from tbl_orders
where (#dept = 'ADMIN' and ord_status = 'ADStat1')
or (#dept = 'PURCH' and ord_status in ('PurchStat1', 'PurchStat2', 'PurchStat3', 'PurchStat4'))
or (#dept = 'IT' and ord_status in ('ADStat1', 'PurchStat1', 'PurchStat2', 'PurchStat3', 'PurchStat4', 'ITStat1', 'ITStat2'));
Here we evaluate a different in depending on the value of #dept. Clearly only one of them actually needs to be evaluated, and the other two don't really need to be there, depending on which value of #dept is provided. Adding an option (recompile) can be beneficial in cases like this. For more information about option (recompile) look here and here.
If for a reason storing those status/department data in tables is not possible you can use union
declare #Dept nvarchar(12);
set #Dept = 'IT'
SELECT *
FROM TBL_ORDERS
WHERE ORD_STATUS IN
(
SELECT distinct *
FROM (VALUES ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4')) AS X(a)
WHERE #Dept ='PURCH'
union all
SELECT distinct *
FROM (VALUES ('ADStat1')) AS X(a)
WHERE #Dept ='ADMIN'
union all
SELECT distinct *
FROM (VALUES ('ADStat1'), ('PurchStat1'), ('PurchStat2'), ('PurchStat3'), ('PurchStat4'), ('ITStat1'), ('ITStat2')) AS X(a)
WHERE #Dept ='IT'
)

Short-circuiting tables

I'm upgrading several identical copies of a database which may already be upgraded partially, and for some reason bool values were stored in an nvarchar(5).
So in the below, (which exists inside an INSERT > SELECT block), I need to check if the column ShowCol exists, fill it with 0 if it does not, or fill it with the result of evaluating the string bool if it does:
CASE
WHEN COL_LENGTH('dbo.TableName', 'ShowCol') IS NULL THEN 0
ELSE IIF(LOWER(ShowCol) = 'false', 0, 1)
END
...but I'm getting an error "Invalid column name 'ShowCol'". I can't seem to short-circuit this, can you help?
Its worth noting that the column if it does exist contains a mix of "false", "False" and "FALSE", so that's the point of the LOWER(). (The True column also occasional trailing spaces to contend with, which is why I'm just dealing with False and everything else is true.)
I suspect that its because of this wrap in LOWER() which is causing the server to always evaluate the expression.
You can’t short circuit the existence of a column (and it has nothing to do with LOWER(); if you remove it, nothing will change).
You’ll need dynamic SQL, e.g.:
DECLARE #sql nvarchar(max) = N'UPDATE trg SET
trg.col1 = src.col1,
trg.col2 = src.col2';
IF COL_LENGTH('dbo.TableName', 'ShowCol') > 0
BEGIN
SET #sql += N', trg.ShowCol = IIF(LOWER(src.ShowCol) = ''false'', 0, 1)';
END
SET #sql += N' ...
FROM dbo.TableName AS trg
INNER JOIN dbo.Origin AS src
ON ...';
EXEC sys.sp_executesql #sql; -- ,N'params', #params;
When you're selecting data, you can fool the parser a little bit by introducing constants to take the place of columns, taking advantage of SQL Server's desire to find a column reference even at a different scope than the syntax would suggest. I talk about this in Make SQL Server DMV Queries Backward Compatible. I don't know of any straightforward way to make that work with writes without dynamic SQL, as the parser does more strict checking there, so it's harder to fool.
Imagine you have these tables:
CREATE TABLE dbo.SourceTable(a int, b int, c int);
INSERT dbo.SourceTable(a,b,c) VALUES(1,2,3);
CREATE TABLE dbo.DestinationWithAllColumns(a int, b int, c int);
INSERT dbo.DestinationWithAllColumns(a,b,c) VALUES(1,2,3);
CREATE TABLE dbo.DestinationWithoutAllColumns(a int, b int);
INSERT dbo.DestinationWithoutAllColumns(a,b) VALUES(1,2);
You can write a SELECT against either of them that produces an int output column called c:
;WITH optional_columns AS
(
SELECT c = CONVERT(int, NULL)
)
SELECT trg.a, trg.b, trg.c
FROM optional_columns
CROSS APPLY
(SELECT a,b,c FROM dbo.DestinationWithAllColumns) AS trg
INNER JOIN dbo.SourceTable AS src ON src.a = trg.a;
Output:
a
b
c
1
2
3
;WITH optional_columns AS
(
SELECT c = CONVERT(int, NULL)
)
SELECT trg.a, trg.b, trg.c
FROM optional_columns
CROSS APPLY
(SELECT a,b,c FROM dbo.DestinationWithoutAllColumns) AS trg
INNER JOIN dbo.SourceTable AS src ON src.a = trg.a;
Output:
a
b
c
1
2
null
So far, so good. But as soon as you try and update:
;WITH optional_columns AS
(
SELECT c = CONVERT(int, NULL)
)
UPDATE trg SET trg.b = src.b, trg.c = src.c
FROM optional_columns
CROSS APPLY
(SELECT a,b,c FROM dbo.DestinationWithoutAllColumns) AS trg
INNER JOIN dbo.SourceTable AS src ON src.a = trg.a;
Msg 4421, Level 16, State 1
Derived table 'trg' is not updatable because a column of the derived table is derived or constant.
Example db<>fiddle

Receiving 0 results from my stored procedure

I am trying to run a stored procedure in SQL Server and I'm getting 0 results. I initially had this running just fine (attached to SSRS) but then users requested a multiple value input for the ProviderName parameter and I realized I was in over my head. I contacted our vendor who provided a KnowledgeBase article which I essentially copied and pasted right in. See below...
ALTER PROCEDURE [dbo].[Test]
(#dStartDate DATETIME
,#dEndDate DATETIME
,#nProviderName VARCHAR(MAX)
,#nAllProviderName VARCHAR(1) = 'N')
AS
BEGIN
DECLARE #dStart AS DATETIME = CONVERT(DATETIME,CONVERT(DATE,#dStartDate)) ;
DECLARE #dEnd AS DATETIME = DATEADD(ms,-3, DATEADD(day,1,CONVERT(DATETIME,CONVERT(DATE,#dEndDate))))
DECLARE #cProviderName AS VARCHAR(MAX) = #nProviderName
DECLARE #tProviderName AS TABLE (PCPID VARCHAR(MAX) NOT NULL);
IF UPPER(#nAllProviderName) = 'N'
BEGIN
INSERT INTO #tPCPName ( PCPID )
SELECT LTRIM(RTRIM(Item))
FROM [dbo].[Auto_Split]('|',#nProviderName ) ;
END;
SELECT ...
WHERE
([TestMnemonic] = 'GLU' OR
[TestMnemonic] = '%HA1C')
AND [Status] != 'DIS CLI'
AND [TextLine] IS NOT NULL
AND [DateTime] BETWEEN #dStart AND #dEnd
AND (UPPER(#nAllProviderName) = 'Y' OR
[PCPID] COLLATE DATABASE_DEFAULT
IN (SELECT PCPID FROM #tProviderName ) ) ;
END
So if I comment out the last 4 lines of code it runs fine. So it's something in that last bit (or something at the top?) I'm hoping this is a quick fix, any and all help is appreciated!
Thanks!
I'm curious about the Collate statement at the bottom. What us your system default and do you really need that when comparing against a temp table you made?
Without Collate
OR [PCPID] IN (SELECT PCPID FROM #tProviderN
I think you need to switch #tProviderName with #tPCPName in last line.
If your #nAllProviderName is "N" than you search PCPID from memory table called #tPCPName
And there is some code missing, so I didn't get the full picture .. If you could put your from and joins statments and your missing "where clause", when your #nAllProviderName is "Y"
and you don't need this part in your SQL
IF UPPER(#nAllProviderName) = 'N'
BEGIN
INSERT INTO #tPCPName ( PCPID )
SELECT LTRIM(RTRIM(Item))
FROM [dbo].[Auto_Split]('|',#nProviderName ) ;
END
if you switch your last line with
AND (UPPER(#nAllProviderName) = 'Y' OR
[PCPID] COLLATE DATABASE_DEFAULT
IN ( SELECT LTRIM(RTRIM(Item))
FROM [dbo].[Auto_Split]('|',#nProviderName ) ) ) ;

Syntax error using multiple CTEs

I have a rather complicated CTE that I'm attempting to incorporate into a stored procedure. It works when just operating straight from SQL Server Management Studio. When I try to create my stored procedure, I get an error:
Msg 102, Level 15, State 1, Procedure spMyCrazyProc, Line 56
Incorrect syntax near ','.
What have I syntactically done incorrectly when trying to incorporate my CTE into a stored procedure?
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[spMyCrazyProc]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[spMyCrazyProc]
GO
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE dbo.spMyCrazyProc
#CompanyId Int,
#EmployeeIds varchar(MAX)
AS
;with SelectedEmployees as (
select * from vwEmployee e
where e.CompanyId = #CompanyId
and (#EmployeeIds is null or #EmployeeIds='' or exists(select ce.SelectedEmployeeId from #myTmpTable ce where ce.SelectedEmployeeId=e.EmployeeId)
), MyStuffA as (
select * from SelectedEmployees
)
select * from MyStuffA
GO
Using sensible coding conventions and thinking about readability (indenting, carriage returns) would have yielded this simple error much more clearly. Your code with 500 character-wide lines removed:
;with SelectedEmployees as
(
select * from vwEmployee e
where e.CompanyId = #CompanyId
and
(
#EmployeeIds is null or #EmployeeIds='' or exists
(
select ce.SelectedEmployeeId from #myTmpTable ce
where ce.SelectedEmployeeId=e.EmployeeId
)
----^----- oops! Missing closing paren here.
), MyStuffA as
(
select * from SelectedEmployees
)
select * from MyStuffA

SQL WHERE ... IN clause with possibly null parameter

I am having some problems with my WHERE clause (using SQL 2008) . I have to create a stored procedure that returns a list of results based on 7 parameters, some of which may be null. The ones which are problematic are #elements, #categories and #edu_id. They can be a list of ids, or they can be null. You can see in my where clause that my particular code works if the parameters are not null. I'm not sure how to code the sql if they are null. The fields are INT in the database.
I hope my question is clear enough. Here is my query below.
BEGIN
DECLARE #elements nvarchar(30)
DECLARE #jobtype_id INT
DECLARE #edu_id nvarchar(30)
DECLARE #categories nvarchar(30)
DECLARE #full_part bit
DECLARE #in_demand bit
DECLARE #lang char(2)
SET #jobtype_id = null
SET #lang = 'en'
SET #full_part = null -- full = 1, part = 0
SET #elements = '1,2,3'
SET #categories = '1,2,3'
SET #edu_id = '3,4,5'
select
jobs.name_en,
parttime.fulltime_only,
jc.cat_id category,
je.element_id elem,
jt.name_en jobtype,
jobs.edu_id minEdu,
education.name_en edu
from jobs
left join job_categories jc
on (jobs.job_id = jc.job_id)
left join job_elements je
on (jobs.job_id = je.job_id)
left join job_type jt
on (jobs.jobtype_id = jt.jobtype_id)
left join education
on (jobs.edu_id = education.edu_id)
left join
(select job_id, case when (jobs.parttime_en IS NULL OR jobs.parttime_en = '') then 1 else 0 end fulltime_only from jobs) as parttime
on jobs.job_id = parttime.job_id
where [disabled] = 0
and jobs.jobtype_id = isnull(#jobtype_id,jobs.jobtype_id)
and fulltime_only = isnull(#full_part,fulltime_only)
-- each of the following clauses should be validated to see if the parameter is null
-- if it is, the clause should not be used, or the SELECT * FROM ListToInt... should be replaced by
-- the field evaluated: ie if #elements is null, je.element_id in (je.element_id)
and je.element_id IN (SELECT * FROM ListToInt(#elements,','))
and jc.cat_id IN (SELECT * FROM ListToInt(#categories,','))
and education.edu_id IN (SELECT * FROM ListToInt(#edu_id,','))
order by case when #lang='fr' then jobs.name_fr else jobs.name_en end;
END
Something like
and (#elements IS NULL OR je.element_id IN
(SELECT * FROM ListToInt(#elements,',')))
and (#categories IS NULL OR
jc.cat_id IN (SELECT * FROM ListToInt(#categories,',')))
....
should do the trick
je.element_id IN (SELECT * FROM ListToInt(#elements,',')) OR #elements IS NULL
that way for each one
Have you tried explicitly comparing to NULL?
and (#elements is null or je.element_id IN (SELECT * FROM ListToInt(#elements,','))
And so on.