Definizione tabella modificata
Se hai davvero bisogno che quelle colonne siano NOT NULL
e hai davvero bisogno della stringa 'default'
come predefinito per engine_slug
, consiglierei di introdurre i valori predefiniti delle colonne:
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
...
L'istruzione DDL sarebbe:
ALTER TABLE watson_searchentry ALTER COLUMN engine_slug DEFAULT 'default';
ecc.
Quindi non devi inserire quei valori manualmente ogni volta.
Inoltre:object_id text NOT NULL, object_id_int INTEGER
? È strano. Immagino che tu abbia le tue ragioni...
Andrò con il tuo requisito aggiornato:
Naturalmente, devi aggiungi un UNICO vincolo per far rispettare i tuoi requisiti:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)
Verrà utilizzato l'indice di accompagnamento. Con questa query per cominciare.
A proposito, non uso quasi mai varchar(n)
a Postgres. Solo text
. Ecco un motivo.
Query con CTE di modifica dei dati
Questo potrebbe essere riscritto come una singola query SQL con espressioni di tabelle comuni che modificano i dati, chiamate anche CTE "scrivibili". Richiede Postgres 9.1 o successivo.
Inoltre, questa query elimina solo ciò che deve essere eliminato e aggiorna ciò che può essere aggiornato.
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;
-
La sottoquery su
django_content_type
restituisce sempre un solo valore? Altrimenti, ilCROSS JOIN
potrebbe causare problemi. -
Il primo CTE
sel
raccoglie le righe da inserire. Nota come scelgo nomi di colonna corrispondenti per semplificare le cose. -
Nel CTE
del
Evito di eliminare le righe che possono essere aggiornate. -
Nel CTE
up
quelle righe vengono invece aggiornate. -
Di conseguenza, evito di inserire righe che non sono state eliminate prima nel
INSERT
finale .
Può essere facilmente racchiuso in una funzione SQL o PL/pgSQL per un uso ripetuto.
Non sicuro per un uso simultaneo intenso. Molto meglio della funzione che avevi, ma non ancora affidabile al 100% contro le scritture simultanee. Ma questo non è un problema secondo le tue informazioni aggiornate.
La sostituzione degli UPDATE con DELETE e INSERT può essere o meno molto più costosa. Internamente ogni AGGIORNAMENTO comporta comunque una nuova versione di riga, a causa di MVCC modello .
La velocità prima
Se non ti interessa davvero preservare le vecchie righe, il tuo approccio più semplice potrebbe essere più veloce:elimina tutto e inserisci nuove righe. Inoltre, il wrapping in una funzione plpgsql consente di risparmiare un po' di sovraccarico di pianificazione. Fondamentalmente la tua funzione, con un paio di piccole semplificazioni e osservando le impostazioni predefinite aggiunte sopra:
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;
Mi astengo persino dall'usare concat_ws()
:È sicuro contro NULL
valori e semplifica il codice, ma un po' più lento della semplice concatenazione.
Inoltre:
Sarebbe più veloce incorporare la logica in questa funzione, se questa è l'unica volta in cui è necessario il trigger. Altrimenti, probabilmente non ne vale la pena.