PostgreSQL Antipatterns: "ื—ื™ื™ื‘ ืœื”ื™ืฉืืจ ืจืง ืื—ื“!"

ื‘-SQL, ืืชื” ืžืชืืจ "ืžื”" ืฉืืชื” ืจื•ืฆื” ืœื”ืฉื™ื’, ืœื "ืื™ืš" ื–ื” ืฆืจื™ืš ืœื”ืชื‘ืฆืข. ืœื›ืŸ, ื”ื‘ืขื™ื” ืฉืœ ืคื™ืชื•ื— ืฉืื™ืœืชื•ืช SQL ื‘ืกื’ื ื•ืŸ "ื›ืžื• ืฉื ืฉืžืข ื›ืš ื›ืชื•ื‘" ืชื•ืคืกืช ืืช ืžืงื•ืžื” ืฉืœ ื”ื›ื‘ื•ื“, ื™ื—ื“ ืขื ืชื›ื•ื ื•ืช ืฉืœ ื—ื™ืฉื•ื‘ ืชื ืื™ื ื‘-SQL.

ื”ื™ื•ื, ื‘ืขื–ืจืช ื“ื•ื’ืžืื•ืช ืคืฉื•ื˜ื•ืช ื‘ื™ื•ืชืจ, ื‘ื•ืื• ื ืจืื” ืœืžื” ื–ื” ื™ื›ื•ืœ ืœื”ื•ื‘ื™ืœ ื‘ื”ืงืฉืจ ืฉืœ ืฉื™ืžื•ืฉ GROUP/DISTINCT ะธ LIMIT ืื™ืชื.

ืขื›ืฉื™ื•, ืื ื›ืชื‘ืช ื‘ื‘ืงืฉื” "ืชื—ื™ืœื” ื—ื‘ืจ ืืช ื”ืฉืœื˜ื™ื ื”ืืœื”, ื•ืœืื—ืจ ืžื›ืŸ ืœื–ืจื•ืง ืืช ื›ืœ ื”ื›ืคื™ืœื•ื™ื•ืช, ืฆืจื™ืš ืœื”ื™ืฉืืจ ืจืง ืื—ื“ ื”ืขืชืง ืขื‘ื•ืจ ื›ืœ ืžืคืชื—" - ื›ืš ื–ื” ื‘ื“ื™ื•ืง ื™ืขื‘ื•ื“, ื’ื ืื ื”ื—ื™ื‘ื•ืจ ืœื ื”ื™ื” ื ื—ื•ืฅ ื›ืœืœ.

ื•ืœืคืขืžื™ื ื™ืฉ ืœืš ืžื–ืœ ื•ื–ื” "ืคืฉื•ื˜ ืขื•ื‘ื“", ืœืคืขืžื™ื ื™ืฉ ืœื–ื” ื”ืฉืคืขื” ืœื ื ืขื™ืžื” ืขืœ ื”ื‘ื™ืฆื•ืขื™ื, ื•ืœืคืขืžื™ื ื–ื” ื ื•ืชืŸ ืืคืงื˜ื™ื ืฉื”ื ืœื’ืžืจื™ ืœื ืฆืคื•ื™ื™ื ืžื ืงื•ื“ืช ืžื‘ื˜ื• ืฉืœ ื”ืžืคืชื—.

PostgreSQL Antipatterns: "ื—ื™ื™ื‘ ืœื”ื™ืฉืืจ ืจืง ืื—ื“!"
ื˜ื•ื‘, ืื•ืœื™ ืœื ื›ืœ ื›ืš ืžืจื”ื™ื‘, ืื‘ืœ...

"ื–ื•ื’ ืžืชื•ืง": JOIN + DISTINCT

SELECT DISTINCT
  X.*
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
WHERE
  Y.bool_condition;

