dbTalk Databases Forums  

Simplest way to write a boolean expression in the SELECT clause?

comp.databases.ms-sqlserver comp.databases.ms-sqlserver


Discuss Simplest way to write a boolean expression in the SELECT clause? in the comp.databases.ms-sqlserver forum.



Reply
 
Thread Tools Display Modes
  #1  
Old   
Tom van Stiphout
 
Posts: n/a

Default Simplest way to write a boolean expression in the SELECT clause? - 02-05-2008 , 07:16 PM






My sproc code, simplified:

declare @x int
declare @y int
declare @Result bit

--Assign @x and @y by running some complex code.
--For testing we will say:
set @x = 1
set @y = 2

--Now I want to compare them, ideally this way would return true or
false:
select @Result = (@x = @y) --DOES NOT WORK

--So far the simplest implementation I found is this:
select case when @x=@y then 0 else 1 end

Not great, because if my first attempt had worked I would next want to
string several expressions together like this:
select @Result = (@x=@y) and (@a=@b) and (@c=0)

Suggestions?
Perhaps a scalar-valued function, or a .NET assembly?

-Tom.

Reply With Quote
  #2  
Old   
Plamen Ratchev
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-05-2008 , 11:39 PM






There is no BOOLEAN data type in SQL. Using CASE to implement this type of
logic is a standard way.

An alternative is to use an old method by David Rozenshtein (used to be
popular for pivoting when CASE was not available years ago). Using your
sample, it will look like this:

-- if you want to return 0 when @x = @y, otherwise 1
SELECT ABS(SIGN(@x - @y))

-- if you want to return 1 when @x = @y, otherwise 0
SELECT 1 - ABS(SIGN(@x - @y))

It is shorter to write, but I think more confusing to read and understand.
And of course, it works fine only for numeric expressions.

HTH,

Plamen Ratchev
http://www.SQLStudio.com


Reply With Quote
  #3  
Old   
--CELKO--
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-06-2008 , 07:29 AM



Quote:
Suggestions?
There are no BOOLEANs in SQL. We use predicates (search conditions)
to discover the state of the database instead of programming with
flags. You are missing one of the major differences in declarative
versus procedural programming.

What you want to do is like doing linked lists in Fortran II -- you
can fake it, but you've missed the point of the language.


Reply With Quote
  #4  
Old   
Andy M
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-06-2008 , 09:34 AM



There are a couple ways to set your @Result flag.

--Use a CASE statement to set the @Result flag
SELECT @RESULT = CASE
WHEN @x = @y THEN 1
ELSE 0
END


--Set @Result = 0 when you declare the variable...
--then in the body of the script, switch the flag to 1 if the values
are equal
SET @Result = 0
IF (@x=@y)
SET @Result = 1


Reply With Quote
  #5  
Old   
Ed Murphy
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-06-2008 , 12:03 PM



--CELKO-- wrote:

Quote:
There are no BOOLEANs in SQL. We use predicates (search conditions)
to discover the state of the database instead of programming with
flags. You are missing one of the major differences in declarative
versus procedural programming.
This is an oversimplification. See
http://en.wikipedia.org/wiki/Boolean_datatype#SQL

Things get opinionated when you start dealing with NULL vs. UNKNOWN,
and "all functions with null inputs have null outputs except ISNULL
and COALESCE" vs. "if A is TRUE, then (A AND B) is TRUE, regardless
of the status of B".

In practice, there are lots of ways to implement the concept of a flag:

* New column (1/0, Y/N, T/F, whatever)
* Interpretation of one or more existing columns (e.g. date and
reason an employee was terminated)
* Eventually, it becomes worthwhile to normalize the flag-dependent
columns into a separate table (details debatable)


Reply With Quote
  #6  
Old   
Robert Klemme
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-06-2008 , 05:24 PM



On 06.02.2008 16:34, Andy M wrote:
Quote:
There are a couple ways to set your @Result flag.

--Use a CASE statement to set the @Result flag
SELECT @RESULT = CASE
WHEN @x = @y THEN 1
ELSE 0
END
You can even save a "=" or does SQL Server not support this form of case?

select @result = case @x when @y then 1 else 0 end

Kind regards

robert


Reply With Quote
  #7  
Old   
--CELKO--
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-06-2008 , 06:17 PM



Quote:
You can even save a "=" or does SQL Server not support this form of case?
Let me do a "cut & paste" here:

The CASE expression is an *expression* and not a control statement;
that is, it returns a value of one data type. SQL-92 stole the idea
and the syntax from the ADA programming language. Here is the BNF for
a <case specification>:

<case specification> ::= <simple case> | <searched case>

