PostgreSQL JSON building an array without null values - arrays

The following query
SELECT jsonb_build_array(jsonb_build_object('use', 'Home'),
CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work')
END)
produces
[{"use":"Home"},null]
When I actually want
[{"use":"Home"}]
How do I go about doing this? json_strip_nulls() does not work for me.

By using a PostgreSQL array like that:
SELECT array_to_json(array_remove(ARRAY[jsonb_build_object('use', 'Home'),
CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END], null))
which does produce:
[{"use": "Home"}]
while, to be sure:
SELECT array_to_json(array_remove(ARRAY[jsonb_build_object('use', 'Home'),
CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END,
jsonb_build_object('real_use', 'NotHome')], null))
does produce:
[{"use": "Home"},{"real_use": "NotHome"}]

Creating a custom function seems to be the simplest way.
create or replace function jsonb_build_array_without_nulls(variadic anyarray)
returns jsonb language sql immutable as $$
select jsonb_agg(elem)
from unnest($1) as elem
where elem is not null
$$;
select
jsonb_build_array_without_nulls(
jsonb_build_object('use', 'home'),
case when 1 = 2 then jsonb_build_object('use', 'work') end
)
jsonb_build_array_without_nulls
---------------------------------
[{"use": "home"}]
(1 row)

I'm assuming this query is generaetd dynamically, somehow. If you're in control of the SQL generation, you can also use ARRAY_AGG(...) FILTER(...) instead, which, depending on your real world query, might be more convenient than using all the different array conversion functions suggested by Patrick.
SELECT (
SELECT json_agg(v) FILTER (WHERE v IS NOT NULL)
FROM (
VALUES
(jsonb_build_object('use', 'Home')),
(CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END)
) t (v)
)
Or also:
SELECT (
SELECT json_agg(v)
FROM (
VALUES
(jsonb_build_object('use', 'Home')),
(CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END)
) t (v)
WHERE v IS NOT NULL
)

One other way that this can be handled is the following:
SELECT jsonb_build_array(
jsonb_build_object('use', 'Home'),
CASE
WHEN 1 = 2 THEN jsonb_build_object('use', 'Work')
ELSE '"null"'
END
) - 'null'
(Unfortunately it's not really possible to do much with null by itself in postgres -- or most other DBs)
In the case above, '"null"' could be replaced with just about any unique string that would not be mistaken for live data in the array. I would not use numbers since - 0 would actually try to remove the first item from the array, rather than a number within the array. But you could probably use '"0"' and remove using something like - '0' if you wanted.
For those not using CASE, a COALESCE could be used to convert nulls into the desired string (alas, no NVL, IFNULL or ISNULL in postgres, but at least COALESCE is portable)

Related

SQL CASE when text field is NULL not working

I'm trying to run the following case statement in SQL:
CASE
WHEN P.Photos_Cloned_From IS NULL
THEN 'http://www.toolboxbarn.com/v/vspfiles/photos/'+P.ProductCode+'-2T.jpg'
ELSE 'http://www.toolboxbarn.com/v/vspfiles/photos/'+P.Photos_Cloned_From+'-2T.jpg'
END AS image_link,
The statements works, but only for those records that are not NULL. For items that are NULL, the statement is not returning the THEN condition.
Any suggestions?
Try this and let us know what happens.
'http://www.toolboxbarn.com/v/vspfiles/photos/'+
COALESCE(P.Photos_Cloned_From,P.ProductCode,'DEFAULT')+'-2T.jpg'
AS image_link,
Using coalesce here is better than the case statement. Some platforms can optimize coalesce and it lets you easily make a default value.
#hogan's Suggestion is a great one to save code, but ultimately it should have the same result as your case statement if you don't introduce his new 'default' case. It is most likely that the ProductCode is also NULL is that a possibility?
Why string + NULL = NULL because NULL is an unknown in sql most db platforms will nullify the entire value when null is aggregated or concatenated.
So Think of the following test cases:
Id ProductCode Photos_Cloned_From
1 1 ClonedFrom
2 2 Null
3 NULL Null
The results of your case expression and Hogan's Suggestion would be:
1) ELSE 'http://www.toolboxbarn.com/v/vspfiles/photos/ClonedFrom-2T.jpg'
2) WHEN 'http://www.toolboxbarn.com/v/vspfiles/photos/2-2T.jpg'
3) WHEN NULL
CASE WHEN ISNULL(P.Photos_Cloned_From,'') = '' THEN 'http://www.toolboxbarn.com/v/vspfiles/photos/'+P.ProductCode+'-2T.jpg'
ELSE 'http://www.toolboxbarn.com/v/vspfiles/photos/'+P.Photos_Cloned_From+'-2T.jpg'
END AS image_link,
SELECT
F.Name
, F.Address
, F.InDate
, ( CASE WHEN (F.Date_Down IS NULL and F.Cod_Down = 0 )THEN 0 ELSE -1 END) as 'sn_Down'
FROM
Folders F

