Equality check for two boolean expressions (TSQL) - sql

I have two tables and I want to join two tables based on the sign of the related columns
I'm joining on the condition a<0 = b<0 but the equality sign gives me a syntax error. I don't want to do (a<0 AND b<0) OR (Not a<0 AND Not b<0) because it doesn't look clean

You know what the answer is:
where (a < 0 and b < 0) or (a >= 0 and b >= 0)
SQL Server doesn't treat boolean values as bona fide values, so you cannot treat them as regular values in other types of expressions.
You can express this using bitwise or ('^') if you really want:
where (case when a < 0 and b < 0 then 1 else 0 end) ^ (case when a >= 0 and b >= 0 then 1 else 0 end) = 1
However, I find that rather inscrutable.

As Arvo suggested, you can use Sign(). Assuming that a and b are integer types:
where Sign( Sign( a ) + 1 ) = Sign( Sign( b ) + 1 )
Explanation: The inner Sign() calls convert the input values to -1, 0 or 1. Adding 1 shifts those values to 0, 1 or 2. The outer Sign() calls collapse that back to 0 or 1 representing negative and non-negative inputs.
This kind of code is occassionally useful, but should be accompanied by a comment explaining the intent. If the technique is truly impenetrable then it should be explained in the comment or a citation provided.

Related

SQL tuple/lexicographic comparison with multiple directions

I need to return elements from a database query based on an inequality using the lexicographic ordering on multiple columns. As described in this question this is straightforward if I'm comparing all the columns in the same direction. Is there a straigtforward way to do this if I want to reverse the direction of the sort on some columns.
For instance, I might have columns A, B and C and values 5, 7, and 23 and I'd like to return something like:
WHERE A < 5 OR (A = 5 AND B > 7) OR (A = 5 AND B = 7 AND C < 23)
Is there any easier way to do this using tuples (I have to construct in a function without knowing the number of columns beforehand)? Note that, some columns are DateTime columns so I can't rely on tricks that apply only to integers (e.g. negating both sides). I'm happy to use postgresql specific tricks.
And, if not, is there a specific way/order I should build expressions like the above to best use multicolumn indexes?
Just thinking if going the CTE route and creating a column which stores 0 or 1 for whether the data passes the specific filter criteria or not.
WITH CTE AS
(
SELECT
..,
...,
CASE
WHEN A < 5 THEN 1
WHEN A = 5 AND B > 7 THEN 1
WHEN A = 5 AND B = 7 AND C < 23 THEN 1
ELSE 0
END AS filter_criteria
)
SELECT
..,
..
FROM
CTE
WHERE filter_criteria = 1
OR, directly applying the CASE statement in the WHERE clause. This reduces the extra step of CTE
WHERE 1 = CASE
WHEN A < 5 THEN 1
WHEN A = 5 AND B > 7 THEN 1
WHEN A = 5 AND B = 7 AND C < 23 THEN 1
ELSE 0
END
Referring to the thread you mentioned, can you try the idea WHERE (col_a, 'value_b') > ('value_a', col_b)

In SQL Server, how can I use the result of a comparison operator as a logical value?

I would like to compute something in an SQL Server result set that is the result of the value of comparison operators on other columns, something like
select (a > 2 AND b > 2 AND (c > 2 OR or c is null)) as "Reject"...
I just can't find any syntax that works.
SQL Server doesn't have a boolean type. Perhaps you intend:
select (case when a > 2 and b > 2 and (c > 2 or c is null)
then 1 else 0
end) as is_reject
SELECT CASE
WHEN A>2 AND B>2 AND (C>2 OR CIS NULL) THEN 'REJECT'
ELSE 'NO REJECT'
END
SQL Server has a limited concept of Booleans. They are used within logical expressions in statements like IF.. ELSE and WHILE and in WHERE clauses, but cannot be assigned to a variable or column directly. Use a CASE statement instead
SELECT
CASE WHEN a > 2 AND b > 2 AND (c > 2 OR c IS NULL)
THEN 1
ELSE 0
END AS "Reject"...
There is a BIT data type that can be either 0 or 1 and is commonly used to store truth values.
Also, SQL Server uses a three valued logic. See: Example of three valued logic in SQL Server

How can I make IF without ELSE on SQL WHERE condition?

