sql >> Database teknologi >  >> RDS >> PostgreSQL

Optimer INSERT / UPDATE / DELETE operationen

Ændret tabeldefinition

Hvis du virkelig har brug for, at disse kolonner er NOT NULL og du har virkelig brug for strengen 'default' som standard for engine_slug , jeg vil råde dig til at indføre kolonnestandarder:

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-sætning ville være:

ALTER TABLE watson_searchentry ALTER COLUMN  engine_slug DEFAULT 'default';

osv.

Så behøver du ikke at indsætte disse værdier manuelt hver gang.

Også:object_id text NOT NULL, object_id_int INTEGER ? Det er underligt. Jeg tror du har dine grunde ...

Jeg følger dit opdaterede krav:

Selvfølgelig skal du tilføje en UNIKK begrænsning for at håndhæve dine krav:

ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)

Det medfølgende indeks vil blive brugt. Ved denne forespørgsel til at begynde med.

BTW, jeg bruger næsten aldrig varchar(n) i Postgres. Bare text . Her er en grund.

Forespørgsel med datamodificerende CTE'er

Dette kunne omskrives som en enkelt SQL-forespørgsel med datamodificerende almindelige tabeludtryk, også kaldet "skrivbare" CTE'er. Kræver Postgres 9.1 eller nyere.
Derudover sletter denne forespørgsel kun det, der skal slettes, og opdaterer det, der kan opdateres.

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;
  • Underforespørgslen på django_content_type returnerer altid en enkelt værdi? Ellers er CROSS JOIN kan give problemer.

  • Det første CTE sel samler de rækker, der skal indsættes. Bemærk, hvordan jeg vælger matchende kolonnenavne for at forenkle tingene.

  • I CTE del Jeg undgår at slette rækker, der kan opdateres.

  • I CTE up disse rækker opdateres i stedet.

  • Derfor undgår jeg at indsætte rækker, der ikke blev slettet før i den sidste INSERT .

Kan nemt pakkes ind i en SQL- eller PL/pgSQL-funktion til gentagen brug.

Ikke sikker til kraftig samtidig brug. Meget bedre end den funktion du havde, men stadig ikke 100% robust mod samtidige skrivninger. Men det er ikke et problem ifølge dine opdaterede oplysninger.

At erstatte OPDATERINGER med DELETE og INSERT kan eller måske ikke være meget dyrere. Internt resulterer hver OPDATERING alligevel i en ny rækkeversion på grund af MVCC model .

Hastighed først

Hvis du er ligeglad med at bevare gamle rækker, kan din enklere tilgang være hurtigere:Slet alt og indsæt nye rækker. Indpakning i en plpgsql-funktion sparer også lidt planlægningsomkostninger. Din funktion grundlæggende, med et par mindre forenklinger og observation af standardindstillingerne tilføjet ovenfor:

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;

Jeg afstår endda fra at bruge concat_ws() :Det er sikkert mod NULL værdier og forenkler kode, men en smule langsommere end simpel sammenkædning.

Også:

Det ville være hurtigere at inkorporere logikken i denne funktion - hvis det er den eneste gang, udløseren er nødvendig. Ellers er det nok ikke besværet værd.




  1. SQLite udtryksbaseret indeks

  2. At få det sidste ord fra en Postgres-streng, deklarativt

  3. MySQL REPLACE variabel streng

  4. mysql database backup med mysqldump