case when but two columns instead of one column

I need some advice and suggestions if there is available some way of doing something like this:
SELECT Status=Case
WHEN clflf.product='active'
THEN 'Active' ELSE 'NotActive' END;
this basically creates active or notactive in one column.
Is there some way of doing a case when or some other technique so that I can have two columns, 'Active' and 'NotActive'?
You need separate case statements, like so.
Select StatusActive = Case when clflf.product='active' then 1 ELSE 0 end
,StatusNotActive = Case when clflf.product <>'active' then 1 ELSE 0 end
You can always do
SELECT active,
1 - active AS notactive
FROM clflf
CROSS APPLY (SELECT CASE
WHEN clflf.product = 'active' THEN 1
ELSE 0
END) CA(active)

Query with integers not working

I've been searching here on stackoverflow and other sources but not found a solution to this
The query below works as expected expect for when either custinfo.cust_cntct_id or custinfo.cust_corrcntct_id = '' (blank not NULL) then I get no results. Both are integer fields and if both have an integer value then I get results. I still want a value returned for either cntct_email or corrcntct_email even if custinfo.cust_cntct_id or custinfo.cust_corrcntct_id = blank
Can someone help me out in making this work? The database is PostgreSQL.
SELECT
cntct.cntct_email AS cntct_email,
corrcntct.cntct_email AS corrcntct_email
FROM
public.custinfo,
public.invchead,
public.cntct,
public.cntct corrcntct
WHERE
invchead.invchead_cust_id = custinfo.cust_id AND
cntct.cntct_id = custinfo.cust_cntct_id AND
corrcntct.cntct_id = custinfo.cust_corrcntct_id;
PostgreSQL won't actually let you test an integer field for a blank value (unless you're using a truly ancient version - 8.2 or older), so you must be using a query generator that's "helpfully" transforming '' to NULL or a tool that's ignoring errors.
Observe this, on Pg 9.2:
regress=> CREATE TABLE test ( a integer );
CREATE TABLE
regress=> insert into test (a) values (1),(2),(3);
INSERT 0 3
regress=> SELECT a FROM test WHERE a = '';
ERROR: invalid input syntax for integer: ""
LINE 1: SELECT a FROM test WHERE a = '';
If you are attempting to test for = NULL, this is not correct. You must use IS NOT NULL or IS DISTINCT FROM NULL instead. Testing for = NULL always results in NULL, which is treated as false in a WHERE clause.
Example:
regress=> insert into test (a) values (null);
INSERT 0 1
regress=> SELECT a FROM test WHERE a = NULL;
a
---
(0 rows)
regress=> SELECT a FROM test WHERE a IS NULL;
a
---
(1 row)
regress=> SELECT NULL = NULL as wrong, NULL IS NULL AS right;
wrong | right
-------+-------
| t
(1 row)
By the way, you should really be using ANSI JOIN syntax. It's more readable and it's much easier to forget to put a condition in and get a cartesian product by accident. I'd rewrite your query for identical functionality and performance but better readability as:
SELECT
cntct.cntct_email AS cntct_email,
corrcntct.cntct_email AS corrcntct_email
FROM
public.custinfo ci
INNER JOIN public.invchead
ON (invchead.invchead_cust_id = ci.cust_id)
INNER JOIN public.cntct
ON (cntct.cntct_id = ci.cust_cntct_id)
INNER JOIN public.cntct corrcntct
ON (corrcntct.cntct_id = ci.cust_corrcntct_id);
Use of table aliases usually keeps it cleaner; here I've aliased the longer name custinfo to ci for brevity.

How to get all values including null in a SQL Server case statement?