I`m trying to make a querie that selects users and if user type equals 1 I need to select those with age. My table:
id (int 11) | type (int 11) | email (varchar 25) | age (int 11)
My querie:
SELECT * FROM users WHERE IF(type = 1, age <> 0)
The problem is that I need to have an ELSE condition, but I dont need one in this case. How can I make an IF inside WHERE without else condition?
Thanks
You can do it with CASE:
SELECT * FROM users
WHERE age = CASE WHEN type <> 1 THEN age ELSE 0 END
Q: How do I make IF without ELSE on SQL WHERE condition ?
A: It's not possible; there is always an ELSE. MySQL IF() function has three arguments. It doesn't matter where the IF() function is used, whether it's part of an expression in a WHERE clause, or an expression in the SELECT list.
As an alternative to the MySQL IF() function, we can use a more portable, more ANSI-standard compliant CASE expression. But that doesn't get away from the crux of the question, about avoiding an ELSE. There is always an ELSE with the CASE expression as well. If we omit the ELSE clause, it's the same as if we had specified ELSE NULL.
As an aside (unrelated to the question that was asked), I don't think we should be storing age as an attribute; typically age is the difference between the current date and a date in the past (date of birth, registration date, etc.)
I'm thinking we don't need an IF function in the WHERE clause. (That's specific to MySQL, so this answer assumes that the target DBMS is MySQL, and not some other RDBMS).
We can use a combination of conditions, combined with NOT, AND, OR and parens so specify an order of operations.
Sample data and example output goes a long way to explaining the spec.
id type age email
-- ---- ---- ----------
1 0 0 1#one
2 1 0 2#two
3 0 1 3#three
4 1 1 4#four
5 0 NULL 5#five
6 1 NULL 6#six
7 NULL NULL 7#seven
8 NULL 0 8#eight
9 NULL 1 9#nine
Which of these rows should be returned, and which rows should be excluded?
Here is an example query (MySQL specific syntax) that returns all rows except row id=2 (type=1, age=0)
SELECT u.id
, u.type
, u.age
, u.email
FROM user u
WHERE NOT ( u.type <=> 1 )
OR NOT ( u.age <=> 0 )
If there's a requirement to incorporate IF functions, we can do that, and return an equivalent result:
SELECT u.id
, u.type
, u.age
, u.email
FROM user u
WHERE NOT ( IF( u.type <=> 1 ,1,0) )
OR NOT ( IF( u.age <=> 0 ,1,0) )
^^^ ^^^^^
In the WHERE clause, an expression will be evaluated as a boolean value. A numeric value of 0 is FALSE, a non-zero value is TRUE, and NULL value is (as always) just NULL.
For a row to be returned, we need the expression in the WHERE clause to evaluate to a non-zero value (to evaluate to TRUE).
The third argument of the IF() function is the "else" value; for that value, we can return TRUE, FALSE or NULL. To exclude rows that do not satisfy the type=1 condition, we return either zero or NULL:
WHERE IF(type = 1, age <> 0 ,0 )
^^
or equivalently:
WHERE IF(type = 1, age <> 0 ,NULL )
^^^^^
If we want rows that don't satisfy type=1 condition to be returned, we can return any non-zero value:
WHERE IF(type = 1, age <> 0 ,42 )
^^^
RECAP:
Addressing the question that was asked:
Q: How do I make IF without ELSE on SQL WHERE condition ?
A: There is always an ELSE value with the MySQL IF() function; in the context of the WHERE clause, the value will be evaluated as a boolean: TRUE, FALSE or NULL.
I think you want:
SELECT *
FROM users
WHERE type <> 1 OR age <> 0;
I was in a similar situation and ended up with the following solution:
SELECT * FROM users WHERE IF(type = 1, age <> 0, 1=0)
The else part here is 1 = 0 which is never true, so you don't select anything in that case.

SQL that should never return anything, but does

I came across the following SQL statement:
SELECT A.NAME
FROM THE_TABLE A
WHERE A.NAME LIKE '%JOHN%DOE%'
AND ((A.NUM_FIELD/1) - (A.NUM_FIELD/2)*2 <> 0)
That last condition, "((A.NUM_FIELD/1) - (A.NUM_FIELD/2)*2 <> 0)" is what baffles me. Depening on the implementation of order of operations, it should always result to 0 or A.NUM_FIELD / 2.
How does SQL still return records from this view? If it always results to half the original value, why have it? (This is a delivered SQL package)
Probably integer division, so an odd NUM_FIELD is going to be one less.
MSDN says:
If an integer dividend is divided by an integer divisor, the result is
an integer that has any fractional part of the result truncated.
if the NUM_FIELD is an integer, and an odd one- then
(A.NUM_FIELD/1) - (A.NUM_FIELD/2)*2
is equal to one
What SQL implementation is this?
As noted,
(x/1) - (x/2)*2
is equivalent to
X - (2*(x/2))
which, if integer division is being performed yields 0 or 1 depending on whether the value is even or odd:
x x/2 2*(x/2) x-(2*(x/2))
- --- ------- -----------
0 0 0 0
1 0 0 1
2 1 2 0
3 1 2 1
4 2 4 0
...
if so, it seems like an odd way way of checking for odd/even values, especially since most SQL implementations that I'm aware of support a modulo operator or function, either x % y or mod(x,y).
The seemingly extraneous division by 1 makes me think the column in question might be floating point. Perhaps the person who coded the query is trying to check for jitter or fuzzyness in the low order bits of the floating point column?
if you modified the query to return all the intermediate values of the computation:
SELECT A.NAME as Name ,
A.NUM_FIELD as X ,
A.NUM_FIELD / 1 as X_over_1 ,
A.NUM_FIELD / 2 as X_over_2 ,
( X.NUM_FIELD / 2 )
* 2 as 2x_over_2 ,
( A.NUM_FIELD / 1 )
- ( A.NUM_FIELD / 2 ) * 2 as Delta ,
case when ( ( A.NUM_FIELD / 1 ) - ( A.NUM_FIELD / 2 ) * 2 ) <> 0
then 'return'
else 'discard'
end as Row_Status
FROM THE_TABLE A
WHERE A.NAME LIKE '%JOHN%DOE%'
and then executed it, what results do you get?

mysql: Average over multiple columns in one row, ignoring nulls

I have a large table (of sites) with several numeric columns - say a through f. (These are site rankings from different organizations, like alexa, google, quantcast, etc. Each has a different range and format; they're straight dumps from the outside DBs.)
For many of the records, one or more of these columns is null, because the outside DB doesn't have data for it. They all cover different subsets of my DB.
I want column t to be their weighted average (each of a..f have static weights which I assign), ignoring null values (which can occur in any of them), except being null if they're all null.
I would prefer to do this with a simple SQL calculation, rather than doing it in app code or using some huge ugly nested if block to handle every permutation of nulls. (Given that I have an increasing number of columns to average over as I add in more outside DB sources, this would be exponentially more ugly and bug-prone.)
I'd use AVG but that's only for group by, and this is w/in one record. The data is semantically nullable, and I don't want to average in some "average" value in place of the nulls; I want to only be counting the columns for which data is there.
Is there a good way to do this?
Ideally, what I want is something like UPDATE sites SET t = AVG(a*#a_weight,b*#b_weight,...) where any null values are just ignored and no grouping is happening.
EDIT: What I ended up using, based on van's and adding in correct weighted averages (assuming that a has already been normalized as needed, in this case to a float 0-1 (1 = better):
UPDATE sites
SET t = (#a_weight * IFNULL(a, 0) + ...) / (IF(a IS NULL, 0, #a_weight) + ...)
WHERE (IF(a IS NULL, 0, 1) + ...) > 0
UPDATE sites
--// TODO: you might need to round it depending on your type
SET t =(COALESCE(a, 0) +
COALESCE(b, 0) +
COALESCE(c, 0) +
COALESCE(d, 0) +
COALESCE(e, 0) +
COALESCE(f, 0)
) /
((CASE WHEN a IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN b IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN c IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN d IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN e IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN f IS NULL THEN 0 ELSE 1 END CASE)
)
WHERE 0<>((CASE WHEN a IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN b IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN c IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN d IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN e IS NULL THEN 0 ELSE 1 END CASE) +
(CASE WHEN f IS NULL THEN 0 ELSE 1 END CASE)
)
You could use COALESCE also in the other parts, but this will not handle the case when you have a rating with value 0 properly because it will be excluded. The WHERE clause avoids DivideByZero, but you might need to have additional UPDATE statement to handle this case, if there is no rating for the entry.