Updating multiple rows with a conditional where clause in Postgres? - sql

I'm trying to update multiple rows in a single query as I have many rows to update at once. In my query, there is a where clause that applies only to certain rows.
For example, I've the following query:
update mytable as m set
column_a = c.column_a,
column_b = c.column_b
from (values
(1, 12, 6, TRUE),
(2, 1, 45, FALSE),
(3, 56, 3, TRUE)
) as c(id, column_a, column_b, additional_condition)
where c.id = m.id
and CASE c.additional_condition when TRUE m.status != ALL(array['active', 'inactive']) end;
The last line in the where clause (m.status != ALL(array['active', 'inactive'])) should only be applied to rows which has TRUE in the value of c.additional_condition. Otherwise, the condition should not be applied.
Is it possible to achieve this in Postgres?

I think that this is what you want:
and CASE
when c.additional_condition THEN m.status != ALL(array['active', 'inactive'])
else TRUE
end

I think the logic you want is:
where c.id = m.id and
( (not c.additional_condition) and orm.status = 'active' )
You can use in or arrays for multiple values:
where c.id = m.id and
( (not c.additional_condition) and orm.status not in ( 'active', 'inacctive') )
I don't see a particular value to use arrays, unless you are passing a value in as an array.

Related

How to compare two adjacent rows in SQL?

In SQL is there a way to compare two adjacent rows. In other words if C2 = BEM and C3 = Compliance or if C4 = Compliance and C5 = BEM, then return true. But consecutive rows are identical like in C6 = BEM and C7 = BEM, then return fail.
Check out the lead() and lag() functions.
They do work best (most reliable) with a sorting field... Your sample does not appear to contain such a field. I added a sorting field in my second solution.
The coalesce() function handles the first row that does not have a preceeding row.
Solution 1 without sort field
create table data
(
A nvarchar(10)
);
insert into data (A) values
('BEM'),
('Compliance'),
('BEM'),
('Compliance'),
('BEM'),
('Compliance'),
('Compliance'),
('Compliance');
select d.A,
coalesce(lag(d.A) over(order by (select null)), '') as lag_A,
case
when d.A <> coalesce(lag(d.A) over(order by (select null)), '')
then 'Ok'
else 'Fail'
end as B
from data d;
Solution 2 with sort field
create table data2
(
Sort int,
A nvarchar(10)
);
insert into data2 (Sort, A) values
(1, 'BEM'),
(2, 'Compliance'),
(3, 'BEM'),
(4, 'Compliance'),
(5, 'BEM'),
(6, 'Compliance'),
(7, 'Compliance'),
(8, 'Compliance');
select d.A,
case
when d.A <> coalesce(lag(d.A) over(order by d.Sort), '')
then 'Ok'
else 'Fail'
end as B
from data2 d
order by d.Sort;
Fiddle with results.
As a starter: a SQL table represents an unordered set of rows; there is no inherent ordering. Assuming that you have a column that defines the ordering of the rows, say id, and that your values are stored in column col, you can use lead() and a case expression as follows:
select col,
case when col = lead(col, 1, col) over(order by id)
then 'Fail' else 'OK'
end as status
from mytable t
Assuming you have some sort of column that you can use to determine the row order then you can use the LEAD window function to get the next value.
SELECT
[A],
CASE
WHEN [A] = LEAD([A], 1, [A]) OVER (ORDER BY SomeSortIndex) THEN 'Fail'
ELSE 'Ok'
END [B]
FROM src
The additional parameters in the LEAD function specify the row offset and default value in case there is no additional row. By using the current value as the default it will cause the condition to be true and display Fail like the last result in your example.

What is wrong with using 'Not In' in this SQL query?

