PostgreSQL เชเชจเซเชŸเชฟเชชเซ‡เชŸเชฐเซเชจ: CTE x CTE

เชฎเชพเชฐเชพ เช•เชพเชฎเชจเซ€ เชฒเชพเช‡เชจเชจเซ‡ เชฒเซ€เชงเซ‡, เชœเซเชฏเชพเชฐเซ‡ เชตเชฟเช•เชพเชธเช•เชฐเซเชคเชพ เชตเชฟเชจเช‚เชคเซ€ เชฒเช–เซ‡ เช›เซ‡ เช…เชจเซ‡ เชตเชฟเชšเชพเชฐเซ‡ เช›เซ‡ เชคเซเชฏเชพเชฐเซ‡ เชฎเชพเชฐเซ‡ เชชเชฐเชฟเชธเซเชฅเชฟเชคเชฟเช“เชจเซ‹ เชธเชพเชฎเชจเซ‹ เช•เชฐเชตเซ‹ เชชเชกเซ‡ เช›เซ‡.เช†เชงเชพเชฐ เชธเซเชฎเชพเชฐเซเชŸ เช›เซ‡, เชคเซ‡ เชฌเชงเซเช‚ เชœเชพเชคเซ‡ เชธเช‚เชญเชพเชณเซ€ เชถเช•เซ‡ เช›เซ‡!ยซ

เช•เซ‡เชŸเชฒเชพเช• เช•เชฟเชธเซเชธเชพเช“เชฎเชพเช‚ (เช…เช‚เชถเชคเชƒ เชกเซ‡เชŸเชพเชฌเซ‡เชเชจเซ€ เช•เซเชทเชฎเชคเชพเช“เชจเซ€ เช…เชœเซเชžเชพเชจเชคเชพเชฅเซ€, เช…เช‚เชถเชคเชƒ เช…เช•เชพเชณ เช‘เชชเซเชŸเชฟเชฎเชพเช‡เชเซ‡เชถเชจเชฅเซ€), เช† เช…เชญเชฟเช—เชฎ "เชซเซเชฐเซ‡เชจเซเช•เซ‡เชธเซเชŸเชพเช‡เชจ" เชจเชพ เชฆเซ‡เช–เชพเชต เชคเชฐเชซ เชฆเซ‹เชฐเซ€ เชœเชพเชฏ เช›เซ‡.

เชชเซเชฐเชฅเชฎ, เชนเซเช‚ เช†เชตเซ€ เชตเชฟเชจเช‚เชคเซ€เชจเซเช‚ เช‰เชฆเชพเชนเชฐเชฃ เช†เชชเซ€เชถ:

-- ะดะปั ะบะฐะถะดะพะน ะบะปัŽั‡ะตะฒะพะน ะฟะฐั€ั‹ ะฝะฐั…ะพะดะธะผ ะฐััะพั†ะธะธั€ะพะฒะฐะฝะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั ะฟะพะปะตะน
WITH RECURSIVE cte_bind AS (
  SELECT DISTINCT ON (key_a, key_b)
    key_a a
  , key_b b
  , fld1 bind_fld1
  , fld2 bind_fld2
  FROM
    tbl
)
-- ะฝะฐั…ะพะดะธะผ min/max ะทะฝะฐั‡ะตะฝะธะน ะดะปั ะบะฐะถะดะพะณะพ ะฟะตั€ะฒะพะณะพ ะบะปัŽั‡ะฐ
, cte_max AS (
  SELECT
    a
  , max(bind_fld1) bind_fld1
  , min(bind_fld2) bind_fld2
  FROM
    cte_bind
  GROUP BY
    a
)
-- ัะฒัะทั‹ะฒะฐะตะผ ะฟะพ ะฟะตั€ะฒะพะผัƒ ะบะปัŽั‡ัƒ ะบะปัŽั‡ะตะฒั‹ะต ะฟะฐั€ั‹ ะธ min/max-ะทะฝะฐั‡ะตะฝะธั
, cte_a_bind AS (
  SELECT
    cte_bind.a
  , cte_bind.b
  , cte_max.bind_fld1
  , cte_max.bind_fld2
  FROM
    cte_bind
  INNER JOIN
    cte_max
      ON cte_max.a = cte_bind.a
)
SELECT * FROM cte_a_bind;

