Incrementally count row numbers for distrinct rows in a join select - postgresql

I have a select that joins two tables, a and b, via a join table, ab.
select a.*, b.*
from a
left join ab on a.id = ab.aid
left join b on b.id = ab.bid;
And this produces
id | athing | id | bthing
----+----------+----+-----------
7 | athing x | 1 | bthing a
7 | athing x | 2 | bthing b
7 | athing x | 3 | bthing c
3 | athing y | 1 | bthing a
(4 rows)
I want a column that incrementally counts the number of rows in a. That is:
count | id | athing | id | bthing
-------+----+----------+----+-----------
1 | 7 | athing x | 1 | bthing a
1 | 7 | athing x | 2 | bthing b
1 | 7 | athing x | 3 | bthing c
2 | 3 | athing y | 1 | bthing a
(4 rows)
I have looked at using the window function row_number(), but that seems to count all the rows.
I want to incrementally count the distinct a rows, regardless of how many rows the joined table creates.
Is this possible in Postgresql? Thank you.

Use row_number() when selecting from the table a (note, the order of the rows in a is defined in over clause):
select a.*, b.*
from (
select row_number() over (order by id desc) as count, *
from a
) a
left join ab on a.id = ab.aid
left join b on b.id = ab.bid;
count | id | athing | id | bthing
-------+----+----------+----+----------
1 | 7 | athing x | 1 | bthing a
1 | 7 | athing x | 2 | bthing b
1 | 7 | athing x | 3 | bthing c
2 | 3 | athing y | 1 | bthing a
(4 rows)
or dense_rank() on the result dataset.
select
dense_rank() over (order by a.id desc) as count,
a.*, b.*
from a
left join ab on a.id = ab.aid
left join b on b.id = ab.bid;
Read about window functions.

Related

Get dummy columns from different tables

I have three different tables that look like that:
Table 1
| id | city|
|----|-----|
| 1 | A |
| 1 | B |
| 2 | C |
Table 2
| id | city|
|----|-----|
| 2 | B |
| 1 | B |
| 3 | C |
Table 3
| id | city|
|----|-----|
| 1 | A |
| 1 | B |
| 2 | A |
I need to create one column for each table, and the dummies values if it's present.
| id | city| is_tbl_1 | is_tbl_2 | is_tbl_3 |
|----|-----|-----------|-------------|------------|
| 1 | A | 1 | 0 | 1 |
| 1 | B | 1 | 1 | 1 |
| 2 | A | 0 | 0 | 1 |
| 2 | C | 1 | 0 | 0 |
| 2 | B | 0 | 1 | 0 |
| 3 | C | 0 | 1 | 0 |
I have tried to add the columns is_tbl# myself on three different selects, UNION all the three tables and group, but it looks ugly, is there a better way to do it?
You can outer-join the 3 tables on id and city, then group by the id and city, and finally count the number of non-null values of the city columns :
SELECT
COALESCE (t1.id, t2.id, t3.id) AS id
, COALESCE (t1.city, t2.city, t3.city) AS city
, count(*) FILTER (WHERE t1.city IS NOT NULL) AS is_tbl_1
, count(*) FILTER (WHERE t2.city IS NOT NULL) AS is_tbl_2
, count(*) FILTER (WHERE t3.city IS NOT NULL) AS is_tbl_3
FROM
t1 AS t1
FULL OUTER JOIN
t2 AS t2 ON t1.id = t2.id AND t1.city = t2.city
FULL OUTER JOIN
t3 AS t3 ON t1.id = t3.id AND t1.city = t3.city
GROUP BY
1,2
ORDER BY
1,2

Reverse column values after grouping

I have a table as follows
id | group | value
------+-------------+----------
1 | 1 | 2
2 | 1 | 4
3 | 1 | 3
4 | 2 | 2
5 | 2 | 9
6 | 2 | 5
I want to group the rows by 'group' with the order of 'id' and create a new column that reverses the 'value' column as follows
id | group | value | reversedvalue
------+-------------+---------+---------
1 | 1 | 2 | 3
2 | 1 | 4 | 4
3 | 1 | 3 | 2
4 | 2 | 2 | 5
5 | 2 | 9 | 9
6 | 2 | 5 | 2
Try the following:
SELECT q1.id,q1.group_id,q1.value,q2.value
FROM
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id) n
FROM your_table
) q1
JOIN
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id DESC) n
FROM your_table
) q2
ON q1.group_id=q2.group_id AND q1.n=q2.n
You also can use CTE:
WITH cte AS(
SELECT *,
ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id) n1,
ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id DESC) n2
FROM your_table
)
SELECT q1.id,q1.group_id,q1.value,q2.value
FROM
cte q1
JOIN
cte q2
ON q1.group_id=q2.group_id AND q1.n1=q2.n2;

