PostgreSQL SELECT DISTINCT on Composite Primary key - postgresql

I have table with this structure:
Column | Type |
id | int |
version | int |
status_id | int | // can be 1 active, 2 suspended, 3 removed
update | Timestamp |
position | Geometry |
Indexes:
"PK_poi" PRIMARY KEY, btree (id, version)
So this is my table structure, basically something will happen at Location , i will create it, then something else will happen and I will update the event with new version.
So data will be like
id | version | status_id | update | position
1 | 1 | 1 | 2018-09-17 10:52:48 | x,y
2 | 1 | 1 | 2018-09-17 10:52:48 | x,y
2 | 2 | 1 | 2018-09-17 11:02:48 | x,y
2 | 3 | 2 | 2018-09-17 11:22:48 | x,y
1 | 2 | 2 | 2018-09-17 11:52:48 | x,y
2 | 4 | 1 | 2018-09-17 12:52:48 | x,y
1 | 3 | 3 | 2018-09-17 12:52:48 | x,y
2 | 5 | 3 | 2018-09-17 13:52:48 | x,y
3 | 1 | 1 | 2018-09-17 14:52:48 | x,y
3 | 2 | 1 | 2018-09-17 14:52:48 | x,y
4 | 1 | 1 | 2018-09-17 16:52:48 | x,y
4 | 2 | 1 | 2018-09-17 16:52:48 | x,y
So I am trying to make a distint select, that returns me the "latest" version within a specified time-interval, based on the timestamp. But only if the "latest" version is not with status - suspended or removed.
So if at 17:52 I query the DB and I say give me the latest events within the last hour, i would expect:
id | version | status_id | update | position
4 | 2 | 1 | 2018-09-17 16:52:48 | x,y
if I say however, give me the latest events from the last 24h, I would expect
id | version | status_id | update | position
3 | 2 | 1 | 2018-09-17 14:52:48 | x,y
4 | 2 | 1 | 2018-09-17 16:52:48 | x,y
I am very confused how to do this, because of the composite key. Can you please give pointers on what exactly should I read?
Thank you in advance

You need row_number to get the latest event for each location.
SELECT *
FROM ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY "update" DESC ) as rn
-- ^^^ create a group for each id
FROM yourTable
WHERE status_id = 1
-- optional if you want the events in a time range
AND "update" > current_timestamp - interval '1 day -- filter the last 24 h events
) as Q
-- optional if you want all events remove it.
WHERE rn = 1 -- filter the last one of each id because is order by update desc

Related

PostgreSQL 9.3:Updating table(order column) from another table, getting same values in rows

I need help with updating table from another table in Postgres Db.
Long story short we ended up with corrupted data in db, and now I need to update one table with values from another.
I have table with this data table wfc:
| step_id | command_id | commands_order |
|---------|------------|----------------|
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 1 | 4 | 3 |
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 2 | 3 | 1 |
| 2 | 3 | 1 |
| 2 | 4 | 3 |
and I want to update values in command_order column from another table, so I can have result like this:
| step_id | command_id | commands_order|
|---------|------------|---------------|
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 1 | 4 | 3 |
| 1 | 1 | 4 |
| 2 | 2 | 0 |
| 2 | 3 | 1 |
| 2 | 3 | 2 |
| 2 | 4 | 3 |
It was looking like easy task, but problem is to update rows for same command_id, it is writing same value in commands_order
SQL that I tried is:
UPDATE wfc
SET commands_order = CAST(sq.input_step_id as INTEGER)
FROM (
SELECT wfp.step_id, wfp.input_command_id, wfp.input_step_id
from wfp
order by wfp.step_id, wfp.input_step_id
) AS sq
WHERE (wfc.step_id=sq.step_id AND wfc.command_id=CAST(sq.input_command_id as INTEGER));
SQL Fiddle http://sqlfiddle.com/#!15/4efff4/4
I am pretty stuck with this, please help.
Thanks in advance.
Assuming you are trying to number the rows in the order in which they were created, and as long as you understand that ctid will chnage on update and with VACCUUM FULL, you can do the following:
select step_id, command_id, rank - 1 as command_order
from (select step_id, command_id, ctid as wfc_ctid, rank() over
(partition by step_id order by ctid)
from wfc) as wfc_ordered;
This will give you the wfc table with the ordering that you want. If you do update the original table, the ctids will change, so it's probably safer to create a copy of the table with the above query.

Postgresql. How to update column with range of integers from from 0 to last row that satisfies WHERE criteria

I have a next table sample, called userz:
+----+---------------+----------+
| id | sort_position | type |
+----+---------------+----------+
| 1 | -5 | admin |
| 2 | -3 | customer |
| 3 | 1 | customer |
| 4 | 8 | employee |
| 5 | 200 | customer |
+----+---------------+----------+
With Mysql If i want to make sort_position of all customer type to start from 0 and ++ until the last row that satisfies WHERE criteria, i can do next:
SET #i=-1;
UPDATE userz
SET sort_position=#i:=#i+1
WHERE type = "customer" ORDER BY sort_position;
and i would receive expected result:
+----+---------------+----------+
| id | sort_position | type |
+----+---------------+----------+
| 1 | -5 | admin |
| 2 | 0 | customer |
| 3 | 1 | customer |
| 4 | 8 | employee |
| 5 | 2 | customer |
+----+---------------+----------+
as you see all customers are now assigned with correct sort_position of 0,1,2
But since i'm working with postgre i need to reach same with it. What i tried so far:
DO $$
DECLARE
i integer := -1;
BEGIN
UPDATE userz
SET sort_position=#i:=#i+1
WHERE type = "customer" ORDER BY sort_position;
END $$;
and i have errors around =#i:=#i+1 , tried different formatting that i googled like =i:=i+1 but still no luck.
Try below SQL;
update userz k
set sort_position =
(select ROW_NUMBER() over(order by sort_position)-1 rnum
from userz src
where src.type ='customer'
and id = k.id)

