Multiple IF clause within WHERE clause in T-SQL - sql

I have a table #t1 containing three columns, one of them named root_symbol.
I want to select all elements from table #t1 meeting the condition of variable #root.
However, variable #root can have four different values, thus I would like to dynamically select the filter applied by the where statement depending on the declared variable value.
declare #root integer
set #root = 1
select * from #t1
where root_symbol in
case when #root = 1 then ('ES')
when #root = 2 then ('EW', 'EW1', 'EW2', 'EW3', 'EW4')
when #root = 3 then ('E1C', 'E2C', 'E3C', 'E4C', 'E5C')
when #root = 4 then ('E1A', 'E2A', 'E3A', 'E4A', 'E5A')
end
I have seen this done using case when, however not with the value matching condition in.
An alternative would be to create an if statement with four different select statements inside, however also here I am not sure how to handle multi clause if statements in sql.
Any idea how to solve this?

Don't use case. Use simple comparisons:
where (#root = 1 and root_symbol in ('ES')) or
(#root = 2 and root_symbol in ('EW', 'EW1', 'EW2', 'EW3', 'EW4')) or
(#root = 3 and root_symbol in ('E1C', 'E2C', 'E3C', 'E4C', 'E5C')) or
(#root = 4 and root_symbol in ('E1A', 'E2A', 'E3A', 'E4A', 'E5A'))
In general, use case expressions in the where clause is a bad idea (there are some exceptions). Because the case forces the order of evaluation of its clauses, it prevents the optimizer from doing anything.

Related

How to exclude SQL variable in output

I have a complex SQL query where I have a few cases that use END AS variableName. I then use variableName to do some logic and then create a new variable which I want in the output result. However when I run the query, all the END AS variableNames that I have used are also outputted in the results.
Is there a way that I can exclude these variables as I only want the final variable that uses these variableNames.
Thanks
EDIT, here is a query explaining my problem
SELECT DISTINCT
mt.src_id AS “SRC_ID”,
CASE
WHEN mt.cd = ‘TAN’ THEN
(
(
SELECT SUM(src_amt)
FROM source_table st
WHERE mt.id = st.id
AND st._cd = ‘TAN’
AND st.amt_cd = ‘ABL’)
)
END AS src_amt
FROM MAIN_TABLE mt
WHERE
mf.dt >= 2021-12-12
AND SRC_AMT > 10
I need SRC_AMT to be used as some sort of logic but when I run the query, it prints out in the output as it's own column. I want to ignore this variable
you can wrap the whole thing into a new select-statement:
select SRC_ID from ( <entire previous query here> )

Snowflake ignores statement in where clause where I'm comparing timestamps

so I'm building a SCD type 2 in snowflake, but it ignores the where clause in which is comparision between "to_timestamp" and "expiry_date". Expiry_date is a variable that is set to '9999-08-17 07:31:29.901000000' (as infinity) and To_timestamp is a column in table. I want to query only the rows that have to_timestamp set to infinity (they are still active) but snowflake seems to ignore this part of where clause. Below is some of the code (it should update the rows that are expired - that means change their "to_timestamp" to current time. and it does but it does to rows with timestamps of all kind - it ignores last line)
SET EXPIRY_DATE_NTZ = '9999-08-17 07:31:29.901000000';
SET CURRENT_DATE_NTZ = TO_TIMESTAMP_NTZ(CURRENT_TIMESTAMP());
UPDATE CUSTOMER_TARGET CT
SET CT.TO_TIMESTAMP = $CURRENT_DATE_NTZ
FROM POC.SNOWFLAKE_POC.CUSTOMER_STAGE CS
WHERE CT.C_CUSTOMER_ID = CS.C_CUSTOMER_ID
AND (CT.C_FIRST_NAME <> CS.C_FIRST_NAME OR CT.C_LAST_NAME <> CS.C_LAST_NAME OR CT.C_BIRTH_YEAR
<> CS.C_BIRTH_YEAR OR CT.C_BIRTH_COUNTRY <> CS.C_BIRTH_COUNTRY OR CT.C_LAST_REVIEW_DATE<>CS.C_LAST_REVIEW_DATE)
AND CT.TO_TIMESTAMP = $EXPIRY_DATE_NTZ;
I have two of these update statements (one for updates and one for deletes) and a merge statement for inserts. And it ignores the comparision in every single one, updating the rows that have "to_timestamp" set to something like "2021-08-24 07:11:53.510000000". I've tried every combination possible (between ... and ..., >= ... <=, <=, >=, comparing in "case" statement of update,...) - nothing. What could be the cause/solution?
As we do not know the structure of CUSTOMER_TARGET I would suggest to explicitly set the data type of EXPIRY_DATE_NTZ variable to match the column data type:
SET EXPIRY_DATE_NTZ = '9999-08-17 07:31:29.901000000';
SELECT $EXPIRY_DATE_NTZ;
DESCRIBE RESULT LAST_QUERY_ID();
to:
-- TIMESTAMP_NTZ as an example
SET EXPIRY_DATE_NTZ = '9999-08-17 07:31:29.901000000'::TIMESTAMP_NTZ;
SELECT $EXPIRY_DATE_NTZ;
DESCRIBE RESULT LAST_QUERY_ID();
By doing that way there are no "implicit conversions" involved in the process.
Another advice is usage of IS DISTINCT FROM instead of <>. IS DISTINCT FROM is NULL safe, which is important if columns are defined as nullable.
UPDATE CUSTOMER_TARGET CT
SET CT.TO_TIMESTAMP = $CURRENT_DATE_NTZ
FROM POC.SNOWFLAKE_POC.CUSTOMER_STAGE CS
WHERE CT.C_CUSTOMER_ID = CS.C_CUSTOMER_ID
AND (CT.C_FIRST_NAME IS DISTINCT FROM CS.C_FIRST_NAME
OR CT.C_LAST_NAME IS DISTINCT FROM CS.C_LAST_NAME
OR CT.C_BIRTH_YEAR IS DISTINCT FROM CS.C_BIRTH_YEAR
OR CT.C_BIRTH_COUNTRY IS DISTINCT FROM CS.C_BIRTH_COUNTRY
OR CT.C_LAST_REVIEW_DATE IS DISTINCT FROM CS.C_LAST_REVIEW_DATE)
AND CT.TO_TIMESTAMP = $EXPIRY_DATE_NTZ;
Your SQL does not have any issues with the filters (ORs are surrounded by the brackets etc). I assume that you have checked the execution profile, and did not see your filter (CT.TO_TIMESTAMP = '9999-08-17 07:31:29.901000000'). In this case, all rows in the target table should have this value in the column TO_TIMESTAMP.
I highly recommend you check the data first. If you are running multiple UPDATE/MERGE commands, you may miss that the data has already updated with this value.

Set a Variable in a case expression

I am looking to pass declared variables to build my string. I think I want to set my variable via a case expression but I have not done this before. Here is what I have done thus far.
DECLARE #stu_conv AS VARCHAR(5)
-- I think I need a select here.
set #stu_conv = CASE WHEN ITMMASTER.STU_0 ='KG' THEN'2.2'END
SELECT
YPRIMAT.YCROPYR_0
,ITMMASTER.TCLCOD_0
,SPRICLIST.DCGVAL_3
,ITMMASTER.TSICOD_2
,ITMMASTER.ACCCOD_0
,(BASPRI_0*#stu_conv) AS ImportstringAS Importstring
FROM LIVE.YPRIMAT
INNER JOIN LIVE.ITMMASTER ON YPRIMAT.ITMREF_0 = ITMMASTER.ITMREF_0
LEFT OUTER JOIN LIVE.SPRICLIST ON ITMMASTER.TCLCOD_0 = SPRICLIST.PLICRI1_0
WHERE SPRICLIST.PLICRD_0 = 'SPL000020'
I don't see the point for using a variable here, and trying to set it outside the query does not make sense, since you most likely want the value to reset for each row.
I would suggest moving the case expression into the query, as follows:
select
y.ycropyr_0,
i.tclcod_0,
s.dcgval_3,
i.tsicod_2,
i.acccod_0,
baspri_0 * case when i.stu_0 = 'KG' then 2.2 else 1 end as importstringas importstring
from live.yprimat y
inner join live.itmmaster i on y.itmref_0 = i.itmref_0
left outer join live.spriclist s on i.tclcod_0 = s.plicri1_0
where s.plicrd_0 = 'SPL000020'
I assumed that you want a value of 1 when stu_0 is not 'KG', but you can change this as needed.
Side note:
I modified your query to use table aliases. This makes the query shorter to write and somehow easier to read
you would need to prefix column baspri_0 with the table it belongs to (as your query is, it is not possible to tell)
I'm not sure why you're declaring a string and then multiplying it, but I would just inline the case (and add a default case?):
,(BASPRI_0 * CASE
WHEN ITMMASTER.STU_0 ='KG'
THEN 2.2
ELSE ???
END) AS Importstring

SQL - WHERE clause on each SET command in UPDATE?

I'm trying to create an SQL query in PHP to update a table.
Is it possible to have a different WHERE clause for each affected row?
eg something like:
UPDATE table
SET val=X WHERE someproperty = 1,
SET val=Y WHERE someproperty = 2
etc?
Any help appreciated. Thanks
Yes, you can with a CASE statement.
UPDATE table
SET val = CASE someproperty
WHEN 1 THEN x
WHEN 2 THEN y
....
ELSE
val
END
Now, there is concern that one CASE statement is less readable when compared to several UPDATE statements. There is a valid argument here. For example, when 1000 rows are being updated, it just feels and looks better to use several UPDATE statements rather than 1000 different conditions to a single CASE.
However, sometimes a CASE statement is more appropriate. If, for example, you are updating rows based on some trait, say the even or odd nature of a field's value the table, then a CASE statement is a wonderfully concise and maintainable way to update rows in the table without having to resort to a huge number of UPDATE statements that all share a specific type of logic. Take this for example:
UPDATE table
SET val = CASE MOD(someproperty, 2)
WHEN 0 THEN x
WHEN 1 THEN y
END
This expression takes the modulus of someproperty and, when 0 (even), assigns value x to val and, when 1 (odd), assigns value y to val. The greater the volume of data being updated by this statement, the cleaner it is compared to doing so by multiple UPDATE statements.
In short, CASE statements are sometimes just as readable/maintainable as UPDATE statements. It all depends on what you are trying to do with them.
EDIT: Added the ELSE clause to be extra safe. The OP may be interested in updating only specific rows so the rest should remain as they prior to the UPDATE.
EDIT: Added a scenario where the CASE statement is a more effective approach than multiple UPDATE statements.
You cannot have multiple WHERE clauses for any SQL statement, however you can use a CASE statement to accomplish what you are trying to do. Another option that you have is to execute multiple UPDATE statements.
Here is a sample using the CASE statement:
UPDATE table
SET val = (
CASE someproperty
WHEN 1 THEN X
WHEN 2 THEN Y
ELSE val
END
);
Here is a sample using multiple UPDATE statements:
UPDATE table SET val=X WHERE someproperty = 1;
UPDATE table SET val=Y WHERE someproperty = 2;
Nope. Make it two updates:
UPDATE table SET val=X WHERE someproperty = 1;
UPDATE table SET val=Y WHERE someproperty = 2;
On second thought, you could use sub-queries or the case statement...
UPDATE table SET val= ( case when someproperty = 1 then X when someproperty = 2 then Y else val END )
You may need to make that a sub query like this:
UPDATE table t1 SET val = ( select CASE when someproperty = 1 then X when someproperty = 2 then Y ELSE val END from table t2 where t1.primarykey = t2.primary key )
UPDATE TABLE
SET VAL CASE SOMEPROPERTY WHEN 1 THEN X WHEN 2 THEN Y END
A compact and easily scaleable way:
UPDATE table1 SET val=ELT(FIND_IN_SET(someproperty, '1, 2'), X, Y);
make the query this way:
$condition = array(1, 2);
$newvals = array('X', 'Y');
$query = "UPDATE table1 SET val=ELT(FIND_IN_SET(someproperty, '". implode(',', $condition). "', ". implode(', ', $newvals). ")";
Use prepare_query to avoid SQL syntax errors if you deal with string values.

Changing stored procedure

I have a proc that print checks if there is any new checks to be print. If there is nothing to issue new checks it wont print any. Now i want to modify this proc like even if i don't have any new checks to be print, it should pick up at least one check to be print.( even if it is already printed). Can you tell me how to do that. Here is the stored proc.
CREATE PROCEDURE [proc_1250_SELCashiersChecksForPrint] AS
SELECT t_DATA_CashiersChecksIssued.ControlNbr,
t_DATA_CashiersChecksIssued.Audit_DateAdded,
t_DATA_CashiersChecksIssued.BatchNbr,
t_DATA_CashiersChecksIssued.SerialNbr,
t_DATA_CashiersChecksIssued.CheckRTN,
t_DATA_CashiersChecksIssued.CheckAccountNbr,
t_DATA_CashiersChecksIssued.Amount,
t_DATA_CashiersChecksIssued.DateIssued,
t_DATA_CashiersChecksIssued.Payee,
t_DATA_CashiersChecksIssued.Address,
t_DATA_CashiersChecksIssued.City,
t_DATA_CashiersChecksIssued.State,
t_DATA_CashiersChecksIssued.Zip,
t_DATA_Reclamation.ClaimId,
t_DATA_Reclamation.NoticeDate,
t_DATA_Reclamation.FirstName,
t_DATA_Reclamation.MiddleName,
t_DATA_Reclamation.LastName,
t_DATA_Reclamation.ClaimTotal,
t_PCD_Claimant.Name AS Agency,
t_DATA_CashiersChecksIssued.IDENTITYCOL
FROM t_DATA_CashiersChecksIssued INNER JOIN
t_DATA_Reclamation ON
t_DATA_CashiersChecksIssued.ControlNbr = t_DATA_Reclamation.ControlNbr
INNER JOIN
t_PCD_Claimant ON
t_DATA_Reclamation.ClaimantCode = t_PCD_Claimant.ClaimantCode
WHERE (t_DATA_CashiersChecksIssued.SerialNbr IS NULL) AND
(t_DATA_CashiersChecksIssued.DateIssued IS NULL)
ORDER BY t_DATA_CashiersChecksIssued.Audit_DateAdded ASC,
t_DATA_CashiersChecksIssued.ControlNbr ASC
Let me know if you need more information.
SELECT TOP 1 t_DATA_CashiersChecksIssued.ControlNbr,
t_DATA_CashiersChecksIssued.Audit_DateAdded,
t_DATA_CashiersChecksIssued.BatchNbr,
t_DATA_CashiersChecksIssued.SerialNbr,
t_DATA_CashiersChecksIssued.CheckRTN,
t_DATA_CashiersChecksIssued.CheckAccountNbr,
t_DATA_CashiersChecksIssued.Amount,
t_DATA_CashiersChecksIssued.DateIssued,
t_DATA_CashiersChecksIssued.Payee, t_DATA_CashiersChecksIssued.Address,
t_DATA_CashiersChecksIssued.City, t_DATA_CashiersChecksIssued.State,
t_DATA_CashiersChecksIssued.Zip, t_DATA_Reclamation.ClaimId,
t_DATA_Reclamation.NoticeDate, t_DATA_Reclamation.FirstName,
t_DATA_Reclamation.MiddleName, t_DATA_Reclamation.LastName,
t_DATA_Reclamation.ClaimTotal, t_PCD_Claimant.Name AS Agency,
t_DATA_CashiersChecksIssued.IDENTITYCOL
FROM t_DATA_CashiersChecksIssued
INNER JOIN t_DATA_Reclamation ON t_DATA_CashiersChecksIssued.ControlNbr = t_DATA_Reclamation.ControlNbr
INNER JOIN t_PCD_Claimant ON t_DATA_Reclamation.ClaimantCode = t_PCD_Claimant.ClaimantCode
ORDER BY t_DATA_CashiersChecksIssued.Audit_DateAdded DESC
Use the TOP n SQL syntax :
if EXISTS ( /* Look for an unprinted check - "date_issued is null" */ )
/* print unprinted checks */
ELSE
select top 1 /* already-printed-checks */
where .... "date_issued is not null"
OR
Do you want to print a "Voided/Cancelled" check - when you do that?
You have to decide how you want to pick your "at least one".
The simplest way (probably) is to remove whatever condition in the WHERE clause is excluding already printed checks. Let's assume that's t_DATA_CashiersChecksIssued.DateIssued IS NULL. Now add a column to your SELECT clause like this: CASE WHEN t_DATA_CashiersChecksIssued.DateIssued IS NULL then 0 ELSE 1 END and make that column first in your ORDER BY clause.
Now in the procedure, fetch just one row from this cursor. If this new column has the value 0, there's at least one new check to be processed and you should iterate through the cursor but stop when you get to an already issued one. If it has the value 1, there are no new checks.
Edit: The other approach would be to do it right in your SQL. Leave the original as is, but add a clause like:
UNION ALL SELECT ... AND ROWNUM = 1 where ... represents your existing query, but with the condition to exclude already printed checks removed. On second thought, this may be simpler.