PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Otimize a operação INSERT / UPDATE / DELETE

Definição de tabela modificada


Se você realmente precisa que essas colunas sejam NOT NULL e você realmente precisa da string 'default' como padrão para engine_slug , aconselho a introduzir padrões de coluna:
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
 ...

A instrução DDL seria:
ALTER TABLE watson_searchentry ALTER COLUMN  engine_slug DEFAULT 'default';

etc.

Então você não precisa inserir esses valores manualmente todas as vezes.

Também:object_id text NOT NULL, object_id_int INTEGER ? Isso é estranho. Acho que você tem seus motivos...

Vou com seu requisito atualizado:

Claro, você deve adicione um ÚNICO restrição para impor seus requisitos:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)

O índice que acompanha será usado. Por esta consulta para iniciantes.

BTW, eu quase nunca uso varchar(n) em Postgres. Apenas text . Aqui está um motivo.

Consulte com CTEs de modificação de dados


Isso pode ser reescrito como uma única consulta SQL com expressões de tabela comuns de modificação de dados, também chamadas de CTEs "graváveis". Requer Postgres 9.1 ou posterior.
Além disso, esta consulta apenas exclui o que deve ser excluído e atualiza o que pode ser atualizado.
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;

  • A subconsulta em django_content_type sempre retorna um único valor? Caso contrário, o CROSS JOIN pode causar problemas.

  • O primeiro CTE sel reúne as linhas a serem inseridas. Observe como eu escolho nomes de coluna correspondentes para simplificar as coisas.

  • No CTE del Evito deletar linhas que podem ser atualizadas.

  • No CTE up essas linhas são atualizadas.

  • Assim, evito inserir linhas que não foram excluídas antes no INSERT final .

Pode ser facilmente encapsulado em uma função SQL ou PL/pgSQL para uso repetido.

Não seguro para uso simultâneo pesado. Muito melhor do que a função que você tinha, mas ainda não é 100% robusta contra gravações simultâneas. Mas isso não é um problema de acordo com suas informações atualizadas.

Substituir os UPDATEs por DELETE e INSERT pode ou não ser muito mais caro. Internamente, cada UPDATE resulta em uma nova versão de linha, devido ao MVCC modelo .

Velocidade primeiro


Se você realmente não se importa em preservar linhas antigas, sua abordagem mais simples pode ser mais rápida:exclua tudo e insira novas linhas. Além disso, agrupar em uma função plpgsql economiza um pouco de sobrecarga de planejamento. Sua função basicamente, com algumas pequenas simplificações e observando os padrões adicionados acima:
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;

Eu até me abstenho de usar concat_ws() :É seguro contra NULL valores e simplifica o código, mas um pouco mais lento do que a simples concatenação.

Também:

Seria mais rápido incorporar a lógica nesta função - se esta for a única vez que o gatilho for necessário. Caso contrário, provavelmente não vale a pena o barulho.