スラグを一意にしたい記事の表があります。
CREATE TABLE article (
title char(50) NOT NULL,
slug char(50) NOT NULL
);
ユーザーがタイトルを入力したとき。 News on Apple
、データベースをチェックして、対応するスラッグが存在するかどうかを確認します。 news-on-Apple
。一致する場合は、一意の数値が見つかるまで数値のサフィックスを付けます。 news-on-Apple-1
。 ORMで再帰を行う代わりに、再帰的なCTEクエリでそれを実現できますか?再帰を停止してエラーを出すべき良い球場番号はありますか?同じタイトルを1000回使用していて、1つの記事を作成するだけで1000のクエリが発生することを想像できます。
再帰CTEについての私の理解が正しくなく、ユニークなスラッグを見つけるためのより良い方法がない可能性があります。代替案があれば提案してください。
まず、あなたはnot使いたい 。 char(50)
varchar(50)
または単にtext
を使用します。続きを読む:
次のルールを想定しています。
-123
_)がサフィックスとして付加されます。以下のすべてのメソッドは競合状態の影響を受けることに注意してください。同時操作は次のスラグに対して同じ「フリー」名を識別する可能性があります。
これを防ぐには、slug
にUNIQUE制約を課し、重複キー違反時にINSERTを繰り返す準備をするか、またはテーブルの開始時にテーブルの書き込みロックを解除しますトランザクション。
接尾辞をダッシュで基本的なスラッグの名前に接着し、基本的なスラッグが別の数字で終わることを許可する場合、仕様はごくわずかですあいまい(コメントを参照)。代わりに、独自のdelimiterをお勧めします(それ以外の場合は許可されません)。
_WITH RECURSIVE
input AS (SELECT 'news-on-Apple'::text AS slug) -- input basic slug here once
, cte AS (
SELECT slug || '-' AS slug -- append '-' once, if basic slug exists
, 1 as suffix -- start with suffix 1
FROM article
JOIN input USING (slug)
UNION ALL
SELECT c.slug, c.suffix + 1 -- increment by 1 ...
FROM cte c
JOIN article a ON a.slug = c.slug || c.suffix -- ... if slug-n already exists
)
(
SELECT slug || suffix AS slug
FROM cte
ORDER BY suffix DESC -- pick the last (free) one
LIMIT 1
) -- parentheses required
UNION ALL -- if the basic slug wasn't taken, fall back to that
SELECT slug FROM input
LIMIT 1;
_
何千ものスラグが同じスラグで競合することを心配している場合、または一般的にパフォーマンスを最適化したい場合は、別の高速なアプローチを検討します。
_WITH input AS (SELECT 'news-on-Apple'::text AS slug
, 'news-on-Apple-'::text AS slug1) -- input basic slug here
SELECT i.slug
FROM input i
LEFT JOIN article a USING (slug)
WHERE a.slug IS NULL -- doesn't exist yet.
UNION ALL
( -- parentheses required
SELECT i.slug1 || COALESCE(right(a.slug, length(i.slug1) * -1)::int + 1, 1)
FROM input i
LEFT JOIN article a ON a.slug LIKE (i.slug1 || '%') -- match up to last "-"
AND right(a.slug, length(i.slug1) * -1) ~ '^\d+$' -- suffix numbers only
ORDER BY right(a.slug, length(i.slug1) * -1)::int DESC
)
LIMIT 1;
_
基本的なスラッグがまだ取得されていない場合、より高価な2番目のSELECT
は実行されない-上記と同じですが、ここでははるかに重要です。 _EXPLAIN ANALYZE
_で確認してください。PostgresはLIMIT
クエリでそのようにスマートです。関連:
先頭の文字列とサフィックスを個別にチェックして、LIKE
式が_text_pattern_ops
_のような基本的なbtreeインデックスを使用できるようにします
_CREATE INDEX article_slug_idx ON article (slug text_pattern_ops);
_
詳細な説明:
max()
を適用する前に、サフィックスを整数に変換してください。テキスト表現の数値は機能しません。
最適な状態にするには、基本的なスラッグから分離されたサフィックスを保存し、必要に応じてスラッグを連結することを検討してください:concat_ws('-' , slug, suffix::text) AS slug
_CREATE TABLE article (
article_id serial PRIMARY KEY
, title text NOT NULL
, slug text NOT NULL
, suffix int
);
_
新しいスラッグのクエリは次のようになります。
_SELECT slug
|| COALESCE((
SELECT '-'::text || (max(suffix) + 1)::text
FROM article a
WHERE a.slug = i.slug), '') As slug
FROM (SELECT 'news-on-Apple'::text AS slug) i -- input basic slug here
_
理想的には、_(slug, suffix)
_の一意のインデックスでサポートされます。
Postgresのany versionでは、VALUES
式で行を提供できます。
_SELECT *
FROM article
JOIN (
VALUES
('slug-foo'::text, 1)
('slug-bar',7)
) u(slug,suffix) USING (slug,suffix);
_
IN
を行タイプの式のセットで使用することもできます。
_SELECT *
FROM article
WHERE (slug,suffix) IN (('slug-foo', 1), ('slug-bar',7));
_
この関連質問の詳細(以下でコメント):
長いリストの場合、JOIN
からVALUES
への式の方が通常は高速です。
Postgresでは9.4(本日リリース!) unnest()
の新しいバリアントを使用して、複数の配列を並列にネスト解除することもできます。
基本的なスラッグの配列と対応するサフィックスの配列(コメントどおり)を指定します。
_SELECT *
FROM article
JOIN unnest('{slug-foo,slug-bar}'::text[]
, '{1,7}'::int[]) AS u(slug,suffix) USING (slug,suffix);
_