How to get the change number? - postgresql

How to increase value when source value is changed?
I have tried rank, dense_rank, row_number without success =(
id | src | how to get this?
--------
1 | 1 | 1
2 | 1 | 1
3 | 7 | 2
4 | 1 | 3
5 | 3 | 4
6 | 3 | 4
7 | 1 | 5
NOTICE: src is guaranteed to be in this order you see
is there simple way to do this?

You can achieve this by nesting two window functions - the first to get whether the src value changed from the previous row, the second to sum the number of changes. Unfortunately Postgres doesn't allow nesting window functions directly, but you can work around that with a subquery:
SELECT
id,
src,
sum(incr) OVER (ORDER BY id)
FROM (
SELECT
*,
(lag(src) OVER (ORDER BY id) IS DISTINCT FROM src)::int AS incr
FROM example
) AS _;
(online demo)

Related

Hibernate - SQL query: How to get all child descandants starting with specific node

I have the following sample data (items) with some kind of recursion. For the sake of simplicity I limited the sample to 2 level. Matter of fact - they could grow quite deep.
+----+--------------------------+----------+------------------+-------+
| ID | Item - Name | ParentID | MaterializedPath | Color |
+----+--------------------------+----------+------------------+-------+
| 1 | Parent 1 | null | 1 | green |
| 2 | Parent 2 | null | 2 | green |
| 4 | Parent 2 Child 1 | 2 | 2.4 | orange|
| 6 | Parent 2 Child 1 Child 1 | 4 | 2.4.6 | red |
| 7 | Parent 2 Child 1 Child 2 | 4 | 2.4.7 | orange|
| 3 | Parent 1 Child 1 | 1 | 1.3 | orange|
| 5 | Parent 1 Child 1 Child | 3 | 1.3.5 | red |
+----+--------------------------+----------+------------------+-------+
I need to get via SQL all children
which are not orange
for a given starting ID
with either starting ID=1. The result should be 1, 1.3.5. When start with ID=4 the should be 2.4.6.
I read little bit and found the CTE should be used. I tried the following simplified definition
WITH w1( id, parent_item_id) AS
( SELECT
i.id,
i.parent_item_id
FROM
item i
WHERE
id = 4
UNION ALL
SELECT
i.id,
i.parent_item_id
FROM
item, JOIN w1 ON i.parent_item_id = w1.id
);
However, this won't even be executed as SQL-statement. I have several question to this:
CTE could be used with Hibernate?
Is there a way have the result via SQL queries? (more or less as recursive pattern)
I'm somehow lost with the recursive pattern combined with selection of color for the end result.
Your query is invalid for the following reasons:
As documented in the manual a recursive CTE requires the RECURSIVE keyword
Your JOIN syntax is wrong. You need to remove the , and give the items table an alias.
If you need the color column, just add it to both SELECTs inside the CTE and filter the rows in the final SELECT.
If that is changed, the following works fine:
WITH recursive w1 (id, parent_item_id, color) AS
(
SELECT i.id,
i.parent_item_id,
i.color
FROM item i
WHERE id = 4
UNION ALL
SELECT i.id,
i.parent_item_id,
i.color
FROM item i --<< missing alias
JOIN w1 ON i.parent_item_id = w1.id
)
select *
from w1
where color <> 'orange'
Note that the column list for the CTE definition is optional, so you can just write with recursive w1 as ....

How to group by counted rows in Postgres?

If I have a table:
id | status
----+--------
2 | 200
1 | 0
4 | 100
3 | 200
5 | 200
I want to count the number of occurrences of each status. I have tried to use the COUNT/OVER function
SELECT status, COUNT(*) OVER () AS all, COUNT(*) OVER (PARTITION by status) as count FROM my_table;
The results are what is expected per the postgres docs on windows "However, window functions do not cause rows to become grouped into a single output row like non-window aggregate calls would. Instead, the rows retain their separate identities"
status | all | count
--------+-------+-------
0 | 5 | 1
100 | 5 | 1
200 | 5 | 3
200 | 5 | 3
200 | 5 | 3
How instead can get an output that combines the rows, so that I only get 1 row per unique status if the partition is required?
status | all | count
--------+-------+-------
0 | 5 | 1
100 | 5 | 1
200 | 5 | 3
No window function necessary in the first stage of the query, i.e. getting the counts per status. Window functions work on the result of the non-windowing part of the query, thus you can have a window function referring the aggregate & non-aggregate columns in a query. To get all_counts, it is sufficient to SUM the status_count over all the rows.
SELECT
status
, COUNT(*) status_count
, SUM(COUNT(*)) OVER () all_count
FROM my_table
GROUP BY status

PostgresQL for each row, generate new rows and merge

I have a table called example that looks as follows:
ID | MIN | MAX |
1 | 1 | 5 |
2 | 34 | 38 |
I need to take each ID and loop from it's min to max, incrementing by 2 and thus get the following WITHOUT using INSERT statements, thus in a SELECT:
ID | INDEX | VALUE
1 | 1 | 1
1 | 2 | 3
1 | 3 | 5
2 | 1 | 34
2 | 2 | 36
2 | 3 | 38
Any ideas of how to do this?
The set-returning function generate_series does exactly that:
SELECT
id,
generate_series(1, (max-min)/2+1) AS index,
generate_series(min, max, 2) AS value
FROM
example;
(online demo)
The index can alternatively be generated with RANK() (example, see also #a_horse_­with_­no_­name's answer) if you don't want to rely on the parallel sets.
Use generate_series() to generate the numbers and a window function to calculate the index:
select e.id,
row_number() over (partition by e.id order by g.value) as index,
g.value
from example e
cross join generate_series(e.min, e.max, 2) as g(value);

POSTGRESQL: Enumerate with the same number if having the same criteria

What I have
id | value
1 | foo
2 | foo
3 | bah
4 | bah
5 | bah
6 | jezz
7 | jezz
8 | jezz
9 | pas
10 | log
What I need:
Enumerate rows as in the following example
id | value | enumeration
1 | foo | 1
2 | foo | 1
3 | bah | 2
4 | bah | 2
5 | bah | 2
6 | jezz | 3
7 | jezz | 3
8 | jezz | 3
9 | pas | 4
10 | log | 5
I've tried row_number with over partition. But this leads to another kind of enumeration.
Thanks for any help
You can use rank() or dense_rank() for that case:
Click: demo:db<>fiddle
SELECT
*,
dense_rank() OVER (ORDER BY value)
FROM
mytable
rank() generates an ordered number to every element of a group, but it creates gaps (if there were 3 elements in the first group, the second group starting at row 4 would get the number 4). dense_rank() avoids these gaps.
Note, this orders the table by the value column alphabetically. So, the result will be: blah == 1, foo == 2, jezz == 3, log == 4, pas == 5.
If you want to keep your order, you need an additional order criterion. In your case you could use the id column to create such a column, if no other is available:
Click: demo:db<>fiddle
First, use first_value() to find the lowest id per value group:
SELECT
*,
first_value(id) OVER (PARTITION BY value ORDER BY id)
FROM
mytable
This first value (foo == 1, blah == 3, ...) can be used to keep the original order when calculating the dense_rank():
SELECT
id,
value,
dense_rank() OVER (ORDER BY first_value)
FROM (
SELECT
*,
first_value(id) OVER (PARTITION BY value ORDER BY id)
FROM
mytable
) s

Select rows that satisfy a certain group condition in psql

Given the following table:
id | value
---+---------
1 | 1
1 | 0
1 | 3
2 | 1
2 | 3
2 | 5
3 | 2
3 | 1
3 | 0
3 | 1
I want the following table:
id | value
---+---------
1 | 1
1 | 0
1 | 3
3 | 2
3 | 1
3 | 0
3 | 1
The table contains ids that have a minimum value of 0.
I have tried using exist and having but to no success.
try this :
select * from foo where id in (SELECT id FROM foo GROUP BY id HAVING MIN(value) = 0)
or that ( with window functions)
select * from
(select *,min(value) over (PARTITION BY id) min_by_id from foo) a
where min_by_id=0
If I'm understanding correctly, it's a fairly simple having clause:
=# SELECT id, MIN(value), MAX(value) FROM foo GROUP BY id HAVING MIN(value) = 0;
id | min | max
----+-----+-----
1 | 0 | 3
3 | 0 | 2
(2 rows)
Did I miss something that is making it more complicated?
It looks it is not possible to use window function in WHERE or HAVING. Below is solution based on JOINs.
JOIN every row with all rows of the same id.
Filter based on second set.
Show result from first set.
The SQL looks like this.
SELECT a.*
FROM a_table AS a
INNER JOIN a_table AS value ON a.id = b.id
WHERE b.value = 0;