I have table called BST as shown below:
Here N is value of node of Binary Tree and P is its Parent node. I have to write a query that will determine if a node is a Root Node, Leaf Node or Inner Node. I wrote below SQL query for this:
select N,
case
when P is null then 'Root'
when N in (select distinct P from BST) then 'Inner'
when N not in (select distinct P from BST) then 'Leaf'
end as type
from BST
However, this is not giving me desired result as last condition for 'Leaf' in Case statement doesn't satisfy for leaf node. I am getting below output in this case:
I have a workaround for now as below query which is giving me expected output:
select N,
case
when P is null then 'Root'
when N in (select distinct P from BST) then 'Inner'
else 'Leaf'
end as type
from BST
Expected Output:
But I can't figure out what's wrong with the first one. Could someone explain me this?
The problem is because one of your P values is null. Remove this by saying select distinct p from t where p is not null in at least the Not In one of your subqueries
http://sqlfiddle.com/#!6/77fb8/3
hence:
select N,
case
when P is null then 'Root'
when N in (select distinct P from BST) then 'Inner'
when N not in (select distinct P from BST where p is not null) then 'Leaf'
end as type
from BST
the null P value gets included in the list of distinct values selected, and not in can not determine if a given value of N is equal/not equal to the null coming from the root node of P.
It's somewhat counter intuitive but nothing is ever equal to or not equal to a null, not even null. using = with one side being null results in null, not true and not false
IN can be used to check if a value IS in the list, but not if it's not, if the list includes a null
1 IN (1,2,null) --true
3 IN (1,2,null) --null, not false, null which isn't true
3 NOT IN (1,2,null) --null, not false, null which isn't true
The ELSE form is the way to go here. Or put the disctinct query in as a subquery in the FROM block and do a left join to it
in is a shorthand for a series of = checks. null, is not a value - it's the lack thereof. Whenever applying it to an operator expecting a value (like =, or in), it results in null, which is not "true".
You can think of null as an "unknown" value. I.e. - is an unknown value in a list of values selected from a table? We can't know.
Thus, you have to handle nulls explicitly, as you did in your second query.
Try this:
DECLARE #DataSource TABLE
(
[N] TINYINT
,[P] TINYINT
);
INSERT INTO #DataSource ([N], [P])
VALUES (1, 2)
,(3, 2)
,(5, 6)
,(7, 6)
,(2, 4)
,(6, 4)
,(4, 15)
,(8, 9)
,(10, 9)
,(12, 13)
,(14, 13)
,(9, 11)
,(13, 11)
,(11, 15)
,(15, NULL);
SELECT DISTINCT
DS1.[N]
,CASE WHEN DS2.[N] IS NULL THEN 'IsLeaf' ELSE CASE WHEN DS3.[N] IS NOT NULL THEN 'Inner' ELSE ' Root' END END AS [Type]
FROM #DataSource DS1
LEFT JOIN #DataSource DS2
ON DS1.[N] = DS2.[P]
LEFT JOIN #DataSource DS3
ON DS1.[P] = DS3.[N]
ORDER BY [Type];
The idea is to use two LEFT JOINs in order to see if the current node is child and if the current not is parent.
Because P has a null value.
You can't compare NULL with the regular (arithmetic) comparison operators. Any arithmetic comparison to NULL will return NULL, even NULL = NULL or NULL <> NULL will yield NULL.
Use IS or IS NOT instead.
Write where notExists instead of not in so that it will not consider nulls
select N,
case
when P is null then 'Root'
when N in (select distinct P from BST) then 'Inner'
when N not exists (select * from BST as t2 where t2.N=t1.N)
then 'Leaf'
end as type
from BST as t1

PostgreSQL use case when result in where clause

I use complex CASE WHEN for selecting values. I would like to use this result in WHERE clause, but Postgres says column 'd' does not exists.
SELECT id, name, case when complex_with_subqueries_and_multiple_when END AS d
FROM table t WHERE d IS NOT NULL
LIMIT 100, OFFSET 100;
Then I thought I can use it like this:
select * from (
SELECT id, name, case when complex_with_subqueries_and_multiple_when END AS d
FROM table t
LIMIT 100, OFFSET 100) t
WHERE d IS NOT NULL;
But now I am not getting a 100 rows as result. Probably (I am not sure) I could use LIMIT and OFFSET outside select case statement (where WHERE statement is), but I think (I am not sure why) this would be a performance hit.
Case returns array or null. What is the best/fastest way to exclude some rows if result of case statement is null? I need 100 rows (or less if not exists - of course). I am using Postgres 9.4.
Edited:
SELECT count(*) OVER() AS count, t.id, t.size, t.price, t.location, t.user_id, p.city, t.price_type, ht.value as houses_type_value, ST_X(t.coordinates) as x, ST_Y(t.coordinates) AS y,
CASE WHEN t.classification='public' THEN
ARRAY[(SELECT i.filename FROM table_images i WHERE i.table_id=t.id ORDER BY i.weight ASC LIMIT 1), t.description]
WHEN t.classification='protected' THEN
ARRAY[(SELECT i.filename FROM table_images i WHERE i.table_id=t.id ORDER BY i.weight ASC LIMIT 1), t.description]
WHEN t.id IN (SELECT rl.table_id FROM table_private_list rl WHERE rl.owner_id=t.user_id AND rl.user_id=41026) THEN
ARRAY[(SELECT i.filename FROM table_images i WHERE i.table_id=t.id ORDER BY i.weight ASC LIMIT 1), t.description]
ELSE null
END AS main_image_description
FROM table t LEFT JOIN table_modes m ON m.id = t.mode_id
LEFT JOIN table_types y ON y.id = t.type_id
LEFT JOIN post_codes p ON p.id = t.post_code_id
LEFT JOIN table_houses_types ht on ht.id = t.houses_type_id
WHERE datetime_sold IS NULL AND datetime_deleted IS NULL AND t.published=true AND coordinates IS NOT NULL AND coordinates && ST_MakeEnvelope(17.831490030182, 44.404640972306, 12.151558389557, 47.837396630872) AND main_image_description IS NOT NULL
GROUP BY t.id, m.value, y.value, p.city, ht.value ORDER BY t.id LIMIT 100 OFFSET 0
To use the CASE WHEN result in the WHERE clause you need to wrap it up in a subquery like you did, or in a view.
SELECT * FROM (
SELECT id, name, CASE
WHEN name = 'foo' THEN true
WHEN name = 'bar' THEN false
ELSE NULL
END AS c
FROM case_in_where
) t WHERE c IS NOT NULL
With a table containing 1, 'foo', 2, 'bar', 3, 'baz' this will return records 1 & 2. I don't know how long this SQL Fiddle will persist, but here is an example: http://sqlfiddle.com/#!15/1d3b4/3 . Also see https://stackoverflow.com/a/7950920/101151
Your limit is returning less than 100 rows if those 100 rows starting at offset 100 contain records for which d evaluates to NULL. I don't know how to limit the subselect without including your limiting logic (your case statements) re-written to work inside the where clause.
WHERE ... AND (
t.classification='public' OR t.classification='protected'
OR t.id IN (SELECT rl.table_id ... rl.user_id=41026))
The way you write it will be different and it may be annoying to keep the CASE logic in sync with the WHERE limiting statements, but it would allow your limits to work only on matching data.

