PostgreSQL Antipatterns: ΡƒΠ΄Π°Ρ€ΠΈΠΌ словарСм ΠΏΠΎ тяТСлому JOIN

ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ ΡΠ΅Ρ€ΠΈΡŽ статСй, посвящСнных исслСдованию малоизвСстных способов ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ Β«Π²Ρ€ΠΎΠ΄Π΅ Π±Ρ‹ простых» запросов Π½Π° PostgreSQL:

НС ΠΏΠΎΠ΄ΡƒΠΌΠ°ΠΉΡ‚Π΅, Ρ‡Ρ‚ΠΎ я Ρ‚Π°ΠΊ сильно Π½Π΅ люблю JOIN… πŸ™‚

Но Π·Π°Ρ‡Π°ΡΡ‚ΡƒΡŽ Π±Π΅Π· Π½Π΅Π³ΠΎ запрос получаСтся ΠΎΡ‰ΡƒΡ‚ΠΈΠΌΠΎ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½Π΅Π΅, Ρ‡Π΅ΠΌ с Π½ΠΈΠΌ. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ сСгодня ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π²ΠΎΠΎΠ±Ρ‰Π΅ ΠΈΠ·Π±Π°Π²ΠΈΡ‚ΡŒΡΡ ΠΎΡ‚ рСсурсоСмкого JOIN β€” с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ словаря.

PostgreSQL Antipatterns: ΡƒΠ΄Π°Ρ€ΠΈΠΌ словарСм ΠΏΠΎ тяТСлому JOIN

Начиная с PostgreSQL 12 Ρ‡Π°ΡΡ‚ΡŒ описанных Π½ΠΈΠΆΠ΅ ситуаций ΠΌΠΎΠΆΠ΅Ρ‚ Π²ΠΎΡΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚ΡŒΡΡ Ρ‡ΡƒΡ‚ΡŒ ΠΈΠ½Π°Ρ‡Π΅ ΠΈΠ·-Π·Π° Π½Π΅-ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ CTE ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ. Π­Ρ‚ΠΎ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π΅Π½ΠΎ ΠΊ ΠΏΡ€Π΅ΠΆΠ½Π΅ΠΌΡƒ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ указания ΠΊΠ»ΡŽΡ‡Π° MATERIALIZED.

Много Β«Ρ„Π°ΠΊΡ‚ΠΎΠ²Β» ΠΏΠΎ ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½Π½ΠΎΠΌΡƒ ΡΠ»ΠΎΠ²Π°Ρ€ΡŽ

Π”Π°Π²Π°ΠΉΡ‚Π΅ возьмСм Π²ΠΏΠΎΠ»Π½Π΅ Ρ€Π΅Π°Π»ΡŒΠ½ΡƒΡŽ ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½ΡƒΡŽ Π·Π°Π΄Π°Ρ‡Ρƒ β€” Π½Π°Π΄ΠΎ вывСсти список входящих сообщСний ΠΈΠ»ΠΈ Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Π·Π°Π΄Π°Ρ‡ с отправитСлями:

25.01 | Иванов И.И. | ΠŸΠΎΠ΄Π³ΠΎΡ‚ΠΎΠ²ΠΈΡ‚ΡŒ описаниС Π½ΠΎΠ²ΠΎΠ³ΠΎ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠ°.
22.01 | Иванов И.И. | ΠΠ°ΠΏΠΈΡΠ°Ρ‚ΡŒ ΡΡ‚Π°Ρ‚ΡŒΡŽ Π½Π° Π₯Π°Π±Ρ€: Тизнь Π±Π΅Π· JOIN.
20.01 | ΠŸΠ΅Ρ‚Ρ€ΠΎΠ² П.П. | ΠŸΠΎΠΌΠΎΡ‡ΡŒ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ запрос.
18.01 | Иванов И.И. | ΠΠ°ΠΏΠΈΡΠ°Ρ‚ΡŒ ΡΡ‚Π°Ρ‚ΡŒΡŽ Π½Π° Π₯Π°Π±Ρ€: JOIN с ΡƒΡ‡Π΅Ρ‚ΠΎΠΌ распрСдСлСния Π΄Π°Π½Π½Ρ‹Ρ….
16.01 | ΠŸΠ΅Ρ‚Ρ€ΠΎΠ² П.П. | ΠŸΠΎΠΌΠΎΡ‡ΡŒ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ запрос.

