How to make an equality test on an array - sql

I am trying to aggregate values on an ID. I return them if they are all the same, but have to create another value 'C' if both are encountered.
CREATE TABLE foo (
fooid int,
foocomm text
);
INSERT INTO foo (fooid,foocomm)
VALUES (1,'A');
INSERT INTO foo (fooid,foocomm)
VALUES (1,'B');
INSERT INTO foo (fooid,foocomm)
VALUES (2,'A');
SELECT
CASE
WHEN array_remove(array_agg(foocomm),NULL) = {'A'} THEN 'A'
WHEN array_remove(array_agg(foocomm),NULL) = {'B'} THEN 'B'
WHEN array_remove(array_agg(foocomm),NULL) = {'A','B'} THEN 'C'
END AS BAR
FROM foo
GROUP BY fooid;
It should yield
fooid,foocomm
1, 'C'
2, 'A'

t=# SELECT fooid,
CASE
WHEN array_remove(array_agg(foocomm order by foocomm),NULL) = '{A}' THEN 'A'
WHEN array_remove(array_agg(foocomm order by foocomm),NULL) = '{B}' THEN 'B'
WHEN array_remove(array_agg(foocomm order by foocomm),NULL) = '{A,B}' THEN 'C'
END AS BAR
FROM foo
GROUP BY fooid;
fooid | bar
-------+-----
1 | C
2 | A
(2 rows)
your query works, just fix array text representation:
https://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-INPUT

Related

How do i create a DB2 UNION query using variables from a list

So i have a union query like:
select count(id)
from table 1
where membernumber = 'x'
and castnumber = 'y'
union
select count(id)
from table 1
where membernumber = 'x'
and castnumber = 'y'
union
etc...
There will be over 200 unions coming from a list 2x 200 table with values for x and y in each row. So each union query has to get the value of x and y from the corresponding row (not in any particular order).
How can i achieve that ?
Thanks
Try this:
DECLARE GLOBAL TEMPORARY TABLE
SESSION.PARAMETERS
(
MEMBERNUMBER INT
, CASTNUMBER INT
) DEFINITION ONLY WITH REPLACE
ON COMMIT PRESERVE ROWS NOT LOGGED;
-- Insert all the the constants in your application with
INSERT INTO SESSION.PARAMETERS
(MEMBERNUMBER, CASTNUMBER)
VALUES (?, ?);
-- I don't know the meaning of the result you want to get
-- but it's equivalent
select distinct count(t.id)
from table1 t
join session.parameters p
on p.membernumber = t.membernumber
and p.castnumber = t.castnumber
group by t.membernumber, t.castnumber;

Showing NULL on purpose when a NULL joined value is present in SQL

I have a table with some input values and a table with lookup values like below:
select input.value, coalesce(mapping.value, input.value) result from (
select 'a' union all select 'c'
) input (value) left join (
select 'a', 'z' union all select 'b', 'y'
) mapping (lookupkey, value) on input.value = mapping.lookupkey
which gives:
value | result
--------------
a | z
c | c
i.e. I want to show the original values as well as the mapped value but if there is none then show the original value as the result.
The above works well so far with coalesce to determine if there is a mapped value or not. But now if I allow NULL as a valid mapped value, I want to see NULL as the result and not the original value, since it does find the mapped value, only that the mapped value is NULL. The same code above failed to achieve this:
select input.value, coalesce(mapping.value, input.value) result from (
select 'a' union all select 'c'
) input (value) left join (
select 'a', 'z' union all select 'b', 'y' union all select 'c', null
) mapping (lookupkey, value) on input.value = mapping.lookupkey
which gives the same output as above, but what I want is:
value | result
--------------
a | z
c | NULL
Is there an alternative to coalesce that can achieve what I want?
I think you just want a case expression e.g.
select input.[value]
, coalesce(mapping.[value], input.[value]) result
, case when mapping.lookupkey is not null then mapping.[value] else input.[value] end new_result
from (
select 'a'
union all
select 'c'
) input ([value])
left join (
select 'a', 'z'
union all
select 'b', 'y'
union all
select 'c', null
) mapping (lookupkey, [value]) on input.[value] = mapping.lookupkey
Returns:
value result new_result
a z z
c c NULL

