簡體   English   中英

Postgresql - 如果不存在則清除插入記錄的方法,如果存在則更新

[英]Postgresql - Clean way to insert records if they don't exist, update if they do

這是我的情況。 我有一個表格,其中包含一堆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)"

好的,我設法創建了一個測試用例。 結果是即使在新插入時也始終執行更新部分。 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規則留給讀者練習。

一些應該知道它或者非常接近這樣的人的好點;-)

PostgreSQL RULEs有什么用?

短篇故事:

  • 規則是否適用於SERIALBIGSERIAL
  • 這些規則是否適用於INSERTUPDATERETURNING子句?
  • 規則是否適用於random()類的東西?

所有這些事情歸結為這樣一個事實,即規則系統不是行驅動的,而是以你想象不到的方式轉換你的陳述。

做你自己和你的團隊一個忙,並停止使用角色做這樣的事情。

編輯 :您的問題在PostgreSQL社區中得到了很好的討論。 搜索關鍵詞是: MERGEUPSERT

我不知道這是否過於主觀,但我對你的解決方案的看法是:它都是關於語義的。 當我進行插入時,我期望插入而不是一些奇特的邏輯可能會插入但可能不是。 確實,這就是功能的用途。

首先,我會嘗試檢查程序中的URL,然后選擇是否插入或更新。 如果結果太慢,我會使用一個函數。 如果將其命名為insert_or_update_url ,則會自動獲得一些免費文檔。 重寫規則要求您具有一些隱含的知識,我通常會盡量避免這種情況。

從好的方面來說:如果有人復制數據但忘記規則和功能,你的解決方案可能會默默地破解(但這可能取決於其他約束),但是缺少的功能會尖叫。 不要誤會我的意思,我認為你的解決方案非常有創意和聰明。 對我的口味來說有點太模糊了。

在Postgres文檔中有一個使用簡單函數實現upsert / merge例子

永遠不要使用規則 - 它們是邪惡的。

在規則限定中,您不能引用其他表而不是舊表。 您應該在規則體中執行此操作。 這完全是因為規則只是一種通知重寫系統應該和不應該執行哪些轉換的方法。 規則不是觸發器,為每一行執行,但它們為查詢計划員提供了良好的按摩,並且很好地重寫計划。 來自文檔:

什么是規則資格? 這是一個限制,告訴我們何時應該執行規則的操作,何時不執行。 此限定條件只能引用偽相關NEW和/或OLD,它們基本上表示作為對象給出的關系(但具有特殊含義)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM