Zmodyfikowana definicja tabeli
Jeśli naprawdę potrzebujesz tych kolumn, aby były NOT NULL
i naprawdę potrzebujesz ciągu 'default'
jako domyślne dla engine_slug
, radziłbym wprowadzić wartości domyślne kolumn:
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
...
Oświadczenie DDL to:
ALTER TABLE watson_searchentry ALTER COLUMN engine_slug DEFAULT 'default';
Itd.
Wtedy nie musisz za każdym razem wstawiać tych wartości ręcznie.
Także:object_id text NOT NULL, object_id_int INTEGER
? To dziwne. Chyba masz swoje powody...
Pójdę z Twoimi zaktualizowanymi wymaganiami:
Oczywiście musisz dodaj UNIKALNE ograniczenie w celu wymuszenia Twoich wymagań:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)
Zostanie użyty towarzyszący indeks. Przez to zapytanie na początek.
BTW, prawie nigdy nie używam varchar(n)
w Postgresie. Po prostu text
. Oto jeden powód.
Zapytanie z CTE modyfikacji danych
Można to przepisać jako pojedyncze zapytanie SQL ze wspólnymi wyrażeniami tabelowymi modyfikującymi dane, zwanymi także „zapisywalnymi” CTE. Wymaga Postgresa 9.1 lub nowszego.
Ponadto to zapytanie usuwa tylko to, co należy usunąć, i aktualizuje to, co można zaktualizować.
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;
-
Podzapytanie w
django_content_type
zawsze zwraca pojedynczą wartość? W przeciwnym razieCROSS JOIN
może powodować kłopoty. -
Pierwsze CTE
sel
zbiera wiersze do wstawienia. Zwróć uwagę, jak wybieram pasujące nazwy kolumn aby uprościć rzeczy. -
W CTE
del
Unikam usuwania wierszy, które można aktualizować. -
W CTE
up
zamiast tego te wiersze są aktualizowane. -
W związku z tym unikam wstawiania wierszy, które nie zostały wcześniej usunięte w końcowym
INSERT
.
Może być łatwo opakowany w funkcję SQL lub PL/pgSQL do wielokrotnego użytku.
Nie jest bezpieczny w przypadku intensywnego jednoczesnego użytkowania. Dużo lepsza niż funkcja, którą miałeś, ale wciąż nie jest w 100% odporna na współbieżne zapisy. Ale to nie jest problem według zaktualizowanych informacji.
Zastąpienie aktualizacji na DELETE i INSERT może, ale nie musi być dużo droższe. Wewnętrznie każda aktualizacja i tak powoduje powstanie nowej wersji wiersza, ze względu na MVCC model .
Najpierw prędkość
Jeśli naprawdę nie zależy Ci na zachowaniu starych wierszy, prostsze podejście może być szybsze:Usuń wszystko i wstaw nowe wiersze. Ponadto zawinięcie w funkcję plpgsql pozwala zaoszczędzić trochę czasu na planowanie. Zasadniczo twoja funkcja, z kilkoma drobnymi uproszczeniami i przestrzeganiem domyślnych ustawień dodanych powyżej:
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;
Powstrzymuję się nawet od używania concat_ws()
:Jest bezpieczny przed NULL
wartości i upraszcza kod, ale nieco wolniej niż zwykła konkatenacja.
Również:
Szybciej byłoby włączyć logikę do tej funkcji - jeśli jest to jedyny moment, w którym potrzebny jest wyzwalacz. W przeciwnym razie prawdopodobnie nie jest to warte zamieszania.