Use variable sql for column name - sql

Before you shout at me in CAPS for not searching - I have! Dynamic SQL is good, dynamic SQL is bad. Learning a lot..
I can accomplish what I'm after by using logic in there WHERE clause, but it adds a significant amount of run time. The query takes 8 seconds if I hard code the criteria and 1:20 if I use the WHERE logic.
Here is what I'd like to do:
Declare #EmployeeToggle varchar(30)
Declare #Employee_ID varchar(30)
Declare #EmployeeField varchar(100)
set #EmployeeToggle = '1'
set #Employee_ID = '1166'
set #EmployeeField = case when #EmployeeToggle = '1' then 'Field1' else
'Field2' end;
select * from Table1 where #EmployeeField = #Employee_ID
I don't think it's possible without dynamic sql. I still don't know whether or not I should use it. It's my thought that it would take the query back down to 8 seconds, because it would immediately know which field to use in the where clause.
Alternatively, a few ways to do it in the where only:
where (( not #EmployeeToggle = '1') or Field1 = #Employee_ID) and
(#EmployeeToggle = '1' or Field2 = #Employee_ID)
where (1=(case when #EmployeeToggle = '1' then 1 else 0 end ) or Field1 =
#Employee_ID)
and (1=(case when #EmployeeToggle = '2' then 1 else 0 end) or Field2 =
#Employee_ID)
These work great (admittedly I copied and pasted these examples), but at the expense of run time.
My final thought, and the way others have done it at my org, is to create two scripts that are identical except for the field used in the where clause. So, if #EmployeeToggle = '1' it will run the first script and if it's '2' it will run the second. I haven't tried that yet, but I assume the runtime will be closer to the 8 seconds at the expense of some ugly code.
Thanks for the help.

Why not just use a single query?
select t.*
from table1
where #EmployeeToggle = '1' and field_1 = #Employee_ID
union all
select t.*
from table1
where #EmployeeToggle <> '1' and field_2 = #Employee_ID;
By using union all, SQL Server should use indexes for each subquery -- and if you have indexes on the fields, the query should be fast.

You can stay with static SQL when using CASE expression in SELECT then filter it.
SELECT *
FROM (
SELECT *,
CASE WHEN #EmployeeToggle = '1' THEN Field1 ELSE Field2 END AS Field1_2
FROM Table1
) t
WHERE
Field1_2 = #Employee_ID

Here is your dynamic query:
Declare #EmployeeToggle varchar(30)
Declare #Employee_ID varchar(30)
Declare #EmployeeField varchar(100)
set #EmployeeToggle = '1'
set #Employee_ID = '1166'
set #EmployeeField = case when #EmployeeToggle = '1' then 'Field1' else
'Field2' end;
DECLARE #SQLString VARCHAR(MAX)
SET #SQLString='select *
from Table1
where '+#EmployeeField+' = '+#Employee_ID+''
PRINT(#SQLString) --If you want to check actual query
EXEC(#SQLString)

Related

Using different set of WHERE clauses in stored procedure depending on Parameter value

I have 2 stored procedures which return the same columns that I am trying to merge into a single procedure. They both have a different set of parameters and both have different WHERE clauses, but they use the same tables and select the exact same rows.
WHERE clause 1: (uses #UIOID, and #Level)
WHERE ( #UIOID = CASE WHEN #Level = 'Single' THEN C.C_UIOID_PK
WHEN #Level = 'Children' THEN CLC.UIOL_P
WHEN #Level = 'Parent' THEN CLP.UIOL_C
END
OR ( #UIOID = '0'
AND #Level = 'All'
)
)
Where clause 2: (Uses #TeamCode, #Year, #IncludeQCodes)
WHERE C.C_IsChild = 0
AND C.C_MOA <> 'ADD'
AND #TeamCode = C.C_OffOrg
AND C.C_Active = 'Y'
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate) AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE 'Q%'
OR #IncludeQCodes = 1 )
Ideally I want to add a new parameter which basically tells it which of the two WHERE clauses to run, but I can't seem to recreate that with CASE statement because as far as I can tell, they only work for a single WHERE clause, not a whole set of different clauses
I want to do this without having to repeat the select statement again and putting the whole thing in IF statements, and i don't want to put the query into a string either. I just want one select statement ideally.
The problem with using temp tables is the query itself takes a while to run without any parameters and is used in a live website, so I don't want it to have to put all records in a temp table and then filter it.
The problem with using a CTE is you can't follow it with an IF statement, so that wouldn't work either.
Here is the sort of logic I am trying to achieve:
SELECT A
B
C
FROM X
IF #WhichOption = 1 THEN
WHERE ( #UIOID = CASE WHEN #Level = 'Single' THEN C.C_UIOID_PK
WHEN #Level = 'Children' THEN CLC.UIOL_P
WHEN #Level = 'Parent' THEN CLP.UIOL_C
END
OR ( #UIOID = '0'
AND #Level = 'All'
)
)
ELSE IF #WhichOption = 2 THEN
WHERE C.C_IsChild = 0
AND C.C_MOA <> 'ADD'
AND #TeamCode = C.C_OffOrg
AND C.C_Active = 'Y'
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate) AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE 'Q%'
OR #IncludeQCodes = 1 )
Save the following process in a procedure. You can also directly insert into a physical table.
declare #varTable Table (columns exactly as Procedures return)
if(condition is met)
begin
insert into #varTable
exec proc1
end
else
begin
insert into #varTable
exec proc2
end
Add the parameter that you said that it would indicate what filter apply :
select XXXXX
from XXXXX
where (#Mode = 1 and ( filter 1 ))
or
(#Mode = 2 and ( filter 2 ))
option(recompile)
If the #Mode parameter is 1 then it will evaluate the filter 1, otherwise it will evaluate the filter 2.
Add an option(recompile) at the end of the statement, so the SQL engine will replace the variables with their values, eliminate the filter that won't be evaluated, and generate an execution plant for just the filter that you want to apply.
PS: Please notice that although these catchall queries are very easy to code and maintain, and generate a perfectly functional and optimal execution, they are not advised for high-demand applications. The option(recompile) forces the engine to recompile and generate a new execution plan at every execution and that would have a noticeable effect on performance if your query needs to be executed hundreds of times per minute. But for the occasional use it's perfectly fine.
Try to use dynamic SQL:
DECLARE #sql NVARCHAR(max), #where NVARCHAR(max), #WhichOption INT = 1;
SET #sql = 'SELECT A
B
C
FROM X';
IF #WhichOption = 1
SET #where = 'WHERE ( #UIOID = CASE WHEN #Level = ''Single'' THEN C.C_UIOID_PK
WHEN #Level = ''Children'' THEN CLC.UIOL_P
WHEN #Level = ''Parent'' THEN CLP.UIOL_C
END
OR ( #UIOID = ''0''
AND #Level = ''All''
)
)';
ELSE IF #WhichOption = 2
SET #where = ' WHERE C.C_IsChild = 0
AND C.C_MOA <> ''ADD''
AND #TeamCode = C.C_OffOrg
AND C.C_Active = ''Y''
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate)
AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE ''Q%''
OR #IncludeQCodes = 1 ) ';
SET #sql = CONCAT(#sql,' ', #where)
PRINT #sql
EXECUTE sp_executesql #sql

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

how to use declared variable to select data from another table in case when condition?

I made select query in which i want to select data based on condition.For this i declared one variable and set value of that variable in else part.I want to use that variable for further select in same else part how can i achieve this?Please Help
declare #stateid int
select CASE WHEN MstCustomerAddressInfo.StateId is null
THEN 24
ELSE set #stateid = MstCustomerAddressInfo.StateId
select mststate.statecode from mststate where MstState.StateId = #stateid
END AS StateCode
No, you can't have SET inside a CASE expression. Even you can't have multiple statements.
Same query you can write as following.
declare #stateid int
select CASE
WHEN MstCustomerAddressInfo.StateId is null THEN 24
ELSE
-- set #stateid = MstCustomerAddressInfo.StateId
(select mststate.statecode
from mststate
where MstState.StateId = MstCustomerAddressInfo.StateId)
END AS StateCode
from [Your_Table]

Querying different table based on a parameter

I have a stored procedure that I would like to query either the production or the "work in progress" table, based on the parameter I am passing in. I could write two separate stored procedures, but I thought this was worth a try.
something along the lines of:
create procedure getUserDetails
#userID int,
#prod varchar(5)
as
begin
select * from
if (#prod = 'true')
Begin
userprod_table
else
userwip_table
end
where something = 'something'
END
Is this at all possible? I wouldn't want to write 2 SP that are almost identical :-/
Why not use a simple if(#prod = 'true') statement like below:
if (#prod = 'true')
begin
select * from userprod_table where something = 'something'
end else
begin
select * from userwip_table where something = 'something'
end
You could use a CTE so that your main query isn't repeated
with usertable as
(
select * from userprod_table where 1 = #flag
union
select * from userwip_table where 0 = #flag
)
select ... from usertable ...

Is it faster to check if length = 0 than to compare it to an empty string?

I've heard that in some programming languages it is faster to check if the length of a string is 0, than to check if the content is "". Is this also true for T-SQL?
Sample:
SELECT user_id FROM users WHERE LEN(user_email) = 0
vs.
SELECT user_id FROM users WHERE user_email = ''
Edit
You've updated your question since I first looked at it. In that example I would say that you should definitely always use
SELECT user_id FROM users WHERE user_email = ''
Not
SELECT user_id FROM users WHERE LEN(user_email) = 0
The first one will allow an index to be used. As a performance optimisation this will trump some string micro optimisation every time! To see this
SELECT * into #temp FROM [master].[dbo].[spt_values]
CREATE CLUSTERED INDEX ix ON #temp([name],[number])
SELECT [number] FROM #temp WHERE [name] = ''
SELECT [number] FROM #temp WHERE LEN([name]) = 0
Execution Plans
Original Answer
In the code below (SQL Server 2008 - I "borrowed" the timing framework from #8kb's answer here) I got a slight edge for testing the length rather than the contents below when #stringToTest contained a string. They were equal timings when NULL. I probably didn't test enough to draw any firm conclusions though.
In a typical execution plan I would imagine the difference would be negligible and if you're doing that much string comparison in TSQL that it will be likely to make any significant difference you should probably be using a different language for it.
DECLARE #date DATETIME2
DECLARE #testContents INT
DECLARE #testLength INT
SET #testContents = 0
SET #testLength = 0
DECLARE
#count INT,
#value INT,
#stringToTest varchar(100)
set #stringToTest = 'jasdsdjkfhjskdhdfkjshdfkjsdehdjfk'
SET #count = 1
WHILE #count < 10000000
BEGIN
SET #date = GETDATE()
SELECT #value = CASE WHEN #stringToTest = '' then 1 else 0 end
SET #testContents = #testContents + DATEDIFF(MICROSECOND, #date, GETDATE())
SET #date = GETDATE()
SELECT #value = CASE WHEN len(#stringToTest) = 0 then 1 else 0 end
SET #testLength = #testLength + DATEDIFF(MICROSECOND, #date, GETDATE())
SET #count = #count + 1
END
SELECT
#testContents / 1000000. AS Seconds_TestingContents,
#testLength / 1000000. AS Seconds_TestingLength
I would be careful about using LEN in a WHERE clause as it could lead to table or index scans.
Also note that if the field is NULLable that LEN(NULL) = NULL, so you would need to define the behaviour, e.g.:
-- Cost .33
select * from [table]
where itemid = ''
-- Cost .53
select * from [table]
where len(itemid) = 0
-- `NULL`able source field (and assuming we treat NULL and '' as the same)
select * from [table]
where len(itemid) = 0 or itemid is NULL
I just tested it in a very limited scenario and execution plan ever so slightly favours comparing it to an empty string. (49% to 51%). This is working with stuff in memory though so it would probably be different if comparing against data from a table.
DECLARE #testString nvarchar(max)
SET #testString = ''
SELECT
1
WHERE
#testString = ''
SELECT
1
WHERE
LEN(#testString) = 0
Edit: This is with SQL Server 2005.
I suspect the answer depends largely on the context. For example, I have been working with expressions in the SELECT list and in user-defined functions. Knowing what I do about the Microsoft .NET Base Class Library and the legacy that Transact-SQL owes to Visual Basic, I suspect that in such circumstances, LEN ( string ) gets the nod.