In TSQL, will the following examples yield different results? - tsql

I have to change an update query in TSQL to include the following code:
(uh.FirstName != cu.FirstName or isnull(uh.FirstName,'|||') != isnull(cu.Firstname,'|||')
But to my mind that seems redundant and it would be better to just use the second part:
isnull(uh.FirstName,'|||') != isnull(cu.Firstname,'|||')
Am I missing something?

It looks like the ISNULL(x,'|||') is just trying to handle considering nulls equivalent. You have to handle each null case and potentially double null depending on whether that is a "match" or not. Here's my suggestion with the commented line being option depending on how you want to handle double null.
(
uh.FirstName != cu.FirstName
OR uh.FirstName IS NULL AND cu.FirstName IS NOT NULL
OR uh.FirstName IS NOT NULL AND cur.FirstName IS NULL
--OR uh.FirstName IS NULL AND cur.FirstName IS NULL
)
. Not sure whether the coalesce is faster than substitution and comparison but my guess is it would be. It also doesn't allow for ambiguity comparing '|||' to NULL which would yield false results with that solution.
*Fixed after HABO's comment

Unless you expect any of the columns to actually contains the literal string value of three pipes ('|||'), you can use the second code snippet safely.
Quick demo:
DECLARE #T AS TABLE
(
c1 varchar(5),
c2 varchar(5)
);
INSERT INTO #T VALUES
('a', 'a'),
('a', 'b'),
('a', null),
(null, 'a'),
(null, null),
('b', 'a');
SELECT *, 'First' as example
FROM #T
WHERE c1 != c2 OR ISNULL(c1, '|||') != ISNULL(c2, '|||')
SELECT *, 'Second' as example
FROM #T
WHERE ISNULL(c1, '|||') != ISNULL(c2, '|||')
Results:
c1 c2 example
a b First
a NULL First
NULL a First
b a First
c1 c2 example
a b Second
a NULL Second
NULL a Second
b a Second

Related

some of range_composite_type operator only check the elements of composite type

db fiddle
create type mytype as (t1 int, t2 date);
create type mytyperange as range(subtype = mytype);
seems now, operators(#>,<#, &&), compare based on the first element of the composite type.
for example:
--left contain the right
select mytyperange (
(1,'2022-01-01')::mytype,
(8, '2022-01-31')::mytype, '[]') #> (2, '2020-01-19')::mytype;
--does the range overlaps, that is, have any common element.
select
mytyperange ((2,'2020-12-30')::mytype,
(2, '2020-12-31')::mytype)
&&
mytyperange(
(1,'2022-01-01')::mytype,
(8, '2022-01-31')::mytype) ;
return false, because first elements(2) in range(1,8) even though the second does not meet requirement.
However << operator make sense.
select
mytyperange (null, (-1, '2022-01-19')::mytype) <<
mytyperange(
(1,'2022-01-01')::mytype,
(8, '2022-01-31')::mytype, '[]') ;
Is possible to make use range composite type to represent that the first elements in [1,8] the second element in ['2022-01-01',2022-01-31'].
that means the first code example should return false.
another question: adjacent operator of composite type range how to validate it.
a better way to explain is: is possible to make mytyperange be equivalent as the following query:
select a, b::date
from generate_series(1,8) a,
generate_series('2022-01-01'::timestamp,
'2022-01-31'::timestamp, interval '1 day') b;

Understanding COALESCE in postgres

Precise question.
Table ROW
value1 a
value2 b
value3 null
value4 d
Function parameters
CREATE OR REPLACE FUNCTION "GetValues"(
"#value1" VARCHAR(50),
"#value2" VARCHAR(50),
"#value3" VARCHAR(50),
"#value4" VARCHAR(50)
)
BEGIN
RETURN QUERY SELECT(
t."value1",
t."value2",
t."value3",
t."value4",
)
FROM "table" as t
WHERE t."value1" = COALESCE("#value1", c."value1")
AND t."value2" = COALESCE("#value2", c."value2")
AND t."value3" = COALESCE("#value3", c."value3")
AND t."value4" = COALESCE("#value4", c."value4");
END;
If I use the above function and only provide the following:
('a', null, null, 'd')
It will return [] even if 'a' and 'd' are found and I found that this only happens if I provide a parameter to search for something that is null and the value of the row is also null.
OLD DESCRIPTION BELOW
I have setup a get which uses COALESCE successfully to search by multiple or 1 parameter(s). However, if any one of those params that are not provided (so default to NULL) are actually NULL in the db because I haven't updated that field before, then it will always return an empty array, even though one of the provided params will successful match to a row in the table.
I just want to know if I need a new system all together to complete this search or if it is just an unfortunate effect of COALESCE?
Below is the relevant snippet.
FROM "table" as t
WHERE t."value1" = COALESCE("#value1", c."value1")
AND t."value2" = COALESCE("#value2", c."value2")
AND t."value3" = COALESCE("#value3", c."value3")
AND t."value4" = COALESCE("#value4", c."value4");
In the above, if I provide value1 and it matches but value4 is NULL in that row, then it will return [].
The return is a table with each of those 4 values.
Should this be a simple row comparison (give out all rows which have the same values as the input parameters)?
This could simply be achieved by the row comparator (documentation):
WHERE row(t.*) IS NOT DISTINCT FROM row("#value1", "#value2", "#value3", "#value4")
demo: db<>fiddle
If NULL as function input parameter should be a wildcard then #kurkle's solution works well.
You could do it like this:
FROM test as t
WHERE ("#value1" IS NULL OR t."value1" = "#value1")
AND ("#value2" IS NULL OR t."value2" = "#value2")
AND ("#value3" IS NULL OR t."value3" = "#value3")
AND ("#value4" IS NULL OR t."value4" = "#value4");
db<>fiddle

How to Insert into table using multiple row returned value from subquery?

How exactly do I get this to work? I am trying my best to form a query that takes the primary keys generated from the first query and then inserts them into the 2nd table along with a static 2nd value(33). I am obviously getting a "more than one row returned by a subquery used as an expression" error. I'm googled my eye balls out and can't figure out this issue. Maybe there's a better way to do what I am trying to do.
I am using Postgresql 9.5 if that matters.
WITH x AS (INSERT INTO OPTIONS (manufacturer_id, category, name, description)
VALUES (
UNNEST(ARRAY['10', '22', '33']),
'ExtColor',
UNNEST(ARRAY['EC', 'IC', 'IO']),
UNNEST(ARRAY['a', 'b', 'c'])
)
RETURNING option_id)
INSERT INTO opt_car_data (car_id, option_id) VALUES ((SELECT option_id FROM x), 33);
WITH x AS (
INSERT INTO options (manufacturer_id, category, name, description)
VALUES (
UNNEST(ARRAY['10', '22', '33']),
'ExtColor',
UNNEST(ARRAY['EC', 'IC', 'IO']),
UNNEST(ARRAY['a', 'b', 'c'])
)
RETURNING option_id
)
INSERT INTO opt_car_data (car_id, option_id)
SELECT option_id, 33
FROM x;

NULL Integer in multi-column in clause

I'm trying to run a multi-column in clause that matches null values as well. Right now I'm using coalesce like so:
select * from table
where (coalesce(foo, ''), coalesce(bar, '')) in (('foo_val', 'bar_val'), ('foo_val', ''));
For integer columns though, this throws 'invalid input syntax for integer: ""' on the coalesce. I could coalesce to -1 instead of an empty string, but was wondering if there was a more elegant solution.
Sample input/output:
Table data:
{{foo: 1, bar: 2}, {foo: 1, bar: NULL}}
User input: [(1, 2), (1, nil)]
Expected output: both rows.
EDIT: I'll try to clarify what I'm trying to do: I want to match DB rows by multiple column value combinations given by the user. That is, if the user inputs [(1,2), (3,4)], I'd want to return rows where column_A == 1 AND column_B == 2, OR column_A == 3 AND column_B == 4. Where I run into trouble is allowing for the user to input NULL and have it matched like any other value. So if the user inputs [(1,NULL)], I'd want to return rows where column_A == 1 AND column_B == NULL (but note I wouldn't want to return them for the previous query, since the user didn't specify he wanted rows with NULL values in column_B).
This code will not just match nulls, it will also match empty strings. If that's what you want, then fine.
Using a hard-coded list in an IN clause, where you wish to also return nulls, the simplest solution is to coalesce against the first value in your IN list. That way you don't have to worry about matching non-null values (like empty strings or -1) which aren't in the list.
select *
from table
where (coalesce(foo, 'foo_val'), coalesce(bar, 'foo_val'))
in (('foo_val', 'bar_val'), ('foo_val', ''));

How does ANSI_NULLS work in TSQL?

SET ANSI_NULLS OFF seems to give different results in TSQL depending on whether you're comparing a field from a table or a value. Can anyone help me understand why the last 2 of my queries give no results? I'm not looking for a solution, just an explanation.
select 1 as 'Col' into #a
select NULL as 'Col' into #b
--This query gives results, as expected.
SET ANSI_NULLS OFF
select * from #b
where NULL = Col
--This query gives results, as expected.
SET ANSI_NULLS OFF
select * from #a
where NULL != Col
--This workaround gives results, too.
select * from #a a, #b b
where isnull(a.Col, '') != isnull(b.Col, '')
--This query gives no results, why?
SET ANSI_NULLS OFF
select * from #a a, #b b
where a.Col != b.Col
--This query gives no results, why?
SET ANSI_NULLS OFF
select * from #a a, #b b
where b.Col != a.Col
The reason the last two queries fail is that SET ANSI_NULLS ON/OFF only applies when you are comparing against a variable or the NULL value. It does not apply when you are comparing column values. From the BOL:
SET ANSI_NULLS ON affects a comparison
only if one of the operands of the
comparison is either a variable that
is NULL or a literal NULL. If both
sides of the comparison are columns or
compound expressions, the setting does
not affect the comparison.
Anything compared to a null value fails. Even comparing two null values will fail. Even the != will fail because of the (IMHO) stupid handling of NULL.
That said, the != queries could be rewritten to say:
select * from #a a where a.Col not in (select b.Col from #b b)
The last query is identical to the second to last query as the order of the comparison doesn't matter.
Incidentally, your workaround works simply because you are testing for a null value in the #b.Col column and explicitly converting it to a '' which then allows your query to do a string compare between them. An alternative way of writing that would be:
select * from #a a, #b b
where a.Col != COALESCE(b.Col, '')