เชตเชฟเชจเช‚เชคเซ€เชจเซ€ เช—เซเชฃเชตเชคเซเชคเชพเชจเซเช‚ เชจเซ‹เช‚เชงเชชเชพเชคเซเชฐ เชฎเซ‚เชฒเซเชฏเชพเช‚เช•เชจ เช•เชฐเชตเชพ เชฎเชพเชŸเซ‡, เชšเชพเชฒเซ‹ เช•เซ‡เชŸเชฒเชพเช• เชฎเชจเชธเซเชตเซ€ เชกเซ‡เชŸเชพ เชธเซ‡เชŸ เชฌเชจเชพเชตเซ€เช:

CREATE TABLE tbl AS
SELECT
  (random() * 1000)::integer key_a
, (random() * 1000)::integer key_b
, (random() * 10000)::integer fld1
, (random() * 10000)::integer fld2
FROM
  generate_series(1, 10000);
CREATE INDEX ON tbl(key_a, key_b);

เชคเซ‡ เชคเชพเชฐเชฃ เช†เชชเซ‡ เช›เซ‡ เช•เซ‡ เชกเซ‡เชŸเชพ เชตเชพเช‚เชšเชตเชพเชฎเชพเช‚ เชเช• เช•เซเชตเชพเชฐเซเชŸเชฐ เช•เชฐเชคเชพ เช“เช›เซ‹ เชธเชฎเชฏ เชฒเชพเช—เซเชฏเซ‹ เช•เซเชตเซ‡เชฐเซ€ เช…เชฎเชฒ:

PostgreSQL เชเชจเซเชŸเชฟเชชเซ‡เชŸเชฐเซเชจ: CTE x CTE[explan.tensor.ru เชชเชฐ เชœเซเช“]

เชŸเซเช•เชกเซ‡ เชŸเซเช•เชกเซ‡ เชคเซ‡เชจเซ‡ เช…เชฒเช— เช•เชฐเซ€เชจเซ‡ เชฒเชˆ เชœเชตเชพเชจเซเช‚

เชšเชพเชฒเซ‹ เชตเชฟเชจเช‚เชคเซ€ เชชเชฐ เชจเชœเซ€เช•เชฅเซ€ เชจเชœเชฐ เช•เชฐเซ€เช เช…เชจเซ‡ เชฎเซ‚เช‚เชเชตเชฃเชฎเชพเช‚ เชฐเชนเซ€เช:

  1. เชœเซ‹ เช•เซ‹เชˆ เชชเซเชจเชฐเชพเชตเชฐเซเชคเชฟเชค CTE เชจเชพ เชนเซ‹เชฏ เชคเซ‹ เช…เชนเซ€เช‚ WITH RECURSIVE เชถเชพ เชฎเชพเชŸเซ‡ เช›เซ‡?
  2. เชเช• เช…เชฒเช— CTE เชฎเชพเช‚ เชฒเช˜เซเชคเซเชคเชฎ/เชฎเชนเชคเซเชคเชฎ เชฎเซ‚เชฒเซเชฏเซ‹เชจเซเช‚ เชœเซ‚เชฅ เชถเชพ เชฎเชพเชŸเซ‡ เชœเซ‹ เชคเซ‡เช“ เช•เซ‹เชˆเชชเชฃ เชฐเซ€เชคเซ‡ เชฎเซ‚เชณ เชจเชฎเซ‚เชจเชพ เชธเชพเชฅเซ‡ เชœเซ‹เชกเชพเชฏเซ‡เชฒเชพ เชนเซ‹เชฏ?
    +25% เชธเชฎเชฏ
  3. เช…เช—เชพเช‰เชจเชพ CTE เชจเชพ เช…เช‚เชคเซ‡ เชฌเชฟเชจเชถเชฐเชคเซ€ 'SELECT * FROM' เชจเซ‹ เช‰เชชเชฏเซ‹เช— เชถเชพ เชฎเชพเชŸเซ‡ เช•เชฐเชตเซ‹?
    +14% เชธเชฎเชฏ

