use*_*760 7 postgresql database-design cte recursive
我有一个文章表,我希望 slug 是独一无二的。
CREATE TABLE article (
title char(50) NOT NULL,
slug char(50) NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
当用户输入标题时,例如News on Apple,我想检查数据库以查看是否存在相应的 slug,例如news-on-apple。如果是这样,我将给一个数值添加后缀,直到找到一个唯一的值,例如news-on-apple-1. 可以通过递归 CTE 查询而不是在我的 ORM 中进行递归来实现。是否有一个很好的大概数字,我应该停止递归和出错。我可以想象人们使用相同的标题 1000 次,这将导致 1000 次查询只是为了创建 1 篇文章。
我对递归 CTE 的理解可能是不正确的,并且没有更好的方法来找到唯一的 slug。请提出任何替代方案。
首先,你不希望使用。使用char(50)varchar(50)或只是text. 阅读更多:
假设以下规则:
-123)为后缀。请注意,以下所有方法都受到竞争条件的影响:并发操作可能会为下一个 slug 标识相同的“空闲”名称。
为了防止它,您可以施加一个 UNIQUE 约束slug并准备在重复键违规时重复 INSERT,或者您在事务开始时在表上取出写锁。
如果您用破折号将后缀粘贴到基本 slug 名称并允许基本 slug 以单独的数字结尾,则规范有点含糊不清(请参阅评论)。我建议您选择一个唯一的分隔符(否则是不允许的)。
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;
Run Code Online (Sandbox Code Playgroud)
如果您担心数以千计的 slug 争夺同一个 slug 或通常想要优化性能,我会考虑一种不同的、更快的方法。
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;
Run Code Online (Sandbox Code Playgroud)
如果尚未使用基本 slug,SELECT则永远不会执行更昂贵的第二个- 与上面相同,但在这里更重要。检查一下EXPLAIN ANALYZE,Postgres 在LIMIT查询方面很聪明。有关的:
检查前导字符串和后缀分开,这样LIKE的表达可以使用基本的B树索引text_pattern_ops像
CREATE INDEX article_slug_idx ON article (slug text_pattern_ops);
Run Code Online (Sandbox Code Playgroud)
详细解释:
在应用之前将后缀转换为整数max()。文本表示中的数字不起作用。
要获得最佳效果,请考虑将后缀与基本 slug 分开存储,并根据需要连接 slug: 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
);
Run Code Online (Sandbox Code Playgroud)
然后对新 slug 的查询变为:
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
Run Code Online (Sandbox Code Playgroud)
理想情况下由 上的唯一索引支持(slug, suffix)。
在任何版本的 Postgres 中,您都可以在VALUES表达式中提供行。
SELECT *
FROM article
JOIN (
VALUES
('slug-foo'::text, 1)
('slug-bar',7)
) u(slug,suffix) USING (slug,suffix);
Run Code Online (Sandbox Code Playgroud)
您还可以使用IN一组较短的行类型表达式:
SELECT *
FROM article
WHERE (slug,suffix) IN (('slug-foo', 1), ('slug-bar',7));
Run Code Online (Sandbox Code Playgroud)
此相关问题下的详细信息(如下所述):
对于长列表,JOINtoVALUES表达式通常更快。
在 Postgres 9.4(今天发布!)中,您还可以使用 的新变体unnest()来并行取消嵌套多个数组。
给定一组基本 slug 和相应的后缀数组(根据评论):
SELECT *
FROM article
JOIN unnest('{slug-foo,slug-bar}'::text[]
, '{1,7}'::int[]) AS u(slug,suffix) USING (slug,suffix);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1715 次 |
| 最近记录: |