ื™ื”ื™ื” ื‘ืจื•ืจ ืžื” ื”ื ืจื•ืฆื™ื ื‘ื—ืจ ืจืฉื•ืžื•ืช X ืฉืขื‘ื•ืจืŸ ื™ืฉ ืจืฉื•ืžื•ืช ื‘-Y ื”ืงืฉื•ืจื•ืช ืœืชื ืื™ ืฉื”ืชืžืœื. ื›ืชื‘ ื‘ืงืฉื” ื“ืจืš JOIN - ืงื™ื‘ืœ ื›ืžื” ืขืจื›ื™ pk ืžืกืคืจ ืคืขืžื™ื (ื‘ื“ื™ื•ืง ื›ืžื” ืขืจื›ื™ื ืžืชืื™ืžื™ื ื”ื•ืคื™ืขื• ื‘-Y). ืื™ืš ืœื”ืกื™ืจ? ื‘ึผึฐื”ึถื—ืœึตื˜ DISTINCT!

ื–ื” "ืžืฉืžื—" ื‘ืžื™ื•ื—ื“ ื›ืืฉืจ ืœื›ืœ ืจืฉื•ืžื” X ื™ืฉ ื›ืžื” ืžืื•ืช ืจืฉื•ืžื•ืช Y ืงืฉื•ืจื•ืช, ื•ืื– ื”ื›ืคื™ืœื•ื™ื•ืช ืžื•ืกืจื•ืช ื‘ื’ื‘ื•ืจื”...

PostgreSQL Antipatterns: "ื—ื™ื™ื‘ ืœื”ื™ืฉืืจ ืจืง ืื—ื“!"

ืื™ืš ืœืชืงืŸ? ืžืœื›ืชื—ื™ืœื”, ื”ื‘ื™ื ื• ืฉื ื™ืชืŸ ืœืฉื ื•ืช ืืช ื”ื‘ืขื™ื” "ื‘ื—ืจ ืจืฉื•ืžื•ืช X ืฉืขื‘ื•ืจืŸ ื‘-Y ื™ืฉ ืœืคื—ื•ืช ืื—ืช ื”ืžืฉื•ื™ื›ืช ืœืชื ืื™ ืฉื”ืชืžืœื" - ืื—ืจื™ ื”ื›ืœ, ืื ื—ื ื• ืœื ืฆืจื™ื›ื™ื ืฉื•ื ื“ื‘ืจ ืžืชืงืœื™ื˜ ื”-Y ืขืฆืžื•.

ืžืงื•ื ืŸ ืงื™ื™ื

SELECT
  *
FROM
  X
WHERE
  EXISTS(
    SELECT
      NULL
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  );

ื—ืœืง ืžื”ื’ืจืกืื•ืช ืฉืœ PostgreSQL ืžื‘ื™ื ื•ืช ืฉื‘-EXISTS ืžืกืคื™ืง ืœืžืฆื•ื ืืช ื”ืขืจืš ื”ืจืืฉื•ืŸ ืฉืขื•ืœื”, ื™ืฉื ื™ื ื™ื•ืชืจ ืœื. ืœื›ืŸ ืื ื™ ืžืขื“ื™ืฃ ืœืฆื™ื™ืŸ ืชืžื™ื“ LIMIT 1 ื‘ืชื•ืš EXISTS.

ื”ืฆื˜ืจืคื•ืช ืœืจื•ื—ื‘

SELECT
  X.*
FROM
  X
, LATERAL (
    SELECT
      Y.*
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  ) Y
WHERE
  Y IS DISTINCT FROM NULL;

ืื•ืชื” ืืคืฉืจื•ืช ืžืืคืฉืจืช, ื‘ืžื™ื“ืช ื”ืฆื•ืจืš, ืœื”ื—ื–ื™ืจ ืžื™ื™ื“ื™ืช ื—ืœืง ืžื”ื ืชื•ื ื™ื ืžืชื•ืš ืจืฉื•ืžืช ื”-Y ื”ืžืฉื•ื™ื›ืช ืฉื ืžืฆืื”. ืืคืฉืจื•ืช ื“ื•ืžื” ื ื™ื“ื•ื ื” ื‘ืžืืžืจ "PostgreSQL Antipatterns: ืฉื™ื ื ื“ื™ืจ ื™ื’ื™ืข ืœืืžืฆืข JOIN".

