CASE expression with NULL value - sql

I'm struggling to understand how to check for a null value in a progress case expression. I want to see if a column exists and use that, if not use the fallback column. For example, William in first name would be over written by Bill in fn.special-char.
I've got the following query:
SELECT
"PUB"."NAME"."LAST-NAME" as LastName,
CASE fn."SPECIAL-CHAR"
WHEN is null THEN "PUB"."NAME"."FIRST-NAME"
ELSE fn."SPECIAL-CHAR"
END as FirstName
FROM "PUB"."NAME"
LEFT OUTER JOIN "PUB"."DAT-DATA" fn on "PUB"."NAME"."NAME-ID" = fn."DAT-SRC-ID" and 11 = fn."FLD-FIELD-ID"
When I run the query I get:
ORBC Progress OpenEdge Wire Protocol driver][OPENEDGE]Syntax error SQL statement at or about "is null then "PUB"."NAME"."FIRST-" (10713)
If I do a select * I see everything. It just doesn't like the null part. I can also change the when is null to when 'bob' and it works.
Is there something different I need to do to use a null value in a progress db query?

The shorthand variation of the case statement (case expression when value then result ...) is a shorthand for a series of equality conditions between the expression and the given values. null, however, is not a value - it's the lack thereof, and must be evaluated explicitly with the is operator, as you tried to do. In order to do this properly, however, you need to use a slightly longer variation of the case syntax - case when condition then result ...:
SELECT
"PUB"."NAME"."LAST-NAME" as LastName,
CASE WHEN fn."SPECIAL-CHAR" IS NULL THEN "PUB"."NAME"."FIRST-NAME"
ELSE fn."SPECIAL-CHAR"
END as FirstName
FROM "PUB"."NAME"
LEFT OUTER JOIN "PUB"."DAT-DATA" fn on "PUB"."NAME"."NAME-ID" = fn."DAT-SRC-ID" and 11 = fn."FLD-FIELD-ID"

Instead of CASE you can use IFNULL function in Progress 4GL.
SELECT
"PUB"."NAME"."LAST-NAME" as LastName,
IFNULL(fn."SPECIAL-CHAR", "PUB"."NAME"."FIRST-NAME") as FirstName
FROM "PUB"."NAME"
LEFT OUTER JOIN "PUB"."DAT-DATA" fn on "PUB"."NAME"."NAME-ID" = fn."DAT-SRC-ID" and 11 = fn."FLD-FIELD-ID"

Related

How to handle NULL values in WHERE clause and change target column based upon its encounter

