Select until row matches in Postgres - postgresql

Given the following data structure:
id | subscription_id | state | created_at | ok
---------+-----------------+-------+----------------------------+----
1 | 1 | error | 2015-06-30 15:20:03.041045 | f
2 | 1 | error | 2015-06-30 15:20:04.582907 | f
3 | 1 | sent | 2015-06-30 22:50:04.50478 | f
4 | 1 | error | 2015-06-30 22:50:06.067279 | f
5 | 1 | error | 2015-07-01 22:50:02.356113 | f
I want to retrieve the last messages with state='error' until the state contains something else.
It should return this:
id | subscription_id | state | created_at | ok
---------+-----------------+-------+----------------------------+----
4 | 1 | error | 2015-06-30 22:50:06.067279 | f
5 | 1 | error | 2015-07-01 22:50:02.356113 | f
Following this question and later this one, I ended up with this query below:
SELECT * from (select id, subscription_id, state, created_at,
bool_and(state='error')
OVER (PARTITION BY state order by created_at, id) AS ok
FROM messages ORDER by created_at) m2
WHERE subscription_id = 1;
However, given that I added PARTITION BY state the query is simply ignoring all state which does not contain error and showing this instead:
id | subscription_id | state | created_at | ok
---------+-----------------+-------+----------------------------+----
1 | 1 | error | 2015-06-30 15:20:03.041045 | f
2 | 1 | error | 2015-06-30 15:20:04.582907 | f
4 | 1 | error | 2015-06-30 22:50:06.067279 | f
5 | 1 | error | 2015-07-01 22:50:02.356113 | f
How should the query be made in order to 'stop' after finding a different state and matching following the example described on the top only the ids 4 and 5?

If I correctly understand, you need this:
select * from messages
where
id > (select coalesce(max(id), 0) from messages where state <> 'error')
and
subscription_id = 1
Assuming that id is unique (PK ?) column and higher id means latest record.
EDIT
Thats correct, as #Marth mentioned, probably you need add ... AND subscription_id = 1 in subquery

No need to PARTITION BY state, you want to SELECT rows where all rows afterward (in the created_at ASC order) are error, ie bool_and(state = 'error') is true:
SELECT * FROM (
SELECT *,
bool_and(state = 'error') OVER (ORDER BY created_at DESC, id) AS only_errors_afterward
FROM sub
) s
WHERE only_errors_afterward
;
┌────┬─────────────────┬───────┬───────────────────────────────┬────┬───────────────────────┐
│ id │ subscription_id │ state │ created_at │ ok │ only_errors_afterward │
├────┼─────────────────┼───────┼───────────────────────────────┼────┼───────────────────────┤
│ 5 │ 1 │ error │ 2015-07-01 22:50:02.356113+02 │ f │ t │
│ 4 │ 1 │ error │ 2015-06-30 22:50:06.067279+02 │ f │ t │
└────┴─────────────────┴───────┴───────────────────────────────┴────┴───────────────────────┘
(2 rows)
Edit: Depending on the expected result you might need a PARTITION BY subscription_id in the window function.

Related

Update current id based on next id in postgresql

I have a table t1
+-----+-------+------+
| id | tID | cID |
+-----+-------+------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 4 |
| 4 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 2 | 2 |
+-----+-------*------*
I have deleted a record where tID is 1 and id is 3 and cID is 4,
now I want that sequence to get updated like record with id 4 gets sequence 3
below is the sample data.
+-----+-------+------+
| id | tID | cID |
+-----+-------+------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 2 | 2 |
+-----+-------+------+
Can I get an updated statement for this problem? How can I achieve this in Postgres?
Given that you know which record you just deleted you can update like this:
UPDATE t1 SET id = id - 1 where id > 3 and tID = 1;
First off presuming that id is (or part of) the Primary Key, this is a very very bad idea. The Primary key really ought to be IMMUTABLE. But if you insist on perusing it then at least do delete and update in a single step. Create an SQL function that handles both: (see fiddle)
create or replace
function remove_from_table1(id_in integer, tid_in integer)
returns void
language sql
as $$
with bye_bye (id, tid) as
( delete
from table1 d
where d.id = id_in
and d.tid = tid_in
returning id, tid
)
update table1 u
set id = u.id - 1
from bye_bye
where u.tid = bye_bye.tid
and u.id > bye_byeid;
$$;
That way later development (developers) would not need to make two separate calls, to complete the task. Even further I would put the function into a protected schema. Revoke delete authority from all schema except the owner. Grant execute of the function as needed. That protects the table from forgetting to call the function and issuing a direct delete.

