Upravená definice tabulky
Pokud opravdu potřebujete, aby tyto sloupce byly NOT NULL
a opravdu potřebujete řetězec 'default'
jako výchozí pro engine_slug
, doporučil bych zavést výchozí hodnoty sloupců:
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
...
Příkaz DDL by byl:
ALTER TABLE watson_searchentry ALTER COLUMN engine_slug DEFAULT 'default';
atd.
Pak nemusíte tyto hodnoty pokaždé vkládat ručně.
Také:object_id text NOT NULL, object_id_int INTEGER
? To je zvláštní. Myslím, že máte své důvody ...
Vezmu váš aktualizovaný požadavek:
Samozřejmě, že musíte přidat UNIQUE omezení pro prosazení vašich požadavků:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)
Použije se doprovodný index. Pro začátek tímto dotazem.
BTW, téměř nikdy nepoužívám varchar(n)
v Postgresu. Stačí text
. To je jeden důvod.
Dotaz pomocí CTE upravujících data
To by se dalo přepsat jako jeden SQL dotaz s běžnými tabulkovými výrazy upravujícími data, nazývané také „zapisovatelné“ CTE. Vyžaduje Postgres 9.1 nebo novější.
Tento dotaz navíc smaže pouze to, co je třeba odstranit, a aktualizuje to, co aktualizovat lze.
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;
-
Dílčí dotaz na
django_content_type
vždy vrací jednu hodnotu? V opačném případěCROSS JOIN
může způsobit potíže. -
První CTE
sel
shromažďuje řádky, které mají být vloženy. Všimněte si, jak vybírám odpovídající názvy sloupců abychom věci zjednodušili. -
V CTE
del
Vyhýbám se mazání řádků, které lze aktualizovat. -
V CTE
up
tyto řádky jsou místo toho aktualizovány. -
V souladu s tím se vyhýbám vkládání řádků, které nebyly předtím smazány, do závěrečného
INSERT
.
Lze jej snadno zabalit do funkce SQL nebo PL/pgSQL pro opakované použití.
Není bezpečný pro těžké souběžné použití. Mnohem lepší než funkce, kterou jste měli, ale stále ne 100% robustní proti souběžným zápisům. Ale to podle vašich aktualizovaných informací není problém.
Nahrazení UPDATE s DELETE a INSERT může, ale nemusí být mnohem dražší. Interně má každá UPDATE každopádně za následek novou verzi řádku kvůli MVCC model .
Nejdříve rychlost
Pokud se opravdu nestaráte o zachování starých řádků, váš jednodušší přístup může být rychlejší:Smažte vše a vložte nové řádky. Zabalení do funkce plpgsql také ušetří trochu režie plánování. Vaše funkce v podstatě s několika drobnými zjednodušeními a dodržením výchozích hodnot přidaných výše:
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;
Dokonce se zdržuji používání concat_ws()
:Je bezpečný proti NULL
hodnot a zjednodušuje kód, ale o něco pomaleji než jednoduché zřetězení.
Také:
Bylo by rychlejší začlenit logiku do této funkce - pokud je to jediný okamžik, kdy je potřeba spoušť. Jinak to asi nestojí za ten povyk.