postgresql - exclude any result with nulls - sql

I'm doing a bunch of sum queries: SELECT col1 + col2 + col3 + ...
Some of the values in some of the columns are null. I'm checking for them by doing
SELECT CASE WHEN col1 is not null and col2 is not null and ...
I'm wondering if there is a more concise syntax for accomplishing this task.

Well, since the sum of any number and null is null, you can do something like the following (with the obvious ... filled in):
select big_honking_sum
from(
select col1+col2+col3+...+coln as big_honking_sum
from some_table
)sum
where big_honking_sum is not null;

Use COALESCE function if you can accept that a NULL value will be treated as 0.
SELECT COALESCE(Col1,0)
+ COALESCE(Col2,0)
+ COALESCE(Col3,0)
AS TOTAL FROM table;
Perhaps you could consider using 0 as default value instead of NULL if applicable.

You can have that even simpler:
SELECT foo
FROM ...
WHERE (-1 IN (col1, col2, col3)) IS NOT NULL
The IN expression will return NULL if (and only if) there is at least one NULL value among the tested values (and no match). So the whole expression evaluates to TRUE if (and only if) there is no NULL.
Edit: I have to correct myself! A positive match would stop the evaluation and return TRUE, even if a NULL is among the values. So you need a value that is guaranteed not to be in the set. Like 0 where all values are > 0 or -1 where all values are positive or you cannot use this expression for the purpose.

Related

Snowflake if String Pattern replace

I have column which should only contain numbers[0-9], But in some cases we started see alphanumeric[eg:p8eSadfghrc] values in that column.
I wanna write a condition where if the value is not completely numeric{0-9}, i wanna replace it with another value from another column.
Something like this?
update t
set col = <other value>
where regexp_like(col, '[^0-9]');
This updates the data. You could also just do this in a query:
select t.*,
(case when regexp_like(col, '[^0-9]') then <other value> else col end)
from t;
In Snowflake, I would recommend try_to_decimal(). It attempts to convert the number to a decimal value (you control the target precision and scale with arguments) and rturns null if that fails:
select t.*, case when try_to_decimal(mycol) is null then myothercol else mycol end newcol
from mytable
If you want an update statement:
update mytable
set mycol = myothercol
where try_to_decimal(mycol) is null
When given no second and third argument, the number of allowed digits is 38, with 0 decimals (which seems to be what you want).

How can I use consecutive conditional clause in SQL?

I was trying to work with CASE WHEN in Postgresql in order to evaluate something and then do another thing. However, I need not only to check two things but they must be checked in consecutive order.
Example, let's say I have 3 columns: col1, col2 and col3.
I want to check first if col1 is greater than 0. After checking this I want to check if col2 is greater than 0. If that's the case, I will create another column which will be the sum of all of them. However, I can't do this:
select case when col1>0 and col2>0 then col1+col2+col3 end as...
I need to do something like this:
select case when col1>0 then (case when col2>0 then col1+col2+col3) else NULL end as...
But that doesn't work. So, what can I do?
You were close. You can do:
select
case when col1 > 0 then case when col2 > 0 then col1 + col2 + col3 end
else NULL end as my_column1
You had missed the inner end.
By the way, a CASE expression evaluates to NULL when no when clause is matched. Therefore, else NULL is redundant.

"!="/NOT perhaps not working properly in SQLite

I have a table with about a hundred rows. It has a column is_gallery that contains either 1, 0, or NULL. If I do...
SELECT * WHERE is_gallery != 1
or
SELECT * WHERE NOT (is_gallery = 1)
it excludes the rows where is_gallery is null. I can manage to get a proper response if I do
SELECT * WHERE (is_gallery = 0 OR is_gallery is null)
But shouldn't the "!=" or NOT work? Isn't there a way to just return the rows where is_gallery doesn't equal 1 without testing for every other possibility?
You can use the IS and IS NOT operators instead of = and !=. These treat NULL like a normal value.
SELECT * FROM yourTable WHERE is_gallery IS NOT 1
The best thing to use is coalesce as in:
SELECT *
WHERE coalesce(is_gallery,0) != 1;
what coalesce does, is replaces any null value in that column with the second parameter. In the example above, any nulls in the "is_gallery" column will be replaced with 0 before it is compared with 1. So will of course return true.
On NULL realize that a NULL value isn't equal to ANYTHING - not even NULL itself. It cannot be compared - so when "comparing", it always will return FALSE. On NULL, it has a special operator which is "IS NULL" or "IS NOT NULL"

