Use variable in WHERE clause - sql-server

My SQL statement in SQL Server looks like this:
DECLARE #forecastYear AS varchar(5)
SET #forecastYear = '2020'
DECLARE #versionName AS varchar(25)
SET #versionName = '20201113_wk'
DECLARE #currentMonth AS varchar(2)
SET #currentMonth = (SELECT current_fc_month FROM tbl_current_month)
SELECT f.record_id
, u.it_ops_j_level_abbr_nm
, f.owner_nm
, f.unit_cd
, f.tbm_cd
, f.tower_nm
, f.description_txt
, f.comment_txt
, f.cost_pool_nm
, f.glac_nr
, f.glac_nm
, f.initiative_nm
, f.priority_nm
, f.growth_nm
, f.it_vendor_nm
, f.jan_amt
, f.feb_amt
, f.mar_amt
, f.apr_amt
, f.may_amt
, f.jun_amt
, f.jul_amt
, f.aug_amt
, f.sep_amt
, f.oct_amt
, f.nov_amt
, f.dec_amt
FROM tbl_new_forecast f
INNER JOIN tbl_unit_tree u
ON f.unit_cd = u.dept_id
WHERE f.version_nm = #versionName
AND f.status_cd = 'Approved'
AND f.entry_type = 'Forecast'
AND f.forecast_yr_id = #forecastYear
AND ABS(f.nov_amt)+ABS(f.dec_amt) <> 0
What I want to do is change the last statement in the WHERE clause based on the value in #currentMonth.
Therefore, if #currentMonth = '3' then the last statement would read
AND ABS(f.mar_amt)+ABS(f.apr_amt)+ABS(f.may_amt) <> 0
If #currentMonth = '7' then it would read
AND ABS(f.jul_amt)+ABS(f.aug_amt)+ABS(f.sep_amt) <> 0
I'm having a hard time figuring out how to accomplish this, because I get a SQL error with this syntax:
AND CASE
WHEN #currentMonth = '10' THEN ABS(f.oct_amt)+ABS(f.nov_amt)+ABS(f.dec_amt) <> 0
END
Any help is appreciated!

If you need a solution, a complex WHERE clause is an option. Note, that in T-SQL CASE is an expression, not a statement:
AND (
((#currentMonth = 1) AND (ABS(f.jan_amt) + ABS(f.feb_amt) + ABS(f.mar_amt) <> 0)) OR
((#currentMonth = 2) AND (ABS(f.feb_amt) + ABS(f.mar_amt) + ABS(f.apr_amt) <> 0)) OR
...
((#currentMonth = 10) AND (ABS(f.oct_amt) + ABS(f.nov_amt) + ABS(f.dec_amt) <> 0)) OR
((#currentMonth = 11) AND (ABS(f.nov_amt) + ABS(f.dec_amt) <> 0)) OR
((#currentMonth = 12) AND (ABS(f.dec_amt) <> 0))
)

Use dynamic SQL
DECLARE #month_dependent varchar (500)=' ';
DECLARE #main_query varchar(1000)=' ';
DECLARE #forecastYear AS varchar(5)
SET #forecastYear = '2020'
DECLARE #versionName AS varchar(25)
SET #versionName = '20201113_wk'
DECLARE #currentMonth char(2)
SET #currentMonth = (SELECT current_fc_month FROM tbl_current_month)
If #currentMonth = '3'
BEGIN set #month_dependent=' AND ABS(f.mar_amt)+ABS(f.apr_amt)+ABS(f.may_amt) <> 0 '; END
If #currentMonth = '7'
BEGIN set #month_dependent=' AND ABS(f.jul_amt)+ABS(f.aug_amt)+ABS(f.sep_amt) <> 0 '; END
set #main_query varchar(1000)=' SELECT f.record_id' +
' , u.it_ops_j_level_abbr_nm ' +
-- ' and all the rest of it! ' +
' FROM tbl_new_forecast f '+
' INNER JOIN tbl_unit_tree u '+
' ON f.unit_cd = u.dept_id ' +
' WHERE f.version_nm = '''+ #versionName + ''' '+
' AND f.status_cd = ''Approved'' '+
' AND f.entry_type = ''Forecast'' '+
' AND f.forecast_yr_id = ''' + #forecastYear + ''' '+
#month_dependent
EXECUTE sp_executesql #main_query ;

You can make this really complex by using a WHERE clause with a lot of ors in it:
AND ({first month condition} OR {Second month condition} OR {third month condition})
Etc. Another option is to place this into a stored procedure and use the month as the trigger to determine which statement to run. Depending on how you are running this, it might be a preferred method, as it can abstract out the details from the application (something you will want if you ever decide to normalize this data).
As for trying to use CASE in a WHERE clause, you have it wrong. The CASE WHEN has to equal something. The correct syntax is like:
AND SomeValue = CASE WHEN ...
You cannot simply use case, as a where is looking for equality (=), inequality (<>), and fuzzy values (LIKE). Thus, this does not work.
AND CASE WHEN ...
As an example, this shows something that fires back 1 to get equivalent rows. But you would need all of your conditions in here, which means the WHEN on month and the ABS() would be the entire condition. You then return 1 to indicate "found it". But you are running this as a monthly query, so filtering by the month and then determining the CASE ... WHEN is where you go.

Related

SQL Server : query that doesn't work with parameters

I am not sure why this query works one way and not the other.
If I use the following query:
Select
dlb.referenceid, dlb.partnum,
pd.width, pd.length,
dlb.partqty,
Convert(date, dlb.entrydatetime, 101) as entrydatetime,
dlb.status, dlb.material
From
tbl_dlbase dlb
Join
tbl_partdetails pd On dlb.referenceid = pd.referenceid
Where
(pd.Spacing = 3)
And ((dlb.status = 'Available') or (dlb.status = 'Reserved'))
Order By
dlb.status
It returns one result as it should.
If I use the exact same query but with parameters:
Select
dlb.referenceid, dlb.partnum,
pd.width, pd.length,
dlb.partqty,
Convert(date, dlb.entrydatetime, 101) as entrydatetime,
dlb.status, dlb.material
From
tbl_dlbase dlb
Join
tbl_partdetails pd On dlb.referenceid = pd.referenceid
Where
(#parameter1 = #Criteria1)
And ((dlb.status = 'Available') or (dlb.status = 'Reserved'))
Order By
dlb.status
And have the following parameters declared and set as follows:
Declare #parameter1 varchar(50)
Declare #Criteria1 varchar(50)
Set #parameter1 = 'pd.Spacing'
Set #Criteria1 = 3
The query returns no results.
I have used this type parameter query many times before but for some reason this time it won't work. Can anyone tell me what stupid thing I am missing or screwed up? I can't seem to see anything wrongs with it.
You can't use parameters as object names without dynamic sql. Here is a fix... just change the print to exec(#sql) when you are happy with it...
Declare #parameter1 varchar(50)
Declare #Criteria1 varchar(50)
Set #parameter1 = 'pd.Spacing'
Set #Criteria1 = '3'
declare #sql varchar(max)
set #sql =
'Select
dlb.referenceid,
dlb.partnum,
pd.width,
pd.length,
dlb.partqty,
Convert(date, dlb.entrydatetime, 101) as entrydatetime,
dlb.status,
dlb.material
From tbl_dlbase dlb
Join tbl_partdetails pd On
dlb.referenceid = pd.referenceid
Where (' + #parameter1 + ' = ' + #Criteria1 + ')
AND ((dlb.status = ''Available'') OR (dlb.status = ''Reserved''))
Order By dlb.status'
print #sql
--exec(#sql)
--sp_executesql #sql
This makes your where clause Where (pd.Spacing = 3) otherwise the where clause is simply comparing the parameters, which obviously aren't the same since they aren't set to the same thing. It's the same thing as writing:
Select 'True'
where #parameter1 = #Criteria1
which is interpreted as
Select 'True'
where 'pd.Spacing'= '3' --string literals, not column names.

Case Statement in where clase in sql server 2014

I am creating a store procedure and in which am I stuck in a problem. I want to query two columns based on condition. If parameter is numeric then query to one column and if it is nonnumeric then query to other column. Following is the procedure.
$
declare #result AS varchar(50)
DECLARE #peopleId AS varchar(50)
if('232332' NOT LIKE '%[^0-9]%')
BEGIN
SET #result='Numeric'
PRINT #result
END
ELSE
BEGIN
set #result='nonNumeric'
print #result
END
select isnull(class.grade,'') as grade,ISNULL(class.room,'') as room,student.prefix as prefix,student.student_id as student_id,(person.first_name+' '+person.last_name) as name,
person.dob as dob,person.people_id as people_id,quit_on,
case when student.student_status='30' then
N'พักการเรียน'
when student.student_status='31' then
N'น.ร.ไปเรียนโครงการฯ'
else ''
end
as quit_reason from school_student student
inner join people_person person on student.person_id=person.id
left join school_classroom_students classStudent on classStudent.student_id=student.id
left join school_classroom class on class.id =classStudent.classroom_id
where student.student_status in('30','31') and student.system_status = 'DC' and student.school_id=#schoolId
AND case
WHEN
#result='nonNumeric' then-- this should execure
person.people_id=#peopleId
else---- this should work
person.first_name+' '+ person.last_name LIKE '%'+#peopleId+'%'
Please help me out on this
Why would use use a separate variable? You can do:
WHEN (person.people_id = try_convert(int, #peopleId) or
try_convert(int, #peopleId) is null and
person.first_name + ' ' + person.last_name LIKE '%' + #peopleId + '%'
)
I question why you are passing a value that is used for both a string and numeric comparison. If I were using a variable, I would do:
declare #personid int;
declare #personname varchar(255);
if #peopleid not like '%[^0-9]%'
set #personname = #peopleid;
else
set #personid = convert(int, #peopleid);
where (personid = #personid or
person.first_name + ' ' + person.last_name LIKE '%' + #personname + '%'
)
The code just seems easier to follow.
Since SQL Server doesn't treat results of CASE expressions as booleans, you have to add an extra comparison. The way to do that is like this:
WHERE 1 = CASE WHEN x THEN 1 WHEN y THEN 0 ELSE 1 END
Conditions which result in rows being included in the result must evaluate to 1, and conditions which don't, must evaluate to something other than 1 (like 0). So the whole CASE expression returns either 0 or 1, and that is compared to 1.
In your code, it would look like this:
AND 1 = case
WHEN
#result='nonNumeric' then case when person.person_id = #peopleId then 1 else 0 end
else when person.first_name+' '+person.last_name LIKE '%'+#peopleId+'%' then 1 else 0 end
end
I added the END.
Just do like that
DECLARE #IsNumeric INT = NULL
DECLARE #IsNotNumeric INT = NULL
DECLARE #peopleId Varchar(50)
SET #peopleId = '123'
IF ISNUMERIC(#peopleId) = 1
BEGIN
SET #IsNumeric = 1
END
ELSE
BEGIN
SET #IsNotNumeric = 1
END
IN WHERE Condition Just Check
AND (#IsNumeric IS NULL OR CONVERT(VARCHAR(500),person.people_id)=#peopleId)
AND (#IsNotNumeric IS NULL OR person.first_name+' '+ person.last_name LIKE '%'+#peopleId+'%')

Get name from variable using index in T-SQL

Using the following two queries
Query 1:
DECLARE #ContentColumnNamesSRC NVARCHAR(4000) = NULL,
SELECT
#ContentColumnNamesSRC = COALESCE(#ContentColumnNamesSRC + ', ', '') + '[' + name + ']'
FROM
tempdb.sys.columns
WHERE
1 = 1
AND object_id = OBJECT_ID('tempdb..#tempTable')
AND column_id < 9 -- First 8 columns are ID data, which is what I am after
Query 2:
DECLARE #ContentColumnNamesDST NVARCHAR(4000) = NULL,
SELECT
#ContentColumnNamesDST = COALESCE(#ContentColumnNamesDST + ', ', '') + '[' + name + ']'
FROM
tempdb.sys.columns
WHERE
1 = 1
AND object_id = OBJECT_ID('Import.dbo.ContentTable')
AND column_id < 9 -- First 8 columns are ID data, which is what I am after
I can get the first 8 columns from each table into a variable.
What I would like to do is find a way to get the values out of the variable, such that I can match the column names.
They should be identical in each table, and I need it to be able to create a dynamic merge statement, such that the columnsnames from each variable
#ContentColumnNamesSRC
and
#ContentColumnNamesDST
line up, so I can use it in a merge statement.
The point of this is to be able to use it in a loop, and all i would have to do is change which tables it looks at and the merge statements would still work.
Ideally, id like to end up with something like the following:
SELECT #StageSQLCore = N'USE Staging;
BEGIN TRANSACTION
MERGE '+#StageTableCore+' AS DST
USING '+#ImportTableCore+' AS SRC
ON (SRC.[Key] = DST.[Key])
WHEN NOT MATCHED THEN
INSERT ('+#StageTableCoreColumns+')
VALUES (
'+#ImportTableCoreColumns+',GETDATE())
WHEN MATCHED
THEN UPDATE
SET
DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.'+#ContentColumnNamesDST[i]' = SRC.'+#ContentColumnNamesSRC[i] +'
,DST.[ETLDate] = GETDATE()
;
COMMIT'
EXEC (#StageSQLCore)
You can generate Merge SQL like this if both the ordinal are matching
DECLARE #MergeSQL NVARCHAR(4000) = NULL
SELECT --*--,
#MergeSQL = COALESCE(#MergeSQL + ', DST.=', '') + QUOTENAME(bc.column_name) + ' = SRC.' + QUOTENAME(bc.COLUMN_NAME) + char(13)
FROM
test.INFORMATION_SCHEMA.COLUMNS tc
inner join testb.INFORMATION_SCHEMA.COLUMNS bc
on tc.TABLE_NAME = bc.TABLE_NAME
and tc.ORDINAL_POSITION = bc.ORDINAL_POSITION
and tc.TABLE_NAME = 'History'
WHERE
tc.ORDINAL_POSITION < 5 -- First 8 columns are ID data, which is what I am after
and bc.ORDINAL_POSITION < 5
select #MergeSQL

Function not returning results when executing via table data

I have a homegrown batch processing engine that I use on my website. I'm trying to create a function that will get me the "NextRunDate" for all of the jobs that are enabled.
Here's the function:
create function dbo.uf_BatchJob_Get_NextRunDate
(
#daysToRun varchar(19),
#hoursToRun varchar(84)
)
returns datetime as begin
set #daysToRun = replace(#daysToRun, ' ', '')
set #hoursToRun = replace(#hoursToRun, ' ', '')
declare #now datetime = getdate()
declare #currentYear int = datepart(yyyy, #now)
declare #currentMonth int = datepart(MM, #now)
declare #currentDay int = datepart(dd, #now)
declare #currentHour int = datepart(hh, #now)
declare #timeCheck datetime = convert(varchar(4), #currentYear) + '/' + convert(varchar(2), #currentMonth) + '/' + convert(varchar(2), #currentDay) + ' ' + convert(varchar(2), #currentHour) + ':00:00.000'
declare #foundDay bit = 0
declare #foundHour bit = 0
while (#foundDay = 0 or #foundHour = 0) begin
-- increment by 1 hour
set #timeCheck = dateadd(hh, 1, #timeCheck)
declare #dayOfWeekCheck int = datepart(dw, #timeCheck) - 1 -- 0 based versus 1 based
declare #hourCheck int = datepart(hh, #timeCheck)
if (charindex(cast(#dayOfWeekCheck as varchar(1)), #daysToRun) > 0) begin
set #foundDay = 1
end else begin
set #foundDay = 0
end
declare #hourIndex int = charindex(cast(#hourCheck as varchar(2)), #hoursToRun)
declare #characterBefore varchar(1) = substring(#hoursToRun, #hourIndex - 1, 1)
declare #characterAfter varchar(1) = substring(#hoursToRun, #hourIndex + len(#hourCheck), 1)
if (#characterBefore = '') begin
set #characterBefore = ','
end
if (#characterAfter = '') begin
set #characterAfter = ''
end
if (#foundDay = 1) begin
if (#hourIndex > 0 and #characterBefore = ',' and #characterAfter = ',') begin
set #foundHour = 1
end else begin
set #foundHour = 0
end
end
end
-- it's been found, let's return the value
return #timeCheck
end
For info, the #daysToRun and #hoursToRun parameters are structured like this: '0, 1, 3, 6' (for days) or '2, 4, 9, 15, 19, 23' (for hours)
When I execute this function sending those example parameters explicitly, it returns exactly what I want. Like this:
select dbo.uf_BatchJob_Get_NextRunDate('0, 1, 3, 6', '2, 4, 9, 15, 19, 23')
But if I call this function in-line with live data, it never responds. Here's how I'm doing that:
select x.BatchProcessId,
x.BatchName,
x.DaysToRun,
x.HoursToRun,
dbo.uf_BatchJob_Get_NextRunDate(x.DaysToRun, x.HoursToRun) [NextRunDate]
from (
select bp.BatchProcessId,
bp.BatchName,
bps.DaysToRun,
bps.HoursToRun
from dbo.BatchProcess bp
join dbo.BatchProcessSchedule bps on bps.BatchProcessId = bp.BatchProcessId
and bps.[Enabled] = 1
where bp.[Enabled] = 1
) x
Why would it not respond when running it the in-line way? The subquery only returns 9 jobs that are enabled, so it's not chunking through very many records...
Edit: Oh, and I threw in the sub-select because I thought it might have been having problems trying to call the function for records that WEREN'T enabled yet, so I just wanted to make sure it was only processing the 9 jobs.
I figured out what was going on.
When the #hoursToRun value passed only has a single "hour", that's when the function never returns anything.
For example:
#hoursToRun = '13' -- which would be 1:00pm
To fix this for now, I just have to add a comma to the end and it works fine, like this:
set #hoursToRun = #hoursToRun + ','

Check if all word fragments exist in a text

I want to check if all given word fragments exist in any order in a given text.
The fragments are supplied by a web application user in a single string separated by spaces like 'abc xyz kj'. They exist in 'mn kj qabc pc xyzw' but do not exist in 'mn kj qabc pc xyw'.
I wrote the following function which works but it looks quite convoluted so I must be doing it wrong. Any ideas on different approaches or how to make it perform?
BTW the database is read only for me so I can't full-text index it and the owners will not do it.
create function dbo.tem_fragmentos(
#texto varchar(max),
#fragmentos varchar(max)
)
returns bit as
begin
declare
#inicio integer = 1,
#fim integer,
#fragmento varchar(max);
set #fragmentos = ltrim(rtrim(#fragmentos));
while charindex(' ', #fragmentos) > 0
set #fragmentos = replace(#fragmentos, ' ', ' ');
while #inicio <= len(#fragmentos) begin
set #fim = charindex(' ', #fragmentos, #inicio + 1);
if #fim = 0 set #fim = len(#fragmentos) + 1;
set #fragmento = substring(#fragmentos, #inicio, #fim - #inicio);
if charindex(#fragmento, #texto) = 0 return 0;
set #inicio = #fim + 1;
end -- while
return 1;
end;
select dbo.tem_fragmentos('clodoaldo pinto neto', ' clo cl nto pinto');
This is how I would do it. Not sure it's any less convoluted...
Create Function dbo.tem_fragmentos
(
#texto varchar(max),
#fragmentos varchar(max)
)
Returns Bit As
Begin
Declare #table Table (fragmentos Varchar(Max))
Set #fragmentos = Ltrim(Rtrim(#fragmentos))
While #fragmentos <> ''
Begin
Insert #table (fragmentos)
Select Left(#fragmentos,Charindex(' ',#fragmentos+' ')-1)
Set #fragmentos = Ltrim(Rtrim(Right(#fragmentos,Len(#fragmentos)-(Charindex(' ',#fragmentos+' ')-1))));
end
If Exists (Select 1
From #table t
Where #texto Not Like '%' + fragmentos + '%')
Begin
Return 0;
End
Return 1;
End;
Select dbo.tem_fragmentos('clodoaldo pinto neto', ' clo cl nto pinto');
I'm assuming your text exists in a db table, else you wouldn't have the db server doing the work. So, why not have your app break the string on spaces and build dynamic SQL like:
select *
from MyTable
where charindex('abc', MyColumn) > 0
and charindex('xyz', MyColumn) > 0
and charindex('kj', MyColumn) > 0
Update:
If you don't want to use dynamic SQL, I would split the input into words in my application, and then pass the list of words in to the query using a table valued parameter (TVP). Then it is a simple left join to determine whether they all match or not.
Sounds like a wildcarded LIKE search should work for you:
declare #texto varchar(max) = 'mn kj q abc pc xyzw',
#fragmentos varchar(max) = 'abc xyz kj'
/*
yes = 'mn kj qabc pc xyzw'
no = 'mn kj qabc pc xyw'
*/
--use your own number table
declare #number table (n int identity(1,1) primary key clustered, x char(1) null);
insert into #number(x)
select top 1000 null from master..spt_values
select [IsMatch] = min(case when #texto like '%'+substring(#fragmentos, n, charindex(' ', #fragmentos + ' ', n) - n)+'%' then 1 else 0 end)
from #number
where n <= datalength(#fragmentos)+1 and
substring(' ' + #fragmentos, N, 1) = ' ';

Resources