Select row by id and it's nearest rows sorted by some value. PostgreSQL

I have chapters table like this:
id | title | sort_number | book_id
1 | 'Chap 1' | 3 | 1
5 | 'Chap 2' | 6 | 1
8 | 'About ' | 1 | 1
9 | 'Chap 3' | 9 | 1
10 | 'Attack' | 1 | 2
Id is unique, sort_number is unique for same book(book_id)
1)How can load all data (3 rows) for 3 chapters (current, next and prev) sorted by sort_number if i have only current chapter id?
2)How can i load current chapter data (1 row) and only id's of next, prev if they exist?
This can be done using window functions
select id, title, sort_number, book_id,
lag(id) over w as prev_chapter,
lead(id) over w as next_chapter
from chapters
window w as (partition by book_id order by sort_number);
With your sample data that returns:
id | title | sort_number | book_id | prev_chapter | next_chapter
---+--------+-------------+---------+--------------+-------------
8 | About | 1 | 1 | | 1
1 | Chap 1 | 3 | 1 | 8 | 5
5 | Chap 2 | 6 | 1 | 1 | 9
9 | Chap 3 | 9 | 1 | 5 |
10 | Attack | 1 | 2 | |
The above query can now be used to answer both your questions:
1)
select id, title, sort_number, book_id
from (
select id, title, sort_number, book_id,
--first_value(id) over w as first_chapter,
lag(id) over w as prev_chapter_id,
lead(id) over w as next_chapter_id
from chapters
window w as (partition by book_id order by sort_number)
) t
where 1 in (id, prev_chapter_id, next_chapter_id)
2)
select *
from (
select id, title, sort_number, book_id,
lag(id) over w as prev_chapter_id,
lead(id) over w as next_chapter_id
from chapters
window w as (partition by book_id order by sort_number)
) t
where id = 1

How to use join with aggregate function in postgresql?

I have 4 tables
Table1
id | name
1 | A
2 | B
Table2
id | name1
1 | C
2 | D
Table3
id | name2
1 | E
2 | F
Table4
id | name1_id | name2_id | name3_id
1 | 1 | 2 | 1
2 | 2 | 2 | 2
3 | 1 | 2 | 1
4 | 2 | 1 | 1
5 | 1 | 1 | 2
6 | 2 | 2 | 1
7 | 1 | 1 | 2
8 | 2 | 1 | 1
9 | 1 | 2 | 1
10 | 2 | 2 | 1
Now I want to join all tables with 4 and get this type of output
name | count
{A,B} | {5, 5}
{C,D} | {5, 6}
{E,F} | {7, 3}
I tried this
select array_agg(distinct(t1.name)), array_agg(distinct(temp.test))
from
(select t4.name1_id, (count(t4.name1_id)) "test"
from table4 t4 group by t4.name1_id
) temp
join table1 t1
on temp.name1_id = t1.id
I am trying to achieve this. Anybody can help me.
Calculate the counts for every table separately and union the results:
select
array_agg(name order by name) as name,
array_agg(count order by name) as count
from (
select 1 as t, name, count(*)
from table4
join table1 t1 on t1.id = name1_id
group by name
union all
select 2 as t, name, count(*)
from table4
join table2 t2 on t2.id = name2_id
group by name
union all
select 3 as t, name, count(*)
from table4
join table3 t3 on t3.id = name3_id
group by name
) s
group by t;
name | count
-------+-------
{A,B} | {5,5}
{C,D} | {4,6}
{E,F} | {7,3}
(3 rows)

Ranking rows according some number

I have table like this
id | name
----------
1 | A
2 | B
5 | C
100 | D
200 | E
201 | F
202 | G
I need ranking rows from 1 to 3 order by id, that is, I need result:
id | name | ranking
---------------------------
1 | A | 1
2 | B | 2
5 | C | 3
100 | D | 1
200 | E | 2
201 | F | 3
202 | G | 1
How to make this?
P.S.
I am trying:
SELECT id, name, row_number() OVER( order by id RANGE BETWEEN 1 AND 3 ) AS ranking FROM t
This gives syntax error.
RANGE is actually used for something else:
http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS
http://www.postgresql.org/docs/current/static/sql-select.html
Try using a modulus instead:
SELECT id, name, 1 + (row_number() OVER( order by id ) - 1) % 3 AS ranking
FROM t