Sql combine WHEN and IF statement with multiple parameters - sql
How to combine Q1 and Q2 into someting like Q5 (But working!) ? Plse? I'M stuck... SQL Server 2008 R2
Please have a look what I've tried so far:
--Q1 does execute without errors:
DECLARE #RfDate date
DECLARE #Description nvarchar(250)
DECLARE #BAccount int
SET #RfDate = '{Rf_Date}'
SET #Description = {dbo.BankstatementLine_Description|type=string}
SET #BAccount = {dbo.BankAccount_Id}
IF #RfDate <> ''
BEGIN
SELECT *
FROM
dbo.BankStatementLine
WHERE
Date >=#RfDate
AND
FkBankAccount = #BAccount
AND
IsDebit = 'true'
AND
Id NOT IN (select FkBankStatementLine from DocumentBankStatementLine)
END
ELSE
BEGIN
SELECT *
FROM
dbo.BankStatementLine
WHERE
Date <> #RfDate
AND
FkBankAccount =#BAccount
AND
IsDebit = 'true'
AND
Id NOT IN (select FkBankStatementLine from DocumentBankStatementLine)
END
--Q2 does execute without errors:
DECLARE #RAmount float
SET #RAmount = {Rf_Amount}
IF #RAmount <>''
BEGIN
SELECT Amount
FROM dbo.BankStatementLine
WHERE Amount=#RAmount
END
ELSE
BEGIN
SELECT Amount
FROM dbo.BankStatementLine
WHERE Amount<>#RAmount
END
After the help from X-Zero (imperative describes my character better then my program skills btw), I came up with the following query which actually does what it needs to do, except for the last two AND OR rules. The LIKE does not have any influence, if I enter ‘PEKING’ in the field #RfAccept, the entries whith Description “0111111111 GPPeking” should be presented, but is does not.....
:
DECLARE #RfDate date
DECLARE #BAccount int
DECLARE #RfAmount decimal
DECLARE #RfAcAmount float
DECLARE #RfKenmerk nvarchar(250)
DECLARE #RfAccept nvarchar(250)
SET #RfDate = '{Rf_Date}'
SET #BAccount = {dbo.BankAccount_Id}
SET #RfAmount = {Rf_Amount}
SET #RfAcAmount = {Rf_AccAmount}
SET #RfKenmerk = {Rf_Betalingskenmerk|type=string}
SET #RfAccept = {Rf_Acceptgiro|type=string}
SELECT *
FROM
dbo.BankStatementLine
WHERE -- All statements can have a value or ''. All statements are not mandatory.
isDebit = 1
AND Id NOT IN (select FkBankStatementLine from DocumentBankStatementLine)
AND fkBankAccount = {dbo.bankAccount_Id}
AND ((Date = #RfDate AND #RfDate <> '')
OR (Date <> #RfDate AND #RfDate = ''))
AND ((Amount = #RfAmount AND #RfAmount <> '')
OR (Amount <> #RfAmount AND #RfAmount = ''))
AND ((Amount = #RfAcAmount AND #RfAcAmount <> '')
OR (Amount <> #RfAcAmount AND #RfAcAmount = ''))
AND((Description LIKE '%#RfAccept%' AND #RfAccept<>'')--When Rf_Acceptgiro has a value, the value must be part of the field Description.
OR (Description <> #RfAccept AND #RfAccept ='')) --OR Return all Description rules
AND((Description LIKE '%#RfKenmerk%' AND #RfKenmerk<>'')--When Rf_Kenmerk has a value, the value must be part of the field Description.
OR (Description <> #RfKenmerk AND #RfKenmerk =''))--OR Return all Description rules
... You seem to have some issues with (not fully) understanding SQL, and all it can do in just general use. These kinds of 'procedures' are generally (although not always) unnecessary. You haven't listed your RDBMS, so I'm writing as if for DB2 (excluding statement parameters), although most major RDBMSs will be able to run the statements largely as is.
-Q1 can be simply re-written as so:
SELECT a.* -- note, make sure to list all columns in actual query, '*' is bad form
FROM dbo.BankStatementLine as a
EXCEPTION JOIN DocumentBankStatementLine as b
ON b.fkBankStatementLine = a.id -- this is your 'NOT IN' statement
WHERE a.isDebit = 'true' -- this is really bad; usea boolean type if available
-- otherwise, use `int`/`char` 1/0, with check constraints
AND a.fkBankAccount = {dbo.bankAccount_id}
AND (({Rf_Date} IS NOT NULL AND a.Date >= {Rf_Date}) -- please name 'a.date' better...
OR ({Rf_Date} IS NULL AND a.Date IS NULL)) -- and use `date` types, and -null-
-Q2 follows the same general principles (although it seems pointless; you already know the amount):
SELECT amount -- it's -really- bad to use `float` types for money
-- use `decimal` or `int` (cents) types instead
FROM dbo.BankStatementLine
WHERE (({Rf_Amount} IS NOT NULL AND amount = {Rf_Amount})
OR ({Rf_Amount} IS NULL AND amount IS NULL))
-Q3 seems to be a combination of Q1 and Q2 (sort of):
SELECT a.*
FROM dbo.BankStatementLine as a -- unless there's a -need- to query a specific
-- schema, leave it off; you'll get a bit of flexibility
EXCEPTION JOIN DocumentBankStatementLine as b
ON b.fkBankStatementLine = a.id
WHERE a.isDebit = 'true'
AND a.fkBankAccount = {dbo.bankAccount_Id} -- this seems like a table name?
AND (({Rf_Date} IS NOT NULL AND a.Date >= {Rf_Date})
OR ({Rf_Date} IS NULL AND a.Date IS NULL))
AND (({Rf_Amount} IS NOT NULL AND amount = {Rf_Amount}) -- didn't know what you
OR ({Rf_AccAmount} IS NOT NULL AND amount = {Rf_AccAmount})) -- actually wanted
-- But `null`s don't compare to anything, so...
-Q4: I'm not sure you can actually nest if statements that way (especially the Amount-(IF #RAmount <> '') line, and you definitely shouldn't. It's also unclear (because of Q3) whether the ordering is important, so I've assumed it is. Try this instead (please note that this, and your original version, only work if there's one row returned):
SELECT *
FROM dbo.BankStatementLine
WHERE amount = COALESCE({RF_Amount}, {RfAccAmount}, (SELECT amount
FROM dbo.BankStatementLine
WHERE amount IS NOT NULL))
-Q5 seems to be some wierd conglomeration of Q3 and Q1. The same re-writable rules apply though... And if you want to combine Q2 and Q1, simply add together the missing predicates from the WHERE clauses (for our purposes, EXCEPTION JOINs are considered WHERE clause predicates):
SELECT a.*
FROM dbo.BankStatementLine as a
EXCEPTION JOIN DocumentBankStatementLine as b
ON b.fkBankStatementLine = a.id
WHERE a.isDebit = 'true'
AND a.fkBankAccount = {dbo.bankAccount_Id}
AND (({Rf_Date} IS NOT NULL AND a.Date >= {Rf_Date})
OR ({Rf_Date} IS NULL AND a.Date IS NULL))
AND (({Rf_Amount} IS NOT NULL AND amount = {Rf_Amount})
OR ({Rf_Amount} IS NULL AND amount IS NULL))
Nesting if statements/clauses is always slightly treacherous, even in normal imperative (Java, C#, C++, etc) languages. In SQL and similar, it's probably just plain wrong. You seem to be thinking too much like an imperative programmer - learn to think in sets (your WHERE and JOIN clauses), and you'll be much better off.
EDIT:
It's not returning the results you expect, because the comparison is case sensitive. This is generally true of most other programming languages, as well.
As a side note, doing a LIKE '%<insertValueHere>' (with a leading %) prevents indicies from being used (because there's no good way for the optimizer to know where in the column the value appears). Wrapping the column in a function isn't going to help much, either, but I believe there are ways to create 'materialized'/computed indicies, such that the value is already available.
In any case, here's the appropriate adjustments. Please note that, if a column will always have a value (as opposed to null), you don't need to do a comparison if the input parameter isn't provided.
DECLARE #RfDate date
DECLARE #BAccount int
DECLARE #RfAmount decimal
DECLARE #RfAcAmount float
DECLARE #RfKenmerk nvarchar(250)
DECLARE #RfAccept nvarchar(250)
SET #RfDate = '{Rf_Date}'
SET #BAccount = {dbo.BankAccount_Id}
SET #RfAmount = {Rf_Amount}
SET #RfAcAmount = {Rf_AccAmount}
SET #RfKenmerk = {Rf_Betalingskenmerk|type=string}
SET #RfAccept = {Rf_Acceptgiro|type=string}
SELECT *
FROM dbo.BankStatementLine
WHERE isDebit = 1 -- Thank you, this is much better. If numeric, add check constraint.
AND Id NOT IN (select FkBankStatementLine from DocumentBankStatementLine)
-- I am unsure of the performance of this relative to other options
AND fkBankAccount = {dbo.bankAccount_Id}
-- Assuming this is an actual date field, non-nullable, you don't need the second comparison
AND ((Date = #RfDate AND #RfDate <> '')
OR (#RfDate = ''))
-- Don't store amounts as strings, just don't...
-- Comparison removed for reason above.
AND ((Amount = #RfAmount AND #RfAmount <> '')
OR (#RfAmount = ''))
-- See notes above
AND ((Amount = #RfAcAmount AND #RfAcAmount <> '')
OR (#RfAcAmount = ''))
-- If you want all rules/whatever, then...
-- If it's case insensitive, it's best to -store- it that way. Otherwise, wrap in function
AND((UPPER(Description) LIKE '%' || UPPER(#RfAccept) || '%' AND #RfAccept<>'')
OR (#RfAccept =''))
-- Same thing here too.
AND((Description LIKE '%#RfKenmerk%' AND #RfKenmerk<>'')
OR (#RfKenmerk =''))
When you're inputting a parameter that says, "I don't care what it is, give me everything", the comparison with the field isn't needed (and you may get better performance by removing the comparison). If you still have to check to make sure the field isn't null or blank or something, then you do have to check it at some point - either in the SQL, or after getting the result set.
Also, if you're searching inside the field with LIKE '%whatever' because it contains multiple values (either as a delimiter/formatted list, or a datastructure), you (most likely) have the tables set up incorrectly; reformat to put the column in it's own table(s) (well, _usually). If it's part of the thing (like searching for a word in a book title), you just have to take the performance hit (well, there are maybe ways around this, but result in somewhat wacky designs).
Looks like you're working with SQL Server (it usually helps to specify):
Amount = CASE WHEN #RAmount <> '' THEN #RAmount
WHEN #RAcAmount <> '' THEN #RacAmount
ELSE Amount END
is what I think you're looking for. If the first parameter is not empty, then use it. If the first parameter is empty, and the second one is not, then use the second one. If both are empty, then return all values.
Related
Problem with field not equal to null in case statement
So I have EXISTS in huge query which looks like this: EXISTS( SELECT * FROM ExistTable WHERE ExTableFieldA = #SomeGuid AND ExTableFieldB = MainTableFieldB AND ExTableFieldA <> ( CASE WHEN MainTableFieldZ = 10 THEN MainTableFieldYYY ELSE NULL END ) ) The problem comes from ELSE part of CASE statement, this ExTableFieldA <> NULL will be always false. I could easily write another parameter #EmptyGuid and make it equal to '00000000-0000-0000-0000-000000000000' and everything will work but is this the best approach ? Pretty much I want to execute another check into the exist for the small size of the records which return the "main" query.
How about removing the case and just using boolean logic? WHERE ExTableFieldA = #SomeGuid AND ExTableFieldB = MainTableFieldB AND (MainTableFieldZ <> 10 OR ExTableFieldA <> MainTableFieldYYY) I would also recommend that you qualify the column names by including the table alias. Note: This does assume that MainTableFieldZ is not NULL. If that is a possibility than that logic can easily be incorporated.
ELSE NULL is implied even if you don't list it, but you could use ISNULL here. ISNULL(ExTableFieldA,'') <> ( CASE WHEN MainTableFieldZ = 10 THEN MainTableFieldYYY ELSE '' END ) You may need to use some other value like 9999 instead of ''
SQL COALESCE and IS NULL are not returning a space when when the query returns NULL
I am trying to optimize a humongous SQL query that was written by a self taught developer that used a ton of functions instead of JOINS. Anyway, I am having trouble displaying a space or a empty string('') when there is no value in the field selected. I've included only the SELECT in question. I am having the weirdest problem or just overlooking the correct answer in troubleshooting. Whenever I use COALESCE, when the field is supposed to be a blank string, it displays a zero. And when I use IS NULL, I get back NULL. All info online seems to point toward using COALESCE(value, '') as depicted in the code. But I am getting a 0 instead of ''. Does anyone see what I'm doing wrong? I'm using SSMS. SELECT pss8.dbo.xml_StripIllegalChars(dbo.rpt_get_series_volume(b.bookkey)) AS p_seriesvol --SELECT to be replaced that works but is slow due to function use I am told ,COALESCE(bd.seriesvolume, '') AS p_seriesvol --my SELECT that won't work! FROM bookdetail bd WHERE --bd.bookkey='303177' bd.bookkey='6002' The bookkeys at the bottom are for testing as I know the top one returns a 1 and the bottom one returns a '' previously when it worked. The SELECT above my commented SELECT is the code that works but is slow... According to what I read online, I am saying 'if there isn't a series volume number, then it equals an empty string.' Does COALESCE not work like this? Can it only return a 0 if the field has no value, or in this case, has no volume number? All help much appreciated. I'm very curious to hear a solution! Here's more intel. This is how the this SELECT works: pss8.dbo.xml_StripIllegalChars(dbo.rpt_get_series_volume(b.bookkey)) AS p_seriesvol The .rpt_get_series_vol function manages to create an empty string with this code... Does this reveal anything? DECLARE #RETURN VARCHAR(5) DECLARE #v_desc VARCHAR(5) DECLARE #i_volumenumber INT SELECT #i_volumenumber = volumenumber FROM bookdetail WHERE bookkey = #i_bookkey and volumenumber <> 0 IF #i_volumenumber > 0 BEGIN SELECT #RETURN = CAST(#i_volumenumber as varchar(5)) END ELSE BEGIN SELECT #RETURN = '' END RETURN #RETURN END
As you are looking for a '0' not a NULL COALESCE()is not useful, instead use a simple CASE: select ..., case bd.seriesvolume when '0' then '' else bd.seriesvolume end as p_seriesvol from ... Or if you want '' for 0 or NULL case when bd.seriesvolume is null or bd.seriesvolume = '0' then '' else bd.seriesvolume end as p_seriesvo
COALESCE() function returns the 1st non null value SELECT COALESCE(NULL, NULL, 'third_value', 'fourth_value'); returns the third value because the third value is the first value that is not null. So in your case COALESCE(bd.seriesvolume, '') AS p_seriesvol if seriesvolume colum value is null then it will return blank string
An expression of non-boolean type specified in a context where a condition is expected, near 'END'
So maybe someone can point me in the right direction of what is causing this error? I've been fighting with this for a couple of hours and searching the web, and I can't figure out what I'm doing wrong here. It's included as part of a stored procedure, I don't know if that matters, if it does I can include that as well. Tables and field names have been changed to protect the innocent... meaning my job. Thanks. SELECT /* The fields are here*/ FROM /* my joins are here */ WHERE (Table.Field = stuff) AND (Table.Field2 = otherstuff) AND (Table2.Field3 = someotherstuff) AND CASE #param1 WHEN 0 THEN 'Table.Field IS NULL' WHEN 1 THEN 'Table.Field2 IS NOT NULL' ELSE '' END Thanks for the responses. Technically egrunin was the correct answer for this question, but OMG Ponies and Mark Byers were pretty much the same thing just missing that last piece. Thanks again.
I'm pretty sure the other answers leave out a case: WHERE (Table.Field = stuff) AND (Table.Field2 = otherstuff) AND (Table2.Field3 = someotherstuff) AND ( (#param1 = 0 and Table.Field IS NULL) OR (#param1 = 1 and NOT Table.Field2 IS NULL) OR (#param1 <> 0 AND #param1 <> 1) -- isn't this needed? )
You are returning a string from your case expression, but only a boolean can be used. The string is not evaluated. You could do what you want using dynamic SQL, or you could write it like this instead: AND ( (#param1 = 0 AND Table.Field IS NULL) OR (#param1 = 1 AND Table.Field IS NOT NULL) )
You can't use CASE in the WHERE clause like you are attempting The text you provided in the CASE would only run if you were using dynamic SQL Use: WHERE Table.Field = stuff AND Table.Field2 = otherstuff AND Table2.Field3 = someotherstuff AND ( (#param1 = 0 AND table.field IS NULL) OR (#param1 = 1 AND table.field2 IS NOT NULL)) ...which doesn't make sense if you already have Table.Field = stuff, etc... Options that would perform better would be to either make the entire query dynamic SQL, or if there's only one parameter - use an IF/ELSE statement with separate queries & the correct WHERE clauses.
Equivalent to VB AndAlso in SQL?
Is there an equivalent to VB's AndAlso/OrElse and C#'s &&/|| in SQL (SQL Server 2005). I am running a select query similar to the following: SELECT a,b,c,d FROM table1 WHERE (#a IS NULL OR a = #a) AND (#b IS NULL OR b = #b) AND (#c IS NULL OR c = #c) AND (#d IS NULL OR d = #d) For example, if the "#a" parameter passed in as NULL there is no point in evaluating the 2nd part of the WHERE clause (a = #a). Is there a way to avoid this either by using special syntax or rewriting the query? Thanks, James.
The only way to guarantee the order of evaluation is to use CASE WHERE CASE WHEN #a IS NULL THEN 1 WHEN a = #a THEN 1 ELSE 0 END = 1 AND /*repeat*/ In my experience this is usually slower then just letting the DB engine sort it out. TerrorAustralis's answer is usually the best option for non-nullable columns
Try this: AND a = ISNULL(#a,a) This function looks at #a. If it is not null it equates the expression AND a = #a If it is null it equates the expression AND a = a (Since this is always true, it replaces the #b is null statement)
The query engine will take care of this for you. Your query, as written, is fine. All operators will "short circuit" if they can.
Another way is to do: IF (#a > 0) IF (#a = 5) BEGIN END Another if after the condition will do an "AndAlso" logic. I want to emphesise that this is just a short way to write: IF (#a > 0) IF (#a = 5) BEGIN END
Take this example: SELECT * FROM Orders WHERE orderId LIKE '%[0-9]%' AND dbo.JobIsPending(OrderId) = 1 Orders.OrderId is varchar(25) dbo.JobIsPending(OrderId) UDF with int parameter No short circuit is made as the conversion fails in dbo.JobIsPending(OrderId) when Orders.OrderId NOT LIKE '%[0-9]%' tested on SQL Server 2008 R2
SQL Server, where field is int?
how can I accomplish: select * from table where column_value is int I know I can probably inner join to the system tables and type tables but I'm wondering if there's a more elegant way. Note that column_value is a varchar that "could" have an int, but not necessarily. Maybe I can just cast it and trap the error? But again, that seems like a hack.
select * from table where column_value not like '[^0-9]' If negative ints are allowed, you need something like where column_value like '[+-]%' and substring(column_value,patindex('[+-]',substring(column_value,1))+1,len(column_value)) not like '[^0-9]' You need more code if column_value can be an integer that exceeds the limits of the "int" type, and you want to exclude such cases.
Here if you want to implement your custom function CREATE Function dbo.IsInteger(#Value VARCHAR(18)) RETURNS BIT AS BEGIN RETURN ISNULL( (SELECT CASE WHEN CHARINDEX('.', #Value) > 0 THEN CASE WHEN CONVERT(int, PARSENAME(#Value, 1)) <> 0 THEN 0 ELSE 1 END ELSE 1 END WHERE ISNUMERIC(#Value + 'e0') = 1), 0) END ISNUMERIC returns 1 when the input expression evaluates to a valid integer, floating point number, money or decimal type; otherwise it returns 0. A return value of 1 guarantees that expression can be converted to one of these numeric types.
I would do a UDF as Svetlozar Angelov suggests, but I would check for ISNUMERIC first (and return 0 if not), and then check for column_value % 1 = 0 to see if it's an integer. Here's what the body might look like. You have to put the modulo logic in a separate branch because it will throw an exception if the value isn't numeric. DECLARE #RV BIT IF ISNUMERIC(#value) BEGIN IF CAST(#value AS NUMERIC) % 1 = 0 SET #RV = 1 ELSE SET #RV = 0 END ELSE SET #RV = 0 RETURN #RV
This should handle all cases without throwing any exceptions: --This handles dollar-signs, commas, decimal-points, and values too big or small, -- all while safely returning an int. DECLARE #IntString as VarChar(50) = '$1,000.' SELECT CAST((CASE WHEN --This IsNumeric check here does most of the heavy lifting. The rest is Integer-Specific ISNUMERIC(#IntString) = 1 --Only allow Int-related characters. This will exclude things like 'e' and other foreign currency characters. AND #IntString NOT LIKE '%[^ $,.\-+0-9]%' ESCAPE '\'--' --Checks that the value is not out of bounds for an Integer. AND CAST(REPLACE(REPLACE(#IntString,'$',''),',','') as Decimal(38)) BETWEEN -2147483648 AND 2147483647 --This allows values with decimal-points for count as an Int, so long as there it is not a fractional value. AND CAST(REPLACE(REPLACE(#IntString,'$',''),',','') as Decimal(38)) = CAST(REPLACE(REPLACE(#IntString,'$',''),',','') as Decimal(38,2)) --This will safely convert values with decimal points to casting later as an Int. THEN CAST(REPLACE(REPLACE(#IntString,'$',''),',','') as Decimal(10)) END) as Int)[Integer] Throw this into a Scalar UDF and call it ReturnInt(). If the value comes back as NULL, then it's not an int (so there's your IsInteger() requirement) If you don't like typing "WHERE ReturnInt(SomeValue) IS NOT NULL", you could throw it into another scalar UDF called IsInt() to call this function and simply return "ReturnInt(SomeValue) IS NOT NULL". The cool thing is, the UDF can serve double duty by returning the "safely" converted int value. Just because something can be an int doesn't mean casting it as an int won't throw a huge exception. This takes care of that for you. Also, I'd avoid the other solutions because this universal approach will handle commas, decimals, dollar signs, and checks the acceptable Int value's range while the other solutions do not - or they require multiple SET operations that prevent you from using the logic in a Scalar-Function for maximum performance. See the examples below and test them against my code and others: --Proves that appending "e0" or ".0e0" is NOT a good idea. select ISNUMERIC('$1' + 'e0')--Returns: 0. select ISNUMERIC('1,000' + 'e0')--Returns: 0. select ISNUMERIC('1.0' + '.0e0')--Returns: 0. --While these are numeric, they WILL break your code -- if you try to cast them directly as int. select ISNUMERIC('1,000')--Returns: 1. select CAST('1,000' as Int)--Will throw exception. select ISNUMERIC('$1')--Returns: 1. select CAST('$1' as Int)--Will throw exception. select ISNUMERIC('10.0')--Returns: 1. select CAST('10.0' as Int)--Will throw exception. select ISNUMERIC('9999999999223372036854775807')--Returns: 1. This is why I use Decimal(38) as Decimal defaults to Decimal(18). select CAST('9999999999223372036854775807' as Int)--Will throw exception. Update: I read a comment here that you want to be able to parse a value like '123.' into an Integer. I have updated my code to handle this as well. Note: This converts "1.0", but returns null on "1.9". If you want to allow for rounding, then tweak the logic in the "THEN" clause to add Round() like so: ROUND(CAST(REPLACE(REPLACE(#IntString,'$',''),',','') as Decimal(10)), 0) You must also remove the "AND" that checks for "decimal-points" to allow for Rounding or Truncation.
Why not use the following and test for 1? DECLARE #TestValue nvarchar(MAX) SET #TestValue = '1.04343234e5' SELECT CASE WHEN ISNUMERIC(#TestValue) = 1 THEN CASE WHEN ROUND(#TestValue,0,1) = #TestValue THEN 1 ELSE 0 END ELSE null END AS Analysis
If you are purely looking to verify a string is all digits and not just CAST-able to INT you can do this terrible, terrible thing: select LEN( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( '-1.223344556677889900e-1' ,'0','') ,'1','') ,'2','') ,'3','') ,'4','') ,'5','') ,'6','') ,'7','') ,'8','') ,'9','') ) It returns 0 when the string was empty or pure digits. To make it a useful check for "poor-man's" Integer you'd have to deal with empty string, and an initial negative sign. And manually make sure it isn't too long for your variety of INTEGER.