My Select SUM query returns null. It should return 0

I'm trying to sum up Customer balances using the following query:
select sum(balance) from mytable where customer = 'john'
However, if the customer has no balance (i.e. no matching rows in the mytable table), my query returns null and not 0. What's the problem?
Try this:
select COALESCE(sum(balance),0) from mytable where customer = 'john'
This should do the work. The coalesce method should return the 0.
That's not a problem. If there are no rows, sum() will return null. It will also return null if all rows have a null balance.
To return zero instead, try:
select isnull(sum(balance),0) from mytable where customer = 'john'
select coalesce(sum(coalesce(balance,0)),0) from mytable where customer = 'john'
Maybe you are thinking of COUNT's behaviours?
COUNT(Field) will return 0 but SUM(Field) returns NULL if there are no matching rows.
You need an ISNULL or COALESCE
COALESCE or ISNULL
Note that it happens only if all values are NULL, or has no values/entries at all.
You can (and should) always test what happens behind the curtains.
Example with custom values (PostgreSQL SQL):
WITH test_data (a, b) as (
SELECT *
FROM (VALUES
('example1', 1),
('example2', 2),
('example3', NULL),
('example3', 3),
(NULL, NULL),
(NULL, 5),
(NULL, 5),
('example4', NULL)
) t
)
SELECT
a,
SUM(b) AS b
FROM test_data
GROUP BY 1
Try this:
select sum(IsNull(balance,0)) from mytable where customer = 'john'

TSQL Order By - List of hard-coded values

I have a query that returns among others a Record Status column. The record status column has several values like: "Active", "Deleted", etc ...
I need to order the results by "Active", then "Deleted", then etc ...
I am currently creating CTEs to bring each set of records then UNION ALL. Is there a better and dynamic way of getting the query done?
Thank you,
you can use CASE on here
ORDER BY CASE WHEN Status = 'Active' THEN 0 ELSE 1 END ASC
but if you have more values for status and you want to sort Active then DELETE
ORDER BY CASE WHEN Status = 'Active' THEN 0
WHEN Status = 'Deleted' THEN 1
ELSE 2
END ASC
For more status values, you can do this:
WITH StatusOrders
AS
(
SELECT StatusOrderID, StatusName
FROM (VALUES(1, 'Active'),
(2, 'Deleted'),
...
n, 'last status')) AS Statuses(StatusOrderID, StatusName)
)
SELECT *
FROM YourTable t
INNER JOIN StatusOrders s ON t.StatusName = s.StatusName
ORDER BY s.StatusOrderID;
WITH
cteRiskStatus
AS
(
SELECT RiskStatusID, RiskStatusName
FROM (VALUES(1, 'Active'),
(2, 'Draft'),
(3, 'Occured'),
(4, 'Escalated'),
(5, 'Closed'),
(6, 'Expired'),
(7, 'Deleted')) AS RiskStatuses(RiskStatusID, RiskStatusName)
)
SELECT * FROM cteRiskStatus
Thanks