"ืœืžื” ืœืฉืœื ื™ื•ืชืจ": DISTINCT [ON] + LIMIT 1

ื™ืชืจื•ืŸ ื ื•ืกืฃ ืฉืœ ื˜ืจื ืกืคื•ืจืžืฆื™ื•ืช ืฉืื™ืœืชื•ืช ื›ืืœื” ื”ื•ื ื”ื™ื›ื•ืœืช ืœื”ื’ื‘ื™ืœ ื‘ืงืœื•ืช ืืช ื”ื—ื™ืคื•ืฉ ืื—ืจ ืจืฉื•ืžื•ืช ืื ื™ืฉ ืฆื•ืจืš ืจืง ื‘ืื—ืช ืื• ื›ืžื” ืžื”ืŸ, ื›ืžื• ื‘ืžืงืจื” ื”ื‘ื:

SELECT DISTINCT ON(X.pk)
  *
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

ื›ืขืช ืื ื• ืงื•ืจืื™ื ืืช ื”ื‘ืงืฉื” ื•ืžื ืกื™ื ืœื”ื‘ื™ืŸ ืžื” ื”-DBMS ืžื•ืฆืข ืœืขืฉื•ืช:

  • ืžื—ื‘ืจื™ื ืืช ื”ืฉืœื˜ื™ื
  • ื™ื™ื—ื•ื“ื™ ืขืœ ื™ื“ื™ X.pk
  • ืžื‘ื™ืŸ ื”ืขืจื›ื™ื ื”ื ื•ืชืจื™ื, ื‘ื—ืจ ืื—ื“

ืื– ืžื” ืงื™ื‘ืœืช? "ืจืง ื›ื ื™ืกื” ืื—ืช" ืžื”ื™ื™ื—ื•ื“ื™ื™ื - ื•ืื ื ื™ืงื— ืืช ื–ื” ืžื”ืœื ื”ื™ื™ื—ื•ื“ื™ื™ื, ื”ืื ื”ืชื•ืฆืื” ืชืฉืชื ื” ืื™ื›ืฉื”ื•?.. "ื•ืื ืื™ืŸ ื”ื‘ื“ืœ, ืœืžื” ืœืฉืœื ื™ื•ืชืจ?"

SELECT
  *
FROM
  (
    SELECT
      *
    FROM
      X
    -- ััŽะดะฐ ะผะพะถะฝะพ ะฟะพะดััƒะฝัƒั‚ัŒ ะฟะพะดั…ะพะดัั‰ะธั… ัƒัะปะพะฒะธะน
    LIMIT 1 -- +1 Limit
  ) X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

ื•ื‘ื“ื™ื•ืง ืื•ืชื• ื ื•ืฉื ืขื GROUP BY + LIMIT 1.

"ืื ื™ ืจืง ืฆืจื™ืš ืœืฉืื•ืœ": GROUP + LIMIT ืžืจื•ืžื–

ื“ื‘ืจื™ื ื“ื•ืžื™ื ืžืชืจื—ืฉื™ื ื‘ืื—ืจื™ื ื‘ื“ื™ืงื•ืช ืื™-ืจื™ืงื ื•ืช ืกื™ืžื ื™ื ืื• CTE ืขื ื”ืชืงื“ืžื•ืช ื”ื‘ืงืฉื”:

...
CASE
  WHEN (
    SELECT
      count(*)
    FROM
      X
    LIMIT 1
  ) = 0 THEN ...

ืคื•ื ืงืฆื™ื•ืช ืฆื‘ื™ืจื” (count/min/max/sum/...) ืžื‘ื•ืฆืขื•ืช ื‘ื”ืฆืœื—ื” ืขืœ ื›ืœ ื”ืกื˜, ืืคื™ืœื• ืœืœื ื”ื•ืจืื•ืช ืžืคื•ืจืฉื•ืช GROUP BY. ืจืง ืขื LIMIT ื”ื ืœื ืžืื•ื“ ื™ื“ื™ื“ื•ืชื™ื™ื.

ื”ื™ื–ื ื™ื›ื•ืœ ืœื—ืฉื•ื‘ "ืื ื™ืฉ ืฉื ืจืฉื•ืžื•ืช, ืื– ืื ื™ ืœื ืฆืจื™ืš ื™ื•ืชืจ ืž-LIMIT". ืื‘ืœ ืืœ ืชืขืฉื” ืืช ื–ื”! ื›ื™ ืœื‘ืกื™ืก ื–ื”:

  • ืœืกืคื•ืจ ืžื” ืฉื”ื ืจื•ืฆื™ื ืœืคื™ ื›ืœ ื”ืจื™ืฉื•ืžื™ื
  • ืœืชืช ื›ืžื” ืฉื•ืจื•ืช ืฉื”ื ืžื‘ืงืฉื™ื

ื‘ื”ืชืื ืœืชื ืื™ ื”ื™ืขื“, ืจืื•ื™ ืœื‘ืฆืข ืื—ืช ืžื”ื”ื—ืœืคื•ืช ื”ื‘ืื•ืช:

  • (count + LIMIT 1) = 0 ืขืœ NOT EXISTS(LIMIT 1)
  • (count + LIMIT 1) > 0 ืขืœ EXISTS(LIMIT 1)
  • count >= N ืขืœ (SELECT count(*) FROM (... LIMIT N))

"ื›ืžื” ืœืฉืงื•ืœ ื‘ื’ืจืžื™ื": DISTINCT + LIMIT

SELECT DISTINCT
  pk
FROM
  X
LIMIT $1

ืžืคืชื— ื ืื™ื‘ื™ ืขืฉื•ื™ ืœื”ืืžื™ืŸ ื‘ืืžืช ื•ื‘ืชืžื™ื ืฉื”ื‘ืงืฉื” ืชืคืกื™ืง ืœื‘ืฆืข. ื‘ืจื’ืข ืฉืื ื• ืžื•ืฆืื™ื $1 ืžื”ืขืจื›ื™ื ื”ืฉื•ื ื™ื ื”ืจืืฉื•ื ื™ื ืฉื ืชืงืœื™ื ื‘ื”ื.

ืžืชื™ืฉื”ื• ื‘ืขืชื™ื“ ื–ื” ืขืฉื•ื™ ื•ื™ืขื‘ื•ื“ ื”ื•ื“ื•ืช ืœืฆื•ืžืช ื—ื“ืฉ ืื™ื ื“ืงืก ื“ืœื’ ืขืœ ืกืจื™ืงื”, ืืฉืจ ื™ื™ืฉื•ืžื• ื ืžืฆื ื›ืขืช ื‘ืขื™ื‘ื•ื“, ืืš ืขื“ื™ื™ืŸ ืœื.

ืœืขืช ืขืชื” ืงื•ื“ื ื›ืœ ื›ืœ ื”ืจืฉื•ืžื•ืช ื™ืื•ื—ื–ืจื•, ื™ื™ื—ื•ื“ื™ื™ื, ื•ืจืง ืžื”ื ื™ื•ื—ื–ืจ ื”ืกื›ื•ื ื”ืžื‘ื•ืงืฉ. ื–ื” ืขืฆื•ื‘ ื‘ืžื™ื•ื—ื“ ืื ืจืฆื™ื ื• ืžืฉื”ื• ื›ื–ื” $ 1 = 4, ื•ื™ืฉ ืžืื•ืช ืืœืคื™ ืจืฉื•ืžื•ืช ื‘ื˜ื‘ืœื”...

ื›ื“ื™ ืœื ืœื”ื™ื•ืช ืขืฆื•ื‘ื™ื ืœืฉื•ื•ื, ื‘ื•ืื• ื ืฉืชืžืฉ ื‘ืฉืื™ืœืชื” ืจืงื•ืจืกื™ื‘ื™ืช "DISTINCT ื”ื•ื ืœืขื ื™ื™ื" ืžืชื•ืš PostgreSQL Wiki:

PostgreSQL Antipatterns: "ื—ื™ื™ื‘ ืœื”ื™ืฉืืจ ืจืง ืื—ื“!"

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”