Oracle NVL problem

I'm trying to pass a null value as something else in my db and it seems to work without a Where clause like so
select NVL(column1, '0') column1 from table1
produces this
0 test1
0 test2
1 test3
But when I add the where clause like so
select NVL(column1, '0') column1 from table1 where column1 <=1
it produces this
1 test3
But now if I add the following to the query it works
select NVL(column1, '0') column1
from table1
where NVL(column1, '0') <=1
But it seems like a long way round to get the value to show correctly with a Where clause
Any ideas what i'm doing wrong?
Thanks in advance
You cannot refer to an alias defined in the SELECT list from the WHERE clause. So if you apply the NVL function in the SELECT list, you'd need the same function call in the WHERE clause (as you've demonstrated) or you would need to apply the function in an inline view
SELECT column1
FROM (SELECT nvl(column1, 0) column1
FROM table1)
WHERE column1 <= 1
Note that for general sanity, if COLUMN1 is a NUMBER, the second parameter to NVL should also be a NUMBER. Otherwise, you're going to do implicit conversions and your comparison operations may end up using string comparison semantics rather than numeric comparison semantics. Since the string '12' is less than the string '2', that can lead to unexpected results.
you have shown the correct way.
an alternative would be to say
OR column IS NULL

How to find least non-null column in one particular row in SQL?

