Create primary index for primary key in postgres with desc ordering

I have a table with time-series data and with a primary key – actually several large tables but this will do as an example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>postgres=# create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated));
CREATE TABLE
postgres=# d foo
Table "public.foo"
Column | Type | Collation | Nullable | Default
---------+--------------------------+-----------+----------+---------
x | character varying | | not null |
y | character varying | | not null |
z | text | | |
updated | timestamp with time zone | | not null |
Indexes:
</code>
<code>postgres=# create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated)); CREATE TABLE postgres=# d foo Table "public.foo" Column | Type | Collation | Nullable | Default ---------+--------------------------+-----------+----------+--------- x | character varying | | not null | y | character varying | | not null | z | text | | | updated | timestamp with time zone | | not null | Indexes: </code>
postgres=# create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated));
CREATE TABLE
postgres=# d foo
                         Table "public.foo"
 Column  |           Type           | Collation | Nullable | Default 
---------+--------------------------+-----------+----------+---------
 x       | character varying        |           | not null | 
 y       | character varying        |           | not null | 
 z       | text                     |           |          | 
 updated | timestamp with time zone |           | not null | 
Indexes:

This will automatically create an index on x, updated. However, many of my queries are looking for “the most recent foo for various values of x” not the oldest so the index is the wrong way round for efficient querying.

For example, If I do:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>explain analyze select distinct on (x,y,updated) * from foo order by x,y,updated;
</code>
<code>explain analyze select distinct on (x,y,updated) * from foo order by x,y,updated; </code>
explain analyze select distinct on (x,y,updated) * from foo order by x,y,updated;

Then it uses the index nicely:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>----------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=0.43..145739.21 rows=158334 width=38) (actual time=0.031..889.771 rows=1583344 loops=1)
-> Index Scan using foo_pkey on foo (cost=0.43..133864.13 rows=1583344 width=38) (actual time=0.030..534.995 rows=1583344 loops=1)
Planning Time: 0.074 ms
Execution Time: 958.419 ms
(4 rows)
</code>
<code>---------------------------------------------------------------------------------------------------------------------------------------- Unique (cost=0.43..145739.21 rows=158334 width=38) (actual time=0.031..889.771 rows=1583344 loops=1) -> Index Scan using foo_pkey on foo (cost=0.43..133864.13 rows=1583344 width=38) (actual time=0.030..534.995 rows=1583344 loops=1) Planning Time: 0.074 ms Execution Time: 958.419 ms (4 rows) </code>
----------------------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=0.43..145739.21 rows=158334 width=38) (actual time=0.031..889.771 rows=1583344 loops=1)
   ->  Index Scan using foo_pkey on foo  (cost=0.43..133864.13 rows=1583344 width=38) (actual time=0.030..534.995 rows=1583344 loops=1)
 Planning Time: 0.074 ms
 Execution Time: 958.419 ms
(4 rows)

but of course shows me the oldest elements.

If I do:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>userdb=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Unique (cost=278945.67..294779.11 rows=158334 width=38) (actual time=3823.677..4808.844 rows=1583344 loops=1)
-> Sort (cost=278945.67..282904.03 rows=1583344 width=38) (actual time=3823.674..4482.405 rows=1583344 loops=1)
Sort Key: x, y, updated DESC
Sort Method: external merge Disk: 79536kB
-> Seq Scan on foo (cost=0.00..29314.44 rows=1583344 width=38) (actual time=0.013..142.781 rows=1583344 loops=1)
Planning Time: 0.075 ms
Execution Time: 4888.878 ms
(7 rows)
</code>
<code>userdb=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------- Unique (cost=278945.67..294779.11 rows=158334 width=38) (actual time=3823.677..4808.844 rows=1583344 loops=1) -> Sort (cost=278945.67..282904.03 rows=1583344 width=38) (actual time=3823.674..4482.405 rows=1583344 loops=1) Sort Key: x, y, updated DESC Sort Method: external merge Disk: 79536kB -> Seq Scan on foo (cost=0.00..29314.44 rows=1583344 width=38) (actual time=0.013..142.781 rows=1583344 loops=1) Planning Time: 0.075 ms Execution Time: 4888.878 ms (7 rows) </code>
userdb=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc;
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=278945.67..294779.11 rows=158334 width=38) (actual time=3823.677..4808.844 rows=1583344 loops=1)
   ->  Sort  (cost=278945.67..282904.03 rows=1583344 width=38) (actual time=3823.674..4482.405 rows=1583344 loops=1)
         Sort Key: x, y, updated DESC
         Sort Method: external merge  Disk: 79536kB
         ->  Seq Scan on foo  (cost=0.00..29314.44 rows=1583344 width=38) (actual time=0.013..142.781 rows=1583344 loops=1)
 Planning Time: 0.075 ms
 Execution Time: 4888.878 ms
(7 rows)

then it gives me the most recent data which is what I want but takes five times as long.

