Zunächst einmal möchten Sie nicht verwenden char(50)
. Verwenden Sie varchar(50)
oder nur text
. Weiterlesen:
Unter Annahme der folgenden Regeln:
- Grundlegende Schnecken enden nie mit einem Strich.
- Doppelte Slugs werden mit einem Bindestrich und einer fortlaufenden Nummer (
-123
) versehen.
Beachten Sie, dass für alle folgenden Methoden Rennbedingungen gelten : Gleichzeitige Vorgänge identifizieren möglicherweise denselben "freien" Namen für den nächsten Slug.
Um sich dagegen zu verteidigen, können Sie eine EINZIGARTIGE Einschränkung auferlegen slug
und darauf vorbereitet sein, ein INSERT bei doppelter Schlüsselverletzung zu wiederholen oder zu Beginn der Transaktion eine Schreibsperre für die Tabelle aufzuheben.
Wenn Sie das Suffix mit einem Bindestrich auf den Namen des Basis-Slugs kleben und zulassen, dass Basic-Slugs in separaten Zahlen enden, ist die Spezifikation ein wenig mehrdeutig (siehe Kommentare). Ich schlage stattdessen ein eindeutiges Trennzeichen Ihrer Wahl vor (das ansonsten nicht zulässig ist).
Effizientes rCTE
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;
Bessere Leistung ohne rCTE
Wenn Sie sich Sorgen machen, dass Tausende von Slugs um denselben Slug konkurrieren oder generell die Leistung optimieren möchten, würde ich einen anderen, schnelleren Ansatz in Betracht ziehen.
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;
Wenn der Grund Slug noch nicht genommen wird, desto teurer zweite SELECT
wird nie ausgeführt - das gleiche wie oben, aber viel wichtiger ist hier. Überprüfen Sie mit EXPLAIN ANALYZE
, Postgres ist auf diese Weise bei LIMIT
Abfragen klug . Verbunden:
LIKE
Suchen Sie separat nach der führenden Zeichenfolge und dem Suffix, damit der Ausdruck einen grundlegenden btree-Index mit text_pattern_ops
like verwenden kann
CREATE INDEX article_slug_idx ON article (slug text_pattern_ops);
Ausführliche Erklärung:
Konvertieren Sie das Suffix in eine Ganzzahl, bevor Sie es anwenden max()
. Zahlen in der Textdarstellung funktionieren nicht.
Leistung optimieren
Um das Optimum zu erzielen, sollten Sie das vom Basis-Slug getrennte Suffix speichern und den Slug nach Bedarf verketten: 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
);
Die Abfrage nach einem neuen Slug lautet dann:
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
Idealerweise mit einem eindeutigen Index unterstützt (slug, suffix)
.
Abfrage nach Liste der Schnecken
In jeder Version von Postgres können Sie Zeilen in einem VALUES
Ausdruck bereitstellen .
SELECT *
FROM article
JOIN (
VALUES
('slug-foo'::text, 1)
('slug-bar',7)
) u(slug,suffix) USING (slug,suffix);
Sie können auch IN
eine Reihe von Zeilenausdrücken verwenden, die kürzer sind:
SELECT *
FROM article
WHERE (slug,suffix) IN (('slug-foo', 1), ('slug-bar',7));
Details unter dieser verwandten Frage (wie unten kommentiert):
Bei langen Listen ist der Ausdruck JOIN
zu einem VALUES
Ausdruck normalerweise schneller.
In Postgres 9.4 (heute veröffentlicht!) unnest()
Können Sie auch die neue Variante verwenden , um mehrere Arrays parallel zu entfernen.
Bei einem Array von Basis-Slugs und einem entsprechenden Array von Suffixen (gemäß Kommentar):
SELECT *
FROM article
JOIN unnest('{slug-foo,slug-bar}'::text[]
, '{1,7}'::int[]) AS u(slug,suffix) USING (slug,suffix);
bmw
,bmw 745
,bmw
, diemax
funktionieren in dritten Slug Wesen führenbmw-746
, obwohl wir im Idealfall wollen würdenbmw-1
hier. Die Verwendungcount
ist eine Option, die jedoch in bestimmten Fällen auch zu unerwarteten Schnecken führen würde. Insbesondere, wenn es einen Konflikt zwischen Titeln gibt, die mit Zahlen enden.