Π’ абстрактном ΠΌΠΈΡ€Π΅ Π°Π²Ρ‚ΠΎΡ€Ρ‹ Π·Π°Π΄Π°Ρ‡ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Π»ΠΈ Π±Ρ‹ Ρ€Π°Π²Π½ΠΎΠΌΠ΅Ρ€Π½ΠΎ Ρ€Π°ΡΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒΡΡ срСди всСх сотрудников нашСй ΠΎΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΠΈ, Π½ΠΎ Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ Π·Π°Π΄Π°Ρ‡ΠΈ приходят, ΠΊΠ°ΠΊ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ, ΠΎΡ‚ достаточно ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½Π½ΠΎΠ³ΠΎ количСства людСй β€” Β«ΠΎΡ‚ Π½Π°Ρ‡Π°Π»ΡŒΡΡ‚Π²Π°Β» Π²Π²Π΅Ρ€Ρ… ΠΏΠΎ ΠΈΠ΅Ρ€Π°Ρ€Ρ…ΠΈΠΈ ΠΈΠ»ΠΈ Β«ΠΎΡ‚ смСТников» ΠΈΠ· сосСдних ΠΎΡ‚Π΄Π΅Π»ΠΎΠ² (Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊΠΈ, Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€Ρ‹, ΠΌΠ°Ρ€ΠΊΠ΅Ρ‚ΠΈΠ½Π³, …).

Π”Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΡ€ΠΈΠΌΠ΅ΠΌ, Ρ‡Ρ‚ΠΎ Π² нашСй ΠΎΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΈΠ· 1000 Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ 20 Π°Π²Ρ‚ΠΎΡ€ΠΎΠ² (ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ Π΄Π°ΠΆΠ΅ мСньшС) ставят Π·Π°Π΄Π°Ρ‡ΠΈ Π² адрСс ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ исполнитСля ΠΈ Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡΡ этим ΠΏΡ€Π΅Π΄ΠΌΠ΅Ρ‚Π½Ρ‹ΠΌ Π·Π½Π°Π½ΠΈΠ΅ΠΌ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΠΊΠΎΡ€ΠΈΡ‚ΡŒ Β«Ρ‚Ρ€Π°Π΄ΠΈΡ†ΠΈΠΎΠ½Π½Ρ‹ΠΉΒ» запрос.

Π‘ΠΊΡ€ΠΈΠΏΡ‚-Π³Π΅Π½Π΅Ρ€Π°Ρ‚ΠΎΡ€

-- сотрудники
CREATE TABLE person AS
SELECT
  id
, repeat(chr(ascii('a') + (id % 26)), (id % 32) + 1) "name"
, '2000-01-01'::date - (random() * 1e4)::integer birth_date
FROM
  generate_series(1, 1000) id;

ALTER TABLE person ADD PRIMARY KEY(id);

-- Π·Π°Π΄Π°Ρ‡ΠΈ с ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹ΠΌ распрСдСлСниСм
CREATE TABLE task AS
WITH aid AS (
  SELECT
    id
  , array_agg((random() * 999)::integer + 1) aids
  FROM
    generate_series(1, 1000) id
  , generate_series(1, 20)
  GROUP BY
    1
)
SELECT
  *
FROM
  (
    SELECT
      id
    , '2020-01-01'::date - (random() * 1e3)::integer task_date
    , (random() * 999)::integer + 1 owner_id
    FROM
      generate_series(1, 100000) id
  ) T
, LATERAL(
    SELECT
      aids[(random() * (array_length(aids, 1) - 1))::integer + 1] author_id
    FROM
      aid
    WHERE
      id = T.owner_id
    LIMIT 1
  ) a;

ALTER TABLE task ADD PRIMARY KEY(id);
CREATE INDEX ON task(owner_id, task_date);
CREATE INDEX ON task(author_id);

ПокаТСм послСдниС 100 Π·Π°Π΄Π°Ρ‡ для ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ исполнитСля:

SELECT
  task.*
, person.name
FROM
  task
LEFT JOIN
  person
    ON person.id = task.author_id
WHERE
  owner_id = 777
ORDER BY
  task_date DESC
LIMIT 100;