I need the WHERE clause to change what column it is evaluating when NULL is encountered. For instance I'm trying to do something like this:
SELECT *
FROM customer c
WHERE CASE WHEN c.cust_id_1(#variable) IS NOT NULL THEN c.cust_id_1 = #variable
ELSE CASE WHEN c.cust_id_2(#variable) IS NOT NULL THEN c.cust_id_2 = #variable
ELSE c.cust_id_3 = #variable
Is something like this possible? One of the 3 cust_id's will not be NULL.
That seems like less than optimal table design, but isn't a simple COALESCE where you're after?
WHERE #variable = COALESCE(cust_id_1, cust_id_2, cust_id_3);
You don't need a CASE expression for this, you just need logical operators
SELECT *
FROM customer c
WHERE
(c.cust_id_1 IS NOT NULL AND c.cust_id_1 = #variable)
OR
(c.cust_id_2 IS NOT NULL AND c.cust_id_2 = #variable)
OR
(c.cust_id_3 IS NOT NULL AND c.cust_id_3 = #variable)

How to perform Case statement inside a select statement?

I wanted to put 'No record' on the column instead of NULL if the datediff function returns a null value.
SELECT concat(e.firstname ,e.lastname) as Fullname,c.shiftcode as Shift, cast(c.datecheckinout as date) Date,datename(month, c.datecheckinout) as RecordMonth,c.timein , c.timeout,
CAST(
CASE
WHEN (datediff(HOUR,c.timein,c.timeout) IS NULL)
THEN 'No record'
END
), FROM tblCheckInOutDetail c RIGHT JOIN tblEmployee e on e.IdEmployee = c.IdEmployee WHERE e.IdEmployee = 55
So far this code only throws Incorrect syntax near 'CAST', expected 'AS'. but I don't know what data type should I put in the CAST parameter , since if there's a record it will show the datetime .
You need to convert the number value to a string. For this, you can use coalesce():
SELECT concat(e.firstname ,e.lastname) as Fullname,c.shiftcode as Shift, cast(c.datecheckinout as date) Date,datename(month, c.datecheckinout) as RecordMonth,c.timein , c.timeout,
COALESCE(CAST(datediff(HOUR, c.timein, c.timeout) AS VARCHAR(255)), 'No record')
FROM tblEmployee e LEFT JOIN
tblCheckInOutDetail c
ON e.IdEmployee = c.IdEmployee
WHERE e.IdEmployee = 55;
Note: I switched the RIGHT JOIN to a LEFT JOIN. They are equivalent logically. But most people find it much easier to follow the logic of the LEFT JOIN, because the table that defines the rows is the first table being read in the FROM clause.
Strictly answering question (though I don't understand why you need a CASE expression if you have working versions of the query), you can easily translate this to a CASE expression:
ISNULL(CAST(datediff(HOUR,c.timein,c.timeout) as varchar),'No Record')
ISNULL really is just nice, convenient shorthand for CASE WHEN a IS NOT NULL THEN a ELSE b END, so:
CASE WHEN DATEDIFF(HOUR, c.timein, c.timeout) IS NOT NULL
THEN CAST(datediff(HOUR,c.timein,c.timeout) as varchar(11))
ELSE 'No Record' END
As you can see, a downside is that if you really really really want a CASE expression, you have to repeat at least the DATEDIFF to cover both the case where the outer row doesn't exist and the case where the outer row exists but one of the values is NULL.
Also note that you should always specify a length for variable types like varchar, even in cases where you think you're safe with the default.
I don't know if this is the correct option or usage.
but this works for me :
ISNULL(CAST(datediff(HOUR,c.timein,c.timeout) as varchar),'No Record')
But can you guys show me how to do this using case expression?

Return 'Yes' or No' from select statement?

tbl_LoanSummary has Sample_Number column. I have to check if Sample_Number column is not null the return 'Y' otherwise return return 'N' from below select statement.
select a.Br_Loan_No ,a.Br_LookupKey, //return IsNull(s.Sample_Number) ='N' or 'Y'
from dbo.tbl_Br a left outer join dbo.tbl_LoanSummary s
on s.Loan_no = a.Br_Loan_No order by a.Br_Loan_No
How to do this?
You can use the case expression for this...
select a.Br_Loan_No,
a.Br_LookupKey,
CASE WHEN s.Sample_Number IS NULL THEN 'N' ELSE 'Y' END AS [HasSample]
from dbo.tbl_Br a left outer join dbo.tbl_LoanSummary s
on s.Loan_no = a.Br_Loan_No order by a.Br_Loan_No
In Oracle, you could also use
select NVL(s.Sample_Number, 'N')
to return N in case of null value
(of course you still need something to have Y in case of not null.)
You'll want to use a CASE expression. It's like an embedded if-statement or switch-statement from traditional programming languages.
SELECT a.Br_Loan_No,
a.Br_LookupKey
CASE
WHEN s.Sample_Number IS NULL THEN 'N'
ELSE 'Y'
END AS sample_number_is_not_null
FROM dbo.tbl_Br a
LEFT JOIN dbo.tbl_LoanSummary s
ON s.Loan_no = a.Br_Loan_No
ORDER BY a.Br_Loan_no
Note that you are creating a computed column here, rather than selecting the raw value of an existing column. It's generally required that you give this column a name, thus the use of the AS sample_number_is_not_null.
There are two forms of the CASE expression. One lets you compare a column or value against several choices. It is like using an implicit equals:
CASE foo
WHEN 3 THEN 'foo is 3!'
WHEN 4 THEN 'foo is 4!'
ELSE 'foo is not 3 or 4'
END
The other form, in the example at the top, lets you use arbitrary expressions in each WHEN clause. It should be noted that each WHEN clause is evaluated in order and the first one to match is the one whose THEN is used as the result. If none of the WHENs match, then the result in the ELSE is used.

SQL Query Not making Sense to me

I think my where clause is wrong.
My dilemma is, if user don't have a record in tbl_dentalBuyerInsurance that means they are taking all of it.
So if user don't have a record in tbl_dentalBuyerInsurance I want them to come back as a result.
I also want them to come back if they do have a record in tbl_dentalBuyerInsurance and it matches using LIKE or equal.
SELECT
[dbo].[tbl_users].*, [dbo].[tbl_dentalBuyerInsurance].*
FROM
[dbo].[tbl_users]
LEFT OUTER JOIN [dbo].[tbl_dentalBuyerInsurance] ON [dbo].[tbl_dentalBuyerInsurance].buyerId = [dbo].[tbl_users].id
LEFT OUTER JOIN [dbo].[tbl_dentalInsurance] ON [dbo].[tbl_dentalInsurance].id = [dbo].[tbl_dentalBuyerInsurance].dentalInsuranceId
WHERE
(
(
[dbo].[tbl_dentalInsurance].companyName LIKE '%Cigna%'
OR [dbo].[tbl_dentalInsurance].companyName = ''
)
AND(
[dbo].[tbl_dentalBuyerInsurance].ppo = 1
OR [dbo].[tbl_dentalBuyerInsurance].ppo = ''
)
AND(
[dbo].[tbl_dentalBuyerInsurance].hmo = 0
OR [dbo].[tbl_dentalBuyerInsurance].hmo = ''
)
)
Given you're using LEFT JOINS, if there's no matching records on the "right" side of the join, all of those right-side fields will be NULL, not empty strings. You'd have to explicitly check for that with .... OR whatever IS NULL, as NULL cannot ever be equal to anything, including itself.
[dbo].[tbl_dentalInsurance].companyName LIKE '%Cigna%'
OR [dbo].[tbl_dentalInsurance].companyName = ''
that means that you are allowinf empty strings, that's your first mistake, and how said MarcB if you are looking for null values so the query is :
[dbo].[tbl_dentalInsurance].companyName LIKE '%Cigna%'
OR [dbo].[tbl_dentalInsurance].companyName is null
if you are allowing empty string so you have to use len function for validate values with lenght 0
saludos

How do I map true/false/unknown to -1/0/null without repetition?

I am currently working on a tool to help my users port their SQL code to SQL-Server 2005. For this purpose, I parse the SQL into a syntax tree, analyze it for constructs which need attentions, modify it and transform it back into T-SQL.
On thing that I want to support, is the "bools are values too" semantics of other RDBMS. For example, MS-Access allows me to write select A.x and A.y as r from A, which is impossible in T-SQL because:
Columns can't have boolean type (column values can't be and'ed)
Logical predicates can not be used where expressions are expected.
Therefore, my transformation routine converts the above statement into this:
select case
when (A.x<>0) and (A.y<>0)
then -1
when not((A.x<>0) and (A.y<>0))
then 0
else
null
end as r
from A;
Which works, but is annoying, because I have to duplicate the logical expression (which can be very complex or contain subqueries etc.) in order to distinguish between true, false and unknown - the latter shall map to null. So I wonder if the T-SQL pro's here know a better way to achieve this?
UPDATE:
I would like to point out, that solutions which try to keep the operands in the integer domain have to take into account, that some operands may be logical expressions in the first place. This means that a efficient solution to convert a bool to a value is stil required. For example:
select A.x and exists (select * from B where B.y=A.y) from A;
I don't think there's a good answer, really it's a limitation of TSQL.
You could create a UDF for each boolean expression you need
CREATE FUNCTION AndIntInt
(
#x as int,#y as int
)
RETURNS int
AS
BEGIN
if (#x<>0) and (#y<>0)
return -1
if not((#x<>0) and (#y<>0))
return 0
return null
END
used via
select AndIntInt(A.x,A.y) as r from A
Boolean handling
Access seems to use the logic that given 2 booleans
Both have to be true to return true
Either being false returns false (regardless of nulls)
Otherwise return null
I'm not sure if this is how other DBMS (Oracle, DB2, PostgreSQL) deal with bool+null, but this answer is based on the Access determination (MySQL and SQLite agree). The table of outcomes is presented below.
X Y A.X AND B.Y
0 0 0
0 -1 0
0 (null) 0
-1 0 0
-1 -1 -1
-1 (null) (null)
(null) 0 0
(null) -1 (null)
(null) (null) (null)
SQL Server helper 1: function for boolean from any "single value"
In SQL Server in general, this function will fill the gap for the missing any value as boolean functionality. It returns a ternary result, either 1/0/null - 1 and 0 being the SQL Server equivalent of true/false (without actually being boolean).
drop function dbo.BoolFromAny
GO
create function dbo.BoolFromAny(#v varchar(max)) returns bit as
begin
return (case
when #v is null then null
when isnumeric(#v) = 1 and #v like '[0-9]%' and (#v * 1.0 = 0) then 0
else 1 end)
end
GO
Note: taking Access as a starting point, only the numeric value 0 evaluates to FALSE
This uses some SQL Server tricks
everything is convertible to varchar. Therefore only one function taking varchar input is required.
isnumeric is not comprehensive, '.' returns 1 for isnumeric but will fail at #v * 1.0, so an explicit test for LIKE [0-9]%`` is required to "fix" isnumeric.
#v * 1.0 is required to overcome some arithmetic issues. If you pass the string "1" into the function without *1.0, it will bomb
Now we can test the function.
select dbo.BoolFromAny('abc')
select dbo.BoolFromAny(1)
select dbo.BoolFromAny(0) -- the only false
select dbo.BoolFromAny(0.1)
select dbo.BoolFromAny(-1)
select dbo.BoolFromAny('')
select dbo.BoolFromAny('.')
select dbo.BoolFromAny(null) -- the only null
You can now safely use it in a query against ANY SINGLE COLUMN, such as
SELECT dbo.BoolFromAny(X) = 1
SQL Server helper 2: function to return result of BOOL AND BOOL
Now the next part is creating the same truth table in SQL Server. This query shows you how two bit columns interact and the simple CASE statement to produce the same table as Access and your more complicated one.
select a.a, b.a,
case
when a.a = 0 or b.a = 0 then 0
when a.a = b.a then 1
end
from
(select 1 A union all select 0 union all select null) a,
(select 1 A union all select 0 union all select null) b
order by a.a, b.a
This is easily expressed as a function
create function dbo.BoolFromBits(#a bit, #b bit) returns bit as
begin
return case
when #a = 0 or #b = 0 then 0
when #a = #b then 1
end
end
SQL Server conversion of other expressions (not of a single value)
Due to lack of support for bit-from-boolean conversion, expressions that are already [true/false/null] in SQL Server require repetition in a CASE statement.
An example is a "true boolean" in SQL Server, which cannot be the result for a column.
select A > B -- A=B resolves to one of true/false/null
from C
Needs to be expressed as
select case when A is null or B is null then null when A > B then 1 else 0 end
from C
But if A is not a scalar value but a subquery like (select sum(x)...), then as you can see A will appear twice and be evaluated twice in the CASE statement (repeated).
FINAL TEST
Now we put all the conversion rules to use in this long expression
SELECT X AND Y=Z AND C FROM ..
( assume X is numeric 5, and C is varchar "H" )
( note C contributes either TRUE or NULL in Access )
This translates to SQL Server (chaining the two functions and using CASE)
SELECT dbo.BoolFromBits(
dbo.BoolFromBits(dbo.BoolFromAny(X), CASE WHEN Y=Z then 1 else 0 end),
dbo.BoolFromAny(C))
FROM ...
Access Bool or bool
For completeness, here is the truth table for Access bool OR bool. Essentially, it is the opposite of AND, so
Both have to be false to return false
Either being true returns true (regardless of nulls)
Otherwise return null
The SQL SERVER case statement would therefore be
case
when a.a = 1 or b.a = 1 then 1
when a.a = b.a then 0
end
(the omission of an ELSE clause is intentional as the result is NULL when omitted)
EDIT: Based on additional information added to the Question and comments made on one of the suggested Answers, I am reformulating this answer:
If you are porting to SQL Server then I would expect that you are also transforming the data to match SQL Server types. If you have a boolean field then True, False, and Unknown map to 1, 0, and NULL as a NULLable BIT field.
With this in mind, you only need to worry about transforming pure boolean values. Expressions such as:
exists (select * from B where B.y=A.y)
and:
A.x in (1,2,3)
are already in a workable form. Meaning, statements like:
IF (EXISTS(SELECT 1 FROM Table))
and:
IF (value IN (list))
are already correct. So you just need to worry about the fact that "1" for "True" is not by itself enough. Hence, you can convert "1" values to boolean expressions by testing if they are in fact equal to "1". For example:
IF (value = 1)
is the equivalent of what you previously had as:
IF (value)
Putting all of this together, you should be able to simply translate all instances of pure boolean values of the old code into boolean expressions in the form of "value = 1" since a 1 will produce a True, 0 will produce False, and NULL will give you False.
HOWEVER, the real complexity is that SELECTing the value vs. testing with it via a WHERE condition is different. Boolean expressions evaluate correctly in WHERE conditions but have no direct representation to SELECT (especially since NULL / Unknown isn't really boolean). So, you can use the "Value = 1" translation in WHERE conditions but you will still need a CASE statement if you want to SELECT it as a result.
As mentioned briefly a moment ago, since NULL / Unknown isn't truly boolean, it is meaningless trying to convert "NULL AND NULL" to NULL for the purposes of a WHERE condition. In effect, NULL is truly FALSE since it cannot be determined to be TRUE. Again, this might be different for your purposes in a SELECT statement which is again why the CASE statement is your only choice there.