SQL Beginner question: CASE AS END for multiple columns?

I am making a stored procedure which creates a target data table (#tmp_target_table), does some checking on it, and outputs the results in a resultset table (#tmp_resultset_table). The resultset table needs to have multiple new columns: user_warning_id, user_warning_note, and user_warning_detail in addition to existing columns from #tmp_target_table.
I have a working stored procedure as in the following but this has some issue. I need to write conditionA, conditionB, and conditionB repeatedly but these conditions will need to be changed in the future. How would you write a code that is more extensible?
<Working code>
SELECT existing_col1, existing_col2,
CASE
WHEN conditionA
THEN user_warning_id_A
WHEN conditionB
THEN user_warning_id_B
WHEN conditionC
THEN user_warning_id_C
END AS user_warning_id,
CASE
WHEN conditionA
THEN user_warning_note_A
WHEN conditionB
THEN user_warning_note_B
WHEN conditionC
THEN user_warning_note_C
END AS user_warning_note,
CASE
WHEN conditionA
THEN user_warning_detail_A
WHEN conditionB
THEN user_warning_detail_B
WHEN conditionC
THEN user_warning_detail_C
END AS user_warning_detail,
existing_col3, existing_col4
INTO #tmp_resultset_table
FROM #tmp_target_table
SELECT * FROM #tmp_resultant_table
In SQL Server, you can use a lateral join (i.e., apply) to arrange the data so you can use a reference table:
select tt.*,
v2.user_warning_id, v2.user_warning_note, v2.user_warning_detail
from #tmp_target_table tt cross apply
(values (case when conditionA then 'a'
when conditionA then 'b'
when conditionA then 'c'
end)
) v(cond) left join
(values ('a', user_warning_id_A, user_warning_note_A, user_warning_detail_A),
('b', user_warning_id_B, user_warning_note_B, user_warning_detail_B),
('c', user_warning_id_C, user_warning_note_C, user_warning_detail_C)
) v2(cond, user_warning, user_warning_note, user_warning_detail)
on v2.cond = v.cond;
This also makes it pretty easy to add more levels, if you like.
Note: You could combine v and v2 into a single values list. I separated them, because you might want to consider making v2 an actual reference table.
EDIT:
DB2 supports lateral joins with the lateral keyword. I don't remember if DB2 supports values(). So try this:
select tt.*,
v2.user_warning_id, v2.user_warning_note, v2.user_warning_detail
from #tmp_target_table tt cross join lateral
(select (case when conditionA then 'a'
when conditionA then 'b'
when conditionA then 'c'
end)
from sysibm.sysdummy1
) v(cond) left join
(select 'a' as cond, user_warning_id_A as user_warning_id, user_warning_note_A as user_warning_note, user_warning_detail_A user_warning_detail
from sysibm.sysdummy1
union all
select 'b', user_warning_id_B, user_warning_note_B, user_warning_detail_B
from sysibm.sysdummy1
union all
select 'c', user_warning_id_C, user_warning_note_C, user_warning_detail_C
from sysibm.sysdummy1
) v2(cond, user_warning, user_warning_note, user_warning_detail)
on v2.cond = v.cond;
You could put the messages into a table and the condition logic into a function.
Just using temp tables so you can test it out.
Warnings
select warningID = 1, note = 'note 1', detail = 'notes on warning 1'
into #warning
union
select warningID = 2, note = 'note 2', detail = 'notes on warning 2'
union
select warningID = 3, note = 'note 3', detail = 'notes on warning 3'
union
select warningID = 4, note = 'note 4', detail = 'notes on warning 4'
Data values that have to meet conditions coming from some table ... #conditions
select condID = 1, val1 = 10, val2 = 1
into #conditions
union
select condID = 2, val1 = 20, val2 = 1
union
select condID = 3, val1 = 5, val2 = 2
union
select condID = 4, val1 = 30, val2 = 1
union
select condID = 4, val1 = 12, val2 = 1
Then a function that determines warnings based on conditions in the data. Takes values as input and returns a warningID
create function testWarningF
(
#val1In int
)
returns int
as
begin
declare #retVal int
select #retVal = case when #val1In <= 10 then 1
when #val1In > 10 and #val1In <=20 then 2
else 3
end
return #retVal
end
go
Then, the SQL ...
select *
from #conditions c
inner join #warning w on w.warningID = dbo.warningF(val1)
... returns this result
condID val1 val2 warningID note detail
1 10 1 1 note 1 notes on warning 1
2 20 1 2 note 2 notes on warning 2
3 5 2 1 note 1 notes on warning 1
4 12 1 2 note 2 notes on warning 2
4 30 1 3 note 3 notes on warning 3
Possibly the simplest method it to move the Conditions into a sub-select, then reference a token in the other select. E.g.
SELECT existing_col1
, existing_col2
, CASE CON
WHEN 'A' THEN user_warning_id_A
WHEN 'B' THEN user_warning_id_B
WHEN 'C' THEN user_warning_id_C END AS user_warning_id
, CASE CON
WHEN 'A' THEN user_warning_note_A
WHEN 'B' THEN user_warning_note_B
WHEN 'C' THEN user_warning_note_C END AS user_warning_note
, CASE CON
WHEN 'A' THEN user_warning_detail_A
WHEN 'B' THEN user_warning_detail_B
WHEN 'C' THEN user_warning_detail_C END AS user_warning_detail
, existing_col3
, existing_col4
FROM (
SELECT T.*
, CASE WHEN conditionA THEN 'A'
WHEN conditionB THEN 'B'
WHEN conditionC THEN 'C' END AS CON
FROM
#tmp_target_table T
)
although Gordon's answer is also neat, even though it adds two joins in the access plan. In Db2 Syntax, this works (on Db2 11.1.3.3 anyway)
select tt.*,
v2.user_warning_id, v2.user_warning_note, v2.user_warning_detail
from #tmp_target_table tt
, (values (case when conditionA then 'a'
when conditionB then 'b'
when conditionC then 'c'
end)
) v(cond) left join
(values ('a', 'user_warning_id_A', 'user_warning_note_A', 'user_warning_detail_A'),
('b', 'user_warning_id_B', 'user_warning_note_B', 'user_warning_detail_B'),
('c', 'user_warning_id_C', 'user_warning_note_C', 'user_warning_detail_C')
) v2(cond, user_warning_id, user_warning_note, user_warning_detail)
on v2.cond = v.cond;
testing with
create table #tmp_target_table(i int);
insert into #tmp_target_table(values 1);
create variable conditionA boolean;
create variable conditionB boolean default true;
create variable conditionC boolean;
returns
I USER_WARNING_ID USER_WARNING_NOTE USER_WARNING_DETAIL
- ----------------- ------------------- ---------------------
1 user_warning_id_B user_warning_note_B user_warning_detail_B

How to determine which `Foos` have no associated `Bars`

I want to find all Foos that do not have any associated Bars.
Here's the query I'm using. It's returning no records:
select * from foos f where f.id not in(select b.foo_id from bars b);
However, I know that the foo with id = 1583 has no associated Bars, so there should be at least one result in my previous query.
Can somebody point out a mistake I am making? Thanks.
The reason is the SQL three valued logic.
Example: both statement will show you '?'.
SELECT CASE WHEN 2 NOT IN (3,4,NULL) THEN 'True' ELSE '?' END AS Test#0
SELECT CASE WHEN 2 <> 3 AND 2 <> 4 AND 2 <> NULL THEN 'True' ELSE '?' END AS Test#0
-- TRUE AND TRUE AND UNKNOWN => UNKNOWN (ELSE -> ?)
The solution is to use NOT EXISTS instead of NOT IN:
DECLARE #foo TABLE(foo_id INT);
INSERT #foo(foo_id)
SELECT 1 UNION ALL SELECT 2;
DECLARE #bars TABLE(bars_id INT IDENTITY(2,2), foo_id INT);
INSERT #bars(foo_id)
SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT NULL;
PRINT '#foo >'
SELECT * FROM #foo
PRINT '#bars >'
SELECT * FROM #bars
PRINT 'Test #1 >'
SELECT *
FROM #foo AS f
WHERE f.foo_id NOT IN (SELECT b.foo_id FROM #bars AS b)
PRINT 'Test #2 >'
SELECT *
FROM #foo AS f
WHERE NOT EXISTS (SELECT b.foo_id FROM #bars AS b WHERE b.foo_id = f.foo_id)
Results:
#foo >
foo_id
-----------
1
2
#bars >
bars_id foo_id
----------- -----------
2 3
4 4
6 NULL
Test #1 >
foo_id
-----------
Test #2 >
foo_id
-----------
1
2
That looks right; if it were me, I'd suspect typo. Have you examined the data?
SELECT * from foos WHERE foos.id = 1583;
SELECT * from bars WHERE bars.foo_id = 1583;
I assume you've recast your statement from your original to remove your actual table names -- so proofread your actual query.
Or try join syntax:
SELECT * FROM foos LEFT JOIN bars on foos.id = bars.foo_id WHERE bars.foo_id IS NULL;

how to normalize / update a "order" column

i have a table "mydata" with some data data :
id name position
===========================
4 foo -3
6 bar -2
1 baz -1
3 knork -1
5 lift 0
2 pitcher 0
i fetch the table ordered using order by position ASC;
the position column value may be non unique (for some reason not described here :-) and is used to provide a custom order during SELECT.
what i want to do :
i want to normalize the table column "position" by associating a unique position to each row which doesnt destroy the order. furthermore the highest position after normalising should be -1.
wished resulting table contents :
id name position
===========================
4 foo -6
6 bar -5
1 baz -4
3 knork -3
5 lift -2
2 pitcher -1
i tried several ways but failed to implement the correct update statement.
i guess that using
generate_series( -(select count(*) from mydata), -1)
is a good starting point to get the new values for the position column but i have no clue how to merge that generated column data into the update statement.
hope somebody can help me out :-)
Something like:
with renumber as (
select id,
-1 * row_number() over (order by position desc, id) as rn
from foo
)
update foo
set position = r.rn
from renumber r
where foo.id = r.id
and position <> r.rn;
SQLFiddle Demo
Try this one -
Query:
CREATE TABLE temp
(
id INT
, name VARCHAR(10)
, position INT
)
INSERT INTO temp (id, name, position)
VALUES
(4, 'foo', -3),
(6, 'bar', -2),
(1, 'baz', -1),
(3, 'knork', -1),
(5, 'lift', 0),
(2, 'pitcher', 0)
SELECT
id
, name
, position = -ROW_NUMBER() OVER (ORDER BY position DESC, id)
FROM temp
ORDER BY position
Update:
UPDATE temp
SET position = t.rn
FROM (
SELECT id, rn = - ROW_NUMBER() OVER (ORDER BY position DESC, id)
FROM temp
) t
WHERE temp.id = t.id
Output:
id name position
----------- ---------- --------------------
4 foo -6
6 bar -5
3 knork -4
1 baz -3
5 lift -2
2 pitcher -1
#a_horse_with_no_name is really near the truth - thank you !
UPDATE temp
SET position=t.rn
FROM (SELECT
id, name,
-((select count( *)
FROM temp)
+1-row_number() OVER (ORDER BY position ASC)) as rn
FROM temp) t
WHERE temp.id=t.id;
SELECT * FROM temp ORDER BY position ASC;
see http://sqlfiddle.com/#!1/d1770/6
update mydata temp1, (select a.*,#var:=#var-1 sno from mydata a, (select #var:=0) b
order by position desc, id asc) temp2
set temp1.position = temp2.sno
where temp1.id = temp2.id;