Setting muliple rows in postgres based on the set values of previous postgres rows

I'm running postgres 9.4
I'm essentially updating an existing unorganized structure to a folder based organization. Im auto-assigning an order number to each item for user reordering, but doing an initial setting of all of these values with a 1 time use update statement. However, It seems like SET is taking my subquery's from clause and not recreating it for each successive row that it sets.
Here's my query example:
UPDATE folder_items
SET order_number =
(SELECT COALESCE(MAX(folder_items_2.order_number), 0) + 1
FROM folder_items AS folder_items_2
WHERE folder_items.parent_folder_id = folder_items_2.parent_folder_id
AND folder_items.folder_set_id = folder_items_2.folder_set_id
AND folder_items.id != folder_items_2.id);
With my initial table:
| folder_id | folder_set_id | order_number
row 1 | 1 | 1 | null
row 2 | 2 | 1 | null
row 3 | 3 | 2 | null
row 4 | 4 | 2 | null
row 5 | 5 | 2 | null
row 6 | 6 | 3 | null
when I run my query I get something like
| folder_id | folder_set_id | order_number
row 1 | 1 | 1 | 1
row 2 | 2 | 1 | 1
row 3 | 3 | 2 | 1
row 4 | 4 | 2 | 1
row 5 | 5 | 2 | 1
row 6 | 6 | 3 | 1
However, I want results that look like this:
| folder_id | folder_set_id | order_number
row 1 | 1 | 1 | 1
row 2 | 2 | 1 | 2
row 3 | 3 | 2 | 1
row 4 | 4 | 2 | 2
row 5 | 5 | 2 | 3
row 6 | 6 | 3 | 1
Is there a way to get these desired results? Is the best way to do some sort of window function that counts how many in the same folder_set_id are underneath each row?
Use ROW_NUMBER to calculate the ORDER_ID, then update the table.
with new_order as (
SELECT "folder_id",
row_number() over ( partition by "folder_set_id"
order by "folder_id") as rn
FROM Table1
)
UPDATE Table1 AS t
SET "order_number" = n.rn
FROM new_order AS n
WHERE t."folder_id" = n."folder_id";
SQL DEMO
OUTPUT
| row_id | folder_id | folder_set_id | order_number |
|--------|-----------|---------------|--------------|
| row 1 | 1 | 1 | 1 |
| row 2 | 2 | 1 | 2 |
| row 3 | 3 | 2 | 1 |
| row 4 | 4 | 2 | 2 |
| row 5 | 5 | 2 | 3 |
| row 6 | 6 | 3 | 1 |

How to set sequence number of sub-elements in TSQL unsing same element as parent?

I need to set a sequence inside T-SQL when in the first column I have sequence marker (which is repeating) and use other column for ordering.
It is hard to explain so I try with example.
This is what I need:
|------------|-------------|----------------|
| Group Col | Order Col | Desired Result |
|------------|-------------|----------------|
| D | 1 | NULL |
| A | 2 | 1 |
| C | 3 | 1 |
| E | 4 | 1 |
| A | 5 | 2 |
| B | 6 | 2 |
| C | 7 | 2 |
| A | 8 | 3 |
| F | 9 | 3 |
| T | 10 | 3 |
| A | 11 | 4 |
| Y | 12 | 4 |
|------------|-------------|----------------|
So my marker is A (each time I met A I must start new group inside my result). All rows before first A must be set to NULL.
I know that I can achieve that with loop but it would be slow solution and I need to update a lot of rows (may be sometimes several thousand).
Is there a way to achive this without loop?
You can use window version of COUNT to get the desired result:
SELECT [Group Col], [Order Col],
COUNT(CASE WHEN [Group Col] = 'A' THEN 1 END)
OVER
(ORDER BY [Order Col]) AS [Desired Result]
FROM mytable
If you need all rows before first A set to NULL then use SUM instead of COUNT.
Demo here

1th and 7th row in grouping

I have this table named Samples. The Date column values are just symbolic date values.
+----+------------+-------+------+
| Id | Product_Id | Price | Date |
+----+------------+-------+------+
| 1 | 1 | 100 | 1 |
| 2 | 2 | 100 | 2 |
| 3 | 3 | 100 | 3 |
| 4 | 1 | 100 | 4 |
| 5 | 2 | 100 | 5 |
| 6 | 3 | 100 | 6 |
...
+----+------------+-------+------+
I want to group by product_id such that I have the 1'th sample in descending date order and a new colomn added with the Price of the 7'th sample row in each product group. If the 7'th row does not exist, then the value should be null.
Example:
+----+------------+-------+------+----------+
| Id | Product_Id | Price | Date | 7thPrice |
+----+------------+-------+------+----------+
| 4 | 1 | 100 | 4 | 120 |
| 5 | 2 | 100 | 5 | 100 |
| 6 | 3 | 100 | 6 | NULL |
+----+------------+-------+------+----------+
I belive I can achieve the table without the '7thPrice' with the following
SELECT * FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Product_Id ORDER BY date DESC) r, * FROM Samples
) T WHERE T.r = 1
Any suggestions?
You can try something like this. I used your query to create a CTE. Then joined rank1 to rank7.
;with sampleCTE
as
(SELECT ROW_NUMBER() OVER (PARTITION BY Product_Id ORDER BY date DESC) r, * FROM Samples)
select *
from
(select * from samplecte where r = 1) a
left join
(select * from samplecte where r=7) b
on a.product_id = b.product_id