เช† เช•เชฟเชธเซเชธเชพเชฎเชพเช‚, เช…เชฎเซ‡ เช–เซ‚เชฌ เชจเชธเซ€เชฌเชฆเชพเชฐ เชนเชคเชพ เช•เซ‡ เช•เชจเซ‡เช•เซเชถเชจ เชฎเชพเชŸเซ‡ Hash Join เชชเชธเช‚เชฆ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซเชฏเซเช‚ เชนเชคเซเช‚, เชจเซ‡เชธเซเชŸเซ‡เชก เชฒเซ‚เชช เชฎเชพเชŸเซ‡ เชจเชนเซ€เช‚, เช•เชพเชฐเชฃ เช•เซ‡ เชชเช›เซ€ เช…เชฎเชจเซ‡ เชฎเชพเชคเซเชฐ เชเช• CTE เชธเซเช•เซ‡เชจ เชชเชพเชธ เชจเชนเซ€เช‚, เชชเชฐเช‚เชคเซ 10K เชชเซเชฐเชพเชชเซเชค เชฅเชฏเชพ เชนเซ‹เชค!

CTE เชธเซเช•เซ‡เชจ เชตเชฟเชถเซ‡ เชฅเซ‹เชกเซเช‚เช…เชนเซ€เช‚ เช†เชชเชฃเซ‡ เชคเซ‡ เชฏเชพเชฆ เชฐเชพเช–เชตเซเช‚ เชœเซ‹เชˆเช CTE เชธเซเช•เซ‡เชจ Seq Scan เชœเซ‡เชตเซเช‚ เชœ เช›เซ‡ - เชเชŸเชฒเซ‡ เช•เซ‡, เช•เซ‹เชˆ เช…เชจเซเช•เซเชฐเชฎเชฃเชฟเช•เชพ เชจเชนเซ€เช‚, เชชเชฐเช‚เชคเซ เชฎเชพเชคเซเชฐ เชเช• เชธเช‚เชชเซ‚เชฐเซเชฃ เชถเซ‹เชง, เชœเซ‡เชจเซ€ เชœเชฐเซ‚เชฐ เชชเชกเชถเซ‡ 10K x 0.3ms = 3000ms cte_max เชฆเซเชตเชพเชฐเชพ เชšเช•เซเชฐ เชฎเชพเชŸเซ‡ เช…เชฅเชตเชพ 1K x 1.5ms = 1500ms cte_bind เชฆเซเชตเชพเชฐเชพ เชฒเซ‚เชช เช•เชฐเชคเซ€ เชตเช–เชคเซ‡!
เชตเชพเชธเซเชคเชตเชฎเชพเช‚, เชคเชฎเซ‡ เชชเชฐเชฟเชฃเชพเชฎ เชคเชฐเซ€เช•เซ‡ เชถเซเช‚ เชฎเซ‡เชณเชตเชตเชพ เชฎเชพเช‚เช—เชคเชพ เชนเชคเชพ? เชนเชพ, เชธเชพเชฎเชพเชจเซเชฏ เชฐเซ€เชคเซ‡ เช† เชคเซ‡ เชชเซเชฐเชถเซเชจ เช›เซ‡ เชœเซ‡ "เชคเซเชฐเชฃ-เชตเชพเชฐเซเชคเชพ" เชชเซเชฐเชถเซเชจเซ‹เชจเซเช‚ เชตเชฟเชถเซเชฒเซ‡เชทเชฃ เช•เชฐเชตเชพเชจเซ€ 5เชฎเซ€ เชฎเชฟเชจเชฟเชŸเชฎเชพเช‚ เช•เซเชฏเชพเช‚เช• เช†เชตเซ‡ เช›เซ‡.