PostgreSQL Antipatterns: ΡƒΠ΄Π°Ρ€ΠΈΠΌ словарСм ΠΏΠΎ тяТСлому JOIN
[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ΡΡ, Ρ‡Ρ‚ΠΎ 1/3 всСго Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ ΠΈ 3/4 Ρ‡Ρ‚Π΅Π½ΠΈΠΉ страниц Π΄Π°Π½Π½Ρ‹Ρ… Π±Ρ‹Π»ΠΈ сдСланы Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ 100 Ρ€Π°Π· ΠΏΠΎΠΈΡΠΊΠ°Ρ‚ΡŒ Π°Π²Ρ‚ΠΎΡ€Π° β€” для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ Π²Ρ‹Π²ΠΎΠ΄ΠΈΠΌΠΎΠΉ Π·Π°Π΄Π°Ρ‡ΠΈ. Но ΠΌΡ‹ ΠΆΠ΅ Π·Π½Π°Π΅ΠΌ, Ρ‡Ρ‚ΠΎ срСди этой сотни всСго 20 Ρ€Π°Π·Π½Ρ‹Ρ… β€” нСльзя Π»ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ это Π·Π½Π°Π½ΠΈΠ΅?

hstore-ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ

Π’ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡΡ Ρ‚ΠΈΠΏΠΎΠΌ hstore для Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ «словаря» ΠΊΠ»ΡŽΡ‡-Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅:

CREATE EXTENSION hstore

Π’ ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ Π½Π°ΠΌ достаточно ΠΏΠΎΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ ID Π°Π²Ρ‚ΠΎΡ€Π° ΠΈ Π΅Π³ΠΎ имя, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΡ‚ΠΎΠΌ ΠΈΠΌΠ΅Ρ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΈΠ·Π²Π»Π΅Ρ‡ΡŒ ΠΏΠΎ этому ΠΊΠ»ΡŽΡ‡Ρƒ:

-- Ρ„ΠΎΡ€ΠΌΠΈΡ€ΡƒΠ΅ΠΌ Ρ†Π΅Π»Π΅Π²ΡƒΡŽ Π²Ρ‹Π±ΠΎΡ€ΠΊΡƒ
WITH T AS (
  SELECT
    *
  FROM
    task
  WHERE
    owner_id = 777
  ORDER BY
    task_date DESC
  LIMIT 100
)
-- Ρ„ΠΎΡ€ΠΌΠΈΡ€ΡƒΠ΅ΠΌ ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ для ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹Ρ… Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ
, dict AS (
  SELECT
    hstore( -- hstore(keys::text[], values::text[])
      array_agg(id)::text[]
    , array_agg(name)::text[]
    )
  FROM
    person
  WHERE
    id = ANY(ARRAY(
      SELECT DISTINCT
        author_id
      FROM
        T
    ))
)
-- ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ связанныС значСния словаря
SELECT
  *
, (TABLE dict) -> author_id::text -- hstore -> key
FROM
  T;

PostgreSQL Antipatterns: ΡƒΠ΄Π°Ρ€ΠΈΠΌ словарСм ΠΏΠΎ тяТСлому JOIN
[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

На ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ пСрсонах Π·Π°Ρ‚Ρ€Π°Ρ‡Π΅Π½ΠΎ Π² 2 Ρ€Π°Π·Π° мСньшС Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ ΠΈ Π² 7 Ρ€Π°Π· мСньшС ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π½ΠΎ Π΄Π°Π½Π½Ρ‹Ρ…! Помимо «ословаривания», этих Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² Π½Π°ΠΌ ΠΏΠΎΠΌΠΎΠ³Π»ΠΎ Π΄ΠΎΡΡ‚ΠΈΡ‡ΡŒ Π΅Ρ‰Π΅ ΠΈ массовоС ΠΈΠ·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ записСй ΠΈΠ· Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π·Π° СдинствСнный ΠΏΡ€ΠΎΡ…ΠΎΠ΄ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ = ANY(ARRAY(...)).

Записи Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹: сСриализация ΠΈ дСсСриализация

Но Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Ρ‚ΡŒ, Ссли Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π² словарС Π½Π΅ ΠΎΠ΄Π½ΠΎ тСкстовоС ΠΏΠΎΠ»Π΅, Π° Ρ†Π΅Π»ΡƒΡŽ запись? Π’ этом случаС Π½Π°ΠΌ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΏΠΎΡΠΎΠ±Π½ΠΎΡΡ‚ΡŒ PostgreSQL Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с записью Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ ΠΊΠ°ΠΊ с Π΅Π΄ΠΈΠ½Ρ‹ΠΌ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ:

...
, dict AS (
  SELECT
    hstore(
      array_agg(id)::text[]
    , array_agg(p)::text[] -- магия #1
    )
  FROM
    person p
  WHERE
    ...
)
SELECT
  *
, (((TABLE dict) -> author_id::text)::person).* -- магия #2
FROM
  T;

Π”Π°Π²Π°ΠΉΡ‚Π΅ Ρ€Π°Π·Π±Π΅Ρ€Π΅ΠΌ, Ρ‡Ρ‚ΠΎ Ρ‚ΡƒΡ‚ Π²ΠΎΠΎΠ±Ρ‰Π΅ происходило:

  1. ΠœΡ‹ взяли p ΠΊΠ°ΠΊ алиас ΠΊ ΠΏΠΎΠ»Π½ΠΎΠΉ записи Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ person ΠΈ собрали ΠΈΠ· Π½ΠΈΡ… массив.
  2. Π­Ρ‚ΠΎΡ‚ массив записСй пСрСкастовали Π² массив тСкстовых строк (person[]::text[]), Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Π΅Π³ΠΎ Π² hstore-ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ Π² качСствС массива Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ.
  3. ΠŸΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ связанной записи ΠΌΡ‹ Π΅Π΅ Π²Ρ‹Ρ‚Π°Ρ‰ΠΈΠ»ΠΈ ΠΈΠ· словаря ΠΏΠΎ ΠΊΠ»ΡŽΡ‡Ρƒ ΠΊΠ°ΠΊ Ρ‚Π΅ΠΊΡΡ‚ΠΎΠ²ΡƒΡŽ строку.
  4. ВСкст Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΠΏΡ€Π΅Π²Ρ€Π°Ρ‚ΠΈΡ‚ΡŒ Π² Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Ρ‚ΠΈΠΏΠ° Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ person (для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ автоматичСски создаСтся ΠΎΠ΄Π½ΠΎΠΈΠΌΠ΅Π½Π½Ρ‹ΠΉ Π΅ΠΉ Ρ‚ΠΈΠΏ).
  5. Β«Π Π°Π·Π²Π΅Ρ€Π½ΡƒΠ»ΠΈΒ» Ρ‚ΠΈΠΏΠΈΠ·ΠΎΠ²Π°Π½Π½ΡƒΡŽ запись Π² столбцы с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ (...).*.

json-ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ

Но Ρ‚Π°ΠΊΠΎΠΉ фокус, ΠΊΠ°ΠΊ ΠΌΡ‹ ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΠ»ΠΈ Π²Ρ‹ΡˆΠ΅, Π½Π΅ ΠΏΡ€ΠΎΠΉΠ΄Π΅Ρ‚, Ссли Π½Π΅Ρ‚ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ Ρ‚Π°Π±Π»ΠΈΡ‡Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ°, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ «раскастовку». Π ΠΎΠ²Π½ΠΎ такая ΠΆΠ΅ ситуация Π²ΠΎΠ·Π½ΠΈΠΊΠ½Π΅Ρ‚, ΠΈ Ссли Π² качСствС источника Π΄Π°Π½Π½Ρ‹Ρ… для сСриализации ΠΌΡ‹ ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ строку CTE, Π° Π½Π΅ Β«Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΉΒ» Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹.

Π’ этом случаС Π½Π°ΠΌ ΠΏΠΎΠΌΠΎΠ³ΡƒΡ‚ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с json:

...
, p AS ( -- это ΡƒΠΆΠ΅ CTE
  SELECT
    *
  FROM
    person
  WHERE
    ...
)
, dict AS (
  SELECT
    json_object( -- Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ это ΡƒΠΆΠ΅ json
      array_agg(id)::text[]
    , array_agg(row_to_json(p))::text[] -- ΠΈ Π²Π½ΡƒΡ‚Ρ€ΠΈ json для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ строки
    )
  FROM
    p
)
SELECT
  *
FROM
  T
, LATERAL(
    SELECT
      *
    FROM
      json_to_record(
        ((TABLE dict) ->> author_id::text)::json -- ΠΈΠ·Π²Π»Π΅ΠΊΠ»ΠΈ ΠΈΠ· словаря ΠΊΠ°ΠΊ json
      ) AS j(name text, birth_date date) -- Π·Π°ΠΏΠΎΠ»Π½ΠΈΠ»ΠΈ Π½ΡƒΠΆΠ½ΡƒΡŽ Π½Π°ΠΌ структуру
  ) j;

Надо ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΏΡ€ΠΈ описании Ρ†Π΅Π»Π΅Π²ΠΎΠΉ структуры ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠ΅Ρ€Π΅Ρ‡ΠΈΡΠ»ΡΡ‚ΡŒ Π½Π΅ всС поля исходной строки, Π° Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ‚Π΅, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π°ΠΌ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Π½ΡƒΠΆΠ½Ρ‹. Если ΠΆΠ΅ Ρƒ нас Π΅ΡΡ‚ΡŒ «родная» Ρ‚Π°Π±Π»ΠΈΡ†Π°, Ρ‚ΠΎ Π»ΡƒΡ‡ΡˆΠ΅ Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠ΅ΠΉ json_populate_record.

Доступ ΠΊ ΡΠ»ΠΎΠ²Π°Ρ€ΡŽ Ρƒ нас происходит ΠΏΠΎ-ΠΏΡ€Π΅ΠΆΠ½Π΅ΠΌΡƒ ΠΎΠ΄Π½ΠΎΠΊΡ€Π°Ρ‚Π½ΠΎ, Π½ΠΎ ΠΈΠ·Π΄Π΅Ρ€ΠΆΠΊΠΈ Π½Π° json-[Π΄Π΅]ΡΠ΅Ρ€ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ достаточно Π²Π΅Π»ΠΈΠΊΠΈ, поэтому Ρ‚Π°ΠΊΠΈΠΌ способом Ρ€Π°Π·ΡƒΠΌΠ½ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π² Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… случаях, ΠΊΠΎΠ³Π΄Π° «чСстный» CTE Scan ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ сСбя Ρ…ΡƒΠΆΠ΅.

ВСстируСм ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ

Π˜Ρ‚Π°ΠΊ, ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΈ Π΄Π²Π° способа сСриализации Π΄Π°Π½Π½Ρ‹Ρ… Π² ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ β€” hstore / json_object. Помимо этого, сами массивы ΠΊΠ»ΡŽΡ‡Π΅ΠΉ ΠΈ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡ€ΠΎΠ΄ΠΈΡ‚ΡŒ Ρ‚ΠΎΠΆΠ΅ двумя способами, с Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½ΠΈΠΌ ΠΈΠ»ΠΈ внСшним ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΊ тСксту: array_agg(i::text) / array_agg(i)::text[].

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΠΌ ΡΡ„Ρ„Π΅ΠΊΡ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ Ρ€Π°Π·Π½Ρ‹Ρ… Π²ΠΈΠ΄ΠΎΠ² сСриализации Π½Π° сугубо синтСтичСском ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ β€” сСриализуСм Ρ€Π°Π·Π½ΠΎΠ΅ количСство ΠΊΠ»ΡŽΡ‡Π΅ΠΉ:

WITH dict AS (
  SELECT
    hstore(
      array_agg(i::text)
    , array_agg(i::text)
    )
  FROM
    generate_series(1, ...) i
)
TABLE dict;

ΠžΡ†Π΅Π½ΠΎΡ‡Π½Ρ‹ΠΉ скрипт : сСриализация

WITH T AS (
  SELECT
    *
  , (
      SELECT
        regexp_replace(ea[array_length(ea, 1)], '^Execution Time: (d+.d+) ms$', '1')::real et
      FROM
        (
          SELECT
            array_agg(el) ea
          FROM
            dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$
              explain analyze
              WITH dict AS (
                SELECT
                  hstore(
                    array_agg(i::text)
                  , array_agg(i::text)
                  )
                FROM
                  generate_series(1, $$ || (1 << v) || $$) i
              )
              TABLE dict
            $$) T(el text)
        ) T
    ) et
  FROM
    generate_series(0, 19) v
  ,   LATERAL generate_series(1, 7) i
  ORDER BY
    1, 2
)
SELECT
  v
, avg(et)::numeric(32,3)
FROM
  T
GROUP BY
  1
ORDER BY
  1;

PostgreSQL Antipatterns: ΡƒΠ΄Π°Ρ€ΠΈΠΌ словарСм ΠΏΠΎ тяТСлому JOIN

На PostgreSQL 11 ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Π΄ΠΎ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° словаря Π² 2^12 ΠΊΠ»ΡŽΡ‡Π΅ΠΉ сСриализация Π² json Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ мСньшС Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ. ΠŸΡ€ΠΈ этом Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ эффСктивной являСтся комбинация json_object ΠΈ Β«Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅Π³ΠΎΒ» прСобразования Ρ‚ΠΈΠΏΠΎΠ² array_agg(i::text).

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π΄Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΊΠ»ΡŽΡ‡Π° ΠΏΠΎ 8 Ρ€Π°Π· β€” вСдь Ссли ΠΊ ΡΠ»ΠΎΠ²Π°Ρ€ΡŽ Π½Π΅ ΠΎΠ±Ρ€Π°Ρ‰Π°Ρ‚ΡŒΡΡ, Ρ‚ΠΎ Π·Π°Ρ‡Π΅ΠΌ ΠΎΠ½ Π½ΡƒΠΆΠ΅Π½?

ΠžΡ†Π΅Π½ΠΎΡ‡Π½Ρ‹ΠΉ скрипт : Ρ‡Ρ‚Π΅Π½ΠΈΠ΅ ΠΈΠ· словаря

WITH T AS (
  SELECT
    *
  , (
      SELECT
        regexp_replace(ea[array_length(ea, 1)], '^Execution Time: (d+.d+) ms$', '1')::real et
      FROM
        (
          SELECT
            array_agg(el) ea
          FROM
            dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$
              explain analyze
              WITH dict AS (
                SELECT
                  json_object(
                    array_agg(i::text)
                  , array_agg(i::text)
                  )
                FROM
                  generate_series(1, $$ || (1 << v) || $$) i
              )
              SELECT
                (TABLE dict) -> (i % ($$ || (1 << v) || $$) + 1)::text
              FROM
                generate_series(1, $$ || (1 << (v + 3)) || $$) i
            $$) T(el text)
        ) T
    ) et
  FROM
    generate_series(0, 19) v
  , LATERAL generate_series(1, 7) i
  ORDER BY
    1, 2
)
SELECT
  v
