这是我的情况。我有一个包含大量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;
这似乎确实很有效。它可能会失去一些观点 “代码可读性”的立场,就像有人在看我的代码一样 第一次必须神奇地了解这个规则,但我 猜测可以通过良好的代码评论来解决 文档。
这个想法是否有任何其他缺点,或者可能是“你的想法 糟透了,你应该这样做/这个/方式而不是“评论?我在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)"
答案 0 :(得分:3)
好的,我设法创建了一个测试用例。结果是即使在新插入时也始终执行更新部分。 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规则留给读者练习。
答案 1 :(得分:2)
有些人应该知道或者非常接近这样的人的一些好处; - )
What are PostgreSQL RULEs good for?
短篇小说:
SERIAL
和BIGSERIAL
?RETURNING
和INSERT
的{{1}}条款?UPDATE
?所有这些事情归结为这样一个事实:规则系统不是行驱动的,而是以你想象不到的方式转换你的陈述。
让自己和你的团队互相帮助,并停止使用角色做这样的事情。
编辑:您的问题在PostgreSQL社区中得到了很好的讨论。搜索关键字为:random()
,MERGE
。
答案 2 :(得分:1)
我不知道这是否过于主观,但我对你的解决方案的看法是:它都是关于语义的。当我进行插入时,我期望插入而不是一些奇特的逻辑可能会插入但可能不是。确实,这就是功能的用途。
首先,我会尝试检查程序中的URL,然后选择是否插入或更新。如果结果太慢,我会使用一个函数。如果您将其命名为insert_or_update_url
,则会自动获得免费的文档。重写规则要求你有一些隐含的知识,我通常会尽量避免这种情况。
从好的方面来说:如果有人复制数据但忘记了规则和功能,你的解决方案可能会默默地破解(但这可能取决于其他限制),但缺少的功能会尖叫。不要误会我的意思,我认为你的解决方案非常有创意和聪明。对我的口味来说有点过于模糊。
答案 3 :(得分:0)
Postgres文档中有一个example of implementing upsert / merge using simple function。
永远不要使用规则 - 它们是邪恶的。
答案 4 :(得分:-1)
您不能在规则资格中引用其他表格而不是旧表格。 您应该在规则正文中执行此操作。 这完全是因为规则只是一种通知重写系统应该和不应该执行哪些转换的方法。规则不是触发器,为每一行执行,但它们为查询计划员提供了良好的按摩,并且很好地重写计划。 来自文档:
什么是规则资格?这是一个限制,告诉我们何时应该执行规则的操作,何时不执行。此限定条件只能引用伪相关NEW和/或OLD,它们基本上代表作为对象给出的关系(但具有特殊含义)。