<simple case> ::=
CASE <case operand>
<simple when clause>...
[<else clause>]
END

<searched case> ::=
CASE
<searched when clause>...
[<else clause>]
END

<simple when clause> ::= WHEN <when operand> THEN <result>

<searched when clause> ::= WHEN <search condition> THEN <result>

<else clause> ::= ELSE <result>

<case operand> ::= <value expression>

<when operand> ::= <value expression>

<result> ::= <result expression> | NULL

<result expression> ::= <value expression>

The searched CASE expression is probably the most used version of the
expression. The WHEN ... THEN ... clauses are executed in left to
right order. The first WHEN clause that tests TRUE returns the value
given in its THEN clause. And, yes, you can nest CASE expressions
inside each other. If no explicit ELSE clause is given for the CASE
expression, then the database will insert a default ELSE NULL clause.
If you want to return a NULL in a THEN clause, then you must use a
CAST (NULL AS <data type>) expression. I recommend always giving the
ELSE clause, so that you can change it later when you find something
explicit to return.

The <simple case expression> is defined as a searched CASE expression
in which all the WHEN clauses are made into equality comparisons
against the <case operand>. For example

CASE iso_sex_code
WHEN 0 THEN 'Unknown'
WHEN 1 THEN 'Male'
WHEN 2 THEN 'Female'
WHEN 9 THEN 'N/A'
ELSE NULL END

could also be written as:

CASE
WHEN iso_sex_code = 0 THEN 'Unknown'
WHEN iso_sex_code = 1 THEN 'Male'
WHEN iso_sex_code = 2 THEN 'Female'
WHEN iso_sex_code = 9 THEN 'N/A'
ELSE NULL END

There is a gimmick in this definition, however. The expression

CASE foo
WHEN 1 THEN 'bar'
WHEN NULL THEN 'no bar'
END

becomes

CASE WHEN foo = 1 THEN 'bar'
WHEN foo = NULL THEN 'no_bar' -- error!
ELSE NULL END

The second WHEN clause is always UNKNOWN.

The SQL-92 Standard defines other functions in terms of the CASE
expression, which makes the language a bit more compact and easier to
implement. For example, the COALESCE () function can be defined for
one or two expressions by