Redshift SQL: How to get today's count and sum of counts from previous 3 days

I have a table with a date and some count like the following:
| Date | Count |
| 2019-01-02 | 100 |
| 2019-01-03 | 101 |
| 2019-01-04 | 99 |
| 2019-01-05 | 95 |
| 2019-01-06 | 90 |
| 2019-01-07 | 88 |
Given this table, what I want to compute is to sum the counts for the previous 3 days for each date like the followings:
| Date | Prev3DaysCount |
| 2019-01-02 | 0 |
| 2019-01-03 | 100 |
| 2019-01-04 | 201 |
| 2019-01-05 | 300 |
| 2019-01-06 | 295 |
| 2019-01-07 | 284 |
For example, the Prev3DaysCount of 284 for 2019-01-07 is from previous 3 days of (99+95+90). I figured that I can use SUM window function but I couldn't figure out how to limit the window to previous 3 days.
You can use a window function (along with a COALESCE to transform the null (in the first row) to 0):
SELECT
day,
COALESCE(
SUM(count) OVER (ORDER BY day ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING),
0
) AS Prev3DaysCount
FROM t;
Returns:
┌────────────┬────────────────┐
│ day │ prev3dayscount │
├────────────┼────────────────┤
│ 2019-01-02 │ 0 │
│ 2019-01-03 │ 100 │
│ 2019-01-04 │ 201 │
│ 2019-01-05 │ 300 │
│ 2019-01-06 │ 295 │
│ 2019-01-07 │ 284 │
└────────────┴────────────────┘
(5 rows)

Recursive CTE - Get descendants (many-to-many relationship)

What I have:
Given a tree (or more like a directed graph) that describes how a system is composed by its generic parts. For now let this system be e.g. the human body and the nodes its body parts.
So for instance 3 could be the liver that has a left and a right lobe (6 and 9), in both of which there are veins (8) (that can also be found at any unspecified place of the liver, hence 8->3) but also in the tongue (5). The lung (7) - which is in the chest (4) - also has a right lobe, and so on... (Well, of course there is no lung in the liver and also a 6->7 would be reasonable so this example wasn't the best but you get it.)
So I have this data in a database like this:
table: part
+----+------------+ id is primary key
| id | name |
+----+------------+
| 1 | head |
| 2 | mouth |
| 3 | liver |
| 4 | chest |
| 5 | tongue |
| 6 | left lobe |
| 7 | lung |
| 8 | veins |
| 9 | right lobe |
+----+------------+
table: partpart
+-------+---------+ part&cont is primary key
| part | cont | part is foreign key for part.id
+-------+---------+ cont is foreign key for part.id
| 2 | 1 |
| 3 | 1 |
| 5 | 2 |
| 6 | 3 |
| 7 | 3 |
| 7 | 4 |
| 8 | 3 |
| 8 | 5 |
| 8 | 6 |
| 8 | 9 |
| 9 | 3 |
| 9 | 7 |
+-------+---------+
What I want to achieve:
I'd like to query all parts that can be found in part 3 and expecting a result like this one:
result of query
+-------+---------+
| part | subpart |
+-------+---------+
| 3 | 6 |
| 3 | 7 |
| 3 | 8 |
| 3 | 9 |
| 6 | 8 |
| 7 | 9 |
| 9 | 8 |
+-------+---------+
I have the feeling that getting the result in this desired format is not feasible, still it would be great to have it as a similar set because my purpose is to display the data for the user like that:
3
├─ 6
│ └─ 8
├─ 7
│ └─ 9
│ └─ 8
├─ 8
└─ 9
└─ 8
How I'm trying:
WITH RECURSIVE tree AS (
SELECT part.id as part, partpart.cont (..where to define subpart?)
FROM part JOIN partpart
ON part.id = partpart.part
WHERE part.id = 3
UNION ALL
SELECT part.id, partpart.cont
FROM (part JOIN partpart
ON part.id = partpart.part
), tree
WHERE partpart.cont = tree.part
)
SELECT part, subpart FROM tree
This is the closest I could do but of course it doesn't work.
Problem solved, here is the query I needed, I hope it once helps someone else too...
WITH RECURSIVE graph AS (
SELECT
p.id AS subpart,
pp.cont AS part
FROM part p JOIN partpart pp
ON p.id = pp.part
WHERE pp.cont = 3
UNION ALL
SELECT
part.id,
partpart.cont
FROM (part JOIN partpart
ON part.id = partpart.part
), graph WHERE partpart.cont = graph.subpart
)
SELECT part, subpart, FROM graph

count() corresponding to max() of different values satisfying some condition

I have the following tables:
user_group
usergrp_id bigint Primary Key
usergrp_name text
user
user_id bigint Primary Key
user_name text
user_usergrp_id bigint
user_loc_id bigint
user_usergrp_id has its corresponding id from the user_group table
user_loc_id has its corresponding id(branch_id) from the branch table.
branch
branch_id bigint Primary Key
branch_name text
branch_type smallint
branch_type By default is set as 1. Although it may contain any value in between 1 and 4.
user_projects
proj_id bigint Primary Key
proj_name text
proj_branch_id smallint
proj_branch_id has its corresponding id(branch_id) from the branch table.
user_approval
appr_id bigint Primary Key
appr_prjt_id bigint
appr_status smallint
appr_approval_by bigint
appr_approval_by has its corresponding id(user_id) from the user table
appr_status may contain different status values like 10,20,30... for a single appr_prjt_id
user_group
usergrp_id | usergrp_name
-------------------------
1 | Admin
2 | Manager
user
user_id | user_name | user_usergrp_id |user_loc_id
---------------------------------------------------
1 | John | 1 | 1
2 | Harry | 2 | 1
branch
branch_id | branch_name | branch_type
-------------------------------------
1 | location1 | 2
2 | location2 | 1
3 | location3 | 4
4 | location4 | 2
5 | location4 | 2
user_projects
proj_id | proj_name | proj_branch_id
------------------------------------
1 | test1 | 1
2 | test2 | 2
3 | test3 | 1
4 | test4 | 3
5 | test5 | 1
6 | test5 | 4
user_approval
appr_id | appr_prjt_id | appr_status | appr_approval_by
-------------------------------------------------------
1 | 1 | 10 | 1
2 | 1 | 20 | 1
3 | 1 | 30 | 1
4 | 2 | 10 | 2
5 | 3 | 10 | 1
6 | 3 | 20 | 2
7 | 4 | 10 | 1
8 | 4 | 20 | 1
Condition: The output must take the MAX() value of appr_status for each appr_prjt_id and count it.
I.e., in the above table appr_prjt_id=1 has 3 different status: 10, 20, 30. Its count must only be shown for status corresponding to 30 in the output (not in the statuses 10 and 20), corresponding to a user group in a particular branch_name. Similarly for each of the other id's in the field appr_prjt_id
SQL Fiddle
Desired Output:
10 | 20 | 30
------> Admin 0 | 1 | 1
|
location1
|
------> Manager 1 | 1 | 0
How can I do that?
SQL Fiddle
SQL Fiddle
select
branch_name, usergrp_name,
sum((appr_status = 10)::integer) "10",
sum((appr_status = 20)::integer) "20",
sum((appr_status = 30)::integer) "30"
from
(
select distinct on (appr_prjt_id)
appr_prjt_id, appr_approval_by, appr_status
from user_approval
order by 1, 3 desc
) ua
inner join
users u on ua.appr_approval_by = u.user_id
inner join
user_group ug on u.user_usergrp_id = ug.usergrp_id
inner join
branch b on u.user_loc_id = b.branch_id
group by branch_name, usergrp_name
order by usergrp_name
The classic solution, that works in most DBMSs is to use a case:
select
branch_name, usergrp_name,
sum(case appr_status when 10 then 1 else 0 end) "10",
But Postgresql has the boolean type and it has a cast to integer (boolean::integer) resulting in 0 or 1 which makes for less verbose code.
In this case it is also possible to do a count in instead of a sum:
select
branch_name, usergrp_name,
count(appr_status = 10 or null) "10",
I indeed prefer the count but I have the impression that it is harder to understand. The trick is to know that count counts anything not null and that a (true or null) is true and a (false or null) is null so it will count whenever the condition is true.

Zend DB Join 2 rows from same table

I have the following table.
---------------------------------------------
check_id | action_id | user_id | dt |
---------------------------------------------
1 | 1 | 6 | 2011-09-17 |
2 | 1 | 6 | 2011-09-18 |
3 | 3 | 6 | 2011-09-19 |
4 | 3 | 6 | 2011-09-20 |
---------------------------------------------
I would like to query this table and get the following result.
-----------------------------------------------
action_id | user_id | dt_start | dt_end |
-----------------------------------------------
1 | 6 | 2011-09-17 | 2011-09-18 |
3 | 6 | 2011-09-19 | 2011-09-20 |
-----------------------------------------------
So I'm using the following query.
$checks->select()
->from(array('c1' => 'checks'), array('dt as dt_start')
->joinLeft(array('c2' => 'checks'), 'c1.action_id = c2.action_id', array('dt as dt_end')
->where('c1.user_id = ?', $userId)
->group('c1.action_id')
But this gives me the following result.
-----------------------------------------------
action_id | user_id | dt_start | dt_end |
-----------------------------------------------
1 | 1 | 2011-09-17 | 2011-09-17 |
1 | 3 | 2011-09-19 | 2011-09-19 |
-----------------------------------------------
Can someone tell me what I am doing wrong?
Try this query, without the group by:
SELECT *
FROM checks c1 LEFT OUTER JOIN checks c2 ON c1.action_id = c2.action_id
WHERE c1.user_d = 6
You'll see that you get matches where c1.dt < c2.dt, which is what you want.
But you also get matches where c1.dt > c2.dt, and where c1.dt = c2.dt.
That is, the self-join includes results where c1 and c2 point the very same row.
Then you use a GROUP BY that collapses multiple rows into one, but MySQL chooses an arbitrary row from the group. (This is an ambiguous query, and if you SET SQL_MODE='ONLY_FULL_GROUP_BY' you'd get an error.)
So your self-join and GROUP BY only returns c1 and c2 that are actually the very same row.
To fix this, you should add a condition that c1.dt < c2.dt.
$checks->select()
->from(array('c1' => 'checks'), array('dt as dt_start')
->joinLeft(array('c2' => 'checks'),
'c1.action_id = c2.action_id AND c1.dt < c2.dt',
array('dt as dt_end')
->where('c1.user_id = ?', $userId)
You probably don't need the GROUP BY at all in that case, assuming that you don't have multiple starts and ends for each action.
By the way, this type of complexity is one reason that it's usually recommended to store event start/end data in a single row, with the start in one column and the end in a second column.