เช…เชฎเซ‡ เชฆเชฐเซ‡เช• เช…เชจเชจเซเชฏ เช•เซ€ เชœเซ‹เชกเซ€ เชฎเชพเชŸเซ‡ เช†เช‰เชŸเชชเซเชŸ เช•เชฐเชตเชพ เชฎเชพเช‚เช—เซ€เช เช›เซ€เช เช•เซ€_เช เชฆเซเชตเชพเชฐเชพ เชœเซ‚เชฅเชฎเชพเช‚เชฅเซ€ เชจเซเชฏเซ‚เชจเชคเชฎ/เชฎเชนเชคเซเชคเชฎ.
เชคเซ‹ เชšเชพเชฒเซ‹ เช† เชฎเชพเชŸเซ‡ เชคเซ‡เชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เช เชตเชฟเชจเซเชกเซ‹ เช•เชพเชฐเซเชฏเซ‹:

SELECT DISTINCT ON(key_a, key_b)
	key_a a
,	key_b b
,	max(fld1) OVER(w) bind_fld1
,	min(fld2) OVER(w) bind_fld2
FROM
	tbl
WINDOW
	w AS (PARTITION BY key_a);

PostgreSQL เชเชจเซเชŸเชฟเชชเซ‡เชŸเชฐเซเชจ: CTE x CTE
[explan.tensor.ru เชชเชฐ เชœเซเช“]

เชฌเช‚เชจเซ‡ เชตเชฟเช•เชฒเซเชชเซ‹เชฎเชพเช‚ เชกเซ‡เชŸเชพ เชตเชพเช‚เชšเชตเชพเชฎเชพเช‚ เชฒเช—เชญเช— 4-5ms เชœเซ‡เชŸเชฒเซ‹ เชธเชฎเชฏ เชฒเชพเช—เซ‡ เช›เซ‡, เชคเซ‡ เชชเช›เซ€ เช…เชฎเชพเชฐเซ‹ เชฌเชงเซ‹ เชธเชฎเชฏ เชฒเชพเชญ เชฅเชพเชฏ เช›เซ‡ -32% - เช† เชคเซ‡เชจเชพ เชถเซเชฆเซเชง เชธเซเชตเชฐเซ‚เชชเชฎเชพเช‚ เช›เซ‡ เช†เชงเชพเชฐ CPU เชฎเชพเช‚เชฅเซ€ เชฒเซ‹เชก เชฆเซ‚เชฐ เช•เชฐเซเชฏเซ‹, เชœเซ‹ เช†เชตเซ€ เชตเชฟเชจเช‚เชคเซ€ เช˜เชฃเซ€ เชตเช–เชค เชชเชฐเซเชฏเชพเชชเซเชค เชฐเซ€เชคเซ‡ เชšเชฒเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡.

เชธเชพเชฎเชพเชจเซเชฏ เชฐเซ€เชคเซ‡, เชคเชฎเชพเชฐเซ‡ เช†เชงเชพเชฐเชจเซ‡ "เช—เซ‹เชณเชพเช•เชพเชฐ เชตเชนเชจ เช•เชฐเชตเชพ, เชšเซ‹เชฐเชธ เชเช•เชจเซ‡ เชฐเซ‹เชฒ เช•เชฐเชตเชพ" เชฎเชพเชŸเซ‡ เชฆเชฌเชพเชฃ เช•เชฐเชตเซเช‚ เชœเซ‹เชˆเช เชจเชนเซ€เช‚.

เชธเซ‹เชฐเซเชธ: www.habr.com

เชเช• เชŸเชฟเชชเซเชชเชฃเซ€ เช‰เชฎเซ‡เชฐเซ‹