What I’d like to get is this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> Table "public.foo"
Column | Type | Collation | Nullable | Default
---------+--------------------------+-----------+----------+---------
x | character varying | | not null |
y | character varying | | not null |
z | integer | | |
updated | timestamp with time zone | | not null |
Indexes:
"foo_pkey" PRIMARY KEY, btree (x, y, updated DESC)
</code>
<code> Table "public.foo" Column | Type | Collation | Nullable | Default ---------+--------------------------+-----------+----------+--------- x | character varying | | not null | y | character varying | | not null | z | integer | | | updated | timestamp with time zone | | not null | Indexes: "foo_pkey" PRIMARY KEY, btree (x, y, updated DESC) </code>
                         Table "public.foo"
 Column  |           Type           | Collation | Nullable | Default 
---------+--------------------------+-----------+----------+---------
 x       | character varying        |           | not null | 
 y       | character varying        |           | not null | 
 z       | integer                  |           |          | 
 updated | timestamp with time zone |           | not null | 
Indexes:
    "foo_pkey" PRIMARY KEY, btree (x, y, updated DESC)

This just produces a syntax error:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated desc));
</code>
<code>create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated desc)); </code>
create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated desc));

I can do this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>postgres=# create index on foo (x, y, updated desc);
CREATE INDEX
postgres=#
</code>
<code>postgres=# create index on foo (x, y, updated desc); CREATE INDEX postgres=# </code>
postgres=# create index on foo (x, y, updated desc);
CREATE INDEX
postgres=#

which fixes the query but then I get two indices which is obviously less performant than one. I can drop the primary key but that feels pretty ugly. Is there a better way to get what I want? Is there a good reason why a PK generates an index with particular ordering and why that ordering being the other way round would be be problematic?

The only references I can see elsewhere are a thread (https://www.postgresql.org/message-id/[email protected]) which suggests that a unique index and no PK is the best option but that’s 15 years old. Is there anything better now?

i.e. Is this really the best I can do?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>=> alter table foo drop constraint foo_pkey;
ALTER TABLE
=> create unique index on foo(x,y,updated desc);
CREATE INDEX
=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=0.43..126019.21 rows=158334 width=38) (actual time=0.025..867.984 rows=1583344 loops=1)
-> Index Scan using foo_x_y_updated_idx on foo (cost=0.43..114144.13 rows=1583344 width=38) (actual time=0.024..521.720 rows=1583344 loops=1)
Planning Time: 0.171 ms
Execution Time: 935.838 ms
(4 rows)
</code>
<code>=> alter table foo drop constraint foo_pkey; ALTER TABLE => create unique index on foo(x,y,updated desc); CREATE INDEX => explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------- Unique (cost=0.43..126019.21 rows=158334 width=38) (actual time=0.025..867.984 rows=1583344 loops=1) -> Index Scan using foo_x_y_updated_idx on foo (cost=0.43..114144.13 rows=1583344 width=38) (actual time=0.024..521.720 rows=1583344 loops=1) Planning Time: 0.171 ms Execution Time: 935.838 ms (4 rows) </code>
=> alter table foo drop constraint foo_pkey;
ALTER TABLE
=> create unique index on foo(x,y,updated desc);
CREATE INDEX
=> explain analyze select distinct on (x,y,updated) * from foo order by x, y, updated desc;
                                                                    QUERY PLAN                                                                     
---------------------------------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=0.43..126019.21 rows=158334 width=38) (actual time=0.025..867.984 rows=1583344 loops=1)
   ->  Index Scan using foo_x_y_updated_idx on foo  (cost=0.43..114144.13 rows=1583344 width=38) (actual time=0.024..521.720 rows=1583344 loops=1)
 Planning Time: 0.171 ms
 Execution Time: 935.838 ms
(4 rows)

10

You cannot create a primary key with an index that sorts descending on a column.

But that may not be necessary for your use case: PostgreSQL can scan the index in descending order just as efficiently as in ascending order, so you should rewrite the query as follows:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>SELECT DISTINCT ON (x, y, updated) *
FROM foo
ORDER BY x DESC, y DESC, updated DESC;
</code>
<code>SELECT DISTINCT ON (x, y, updated) * FROM foo ORDER BY x DESC, y DESC, updated DESC; </code>
SELECT DISTINCT ON (x, y, updated) *
FROM foo
ORDER BY x DESC, y DESC, updated DESC;

That will scan the primary key index backwards and does not require a sort.

If it is important for you that the result is sorted in ascending order for x and y, you either have to bite the bullet and create a second index, or you add another sort after the DISTINCT, which should be a bit cheaper than sorting before the DISTINCT:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>SELECT *
FROM (SELECT DISTINCT ON (x, y, updated) *
FROM foo
ORDER BY x DESC, y DESC, updated DESC
) AS q
ORDER BY x, y;
</code>
<code>SELECT * FROM (SELECT DISTINCT ON (x, y, updated) * FROM foo ORDER BY x DESC, y DESC, updated DESC ) AS q ORDER BY x, y; </code>
SELECT *
FROM (SELECT DISTINCT ON (x, y, updated) *
      FROM foo
      ORDER BY x DESC, y DESC, updated DESC
     ) AS q
ORDER BY x, y;

4

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật