sql >> Base de Datos >  >> RDS >> PostgreSQL

Optimizar la operación INSERTAR / ACTUALIZAR / ELIMINAR

Definición de tabla modificada

Si realmente necesita que esas columnas sean NOT NULL y realmente necesitas la cadena 'default' como predeterminado para engine_slug , recomendaría introducir valores predeterminados de columna:

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
 ...

La declaración DDL sería:

ALTER TABLE watson_searchentry ALTER COLUMN  engine_slug DEFAULT 'default';

Etc.

Entonces no tiene que insertar esos valores manualmente cada vez.

También:object_id text NOT NULL, object_id_int INTEGER ? Eso es extraño. Supongo que tienes tus razones...

Iré con su requisito actualizado:

Por supuesto, debes añade un ÚNICO restricción para hacer cumplir sus requisitos:

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

Se utilizará el índice adjunto. Por esta consulta para empezar.

Por cierto, casi nunca uso varchar(n) en Postgres. Solo text . Esta es una razón.

Consulta con CTE de modificación de datos

Esto podría reescribirse como una sola consulta SQL con expresiones de tabla comunes que modifican datos, también llamadas CTE "escribibles". Requiere Postgres 9.1 o posterior.
Además, esta consulta solo elimina lo que debe eliminarse y actualiza lo que puede actualizarse.

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 subconsulta en django_content_type siempre devuelve un solo valor? De lo contrario, CROSS JOIN podría causar problemas.

  • El primer CTE sel reúne las filas que se van a insertar. Observe cómo elijo nombres de columna coincidentes para simplificar las cosas.

  • En el CTE del Evito eliminar filas que se pueden actualizar.

  • En el CTE up esas filas se actualizan en su lugar.

  • En consecuencia, evito insertar filas que no se eliminaron antes en el INSERT final .

Se puede envolver fácilmente en una función SQL o PL/pgSQL para uso repetido.

No es seguro para un uso intensivo simultáneo. Mucho mejor que la función que tenía, pero aún no es 100% robusto contra escrituras simultáneas. Pero eso no es un problema según tu información actualizada.

Reemplazar las ACTUALIZACIONES con ELIMINAR e INSERTAR puede o no ser mucho más costoso. Internamente, cada ACTUALIZACIÓN da como resultado una nueva versión de fila de todos modos, debido a MVCC modelo .

Velocidad primero

Si realmente no le importa conservar las filas antiguas, su enfoque más simple puede ser más rápido:elimine todo e inserte nuevas filas. Además, envolver en una función plpgsql ahorra un poco de gastos generales de planificación. Su función básicamente, con un par de simplificaciones menores y observando los valores predeterminados agregados anteriormente:

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;

Incluso me abstengo de usar concat_ws() :Es seguro contra NULL valores y simplifica el código, pero un poco más lento que la concatenación simple.

También:

Sería más rápido incorporar la lógica en esta función, si esta es la única vez que se necesita el disparador. De lo contrario, probablemente no valga la pena el alboroto.