5

这是我的情况。我有一张包含一堆 URL 和与之关联的抓取日期的表格。当我的程序处理一个 URL 时,我想插入一个带有抓取日期的新行。如果 URL 已经存在,我想将抓取日期更新为当前日期时间。对于 MS SQL 或 Oracle,我可能会为此使用 MERGE 命令。对于 mySQL,我可能会使用 ON DUPLICATE KEY UPDATE 语法。

我可以在我的程序中执行多个查询,这可能是线程安全的,也可能不是线程安全的。我可以编写一个具有各种 IF...ELSE 逻辑的 SQL 函数。但是,为了尝试我以前从未使用过的 Postgres 功能,我正在考虑创建一个 INSERT 规则 - 如下所示:

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;

这似乎真的很好用。它可能在“代码可读性”的角度上失去了一些观点,因为第一次看我的代码的人必须神奇地知道这个规则,但我想这可以通过良好的代码注释和文档来解决。

这个想法是否还有其他缺点,或者可能是“你的想法很糟糕,你应该这样做 /this/ 方式”评论?如果这很重要,我在 PG 9.0 上。

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

"Insert  (cost=2.79..2.81 rows=1 width=0)"
"  InitPlan 1 (returns $0)"
"    ->  Seq Scan on pages p  (cost=0.00..2.79 rows=1 width=0)"
"          Filter: ('http://www.foo.com'::text = lower((url)::text))"
"  ->  Result  (cost=0.00..0.01 rows=1 width=0)"
"        One-Time Filter: ($0 IS NOT TRUE)"
""
"Update  (cost=2.79..5.46 rows=1 width=111)"
"  InitPlan 1 (returns $0)"
"    ->  Seq Scan on pages p  (cost=0.00..2.79 rows=1 width=0)"
"          Filter: ('http://www.foo.com'::text = lower((url)::text))"
"  ->  Result  (cost=0.00..2.67 rows=1 width=111)"
"        One-Time Filter: $0"
"        ->  Seq Scan on pages  (cost=0.00..2.66 rows=1 width=111)"
"              Filter: ((url)::text = 'http://www.foo.com'::text)"
4

5 回答 5

5

好的,我设法创建了一个测试用例。结果是始终执行更新部分,即使是在新插入时也是如此。COPY 似乎绕过了规则系统。[为清楚起见,我已将其放入单独的回复中]

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)

该视图对于防止重写系统进入递归是必要的。DELETE 规则的构建留给读者作为练习。

于 2011-09-18T21:56:51.863 回答
2

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

PostgreSQL 规则有什么用?

短篇故事:

  • 规则是否适用于SERIALand BIGSERIAL
  • 规则是否适用于and的RETURNING子句?INSERTUPDATE
  • 这些规则是否适用于类似的东西random()

所有这些都归结为这样一个事实,即规则系统不是行驱动的,而是以您从未想象过的方式转换您的语句。

帮自己和你的队友一个忙,停止使用角色来做类似的事情。

编辑:您的问题在 PostgreSQL 社区中得到了很好的讨论。搜索关键字是:MERGE, UPSERT

于 2011-09-18T19:43:42.323 回答
1

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

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

从好的方面来说:如果有人复制了数据但忘记了规则和函数,您的解决方案可能会默默地中断(但这可能取决于其他约束),但缺少的函数会尖叫。不要误会我的意思,我认为您的解决方案非常有创意和聪明。对我的口味来说有点太模糊了。

于 2011-09-18T19:36:21.190 回答
0

在 Postgres 文档中有一个使用简单函数实现upsert / merge的示例。

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

于 2011-09-18T22:04:57.380 回答
-1

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

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

于 2011-09-18T19:34:53.313 回答