Вот моя ситуация. У меня есть таблица с кучей URL-адресов и дат сканирования, связанных с ними. Когда моя программа обрабатывает URL-адрес, я хочу ВСТАВИТЬ новую строку с датой сканирования. Если URL-адрес уже существует, я хочу обновить дату сканирования до текущей даты и времени. С MS SQL или Oracle я бы, вероятно, использовал для этого команду MERGE. С mySQL я бы, вероятно, использовал синтаксис ON DUPLICATE KEY UPDATE.
Я мог бы выполнять несколько запросов в своей программе, которые могут быть потокобезопасными, а могут и не быть. Я мог бы написать функцию SQL, которая имеет различную логику IF...ELSE. Однако, чтобы опробовать функции 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)"