1) COALESCE (<value exp #1>) is equivalent to (<value exp #1>)

2) COALESCE (<value exp #1>, <value exp #2>) is equivalent to

CASE WHEN <value exp #1> IS NOT NULL
THEN <value exp #1>
ELSE <value exp #2> END

then we can recursively define it for (n) expressions, where (n >= 3),
in the list by

COALESCE (<value exp #1>, <value exp #2>, . . ., n), as equivalent to:

CASE WHEN <value exp #1> IS NOT NULL
THEN <value exp #1>
ELSE COALESCE (<value exp #2>, . . ., n)
END

Likewise, NULLIF (<value exp #1>, <value exp #2>) is equivalent to:

CASE WHEN <value exp #1> = <value exp #2>
THEN NULL
ELSE <value exp #1> END

It is important to be sure that you have a THEN or ELSE clause with a
data type that the compiler can find to determine the highest data
type for the expression.

A trick in the WHERE clause is use it for a complex predicate with
material implications.

WHERE CASE
WHEN <search condition #1>
THEN 1
WHEN <search condition #2>
THEN 1
...
ELSE 0 END = 1

Gert-Jan Strik posted some exampels of how ISNULL() and COALESCE() on
2004 Aug 19

CREATE TABLE #t(a CHAR(1));
INSERT INTO #t VALUES (NULL);
SELECT ISNULL(a,'abc') FROM #t;
SELECT COALESCE(a, 'abc') FROM #t;
DROP TABLE #t;

He always use COALESCE, with the exception of the following type of
situation, because of its performance consequences:

SELECT ...,
ISNULL((SELECT COUNT(*) -- or other aggregate
FROM B
WHERE B.key = A.key), 0)
FROM A;

Likewise, Alejandro Mesa cam up with this example:

SELECT 13 / COALESCE(CAST(NULL AS INTEGER), 2.00); -- promote to
highest type (decimal)
SELECT 13 / ISNULL(CAST(NULL AS INTEGER), 2.00); -- promote to first
type (integer)



Reply With Quote
  #8  
Old   
Tom van Stiphout
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-06-2008 , 07:42 PM



On Tue, 05 Feb 2008 18:16:44 -0700, Tom van Stiphout
<no.spam.tom7744 (AT) cox (DOT) net> wrote:

Thanks everyone for your thoughts and suggestions. As I am reading
them I think it may be best not to simplify my scenario but spell it
out. I am certainly a big proponent of normalized database design and
I'm not using flags or procedural code without some thought as to
alternatives.
SQL2005. Database is about a Parts Catalog. We have Categories,
Subcategories etc at infinitum (typically <=5), and at the lowest
level we have about 50,000 Parts.
Both Categories and Parts may have a defined life time, so both tables
have EffectiveDate, ExpireDate, IsDeleted.
We added a computed column IsActive, which is set to:
([dbo].[fnIsActive]([EffectiveDate],[ExpireDate],[IsDeleted]))

The scalar function fnIsActive is below. I felt the procedural code
was probably not very fast, so I was looking to rewrite. Ideally
something like:
select @Return = (EffectiveDate<getdate()) AND
(ExpireDate>getdate()) AND (IsDeleted=0)
But as we have concluded that's not possible in T-SQL.
So I was looking for alternatives.

The function fnIsActive follows:
ALTER FUNCTION [dbo].[fnIsActive] (
@EffectiveDate datetime
, @ExpireDate datetime
, @IsDeleted bit
)
RETURNS bit
AS
BEGIN
DECLARE @ReturnValue bit

IF @IsDeleted = 1
BEGIN
SET @ReturnValue = 0
END
ELSE
BEGIN
--Is the current date before the effective date?
IF @ReturnValue IS NULL
BEGIN
IF GETDATE() < @EffectiveDate
BEGIN
SET @ReturnValue = 0
END
END

--Is the current date after the expiration date?
IF @ReturnValue IS NULL
BEGIN
IF GETDATE() > @ExpireDate
BEGIN
SET @ReturnValue = 0
END
END

--Must be OK
IF @ReturnValue IS NULL
BEGIN
SET @ReturnValue = 1
END
END

RETURN @ReturnValue
END

Thank you,

-Tom.


Quote:
My sproc code, simplified:

declare @x int
declare @y int
declare @Result bit

--Assign @x and @y by running some complex code.
--For testing we will say:
set @x = 1
set @y = 2

--Now I want to compare them, ideally this way would return true or
false:
select @Result = (@x = @y) --DOES NOT WORK

--So far the simplest implementation I found is this:
select case when @x=@y then 0 else 1 end

Not great, because if my first attempt had worked I would next want to
string several expressions together like this:
select @Result = (@x=@y) and (@a=@b) and (@c=0)

Suggestions?
Perhaps a scalar-valued function, or a .NET assembly?

-Tom.

Reply With Quote
  #9  
Old   
--CELKO--
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-07-2008 , 10:37 AM



Quote:
I'm not using flags or procedural code without some thought as to alternatives.
Let's talk about those options. First, I turned your dialect into
Standard SQL whenever possible and your data element names into
ISO-11179 forms.

Quote:
Database is about a Parts Catalog. We have Categories, Subcategories etc at infinitum (typically <= 5), and at the lowest level we have about 50,000 Parts. Both Categories and Parts may have a defined life time, so both tables have effective_date, expire_date, deleted_flag. We added a computed column active_flag, which is set to:
(active_flagdbo.active_flag SetActiveFlag(active_flageffective_date,
active_flagexpire_date, active_flagdeleted_flag))

The scalar function SetActiveFlag is below. I felt the procedural code
was probably not very fast, so I was looking to rewrite. Ideally
something like:

SET @return = (effective_date < CURRENT_TIMESTAMP)
AND (expire_date > CURRENT_TIMESTAMP)
AND (deleted_flag = 0) <<

That is very nice in a procedural language that has BOOLEAN data
types. Did you notice that your mindset is still stuck in your native
language? Too many parens that we used in C to force order of
execution in early compilers, and the failure to use a NOT BETWEEN
short hand are the give-away that you are still thinking in C, C++,
VB, Fortran, or whatever. Also, an SQL programmer would put part of
this into a CHECK() constraint, not DML.

Quote:
But as we have concluded that's not possible in T-SQL. So I was looking for alternatives.
But you have not written SQL yet! You are still faking your
procedural language in SQL and hitting a wall. Let's take it a step
at a time, so you can see how to think in sets. I cleaned up
SetActiveFlag follows (cleaned up just a little, but still procedural
code. You seem to like to do a lot of needless IF-THEN testing rather
than just using a RETURN() when you have an answer. That is bad
procedural programming and has nothing to do with SQL; you might want
to read Gries and some of the books on Structured Programming.

CREATE FUNCTION SetActiveFlag -- notice <verb><object> naming
(@effective_date DATETIME,
@expire_date DATETIME,
@deleted_flag BIT) -- <attribute><property> naming
RETURNS BIT
AS
BEGIN
DECLARE @return_flag BIT; -- useless local variable
IF @deleted_flag = 1
SET @return_flag = 0
ELSE IF @return_flag IS NULL
IF GETDATE() < @effective_date
SET @return_flag = 0;
--Is the current date after the expiration date?
IF @return_flag IS NULL
IF GETDATE() > @expire_date
SET @return_flag = 0;
--Must be OK
IF @return_flag IS NULL
SET @return_flag = 1;
RETURN @return_flag;
END;

Here is a version using SQL instead of structured procedural code.

CREATE FUNCTION SetActiveFlag
(@effective_date DATETIME, @expire_date DATETIME, @deleted_flag BIT)
RETURNS BIT
AS
RETURN (CASE
WHEN @deleted_flag = 1 THEN 0
WHEN CURRENT_TIMESTAMP NOT BETWEEN @effective_date AND @expire_date
THEN 0 ELSE 1 END);

This still stinks; we know that (deleted_flag, active_flag) have to
have opposite values, so at least one of them is redundant. We don't
do redundancy in an RDBMS. In fact, both of them are redundant; the
expiration date tells us if something is active.

This should be handled in the DDL that you never posted. SQL is
declarative. So some wild guesses are in order:

Quote:
Both Categories and Parts may have a defined life time ..
I find that a bit weird; books come and go from a library, but the
Dewey Decimal Classification system remains pretty stable. Can I
assume that every part falls into a category? Do you set future
expiration dates? Lot of questions here. But let's start with a
typical history table:

CREATE TABLE InventoryHistory
(part_nbr CHAR(15) NOT NULL,
effective_date DATETIME NOT NULL,
PRIMARY KEY (part_nbr, effective_date),
expire_date DATETIME, -- null means current
CHECK (effective_date < expire_date),
part_category CHAR(10) NOT NULL
REFERENCES PartsCategories(part_category)
ON UPDATE CASCADE,
etc.);

Then you create an updatable VIEW of the current Inventory and use
it:

CREATE VIEW Inventory (...)
AS
SELECT ..
FROM InventoryHistory
WHERE CURRENT_TIMESTAMP <= COALESCE (expire_date,
CURRENT_TIMESTAMP);

The part categories can be in a nested sets model table (Google it if
you don't know this standard pattern or get a copy of TREES &
HIERARCHIES IN SQL). I am not sure how to handle the changing
categories, since I have no idea what the rate of change is or even
how they change (i.e. in Dewey Decimal, logic moved from the 100's
(Philosophy) to the 500's (math); new categories were created under
the 500's (nanotech); etc.) so the safest way is to create a forest
table.

Each tree in the forest will be at least 50,001 rows, according to
your specs, so I will guess they fall into 250 categories in the
average tree (I am not going to do the Bell numbers or what that
series is called for the number of possible trees). This is not big
on modern computer, and partitioning the table by time periods should
really help.

CREATE PartCategories -- leaving out a lot of constraints
(part_nbr CHAR(15) NOT NULL,
part_category CHAR(15) NOT NULL,
lft INTEGER NOT NULL CHECK (lft > 0),
rgt INTEGER NOT NULL CHECK (rgt > 0),
C HECK (lft < rgt), -- assume all the usual nested sets constraints
effective_date DATETIME NOT NULL
expire_date DATETIME, -- null means current
CHECK (effective_date < expire_date),
PRIMARY KEY (effective_date, part_category, part_nbr),
etc.)

The (effective_date, expire_date) pair defines the heirarchy during
that time period -- a tree in a forest identified by its effective
date. Again, you can use a VIEW to find the current hierarchy. Now
add a calendar table and use BEWTWEEN predicates with (effective_date,
expire_date) pairs in the history tables to determine the state of the
database for any day you have.

You can now write very simple SQL and app code using VIEWs because all
the effort has been moved to the DDL. You do not have to do data
integrity checking in hundred of programs. Your SQL will port and
should scale. But again, this is skeleton based on your narrative and
a lack of specs.



Reply With Quote
  #10  
Old   
Robert Klemme
 
Posts: n/a

Default Re: Simplest way to write a boolean expression in the SELECT clause? - 02-08-2008 , 04:14 PM



On 07.02.2008 01:17, --CELKO-- wrote:
Quote:
You can even save a "=" or does SQL Server not support this form of case?

Let me do a "cut & paste" here:

The CASE expression is an *expression* and not a control statement;
I did not claim that it is a control statement and everybody seems
pretty aware the status of "case".

Cheers

robert


Reply With Quote
Reply




Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off



Powered by vBulletin Version 3.5.3
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.