Database Postgresql—如果记录不存在,则插入记录的干净方法';不存在,如果存在则更新

Database Postgresql—如果记录不存在,则插入记录的干净方法';不存在,如果存在则更新,database,postgresql,rules,upsert,Database,Postgresql,Rules,Upsert,这是我的情况。我有一个包含一堆URL和爬网的表 与之关联的日期。当我的程序处理URL时,我希望 插入具有爬网日期的新行。如果URL已经存在,我将 要将爬网日期更新为当前日期时间。使用MS SQL或 Oracle我可能会为此使用MERGE命令。有了mySQL,我会 可能使用重复密钥更新语法 我可以在我的程序中执行多个查询,可能是,也可能不是 线程安全。我可以写一个SQL函数,它有各种IF…ELSE 逻辑。然而,为了尝试Postgres的功能,我已经 以前从未使用过,我正在考虑创建一个插入规则- 大

这是我的情况。我有一个包含一堆URL和爬网的表 与之关联的日期。当我的程序处理URL时,我希望 插入具有爬网日期的新行。如果URL已经存在,我将 要将爬网日期更新为当前日期时间。使用MS SQL或 Oracle我可能会为此使用MERGE命令。有了mySQL,我会 可能使用重复密钥更新语法

我可以在我的程序中执行多个查询,可能是,也可能不是 线程安全。我可以写一个SQL函数,它有各种IF…ELSE 逻辑。然而,为了尝试Postgres的功能,我已经 以前从未使用过,我正在考虑创建一个插入规则- 大概是这样的:

CREATE RULE Pages_Upsert AS ON INSERT TO Pages
  WHERE EXISTS (SELECT 1 from Pages P where NEW.Url = P.Url)
  DO INSTEAD
     UPDATE Pages SET LastCrawled = NOW(), Html = NEW.Html WHERE Url = NEW.Url;
这似乎真的很有效。它可能会在比赛中失去一些分数 “代码可读性”的观点,就像有人在看我的代码 第一次必须神奇地知道这个规则,但我 我想这可以通过良好的代码注释和 文件

这个想法还有其他缺点吗,或者可能是“你的想法” 糟透了,你应该这样做,而不是“评论”?我在9.0页,如果 这很重要

更新:查询计划,因为有人需要它:)


您不能在规则限定中引用除旧表和新表之外的其他表。 您应该在规则体中执行此操作。 这都是因为规则只是一种通知重写系统应该和不应该执行哪些转换的方法。规则不是触发器,对每一行都执行,但它们给查询规划器一个很好的信息,并要求它很好地重写计划。 从文档中:

什么是规则限定?它是一个限制,告诉何时应该执行规则的操作,何时不执行。此限定只能引用新的和/或旧的伪关系,它们基本上表示作为对象给出的关系(但具有特殊含义)


我不知道这是否太主观了,但我对你的解决方案的看法是:这都是关于语义的。当我做一个插入时,我期望的是一个插入,而不是一些可能做插入但可能不做的奇特逻辑。事实上,这就是函数的用途

首先,我会尝试在您的程序中检查URL,然后选择是插入还是更新。如果结果太慢,我会使用函数。如果您将其命名为
insert\u或\u update\u url
,您将自动免费获得一些文档。重写规则要求你有一些隐含的知识,我通常会尽量避免


有利的一面是:如果有人复制数据但忘记了规则和函数,您的解决方案可能会无声地崩溃(但这可能取决于其他约束),但丢失的函数会崩溃。别误会我的意思,我认为你的解决方案非常有创意和聪明。对我来说,只是有点太模糊了。

一些应该知道的人或与这样的人非常亲近的人的优点;-)

短篇故事:

  • 这些规则是否适用于
    SERIAL
    BIGSERIAL
  • 规则是否与
    插入
    更新
    返回
    子句配合良好
  • 规则是否适用于
    random()
所有这些都归结为一个事实,即规则系统不是行驱动的,而是以一种你从未想象过的方式转换你的语句

帮你自己和你的队友一个忙,不要再为这样的事情使用角色了


编辑:PostgreSQL社区对您的问题进行了深入讨论。搜索关键字是:
MERGE
UPSERT

好的,我设法创建了一个测试用例。结果是更新部分总是被执行,即使是在一个新的插入上。复制似乎绕过了规则系统。 [为清楚起见,我已将此作为单独答复]

DROP TABLE pages CASCADE;
CREATE TABLE pages
    ( url VARCHAR NOT NULL  PRIMARY KEY
    , html VARCHAR
    , last TIMESTAMP
    );

INSERT INTO pages(url,html,last) VALUES ('www.example.com://page1' , 'meuk1' , '2001-09-18 23:30:00'::timestamp );

CREATE RULE Pages_Upsert AS ON INSERT TO pages
  WHERE EXISTS (SELECT 1 from pages P where NEW.url = P.url)
     DO INSTEAD (
     UPDATE pages SET html=new.html , last = NOW() WHERE url = NEW.url
    );

INSERT INTO pages(url,html,last) VALUES ('www.example.com://page2' , 'meuk2' , '2002-09-18 23:30:00':: timestamp );
INSERT INTO pages(url,html,last) VALUES ('www.example.com://page3' , 'meuk3' , '2003-09-18 23:30:00':: timestamp );

INSERT INTO pages(url,html,last) SELECT pp.url || '/added'::text, pp.html || '.html'::text , pp.last + interval '20 years' FROM pages pp;

COPY pages(url,html,last) FROM STDIN;
www.example.com://pageX     stdin   2000-09-18 23:30:00
\.

SELECT * FROM pages;
结果是:

              url              |    html    |            last            
