Trước hết, bạn không muốn sử dụng char(50)
. Sử dụng varchar(50)
hoặc chỉ text
. Đọc thêm:
Giả sử các quy tắc sau:
- Sên cơ bản không bao giờ kết thúc với một dấu gạch ngang.
- Sên trùng lặp được thêm vào một dấu gạch ngang và một số liên tiếp (
-123
).
Lưu ý rằng tất cả các phương pháp sau phải tuân theo điều kiện cuộc đua : các hoạt động đồng thời có thể xác định cùng tên "miễn phí" cho sên tiếp theo.
Để chống lại điều đó, bạn có thể áp đặt một ràng buộc KHÔNG GIỚI HẠN slug
và sẵn sàng lặp lại một CHỨNG CHỈ khi vi phạm khóa trùng lặp hoặc bạn lấy ra một khóa ghi trên bàn khi bắt đầu giao dịch.
Nếu bạn dán hậu tố vào tên sên cơ bản bằng dấu gạch ngang và cho phép các sên cơ bản kết thúc bằng các số riêng biệt, thì thông số kỹ thuật là một chút mơ hồ (xem bình luận). Tôi đề nghị một dấu phân cách duy nhất của sự lựa chọn của bạn thay vào đó (không được phép).
RCTE hiệu quả
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;
Hiệu suất tốt hơn mà không cần rCTE
Nếu bạn lo lắng về hàng ngàn con sên cạnh tranh cho cùng một con sên hoặc thường muốn tối ưu hóa hiệu suất, tôi sẽ xem xét một cách tiếp cận khác, nhanh hơn.
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;
Nếu sên cơ bản là không được nêu ra, thứ hai đắt hơn SELECT
là không bao giờ thực hiện - tương tự như trên, nhưng nhiều hơn nữa quan trọng ở đây. Kiểm tra với EXPLAIN ANALYZE
, Postgres là thông minh theo cách đó với LIMIT
các truy vấn. Liên quan:
Kiểm tra riêng chuỗi đầu và hậu tố, vì vậy LIKE
biểu thức có thể sử dụng chỉ mục btree cơ bản với text_pattern_ops
like
CREATE INDEX article_slug_idx ON article (slug text_pattern_ops);
Giải thích chi tiết:
Chuyển đổi hậu tố thành số nguyên trước khi bạn áp dụng max()
. Các số trong biểu diễn văn bản không hoạt động.
Tối ưu hóa hiệu suất
Để có được sự tối ưu, hãy xem xét việc lưu trữ hậu tố tách biệt với sên cơ bản và nối sên khi cần: 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
);
Truy vấn cho một con sên mới sau đó trở thành:
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
Hỗ trợ lý tưởng với một chỉ số duy nhất trên (slug, suffix)
.
Truy vấn danh sách sên
Trong bất kỳ phiên bản nào của Postgres, bạn có thể cung cấp các hàng trong một VALUES
biểu thức.
SELECT *
FROM article
JOIN (
VALUES
('slug-foo'::text, 1)
('slug-bar',7)
) u(slug,suffix) USING (slug,suffix);
Bạn cũng có thể sử dụng IN
với một tập hợp các biểu thức loại hàng ngắn hơn:
SELECT *
FROM article
WHERE (slug,suffix) IN (('slug-foo', 1), ('slug-bar',7));
Chi tiết theo câu hỏi liên quan này (như bình luận bên dưới):
Đối với danh sách dài, JOIN
đến một VALUES
biểu hiện thường nhanh hơn.
Trong Postgres 9.4 (phát hành ngày hôm nay!) Bạn cũng có thể sử dụng biến thể mới của unnest()
nhiều mảng không nhất quán song song.
Cho một mảng các sên cơ bản và một mảng các hậu tố tương ứng (theo nhận xét):
SELECT *
FROM article
JOIN unnest('{slug-foo,slug-bar}'::text[]
, '{1,7}'::int[]) AS u(slug,suffix) USING (slug,suffix);
bmw
,bmw 745
,bmw
, cácmax
chức năng sẽ dẫn đến sên con thứ babmw-746
, mặc dù chúng tôi sẽ lý tưởng muốnbmw-1
ở đây. Sử dụngcount
là một tùy chọn nhưng điều đó cũng sẽ tạo ra sên bất ngờ trong một số trường hợp nhất định. Cụ thể khi có xung đột giữa các tiêu đề kết thúc bằng số.