An expression of non-boolean type specified in a context where a condition is expected, near 'END' - sql

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.

Related

Will Using Short-Circuiting in WHERE Clause Improve Speed

Use case: I am going to be using SQL Server to retrieve values from a large table (1,000,000+ rows) where many different columns can be used as filter criteria, some more frequently used than others.
Questions
Would it be faster to utilize short-circuiting in the WHERE clause so that less comparisons are done?
Should the most commonly used criteria be filtered first to do even less comparisons?
Should the most commonly used criteria be indexed?
Example
No short circuiting
SELECT value
FROM AssignmentTable
WHERE (criteriaOne = <criteriaOneValue> OR criteriaOne IS NULL)
AND (criteriaTwo = <criteriaTwoValue> OR criteriaTwo IS NULL)
AND (criteriaThree = <criteriaThreeValue> OR criteriaThree IS NULL)
AND ... for all criteria (roughly 15)
With short circuiting
SELECT value
FROM AssignmentTable
WHERE 1 =
CASE
WHEN (criteriaOne = <criteriaOneValue> OR criteriaOne IS NULL) THEN
CASE
WHEN (criteriaTwo = <criteriaTwoValue> OR criteriaTwo IS NULL) THEN
CASE
WHEN (criteriaThree = <criteriaThreeValue> OR criteriaThree IS NULL) THEN 1
ELSE 0
END
ELSE 0
END
ELSE 0
END
The pattern for doing this without dynamic SQL in SQL Server is to use OPTION (RECOMPILE) to prune the un-needed predicates before the query optimizer generates a query plan.
EG:
SELECT value
FROM AssignmentTable
WHERE (Column1 = #column1 OR #column1 IS NULL)
AND (Column2 = #column2 OR #column2 IS NULL)
AND (Column3 = #column3 OR #column3 IS NULL)
AND ... for all criteria (roughly 15)
OPTION (RECOMPILE)
See the classic Dynamic Search Conditions in T-SQL for a complete discussion of the alternatives.

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 combine WHEN and IF statement with multiple parameters

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.

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

How to flip bit fields in T-SQL?

I'm trying to flip a bit field in SQL Server using an update query, that is, I want to make all the 0's into 1's and vice versa. What's the most elegant solution?
There doesn't seem to be a bitwise NOT operator in T-SQL (unless I'm missing something obvious) and I haven't been able to find any other way of performing the update.
You don't need a bitwise-not for this -- just XOR it with 1 / true.
To check it:
select idColumn, bitFieldY, bitFieldY ^ 1 as Toggled
from tableX
To update:
update tableX
set bitFieldY = bitFieldY ^ 1
where ...
MSDN T-SQL Exclusive-OR (^)
Why not a simple bitfield = 1 - bitfield?
Another way is
DECLARE #thebit bit = 1, #theflipbit bit
SET #theflipbit = ~ #thebit
SELECT #theflipbit
where "~" means "NOT" operator. It's clean and you get a good code to read.
"negate the bit" is even cleaner and it does exactly what the "NOT" operator was designed for.
I was pretty sure that most SQL flavors had a bitwise NOT, so I checked and there does appear to be one in TSQL.
From the documentation, it's the character ~.
UPDATE tblTest SET MyBitField = CASE WHEN MyBitField = 1 THEN 0 ELSE 1 END
It's bland but everyone will understand what it's doing.
EDIT:
You might also need to account for nulls as suggested in the comments. Depends on your req's of course.
UPDATE tblTest SET
MyBitField = CASE
WHEN MyBitField = 1 THEN 0
WHEN MyBitField = 0 THEN 1
ELSE NULL -- or 1 or 0 depending on requirements
END
A simple bitwise NOT operator (~) worked for me in SQL Server 2014 - 12.0.2269.0
In the update clause inside your T-SQL -
Update TableName
SET [bitColumnName] = ~[bitColumnName],
....
WHERE ....
Hope this helps
Ref - https://learn.microsoft.com/en-us/sql/t-sql/language-elements/bitwise-not-transact-sql
Did you try this?
UPDATE mytable SET somecolumn =
CASE WHEN somecolumn = 0 THEN 1
WHEN somecolumn IS NULL THEN NULL
WHEN somecolumn = 1 THEN 0
END
query (vb)
x = "select x from table"
update (vb)
"update table set x=" Not(x*(1))