Модифицирана дефиниция на таблица
Ако наистина имате нужда тези колони да бъдат NOT NULL
и наистина се нуждаете от низа 'default'
по подразбиране за engine_slug
, бих посъветвал да се въведат стойности по подразбиране на колоните:
COLUMN | TYPE | Modifiers
-----------------+-------------------------+---------------------
id | INTEGER | NOT NULL DEFAULT ...
engine_slug | CHARACTER VARYING(200) | NOT NULL DEFAULT 'default'
content_type_id | INTEGER | NOT NULL
object_id | text | NOT NULL
object_id_int | INTEGER |
title | CHARACTER VARYING(1000) | NOT NULL
description | text | NOT NULL DEFAULT ''
content | text | NOT NULL
url | CHARACTER VARYING(1000) | NOT NULL DEFAULT ''
meta_encoded | text | NOT NULL DEFAULT '{}'
search_tsv | tsvector | NOT NULL
...
DDL изразът би бил:
ALTER TABLE watson_searchentry ALTER COLUMN engine_slug DEFAULT 'default';
И т.н.
Тогава не е необходимо да вмъквате тези стойности ръчно всеки път.
Също така:object_id text NOT NULL, object_id_int INTEGER
? Това е странно. Предполагам, че имате своите причини...
Ще изпълня актуализираното ви изискване:
Разбира се, четрябвате добавете УНИКАЛНО ограничение за налагане на вашите изисквания:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)
Ще се използва придружаващият индекс. С тази заявка за начало.
Между другото почти никога не използвам varchar(n)
в Postgres. Само text
. Ето една причина.
Заявка с CTE, модифициращи данни
Това може да бъде пренаписано като единична SQL заявка с модифициращи данни общи таблични изрази, наричани също "записваеми" CTE. Изисква Postgres 9.1 или по-нова версия.
Освен това, тази заявка изтрива само това, което трябва да бъде изтрито, и актуализира това, което може да бъде актуализирано.
WITH ctyp AS (
SELECT id AS content_type_id
FROM django_content_type
WHERE app_label = 'web'
AND model = 'member'
)
, sel AS (
SELECT ctyp.content_type_id
,m.id AS object_id_int
,m.id::text AS object_id -- explicit cast!
,m.name AS title
,concat_ws(' ', u.email,m.normalized_name,c.name) AS content
-- other columns have column default now.
FROM web_user u
JOIN web_member m ON m.user_id = u.id
JOIN web_country c ON c.id = m.country_id
CROSS JOIN ctyp
WHERE u.is_active
)
, del AS ( -- only if you want to del all other entries of same type
DELETE FROM watson_searchentry w
USING ctyp
WHERE w.content_type_id = ctyp.content_type_id
AND NOT EXISTS (
SELECT 1
FROM sel
WHERE sel.object_id_int = w.object_id_int
)
)
, up AS ( -- update existing rows
UPDATE watson_searchentry
SET object_id = s.object_id
,title = s.title
,content = s.content
FROM sel s
WHERE w.content_type_id = s.content_type_id
AND w.object_id_int = s.object_id_int
)
-- insert new rows
INSERT INTO watson_searchentry (
content_type_id, object_id_int, object_id, title, content)
SELECT sel.* -- safe to use, because col list is defined accordingly above
FROM sel
LEFT JOIN watson_searchentry w1 USING (content_type_id, object_id_int)
WHERE w1.content_type_id IS NULL;
-
Подзаявката за
django_content_type
винаги връща една стойност? В противен случайCROSS JOIN
може да причини проблеми. -
Първият CTE
sel
събира редовете за вмъкване. Обърнете внимание как избирам съвпадащи имена на колони за опростяване на нещата. -
В CTE
del
Избягвам да изтривам редове, които могат да бъдат актуализирани. -
В CTE
up
вместо това тези редове се актуализират. -
Съответно избягвам да вмъквам редове, които не са били изтрити преди това в крайния
INSERT
.
Може лесно да се обвие в SQL или PL/pgSQL функция за многократна употреба.
Не е сигурно за тежка едновременна употреба. Много по-добра от функцията, която сте имали, но все още не е 100% устойчива срещу едновременни записи. Но това не е проблем според вашата актуализирана информация.
Замяната на актуализациите с DELETE и INSERT може или не може да бъде много по-скъпа. Вътрешно всяка АКТУАЛИЗАЦИЯ така или иначе води до нова версия на ред, поради MVCC модел .
Първо скоростта
Ако наистина не ви е грижа за запазването на стари редове, вашият по-прост подход може да е по-бърз:Изтрийте всичко и вмъкнете нови редове. Освен това обвиването във функция plpgsql спестява малко допълнителни разходи за планиране. Основно вашата функция, с няколко незначителни опростявания и спазване на добавените по-горе настройки по подразбиране:
CREATE OR REPLACE FUNCTION update_member_search_index()
RETURNS VOID AS
$func$
DECLARE
_ctype_id int := (
SELECT id
FROM django_content_type
WHERE app_label='web'
AND model = 'member'
); -- you can assign at declaration time. saves another statement
BEGIN
DELETE FROM watson_searchentry
WHERE content_type_id = _ctype_id;
INSERT INTO watson_searchentry
(content_type_id, object_id, object_id_int, title, content)
SELECT _ctype_id, m.id, m.id::int,m.name
,u.email || ' ' || m.normalized_name || ' ' || c.name
FROM web_member m
JOIN web_user u USING (user_id)
JOIN web_country c ON c.id = m.country_id
WHERE u.is_active;
END
$func$ LANGUAGE plpgsql;
Дори се въздържам да използвам concat_ws()
:Безопасно е срещу NULL
стойности и опростява кода, но малко по-бавно от обикновеното конкатениране.
Също така:
Би било по-бързо да се включи логиката в тази функция - ако това е единственият път, когато тригерът е необходим. В противен случай вероятно не си струва шума.