I have a below table.
cid
oid
order_date
1
12
2020-07-01 13:19:16.235
1
12
2020-07-01 13:19:21.549
1
23
2020-07-27 13:00:18.446
1
34
2021-08-17 09:42:20.778
1
55
2022-08-01 13:37:53.340
1
55
2022-08-01 13:38:07.564
1
55
2022-08-01 13:38:28.201
1
09
2022-08-03 10:32:24.202
I tried the below query.
select
cid,
oid,
dense_rank() over (partition by oid order by order_date) as oid_history
from
master.t1
where
cid = 1
order by
order_date asc;
Got the below output.
cid
oid
order_date
oid_history
1
12
2020-07-01 13:19:16.235
1
1
12
2020-07-01 13:19:21.549
2
1
23
2020-07-27 13:00:18.446
1
1
34
2021-08-17 09:42:20.778
1
1
55
2022-08-01 13:37:53.340
1
1
55
2022-08-01 13:38:07.564
2
1
55
2022-08-01 13:38:28.201
3
1
09
2022-08-03 10:32:24.202
1
Expected output.
cid
oid
order_date
oid_history
1
12
2020-07-01 13:19:16.235
1
1
12
2020-07-01 13:19:21.549
1
1
23
2020-07-27 13:00:18.446
2
1
34
2021-08-17 09:42:20.778
3
1
55
2022-08-01 13:37:53.340
4
1
55
2022-08-01 13:38:07.564
4
1
55
2022-08-01 13:38:28.201
4
1
09
2022-08-03 10:32:24.202
5
Thank you:)
Can you try this one?
select
cid,
oid,
order_date,
dense_rank() over (partition by cid order by oid) as oid_history
from
mytable -- master.t1
where
cid = 1
order by
order_date asc;
+-----+-----+-------------------------+-------------+
| CID | OID | ORDER_DATE | OID_HISTORY |
+-----+-----+-------------------------+-------------+
| 1 | 12 | 2020-07-01 13:19:16.235 | 1 |
| 1 | 12 | 2020-07-01 13:19:21.549 | 1 |
| 1 | 23 | 2020-07-27 13:00:18.446 | 2 |
| 1 | 34 | 2021-08-17 09:42:20.778 | 3 |
| 1 | 55 | 2022-08-01 13:37:53.340 | 4 |
| 1 | 55 | 2022-08-01 13:38:07.564 | 4 |
| 1 | 55 | 2022-08-01 13:38:28.201 | 4 |
+-----+-----+-------------------------+-------------+
Based on your new question, here is the answer:
select
cid,
oid,
order_date,
CONDITIONAL_CHANGE_EVENT( oid ) over (partition by cid order by ORDER_DATE ) + 1 as oid_history
from
mytable -- master.t1
where
cid = 1
order by oid_history;
+-----+-----+-------------------------+-------------+
| CID | OID | ORDER_DATE | OID_HISTORY |
+-----+-----+-------------------------+-------------+
| 1 | 12 | 2020-07-01 13:19:16.235 | 1 |
| 1 | 12 | 2020-07-01 13:19:21.549 | 1 |
| 1 | 23 | 2020-07-27 13:00:18.446 | 2 |
| 1 | 34 | 2021-08-17 09:42:20.778 | 3 |
| 1 | 55 | 2022-08-01 13:37:53.340 | 4 |
| 1 | 55 | 2022-08-01 13:38:07.564 | 4 |
| 1 | 55 | 2022-08-01 13:38:28.201 | 4 |
| 1 | 09 | 2022-08-03 10:32:24.202 | 5 |
+-----+-----+-------------------------+-------------+
I didn't want to update my answer (my comment explains the reason) but Pankaj already answered, so I also had to share my answer. Now, I'm waiting for another hidden requirement to modify my answer.
From the expected output, it looks like a use-case for conditional_change_event.
with data (cid, oid, order_date) as (
select * from values
(1,12,'2020-07-01 13:19:16.235'::date),
(1,12,'2020-07-01 13:19:21.549'::date),
(1,23,'2020-07-27 13:00:18.446'::date),
(1,34,'2021-08-17 09:42:20.778'::date),
(1,55,'2022-08-01 13:37:53.340'::date),
(1,55,'2022-08-01 13:38:07.564'::date),
(1,55,'2022-08-01 13:38:28.201'::date),
(1,09,'2022-08-03 10:32:24.202'::date)
)select *,
1+conditional_change_event (oid) over (order by cid) as oid_history
from data;
CID
OID
ORDER_DATE
OID_HISTORY
1
12
2020-07-01
1
1
12
2020-07-01
1
1
23
2020-07-27
2
1
34
2021-08-17
3
1
55
2022-08-01
4
1
55
2022-08-01
4
1
55
2022-08-01
4
1
9
2022-08-03
5
Related
Hi I have a a table as :
date_key
month
customer_id
2022-01-01
1
1
2022-01-23
1
1
2022-02-02
2
1
2022-02-15
2
1
2022-02-16
2
1
2022-02-18
2
1
2022-02-16
2
1
2022-05-18
5
1
2022-06-11
6
1
2022-06-12
6
1
2022-06-13
6
1
2022-06-15
6
1
and want to lag on last previous month above
date_key
month
customer_id
lastMonth
2022-01-01
1
1
2022-01-23
1
1
2022-02-02
2
1
1
2022-02-15
2
1
1
2022-02-16
2
1
1
2022-02-18
2
1
1
2022-02-16
2
1
1
2022-05-18
5
1
2
2022-06-11
6
1
5
2022-06-12
6
1
5
2022-06-13
6
1
5
2022-06-15
6
1
5
I tried using
select *
lag(month,1) over(partition by customer_id order by month) lastMonth
from table
However this does not seem to get the result as needed.
Please do help.
Try this one:
SELECT *,
LAST_VALUE(month) OVER (
PARTITION BY customer_id
ORDER BY EXTRACT(YEAR FROM date_key) * 12 + month
RANGE BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS lastMonth
FROM sample_table
ORDER BY date_key;
Query results:
+-----+------------+-------+-------------+-----------+
| Row | date_key | month | customer_id | lastMonth |
+-----+------------+-------+-------------+-----------+
| 1 | 2022-01-01 | 1 | 1 | null |
| 2 | 2022-01-23 | 1 | 1 | null |
| 3 | 2022-02-02 | 2 | 1 | 1 |
| 4 | 2022-02-15 | 2 | 1 | 1 |
| 5 | 2022-02-16 | 2 | 1 | 1 |
| 6 | 2022-02-16 | 2 | 1 | 1 |
| 7 | 2022-02-18 | 2 | 1 | 1 |
| 8 | 2022-05-18 | 5 | 1 | 2 |
| 9 | 2022-06-11 | 6 | 1 | 5 |
| 10 | 2022-06-12 | 6 | 1 | 5 |
| 11 | 2022-06-13 | 6 | 1 | 5 |
| 12 | 2022-06-15 | 6 | 1 | 5 |
+-----+------------+-------+-------------+-----------+
base_table
month id sales cumulative_sales
2021-01-01 33205 10 10
2021-02-01 33205 15 25
Based on the base table above, I would like to add more rows up to the current month,
even if there is no sales.
Expected table
month id sales cumulative_sales
2021-01-01 33205 10 10
2021-02-01 33205 15 25
2021-03-01 33205 0 25
2021-04-01 33205 0 25
2021-05-01 33205 0 25
.........
2021-11-01 33205 0 25
My query stops at
select month, id, sales,
sum(sales) over (partition by id
order by month
rows between unbounded preceding and current row) as cumulative_sales
from base_table
This works. Assumes the month column is constrained to hold only "first of the month" dates. Use the desired hard-coded start date, or use another CTE to get the earliest date from base_table:
with base_table as (
select *
from (values
('2021-01-01'::date,33205,10)
,('2021-02-01' ,33205,15)
,('2021-01-01' ,12345,99)
,('2021-04-01' ,12345,88)
) dat("month",id,sales)
)
select cal.dt::date
,list.id
,coalesce(dat.sales,0) as sales
,coalesce(sum(dat.sales) over (partition by list.id order by cal.dt),0) as cumulative_sales
from generate_series('2020-06-01' /* use desired start date here */,current_date,'1 month') cal(dt)
cross join (select distinct id from base_table) list
left join base_table dat on dat."month" = cal.dt and dat.id = list.id
;
Results:
| dt | id | sales | cumulative_sales |
+------------+-------+-------+------------------+
| 2020-06-01 | 12345 | 0 | 0 |
| 2020-07-01 | 12345 | 0 | 0 |
| 2020-08-01 | 12345 | 0 | 0 |
| 2020-09-01 | 12345 | 0 | 0 |
| 2020-10-01 | 12345 | 0 | 0 |
| 2020-11-01 | 12345 | 0 | 0 |
| 2020-12-01 | 12345 | 0 | 0 |
| 2021-01-01 | 12345 | 99 | 99 |
| 2021-02-01 | 12345 | 0 | 99 |
| 2021-03-01 | 12345 | 0 | 99 |
| 2021-04-01 | 12345 | 88 | 187 |
| 2021-05-01 | 12345 | 0 | 187 |
| 2021-06-01 | 12345 | 0 | 187 |
| 2021-07-01 | 12345 | 0 | 187 |
| 2021-08-01 | 12345 | 0 | 187 |
| 2021-09-01 | 12345 | 0 | 187 |
| 2021-10-01 | 12345 | 0 | 187 |
| 2021-11-01 | 12345 | 0 | 187 |
| 2020-06-01 | 33205 | 0 | 0 |
| 2020-07-01 | 33205 | 0 | 0 |
| 2020-08-01 | 33205 | 0 | 0 |
| 2020-09-01 | 33205 | 0 | 0 |
| 2020-10-01 | 33205 | 0 | 0 |
| 2020-11-01 | 33205 | 0 | 0 |
| 2020-12-01 | 33205 | 0 | 0 |
| 2021-01-01 | 33205 | 10 | 10 |
| 2021-02-01 | 33205 | 15 | 25 |
| 2021-03-01 | 33205 | 0 | 25 |
| 2021-04-01 | 33205 | 0 | 25 |
| 2021-05-01 | 33205 | 0 | 25 |
| 2021-06-01 | 33205 | 0 | 25 |
| 2021-07-01 | 33205 | 0 | 25 |
| 2021-08-01 | 33205 | 0 | 25 |
| 2021-09-01 | 33205 | 0 | 25 |
| 2021-10-01 | 33205 | 0 | 25 |
| 2021-11-01 | 33205 | 0 | 25 |
The cross join pairs every date output by generate_series() with every id value from base_table.
The left join ensures that no dt+id pairs get dropped from the output when no such record exists in base_table.
The coalesce() functions ensure that the sales and cumulative_sales show 0 instead of null for dt+id combinations that don't exist in base_table. Remove them if you don't mind seeing nulls in those columns.
I have the following table (known as table1):
row_id session_id date_end user_id item_id
---------------------------------------------------
3962 5958255 2017-11-07 3249480 1
4553 5959689 2017-11-07 3249484 1
4554 5959689 2017-11-07 3249484 1
8775 5968439 2017-11-08 3249492 4
6706 5965190 2017-11-08 3249492 2
6779 5965280 2017-11-08 3249492 3
6778 5965280 2017-11-08 3249492 3
8774 5968439 2017-11-08 3249492 4
6685 5965159 2017-11-08 3249502 1
5314 5962257 2017-11-07 3249504 1
5315 5962257 2017-11-07 3249504 1
13564 5982665 2017-11-09 3249510 1
13565 5982665 2017-11-09 3249510 1
238 5941818 2017-11-06 3249540 1
8078 5967039 2017-11-08 3249540 3
13981 5984747 2017-11-09 3249540 4
127080 6267047 2017-11-30 3249540 10
When querying this database I need 3 new columns:
The count of items that are bought by each user
The count of items that are bought that contain same item_id as current row
The count of items that are bought that contain different item_id as that in the current row
However, I need all of these counts to be made with respect to a 30-day period. For example, the row for user_id 3249492 should read:
row_id session_id date_end user_id item_id total same diff
8775 5968439 2017-11-08 3249492 4 5 1 3
6706 5965190 2017-11-08 3249492 2 4 0 3
6779 5965280 2017-11-08 3249492 3 3 1 1
6778 5965280 2017-11-08 3249492 3 2 0 1
8774 5968439 2017-11-08 3249492 4 1 0 0
I have the following:
SELECT row_id, session_id, date_end, user_id, item_id,
COUNT(item_id) OVER (PARTITION BY user_id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as total,
COUNT(item_id) OVER (PARTITION BY user_id, item_id ORDER BY item_id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as same
FROM table1
Which yields the correct values for total and same but does not take into account the 30-day window. Also, I have no idea where to start with the diff column.
SQL Fiddle: http://sqlfiddle.com/#!17/ac833/2
This PostgreSQL 9.6
Any help would be greatly appreciated.
30 day running count
Instead of using a window function we can use a self join to get the 30 day running count.
WITH thirty_days_window AS (
SELECT table1.row_id, table1.item_id, "window".item_id AS other_item_id
FROM table1 join table1 AS "window" ON "window".user_id = table1.user_id AND
"window".date_end BETWEEN table1.date_end - interval '30 days' AND table1.date_end AND
"window".row_id <= table1.row_id
),
counts AS (
SELECT row_id,
COUNT(*) AS total,
COUNT(CASE WHEN item_id = other_item_id THEN 1 END) - 1 AS same,
COUNT(CASE WHEN item_id != other_item_id THEN 1 END) AS diff
FROM thirty_days_window GROUP BY row_id)
SELECT table1.row_id, session_id, date_end, user_id, table1.item_id,
total, same, diff
FROM table1 JOIN counts ON counts.row_id = table1.row_id
ORDER BY row_id;
The first part thirty_days_window creates the window by joining every row with all rows with the same user_id happening in a window of 30 days. We also assume that we only want rows with a lower row_id then the current.
Next we count the rows. same counts only rows where the item_id is the same as the item_id of the joined row (subtracting 1 to remove the original row), diff does exactly the opposite, get all rows where item_id is different from the joined row.
Finally we join back to the original table to add the session_id user_id and date_end.
The final result using the data in the fiddle:
row_id | session_id | date_end | user_id | item_id | total | same | diff
--------+------------+------------+---------+---------+-------+------+------
6706 | 5965190 | 2017-11-08 | 3249492 | 151 | 1 | 0 | 0
6778 | 5965280 | 2017-11-08 | 3249492 | 151 | 2 | 1 | 0
6779 | 5965280 | 2017-11-08 | 3249492 | 158 | 3 | 0 | 2
8774 | 5968439 | 2017-11-08 | 3249492 | 151 | 4 | 2 | 1
8775 | 5968439 | 2017-11-08 | 3249492 | 158 | 5 | 1 | 3
47046 | 6063745 | 2017-11-15 | 3263305 | 157 | 1 | 0 | 0
47047 | 6063745 | 2017-11-15 | 3263305 | 158 | 2 | 0 | 1
59887 | 6094293 | 2017-11-16 | 3263305 | 157 | 3 | 1 | 1
59888 | 6094294 | 2017-11-16 | 3263305 | 157 | 4 | 2 | 1
60343 | 6095456 | 2017-11-16 | 3263305 | 157 | 5 | 3 | 1
60344 | 6095457 | 2017-11-16 | 3263305 | 157 | 6 | 4 | 1
69112 | 6116357 | 2017-11-17 | 3263305 | 157 | 7 | 5 | 1
71085 | 6119700 | 2017-11-18 | 3263305 | 157 | 8 | 6 | 1
71508 | 6120421 | 2017-11-18 | 3250078 | 157 | 1 | 0 | 0
71509 | 6120421 | 2017-11-18 | 3250078 | 152 | 2 | 0 | 1
71510 | 6120421 | 2017-11-18 | 3250078 | 156 | 3 | 0 | 2
71511 | 6120421 | 2017-11-18 | 3250078 | 154 | 4 | 0 | 3
71512 | 6120421 | 2017-11-18 | 3250078 | 151 | 5 | 0 | 4
71513 | 6120421 | 2017-11-18 | 3250078 | 158 | 6 | 0 | 5
72242 | 6121399 | 2017-11-18 | 3263305 | 157 | 9 | 7 | 1
75696 | 6126280 | 2017-11-19 | 3263305 | 157 | 10 | 8 | 1
76082 | 6126777 | 2017-11-19 | 3263305 | 157 | 11 | 9 | 1
77546 | 6129039 | 2017-11-19 | 3263305 | 157 | 12 | 10 | 1
83754 | 6143858 | 2017-11-20 | 3263305 | 157 | 13 | 11 | 1
91331 | 6167552 | 2017-11-22 | 3263305 | 157 | 14 | 12 | 1
92431 | 6171560 | 2017-11-22 | 3263305 | 157 | 15 | 13 | 1
95073 | 6177870 | 2017-11-23 | 3263305 | 157 | 16 | 14 | 1
95302 | 6178780 | 2017-11-23 | 3263305 | 157 | 17 | 15 | 1
287471 | 7164221 | 2018-02-10 | 4516965 | 154 | 1 | 0 | 0
288750 | 7170955 | 2018-02-11 | 4516965 | 158 | 2 | 0 | 1
288751 | 7170955 | 2018-02-11 | 4516965 | 151 | 3 | 0 | 2
(31 rows)
Edit
After thinking about this for a bit, it's possible to do the query in one select:
SELECT table1.row_id, MIN(table1.session_id),
MIN(table1.date_end), MIN(table1.user_id), MIN(table1.item_id),
COUNT(*) as total,
COUNT(CASE WHEN table1.item_id = windw.item_id THEN 1 END) - 1 AS same,
COUNT(CASE WHEN table1.item_id != windw.item_id THEN 1 END)
FROM table1 JOIN table1 AS windw ON windw.user_id = table1.user_id AND
windw.date_end BETWEEN table1.date_end - INTERVAL '30 days' AND table1.date_end AND
windw.row_id <= table1.row_id
GROUP BY table1.row_id ORDER BY table1.row_id;
I have two tables. The first inv containing records of invoices, the second containing payments. I want to match the payments in the inv table by inv_amount and inv_date. There might be more than one invoice with the same amount on the same day and also more than one payment of the same amount on the same day.
The payment should be matched with the first matching invoice and every payment must only be matched once.
This is my data:
Table inv
inv_id | inv_amount | inv_date | inv_number
--------+------------+------------+------------
1 | 10 | 2018-01-01 | 1
2 | 16 | 2018-01-01 | 1
3 | 12 | 2018-02-02 | 2
4 | 14 | 2018-02-03 | 3
5 | 19 | 2018-02-04 | 3
6 | 19 | 2018-02-04 | 5
7 | 5 | 2018-02-04 | 6
8 | 40 | 2018-02-04 | 7
9 | 19 | 2018-02-04 | 8
10 | 19 | 2018-02-05 | 9
11 | 20 | 2018-02-05 | 10
12 | 20 | 2018-02-07 | 11
Table pay
pay_id | pay_amount | pay_date
--------+------------+------------
1 | 10 | 2018-01-01
2 | 12 | 2018-02-02
4 | 19 | 2018-02-04
3 | 14 | 2018-02-03
5 | 5 | 2018-02-04
6 | 19 | 2018-02-04
7 | 19 | 2018-02-05
8 | 20 | 2018-02-07
My Query:
SELECT DISTINCT ON (inv.inv_id) inv.inv_id,
inv.inv_amount,
inv.inv_date,
inv.inv_number,
pay.pay_id
FROM ("2016".pay
RIGHT JOIN "2016".inv ON (((pay.pay_amount = inv.inv_amount) AND (pay.pay_date = inv.inv_date))))
ORDER BY inv.inv_id
resulting in:
inv_id | inv_amount | inv_date | inv_number | pay_id
--------+------------+------------+------------+--------
1 | 10 | 2018-01-01 | 1 | 1
2 | 16 | 2018-01-01 | 1 |
3 | 12 | 2018-02-02 | 2 | 2
4 | 14 | 2018-02-03 | 3 | 3
5 | 19 | 2018-02-04 | 3 | 4
6 | 19 | 2018-02-04 | 5 | 4
7 | 5 | 2018-02-04 | 6 | 5
8 | 40 | 2018-02-04 | 7 |
9 | 19 | 2018-02-04 | 8 | 6
10 | 19 | 2018-02-05 | 9 | 7
11 | 20 | 2018-02-05 | 10 |
12 | 20 | 2018-02-07 | 11 | 8
The record inv_id = 6 should not match with pay_id = 4 for it would mean that payment 4 was inserted twice
Desired result:
inv_id | inv_amount | inv_date | inv_number | pay_id
--------+------------+------------+------------+--------
1 | 10 | 2018-01-01 | 1 | 1
2 | 16 | 2018-01-01 | 1 |
3 | 12 | 2018-02-02 | 2 | 2
4 | 14 | 2018-02-03 | 3 | 3
5 | 19 | 2018-02-04 | 3 | 4
6 | 19 | 2018-02-04 | 5 | <- should be empty**
7 | 5 | 2018-02-04 | 6 | 5
8 | 40 | 2018-02-04 | 7 |
9 | 19 | 2018-02-04 | 8 | 6
10 | 19 | 2018-02-05 | 9 | 7
11 | 20 | 2018-02-05 | 10 |
12 | 20 | 2018-02-07 | 11 | 8
Disclaimer: Yes I asked that question yesterday with the original data but someone pointed out that my sql was very hard to read. I, therefore, tried to create a cleaner representation of my problem.
For convenience, here's an SQL Fiddle to test: http://sqlfiddle.com/#!17/018d7/1
After seeing the example I think I've got the query for you:
WITH payments_cte AS (
SELECT
payment_id,
payment_amount,
payment_date,
ROW_NUMBER() OVER (PARTITION BY payment_amount, payment_date ORDER BY payment_id) AS payment_row
FROM payments
), invoices_cte AS (
SELECT
invoice_id,
invoice_amount,
invoice_date,
invoice_number,
ROW_NUMBER() OVER (PARTITION BY invoice_amount, invoice_date ORDER BY invoice_id) AS invoice_row
FROM invoices
)
SELECT invoice_id, invoice_amount, invoice_date, invoice_number, payment_id
FROM invoices_cte
LEFT JOIN payments_cte
ON payment_amount = invoice_amount
AND payment_date = invoice_date
AND payment_row = invoice_row
ORDER BY invoice_id, payment_id
i have 2 tables tab1 and tab2, tab2(tab1_id) references tab1(id)
tab2 has different values for the tab1(id)
i need a join which will join tab1 with action column from tab2,
and latest value for the id.
tab1 :-
id | user_file_id | created_date | modified_date
----+--------------+---------------------+---------------------
2 | 102 | 2012-01-12 01:23:46 | 2012-03-04 16:52:28
4 | 104 | 2012-01-12 15:45:10 | 2012-01-15 02:23:40
6 | 106 | 2012-01-18 00:14:34 | 2012-01-24 20:17:49
7 | 107 | 2012-02-02 01:07:14 | 2012-04-17 09:29:17
8 | 108 | 2012-02-15 13:16:24 | 2012-03-26 10:30:51
9 | 109 | 2012-02-20 18:08:48 | 2012-04-09 06:14:58
10 | 110 | 2012-02-24 20:49:10 | 2012-03-23 11:36:41
11 | 111 | 2012-03-05 22:38:14 | 2012-03-16 04:29:35
(8 rows)
tab2:-
id | action | tab1_id
----+--------+---------
1 | 1 | 2
3 | 2 | 2
4 | 1 | 2
5 | 2 | 2
6 | 1 | 2
7 | 3 | 2
2 | 1 | 4
8 | 1 | 6
9 | 1 | 7
10 | 1 | 8
11 | 1 | 9
12 | 1 | 10
13 | 1 | 11
(13 rows)
the both tab1 and tab2 joined to get the output as :-
id | user_file_id | created_date | modified_date | action
----+--------------+---------------------+---------------------+--------
2 | 102 | 2012-01-12 01:23:46 | 2012-03-04 16:52:28 | 3
4 | 104 | 2012-01-12 15:45:10 | 2012-01-15 02:23:40 | 1
6 | 106 | 2012-01-18 00:14:34 | 2012-01-24 20:17:49 | 1
7 | 107 | 2012-02-02 01:07:14 | 2012-04-17 09:29:17 | 1
8 | 108 | 2012-02-15 13:16:24 | 2012-03-26 10:30:51 | 1
9 | 109 | 2012-02-20 18:08:48 | 2012-04-09 06:14:58 | 1
10 | 110 | 2012-02-24 20:49:10 | 2012-03-23 11:36:41 | 1
11 | 111 | 2012-03-05 22:38:14 | 2012-03-16 04:29:35 | 1
(8 rows)
Try:
select t1.*, t2.action
from tab1 t1
join (select t.*,
row_number() over (partition by tab1_id order by id desc) rn
from tab2 t) t2
on t1.id = t2.tab1_id and t2.rn = 1
Change the join to a left join if you want to allow for a row on tab1 having no actions recorded on tab2.
SELECT tab1.*, t2.action
FROM tab1
JOIN (
SELECT DISTINCT ON (tab1_id) tab1_id
, first_value(action) OVER (PARTITION BY tab1_id
ORDER BY id DESC) AS action
FROM tab2
) t2 ON tab1.id = t2.tab1_id
#Mark already mentioned the alternative LEFT JOIN.