I am trying to find the lowest number in two columns of a row in the same table, with the caveat that one of the columns may be null in a particular row. If one of the columns is null, I want the value in the other column returned for that row, as that is the lowest non-null column in this case. If I use the least() function in MySQL 5.1:
select least(1,null)
This returns null, which is not what I want. I need the query to return 1 in this case.
I've been able to get the result I want in general with this query:
select least(coalesce(col1, col2)) , coalesce(col2,col1))
As long as col1 and col2 are both not null each coalesce statement will return a number, and the least() handles finding the lowest.
Is there a simpler/faster way to do this? I'm using MySQL in this instance but general solutions are welcomed.
Unfortunately (for your case) behaviour of LEAST was changed in MySQL 5.0.13 (http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_least) - it used to return NULL only if all arguments are NULL.
This change was even reported as a bug: http://bugs.mysql.com/bug.php?id=15610
But the fix was only to MySQL documentation, explaining new behaviour and compatibility break.
Your solution was one of the recommended workarounds. Another can be using IF operator:
SELECT IF(Col1 IS NULL OR Col2 IS NULL, COALESCE(Col1, Col2), LEAST(Col1,Col2))
Depending on your corner case situation of having all values be null, I would go for such syntax, which is more readable (An easier solution if you have exactly two columns is below!)
SELECT LEAST( IFNULL(5, ~0 >> 1), IFNULL(10, ~0 >> 1) ) AS least_date;
-- Returns: 5
SELECT LEAST( IFNULL(null, ~0 >> 1), IFNULL(10, ~0 >> 1) ) AS least_date;
-- Returns: 10
SELECT LEAST( IFNULL(5, ~0 >> 1), IFNULL(null, ~0 >> 1) ) AS least_date;
-- Returns: 5
SELECT LEAST( IFNULL(null, ~0 >> 1), IFNULL(null, ~0 >> 1)) AS least_date
-- Returns: #MAX_VALUE (If you need to use it as default value)
SET #MAX_VALUE=~0 >> 1;
SELECT LEAST( IFNULL(null, #MAX_VALUE), IFNULL(null, #MAX_VALUE)) AS least_date;
-- Returns: #MAX_VALUE (If you need to use it as default value). Variables just makes it more readable!
SET #MAX_VALUE=~0 >> 1;
SELECT NULLIF(
LEAST( IFNULL(null, #MAX_VALUE), IFNULL(null,#MAX_VALUE)),
#MAX_VALUE
) AS least_date;
-- Returns: NULL
That is my prefered way if
you can ensure that at least one column cannot be NULL
in corner case situation (all columns are NULL) you want a non-null default value which greater than any possible value or can get limited to a certain threshold
You can deal with variables to make this statement even more readable
If you question yourself what ~0 >> 1 means:
It's just a short hand for saying "Give me the greatest number available". See also: https://stackoverflow.com/a/2679152/2427579
Even better, if you have only two columns, you can use:
SELECT LEAST( IFNULL(#column1, #column2), IFNULL(#column2, #column1) ) AS least_date;
-- Returns: NULL (if both columns are null) or the least value
This is how I solved it:
select coalesce(least(col1, col2), col1, col2)
If one value is NULL, the query will return the first non-NULL value. You can even add a default value as the last parameter, if both values can be NULL.
This may perform a bit better (may have to be converted to corresponding MySql syntax):
SELECT
CASE
WHEN Col1 IS NULL THEN Col2
WHEN Col2 IS NULL THEN Col1
ELSE Least(Col1, Col2)
END
Another alternative (probably slower though, but worth a try):
SELECT Col1
WHERE Col2 IS NULL
UNION
SELECT Col2
WHERE Col1 IS NULL
UNION
SELECT least(Col1, Col2)
WHERE Col1 IS NOT NULL AND Col2 IS NOT NULL
Why not set the value of one column to be equal to the other column when it's NULL?
SELECT LEAST(IFNULL(COL1, COL2), IFNULL(COL2, COL1));
with the code above, the null value will be ignored unless both are null.
e.g.
COL1 = NULL, COL2 = 5
LEAST(IFNULL(NULL, 5), IFNULL(5, NULL)) -> LEAST(5, 5) -> 5
COL1 = 3, COL2 = NULL
LEAST(IFNULL(3, NULL), IFNULL(NULL, 3)) -> LEAST(3, 3) -> 3
COL1 = NULL, COL2 = NULL
LEAST(IFNULL(NULL, NULL), IFNULL(NULL, NULL)) -> LEAST(NULL, NULL) -> NULL
SELECT
MIN(LEAST(COALESCE(COL1, COL2), COALESCE(COL2,CO1)))
WHERE COL1 IS NOT NULL
AND COL2 IS NOT NULL;
I've created a function which handles any number of dates, by concatenating them with a separator (CONCAT_WS) as first parameter to the function.
CONCAT_WS besides dynamic number of parameters, will remove all NULL dates ;)
The function accepts two parameters:
delimiter separated string of dates as TEXT
delimiter as TEXT (same as used on CONCAT_WS !!) - you can remove it if you use only preferred separator on CONCAT_WS.
CREATE FUNCTION `min_date`(`dates` TEXT, `delim` VARCHAR(10)) RETURNS DATE NO SQL DETERMINISTIC
BEGIN
DECLARE `result` DATE DEFAULT NULL;
DECLARE `count` TINYINT DEFAULT 0;
DECLARE `temp` DATE DEFAULT NULL;
IF `delim` IS NULL THEN SET `delim` = ','; END IF;
IF `dates` IS NOT NULL AND CHAR_LENGTH(`dates`) > 0 THEN
SET `count` = LENGTH(`dates`) - LENGTH(REPLACE(`dates`, `delim`, SPACE(CHAR_LENGTH(`delim`) - 1)));
WHILE `count` >= 0 DO
SET `temp` = SUBSTRING_INDEX(SUBSTRING_INDEX(`dates`, `delim`, `count` + 1), `delim`, -1);
IF `result` IS NULL OR `result` > `temp` THEN SET `result` = `temp`; END IF;
SET `count` = `count` - 1;
END WHILE;
END IF;
RETURN `result`;
END
Then, you can use in any combination of date fields or as static strings (as long as are valid dates or NULL):
SELECT min_date(CONCAT_WS(',', `date_column_1`, NULL, '2019-03-04', `date_column_2`), ',') AS `min_date`
One simple (yet not beautiful) solution is the following.
If you're looking for the smallest non-null value, you can use IFNULL with the second parameter beingthe 'INT limit'
ORDER BY LEAST(
IFNULL(properties.sale_value, 2147483647),
IFNULL(properties.rental_value, 2147483647),
IFNULL(properties.daily_rental_value, 2147483647)
) ASC
And if you're looking for the biggest non-null value, you can use IFNULL with the second parameter being 1, ( or the first negative value below your limit, if you don't know it, use the negative int limit )
ORDER BY GREATEST(
IFNULL(properties.sale_value, 1),
IFNULL(properties.rental_value, 1),
IFNULL(properties.daily_rental_value, 1)
) ASC