I have a big stored procedure, and basically I want to select all values (including null) if my variable #DimBrowserId is set to 0. I am using a case statement, however this is only catching values that actually have something and ignoring the NULL valued fields. Because I am using the = clause in the WHERE I cannot do IS NULL. I do not want to have to write 2 IF statements because the stored procedure would then be enormous, so I want to know how to get null values as well. Here is my code:
SELECT
DATEPART(yy, DATEADD(mi, #Mdelta, d.DimDateValue)),
DisableCount = COUNT(*)
FROM
dbo.FactDisable AS f
JOIN
dbo.DimDate AS d ON f.DimDateId = d.DimDateId
JOIN
dbo.DimDevice AS v ON f.DimDeviceId = v.DimDeviceId
WHERE
d.DimDateValue >= #StartDateGMT
AND d.DimDateValue <= #EndDateGMT
AND f.IsTest = #IncludeTest
AND f.DimProductId = #DimProductId
AND v.DimBrowserId = CASE
WHEN #DimBrowserId = 0 THEN v.DimBrowserId
ELSE #DimBrowserId
END
GROUP BY
DATEPART(yy, DATEADD(mi, #Mdelta, d.DimDateValue))
The code is near the CASE clause.
Thanks
Change that line to be
AND (#DimBrowserID = 0 OR #DimBrowserID = v.DimBrowserId)
If #DimBroserID is 0 then no filtering will be applied for this line.
Use ISNULL:
SELECT DATEPART(yy,DATEADD(mi,#Mdelta,d.DimDateValue)),
DisableCount=COUNT(*)
FROM dbo.FactDisable AS f
JOIN dbo.DimDate AS d ON f.DimDateId = d.DimDateId
JOIN dbo.DimDevice AS v ON f.DimDeviceId = v.DimDeviceId
WHERE d.DimDateValue >= #StartDateGMT AND d.DimDateValue <= #EndDateGMT
AND f.IsTest = #IncludeTest AND f.DimProductId = #DimProductId
AND v.DimBrowserId = CASE WHEN ISNULL(#DimBrowserId,0) = 0 THEN v.DimBrowserId ELSE #DimBrowserId END
GROUP BY DATEPART(yy,DATEADD(mi,#Mdelta,d.DimDateValue))
CASE WHEN COALESCE(#MightBeNull, 0) = 0 THEN ZeroResult ...
will be treated as zero if #MightBeNull is null, and whatever #MightBeNull is if it's not null.
Assuming null means any browser, a better data model for this scenario might be to set an ID that identifies any browser, instead of setting it to null.
You probably know what you are running into is NULL does not equal NULL in a comparison.
Assuming you don't have control of the data model to fix that, one option would be to coalesce your NULL values to an unused id.
The resulting WHERE clause would look like this, assuming -1 is the unused value you choose.
AND COALESCE(v.DimBrowserId, -1) = CASE WHEN #DimBrowserId = 0 THEN COALESCE(v.DimBrowserId, -1) ELSE #DimBrowserId END

Using a bit input in stored procedure to determine how to filter results in the where clause

I'm beating my head against the wall here... can't figure out a way to pull this off.
Here's my setup:
My table has a column for the date something was completed. If it was never completed, the field is null. Simple enough.
On the front end, I have a checkbox that defaults to "Only show incomplete entries". When only pulling incomplete entries, it's easy.
SELECT
*
FROM Sometable
WHERE Completed_Date IS NULL
But offering the checkbox option complicates things a great deal. My checkbox inputs a bit value: 1=only show incomplete, 0=show all.
The problem is, I can't use a CASE statement within the where clause, because an actual value uses "=" to compare, and checking null uses "IS". For example:
SELECT
*
FROM Sometable
WHERE Completed_Date IS <---- invalid syntax
CASE WHEN
...
END
SELECT
*
FROM Sometable
WHERE Completed_Date =
CASE WHEN #OnlyIncomplete = 1 THEN
NULL <----- this translates to "WHERE Completed_Date = NULL", which won't work.. I have to use "IS NULL"
...
END
Any idea how to accomplish this seemly easy task? I'm stumped... thanks.
...
WHERE #OnlyIncomplete = 0
OR (#OnlyIncomplete = 1 AND Completed_Date IS NULL)
Hmmm... I think what you want is this:
SELECT
*
FROM Sometable
WHERE Completed_Date IS NULL OR (#OnlyIncomplete = 0)
So that'll show Date=NULL plus, if OnlyIncomplete=0, Date != Null. Yeah, I think that's it.
If you still want to use a CASE function (although it may be overkill in this case) :
SELECT
*
FROM Sometable
WHERE 1 =
(CASE WHEN #OnlyIncomplete = 0 THEN 1
WHEN #OnlyIncomplete = 1 AND Completed_Date IS NULL THEN 1
END)

Resources