Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SQLアンチパターン第2版 データベースプログラミングで陥りがちな失敗とその対策 / Intr...

SQLアンチパターン第2版 データベースプログラミングで陥りがちな失敗とその対策 / Intro to SQL Antipatterns 2nd

SQLアンチパターン第2版 データベースプログラミングで陥りがちな失敗とその対策
Developers Summit 2025 Summer
2025年7月18日(金)
https://event.shoeisha.jp/devsumi/20250717/session/5937

Avatar for Takuto Wada

Takuto Wada

July 18, 2025
Tweet

More Decks by Takuto Wada

Other Decks in Programming

Transcript

  1. ॻ੶ʰ42-Ξϯνύλʔϯୈ൛ʱ w ೥݄ൃച w ϖʔδʢϖʔδ૿ʣ w ৽ͨʹͭͷষͱͷϛχɾΞϯνύλʔϯ͕௥Ճ w ࣮͸ݮͬͨষ΋͋ΔʢϚδοΫϏʔϯζʣ w

    طଘͷষ΋େ෯վగʢมߋՕॴ͸໿Օॴʣ w ίʔυαϯϓϧ͕1)1͔Β1ZUIPOʹ IUUQTXXXPSFJMMZDPKQCPPLT
  2. ɹ$47ྻΛෳ਺ͷߦʹ෼ׂ͢Δ ɹʮࢲͷίϯϐϡʔλʔͰ͸ಈ࡞͍ͯ͠Δͷʹʯ ɹ#*(*/5͸े෼ʹେ͖͍ʁ ɹՁ֨ͷอଘ ɹ༧໿ޠ ɹ͢΂ͯͷྻʹΠϯσοΫεΛ࡞੒͢Δ ɹ/05*/ /6--  ɹϙʔλϒϧ42-

    ɹΫΤϦͰϥϯμϜʹෳ਺ߦΛऔಘ͢Δ ɹϋογϡจࣈྻΛ7"3$)"3ܕͰ֨ೲ͢Δ ɹҾ༻ූ಺ͷΫΤϦύϥϝʔλ ɹάϧʔϓ͝ͱͷࣗಈΠϯΫϦϝϯτ ɹߏจΤϥʔϝοηʔδղಡͷ͢͢Ί ɹ໊લͷมߋ ɹ.Z42-ͷετΞυϓϩγʔδϟ ֤ষ຤ͷϛχɾΞϯνύλʔϯʢશͯ৽نॻ͖ԼΖ͠ʣ
  3. CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, product_name VARCHAR(1000),

    account_id BIGINT UNSIGNED, -- 他の列. . . FOREIGN KEY (account_id) REFERENCES Accounts(account_id) ); INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder', 12); มߋલͷςʔ ϒϧఆٛ
  4. ΞϯνύλʔϯɿΧϯϚ۠੾ΓϑΥʔϚοτͷϦετΛ֨ೲ͢Δ CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, product_name

    VARCHAR(1000), account_id VARCHAR(100), -- カンマ区切りのリスト -- 他の列 . . . ); INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder', '12,34'); カンマ区切りで 多対多関連を格納してしまう # " %
  5. ಛఆͷ੡඼ʹؔ࿈͢ΔΞΧ΢ϯτͷݕࡧ SELECT * FROM Products AS p JOIN Accounts AS

    a ON p.account_id REGEXP '\\b' || a.account_id || '\\b' WHERE p.product_id = 123; JOINに正規表現を使わなければならない…… # " %
  6. ू໿ΫΤϦͷ࡞੒ SELECT product_id, LENGTH(account_id) - LENGTH(REPLACE(account_id, ',', '')) + 1

    AS contacts_per_product FROM Products; カンマ区切りの文字列の長さからカンマ以外の文字列 の長さを引いたものを計算するような、本来は不要な トリックを使わなければならない # " %
  7. ಛఆͷ੡඼ʹؔ࿈͢ΔΞΧ΢ϯτͷߋ৽ product_id_to_search = 2 value_to_remove = '34' query = "SELECT

    product_id, account_id FROM Products WHERE product_id = %s" cursor.execute(query, (product_id_to_search,)) for (row) in cursor: (product_id, account_ids) = row account_id_list = account_ids.split(",") account_id_list.remove(value_to_remove) account_ids = ",".join(account_id_list) query = "UPDATE Products SET account_id = %s WHERE product_id = %s" cursor.execute(query, (account_ids, product_id,)) IDをリストから削除するには、 SQLを2つ発行しなければならない。 古いリストの取得と、更新したリストの保存 # " %
  8. ΞΧ΢ϯτ*%ͷଥ౰ੑݕূ INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual

    TurboBuilder', '12,34,banana'); アカウントIDではない不正な文字列も入ってしまう # " %
  9. Ϧετͷ௕͞ͷ੍ݶ UPDATE Products SET account_id = '10,14,18,22,26,30,34,38,42,46' WHERE product_id =

    123; UPDATE Products SET account_id = '101418,222630,343842,467790' WHERE product_id = 123; アカウントIDの長さ次第で 関連づけられる数が左右される アカウントIDの長さ次第で 関連付けられる数が左右される # " %
  10. CREATE TABLE Contacts ( product_id BIGINT UNSIGNED NOT NULL, account_id

    BIGINT UNSIGNED NOT NULL, PRIMARY KEY (product_id, account_id), FOREIGN KEY (product_id) REFERENCES Products(product_id), FOREIGN KEY (account_id) REFERENCES Accounts(account_id) ); ղܾࡦɿަࠩςʔ ϒϧΛ࡞੒͢Δ
  11. SELECT p.* FROM Products AS p JOIN Contacts AS c

    ON (p.product_id = c.product_id) WHERE c.account_id = 34; ಛఆͷΞΧ΢ϯτʹؔ࿈͢Δ੡඼ͷݕࡧʗಛఆͷ੡඼ʹؔ࿈͢ΔΞΧ΢ϯτͷݕࡧ SELECT a.* FROM Accounts AS a JOIN Contacts AS c ON (a.account_id = c.account_id) WHERE c.product_id = 123; 正規表現はもう不要で、 インデックスも活用できる 正規表現はもう不要で、 インデックスも活用できる
  12. SELECT product_id, COUNT(*) AS accounts_per_product FROM Contacts GROUP BY product_id;

    ू໿ΫΤϦͷ࡞੒ SELECT account_id, COUNT(*) AS products_per_account FROM Contacts GROUP BY account_id; シンプルに集約関数を使うだけでいい。 カンマ区切りの文字列の長さからカンマ以外の文字列 の長さを引いたものを計算するようなトリックは不要 シンプルに集約関数を使うだけでいい。 カンマ区切りの文字列の長さからカンマ以外の文字列の 長さを引いたものを計算するようなトリックはもう不要
  13. ಛఆͷ੡඼ʹؔ࿈͢ΔΞΧ΢ϯτͷߋ৽ INSERT INTO Contacts (product_id, account_id) VALUES (456, 34); DELETE

    FROM Contacts WHERE product_id = 456 AND account_id = 34; 関連付けの更新は 交差テーブルへの INSERT や DELETE 関連付けの更新は 交差テーブルへ INSERT や DELETE するだけ
  14. ϛχɾΞϯνύλʔϯɿ$47ྻΛෳ਺ͷߦʹ෼ׂ͢Δ WITH RECURSIVE cte AS ( SELECT product_id, product_name, SUBSTRING_INDEX(account_id,

    ',', 1) AS account_id, SUBSTRING(account_id, LENGTH(SUBSTRING_INDEX(account_id, ',', 1))+2) AS remainder FROM Products UNION ALL SELECT product_id, product_name, SUBSTRING_INDEX(remainder, ',', 1), SUBSTRING(remainder, LENGTH(SUBSTRING_INDEX(remainder, ',', 1))+2) FROM cte WHERE LENGTH(remainder) > 0 ) SELECT product_id, product_name, account_id FROM cte; カンマ区切りの列を再帰SQLで 複数の行に見せかけるトリック # " %