, avg(et)::numeric(32,3)
FROM
  T
GROUP BY
  1
ORDER BY
  1;

PostgreSQL Antipatterns: ΡƒΠ΄Π°Ρ€ΠΈΠΌ словарСм ΠΏΠΎ тяТСлому JOIN

Π˜β€¦ ΡƒΠΆΠ΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ ΠΏΡ€ΠΈ 2^6 ΠΊΠ»ΡŽΡ‡Π΅ΠΉ Ρ‡Ρ‚Π΅Π½ΠΈΠ΅ ΠΈΠ· json-словаря Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΠΊΡ€Π°Ρ‚Π½ΠΎ ΠΏΡ€ΠΎΠΈΠ³Ρ€Ρ‹Π²Π°Ρ‚ΡŒ Ρ‡Ρ‚Π΅Π½ΠΈΡŽ ΠΈΠ· hstore, для jsonb Ρ‚ΠΎ ΠΆΠ΅ самоС происходит ΠΏΡ€ΠΈ 2^9.

Π˜Ρ‚ΠΎΠ³ΠΎΠ²Ρ‹Π΅ Π²Ρ‹Π²ΠΎΠ΄Ρ‹:

  • Ссли Π½Π°Π΄ΠΎ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ JOIN с ΠΌΠ½ΠΎΠ³ΠΎΠΊΡ€Π°Ρ‚Π½ΠΎ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡŽΡ‰ΠΈΠΌΠΈΡΡ записями β€” Π»ΡƒΡ‡ΡˆΠ΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ «ословариваниС» Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹
  • Ссли ваш ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΠΎ малСнький ΠΈ Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π²Ρ‹ ΠΈΠ· Π½Π΅Π³ΠΎ Π±ΡƒΠ΄Π΅Ρ‚Π΅ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ β€” ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ json[b]
  • Π²ΠΎ всСх ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Ρ… случаях hstore + array_agg(i::text) Π±ΡƒΠ΄Π΅Ρ‚ эффСктивнСС

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com