-------------------------------+------------+----------------------------
 www.example.com://page1       | meuk1      | 2001-09-18 23:30:00
 www.example.com://page2       | meuk2      | 2011-09-18 23:48:30.775373
 www.example.com://page3       | meuk3      | 2011-09-18 23:48:30.783758
 www.example.com://page1/added | meuk1.html | 2011-09-18 23:48:30.792097
 www.example.com://page2/added | meuk2.html | 2011-09-18 23:48:30.792097
 www.example.com://page3/added | meuk3.html | 2011-09-18 23:48:30.792097
 www.example.com://pageX       | stdin      | 2000-09-18 23:30:00
 (7 rows)
更新:只是为了证明这是可以做到的:

INSERT INTO pages(url,html,last) VALUES ('www.example.com://page1' , 'meuk1' , '2001-09-18 23:30:00'::timestamp );
CREATE VIEW vpages AS (SELECT * from pages);

CREATE RULE Pages_Upsert AS ON INSERT TO vpages
  DO INSTEAD (
     UPDATE pages p0
     SET html=NEW.html , last = NOW() WHERE p0.url = NEW.url
    ;
     INSERT INTO pages (url,html,last)
    SELECT NEW.url, NEW.html, NEW.last
        WHERE NOT EXISTS ( SELECT * FROM pages p1 WHERE p1.url = NEW.url)
    );

CREATE RULE Pages_Indate AS ON UPDATE TO vpages
  DO INSTEAD (
     INSERT INTO pages (url,html,last)
    SELECT NEW.url, NEW.html, NEW.last
        WHERE NOT EXISTS ( SELECT * FROM pages p1 WHERE p1.url = OLD.url)
        ;
     UPDATE pages p0
     SET html=NEW.html , last = NEW.last WHERE p0.url = NEW.url
        ;
    );

INSERT INTO vpages(url,html,last) VALUES ('www.example.com://page2' , 'meuk2' , '2002-09-18 23:30:00':: timestamp );
INSERT INTO vpages(url,html,last) VALUES ('www.example.com://page3' , 'meuk3' , '2003-09-18 23:30:00':: timestamp );

INSERT INTO vpages(url,html,last) SELECT pp.url || '/added'::text, pp.html || '.html'::text , pp.last + interval '20 years' FROM vpages pp;
UPDATE vpages SET last = last + interval '-10 years' WHERE url = 'www.example.com://page1' ;

-- Copy does NOT work on views
-- COPY vpages(url,html,last) FROM STDIN;
-- www.example.com://pageX    stdin    2000-09-18 23:30:00
-- \.

SELECT * FROM vpages;
结果:

INSERT 0 1
INSERT 0 1
INSERT 0 3
UPDATE 1
              url              |    html    |        last         
-------------------------------+------------+---------------------
 www.example.com://page2       | meuk2      | 2002-09-18 23:30:00
 www.example.com://page3       | meuk3      | 2003-09-18 23:30:00
 www.example.com://page1/added | meuk1.html | 2021-09-18 23:30:00
 www.example.com://page2/added | meuk2.html | 2022-09-18 23:30:00
 www.example.com://page3/added | meuk3.html | 2023-09-18 23:30:00
 www.example.com://page1       | meuk1      | 1991-09-18 23:30:00
(6 rows)
该视图是防止重写系统进入递归所必需的。 删除规则的构造留给读者作为练习。

Postgres文档中有一个


永远不要使用规则——它们是邪恶的。

是的,我有点同意。如果我的程序的多个实例同时运行,那么先检查行是否存在速度太慢,并且不是事务安全的。PG函数可能会解决这个问题,而且速度更快。表模式禁止同一URL存在两次,因此复制数据不是问题,而且这些数据本质上是暂时的,因此不是非常重要。也许有一天,PG将具有UPSERT能力,这不会成为问题。您的“WHERE EXISTS(从P WHERE NEW.Url=P.Url页面中选择1)”资格无效。你试过了吗?我刚试过。这是可以接受的,而且似乎有效。很抱歉但根据文件,它不应该起作用。这可能是因为,虽然它可以工作,但当它遇到更复杂的查询(如插入到..选择从..或自连接)时,它会生成错误的计划。我认为普遍的共识是“是的,这至少在我的特定情况下会工作”,但“这是黑客行为,你在玩火,很难阅读,规则很糟糕,嗯,凯。”。。这是一个很好的借口来玩弄我以前没有使用过的PG功能,但我认为从长远来看,这不是正确的解决方案。请将生成的查询计划(对于目标表上的插入操作)添加到post。顺便说一句,在较低的位置(url)上实际上有一个索引在页面上——我认为这是在进行顺序扫描,因为目前表中只有4行,而且计划者认为只查看4行比点击索引更快。猜猜看,谢谢。我也差不多。更新部分总是发生。如果您实际插入页面(…)值(…),lastcrawled的值将被now()覆盖,即使在插入之前密钥不存在
INSERT 0 1
INSERT 0 1
INSERT 0 3
UPDATE 1
              url              |    html    |        last         
-------------------------------+------------+---------------------
 www.example.com://page2       | meuk2      | 2002-09-18 23:30:00
 www.example.com://page3       | meuk3      | 2003-09-18 23:30:00
 www.example.com://page1/added | meuk1.html | 2021-09-18 23:30:00
 www.example.com://page2/added | meuk2.html | 2022-09-18 23:30:00
 www.example.com://page3/added | meuk3.html | 2023-09-18 23:30:00
 www.example.com://page1       | meuk1      | 1991-09-18 23:30:00
(6 rows)