Создать первичный индекс для первичного ключа в Postgres с упорядочиванием по убыванию

У меня есть таблица с данными временных рядов и с первичным ключом - на самом деле несколько больших таблиц, но это подойдет в качестве примера:

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:

Это автоматически создаст обновленный индекс для x. Однако многие из моих запросов ищут «самый последний foo для различных значений x», а не самый старый, поэтому индекс является неправильным для эффективных запросов.

Например, если я это сделаю:

explain analyze select distinct on (x,y,updated) * from foo order by x,y,updated;

Затем он прекрасно использует индекс:

----------------------------------------------------------------------------------------------------------------------------------------
 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)

но, конечно, показывает мне самые старые элементы.

Если я это сделаю:

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)

затем он дает мне самые последние данные, а это то, что мне нужно, но это занимает в пять раз больше времени.

Я хотел бы получить следующее:

                         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)

Это просто приводит к синтаксической ошибке:

create table foo (x varchar, y varchar, z text, updated timestamptz, primary key(x, y, updated desc));

Я могу сделать это:

postgres=# create index on foo (x, y, updated desc);
CREATE INDEX
postgres=#

который исправляет запрос, но затем я получаю два индекса, которые явно менее производительны, чем один. Я могу удалить первичный ключ, но это выглядит довольно некрасиво. Есть ли лучший способ получить то, что я хочу? Есть ли веская причина, по которой ПК генерирует индекс с определенным порядком и почему обратный порядок может быть проблематичным?

Единственные ссылки, которые я вижу в других местах, - это ветка (https://www.postgresql.org/message-id/[email protected]), в которой предполагается, что уникальный индекс и отсутствие PK - лучший вариант, но это 15 лет. Есть ли что-нибудь лучше сейчас?

то есть действительно ли это лучшее, что я могу сделать?

=> 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)

Потому что CREATE TABLE: ограничение PRIMARY KEY указывает, что столбец или столбцы таблицы могут содержать только уникальные (не повторяющиеся), ненулевые значения. и добавление ограничения PRIMARY KEY автоматически создаст уникальный индекс btree. Таким образом, ПК является ограничением, а не индексом, хотя для него создан общий индекс. Если вам нужно что-то менее общее, вам нужно будет создать специальный индекс для столбцов.

Adrian Klaver 02.09.2024 18:53

уникальный индекс и отсутствие ПК - лучший вариант... Есть ли что-нибудь лучше сейчас? Нет, все еще в силе и для того, чтобы вы хотели лучшего.

Belayer 02.09.2024 19:06

Какую проблему оно должно решить? И почему ваш текущий запрос не может выбрать правильный план запроса? Пожалуйста, поделитесь результатом explain(analyze, verbose, buffers, settings) для этого оператора SQL и самого оператора.

Frank Heikens 02.09.2024 19:09

@AdrianKlaver Да, мой вопрос в том, могу ли я заставить его использовать другой индекс для выполнения той же работы, и Belayer звучит так, как будто ответ по-прежнему отрицательный.

Richard Wheeldon 02.09.2024 19:15

1) Это достаточно легко проверить: запустите запрос, используя EXPLAIN ANALYZE без create index on foo (x, updated desc); и с EXPLAIN ANALYZE, и посмотрите, каковы результаты? 2) Согласно комментарию @FrankHeikens, вам следует сделать это с вашей текущей настройкой и добавить результаты в виде текста к тексту вашего вопроса.

Adrian Klaver 02.09.2024 19:19

Сравнивали ли вы производительность (x, updated) и (x, updated DESC), используя реальные запросы, или это всего лишь предположения? Может быть разница в производительности, если DISTINCT ON (x) ... ORDER BY x, updated DESC используется для определения самого последнего обновления для каждого x; однако SELECT x, MAX(updated) ... GROUP BY x, скорее всего, будет работать сравнимо с любым вариантом.

JohnH 02.09.2024 20:31

Первичный ключ и индексный сервер имеют разные цели. Первый обеспечивает уникальность строк, а второй помогает повысить производительность запросов. В некоторых случаях один из них может служить обеим целям. Однако, похоже, это не так.

The Impaler 03.09.2024 14:50

Какую версию вы используете? Имея только индекс pk, я получаю инкрементную сортировку, которая должна быть довольно хорошей.

jjanes 03.09.2024 20:14

@jjanes Aurora бессерверная — «PostgreSQL 14.9 на aarch64-unknown-linux-gnu, скомпилированная aarch64-unknown-linux-gnu-gcc (GCC) 9.5.0, 64-разрядная версия»

Richard Wheeldon 03.09.2024 22:33

Похоже, что возможность использовать инкрементальную сортировку не была расширена до DISTINCT (и, очевидно, не до DISTINCT ON) до версии 16.

jjanes 04.09.2024 19:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не можете создать первичный ключ с индексом, который сортирует столбец по убыванию.

Но для вашего случая использования это может и не потребоваться: PostgreSQL может сканировать индекс в порядке убывания так же эффективно, как и в порядке возрастания, поэтому вам следует переписать запрос следующим образом:

SELECT DISTINCT ON (x, y, updated) *
FROM foo
ORDER BY x DESC, y DESC, updated DESC;

Это позволит просканировать индекс первичного ключа назад и не потребует сортировки.

Если для вас важно, чтобы результат был отсортирован по возрастанию для x и y, вам придется либо стиснуть зубы и создать второй индекс, либо добавить еще одну сортировку после DISTINCT, что должно быть немного дешевле, чем сортировка до этого. DISTINCT:

SELECT *
FROM (SELECT DISTINCT ON (x, y, updated) *
      FROM foo
      ORDER BY x DESC, y DESC, updated DESC
     ) AS q
ORDER BY x, y;

Я обновил вопрос, добавив «отдельные» примеры, которые показывают, что это не обязательно так.

Richard Wheeldon 03.09.2024 10:37

Он работает для «объяснения анализа выбора x, y, max (обновлено) из группы foo на 1,2;» против «объясните анализ выбора x, y, min(обновлено) из группы foo на 1,2;» и я не совсем понимаю, почему отдельные элементы ведут себя так по-другому.

Richard Wheeldon 03.09.2024 10:44

Итак, «объясните анализ, выберите различные по (x,y,updated) * from foo в порядке x desc, y desc, обновленного desc;» дает мне «Сканирование индекса назад», так что этот вид работает, и его полезно знать, но опция уникального индекса работает в два раза быстрее, так что это то, что я собираюсь сделать. Спасибо. Исходное утверждение: «Вы не можете создать первичный ключ с индексом, который сортируется по убыванию столбца». это правильный ответ (хотя он мне не нужен)

Richard Wheeldon 05.09.2024 10:41

Верно, но я хотел показать вам выход, а не просто сказать: «Вы не можете этого получить». DISTINCT ON работает следующим образом: результат сортируется в соответствии с ORDER BY, и для каждой группы, определенной в предложении DISTINCT ON, используется первая строка результата. Таким образом, индекс можно использовать для DISTINCT ON, если его можно использовать для предложения ORDER BY.

Laurenz Albe 05.09.2024 11:20

Другие вопросы по теме

Введите ошибку при заполнении модели в Django с помощью UniqueConstraint
Каков типичный метод предотвращения дублирования в таблице PostgreSQL?
Лучшее решение для первичного ключа Django Composite из внешних ключей
Проблема JPA Hibernate для отношений один к одному со встроенным идентификатором
Google Spanner - Мутация Java - Удалить строки с подмножеством значений составного ключа
Сайт администратора django возвращает исключение MultipleObjectsReturned с импортированной устаревшей базой данных inspectdb и составным первичным ключом
Таблицы SQLITE JOIN на составном первичном ключе
SQL: эквивалентно ли создание нескольких внешних ключей с помощью одного оператора созданию их с помощью одного оператора?
SQLAlchemy: выберите самую последнюю строку для всех идентификаторов в одной таблице с составным первичным ключом
Как сгенерировать конкатенированный первичный ключ при импорте CSV (MariaDB/MySQL)?