在 Postgres 的单个索引中包含多个列
Posted
技术标签:
【中文标题】在 Postgres 的单个索引中包含多个列【英文标题】:Including multiple columns in a single index in Postgres 【发布时间】:2011-07-02 18:03:52 【问题描述】:我有一个包含两列的“用户”表,“电子邮件”和“新电子邮件”。我需要:
覆盖两列的不区分大小写的唯一性约束 - 即,如果“Bob@Example.com”出现在一行的“电子邮件”列中,则将“bob@example.com”插入另一行(甚至同一行的)“new_email”列应该失败。
在“电子邮件”或“新电子邮件”字段中快速不区分大小写地搜索给定电子邮件地址 - 即查找新电子邮件或电子邮件为“Bob@example.com”的行,不区分大小写。
我知道我可以通过创建一个相关的“电子邮件”表来更轻松地做到这一点,但我希望从多个应用程序中查找该表中的用户(按主键),我想避免在各个地方复制连接逻辑以检索他们的电子邮件。因此,如果可能的话,我认为某种表达索引是最好的。
如果这不可能,我想我的下一个最佳选择是创建一个视图,其他应用程序可以使用该视图轻松获取用户的电子邮件及其其他信息,但我不知道该怎么做要么。
我正在使用 Postgres 8.4。谢谢!
【问题讨论】:
【参考方案1】:我认为您必须使用触发器来强制执行跨列唯一性约束。如果您在每列上添加唯一索引,然后触发类似这样的事情(未经测试,我的头代码顶部):
CREATE FUNCTION no_dups_allowed() RETURNS trigger AS $$
DECLARE
r ROW;
BEGIN
SELECT 1 INTO r
FROM users
WHERE LOWER(email) = LOWER(NEW.email_new)
OR LOWER(email_new) = LOWER(NEW.email);
IF FOUND THEN
-- Found a duplicate so it is time for a hissy fit!
RAISE 'Duplicate email address found' USING ERRCODE = 'unique_violation';
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
您需要类似的东西作为 BEFORE INSERT 和 BEFORE UPDATE 触发器。该触发器将负责捕获跨列重复项,而唯一索引将负责处理列内重复项。
一些有用的参考资料:
FOUND
RAISE
Triggers
Trigger Procedures
无论如何,您都需要为您的查询使用单独的索引,并且使用索引的唯一性一半可以简化您的触发器,因为它只处理跨列部分;如果您尝试在触发器中完成所有操作,那么您必须注意更新行而不真正更改 email
或 email_new
列。
对于查询的一半,您可以 create a view 使用 UNION
来组合两列。您还可以创建一个函数来将用户的电子邮件地址合并到一个列表中。如果不了解这些其他查询的更多细节,很难说哪个最好,但我怀疑修复所有其他查询以了解 email
和 email_new
将是最好的方法;您必须更新所有其他查询才能使用视图或函数,那么为什么要构建视图或函数呢?
【讨论】:
两个小写的UNIQUE INDEX
函数和表格检查是你最好的选择。不必调用触发器来解决此问题。使用下面@Scott 建议的推导。
@Sean:Scott 的解决方案很好,但 COALESCE 留下了一个漏洞。
同意,我不会使用COALESCE
或两列将旧值和当前值存储在一个表中。【参考方案2】:
不需要触发器。试试这个:
create table et (email text, email2 text);
create unique index et_u on et (coalesce(lower(email),lower(email2)));
insert into et (email,email2) values ('scott@gmail.com',NULL);
insert into et (email,email2) values ('scott@gmail.com',NULL);
ERROR: duplicate key value violates unique constraint "et_u"
insert into et (email,email2) values (NULL,'scott@gmail.com');
ERROR: duplicate key value violates unique constraint "et_u"
insert into et (email,email2) values (NULL,'Scott@gmail.com');
ERROR: duplicate key value violates unique constraint "et_u"
【讨论】:
嗯,这并不完美,我可以同时在两个字段中输入相同的电子邮件。因此,您还需要一个检查约束,即 emailemail2 。 alter table et add constraint et_nn check (emailemail2); 这很有趣。 Postgres 是否足够聪明,可以在查询电子邮件地址时使用该索引?喜欢WHERE lower(email) = 'scott@gmail.com'
?还是有另一种方法可以让我对查询进行表述以使其做到这一点?
如果在还没有“yy@example.com”地址的情况下插入(NULL,'xx@example.com')
,然后插入('yy@example.com','xx@example.com')
,则会失败,COALESCE 有效地隐藏了email2
以防止唯一性检查email
不为 NULL。以上是关于在 Postgres 的单个索引中包含多个列的主要内容,如果未能解决你的问题,请参考以下文章