Comprobación más rápida si existe una fila en PostgreSQL

177

Tengo un montón de filas que necesito insertar en la tabla, pero estas inserciones siempre se realizan en lotes. Entonces, quiero verificar si existe una sola fila del lote en la tabla porque sé que todas fueron insertadas.

Por lo tanto, no es una verificación de clave principal, pero no debería importar demasiado. Me gustaría verificar solo una fila, por lo que count(*)probablemente no sea bueno, así existsque supongo que es algo así .

Pero como soy bastante nuevo en PostgreSQL, prefiero preguntar a las personas que lo saben.

Mi lote contiene filas con la siguiente estructura:

userid | rightid | remaining_count

Entonces, si la tabla contiene alguna fila con siempre userid, significa que todos están presentes allí.

Valentin Kuzub
fuente
¿Desea ver si la tabla tiene CUALQUIER fila o alguna fila de su lote?
JNK
cualquier fila de mi lote sí. todos comparten el mismo campo y editarán un poco.
Valentin Kuzub
Por favor aclara tu pregunta. ¿Desea agregar un lote de registros, todo o nada? ¿Hay algo especial en el recuento? (Por cierto, una palabra reservada, poco práctica como nombre de columna)
wildplasser
Bien, estaba tratando de simplificar un poco la situación real, pero nos estamos acercando cada vez más a la implementación real. Una vez que se insertan esas filas (hay otro campo para fecha_) empiezo a disminuir los derechos para el usuario especificado, ya que usan derechos específicos, una vez que los derechos se convierten en 0 ya no pueden realizar esas acciones para esa fecha. esa es la verdadera historia
Valentin Kuzub
1
Simplemente muestre (la parte relevante de) las definiciones de la tabla y diga qué piensa hacer.
wildplasser

Respuestas:

345

Use la palabra clave EXISTE para el retorno VERDADERO / FALSO:

select exists(select 1 from contact where id=12)
StartupGuy
fuente
21
Extensión de esto, puede nombrar la columna devuelta para una fácil referencia. Ej .select exists(select 1 from contact where id=12) AS "exists"
Rowan, el
3
Esto es mejor, porque siempre devolverá un valor (verdadero o falso) en lugar de a veces Ninguno (dependiendo de su lenguaje de programación) que podría no expandirse de la manera esperada.
isaaclw
1
Tengo Seq Scan con el uso de este método. ¿Yo hago algo mal?
FiftiN
2
@ Michael.MI tiene una tabla de base de datos con 30 millones de filas y cuando uso existso limit 1tengo una fuerte caída del rendimiento porque Postgres usa Seq Scan en lugar de Index Scan. Y analyzeno ayuda.
FiftiN
2
@maciek, por favor, comprenda que 'id' es una clave principal, por lo que "LIMIT 1" no tendría sentido ya que solo hay un registro con esa identificación
StartupGuy el
34

¿Qué tal simplemente:

select 1 from tbl where userid = 123 limit 1;

donde 123está el ID de usuario del lote que está a punto de insertar.

La consulta anterior devolverá un conjunto vacío o una sola fila, dependiendo de si hay registros con el ID de usuario dado.

Si esto resulta ser demasiado lento, podría considerar crear un índice tbl.userid.

incluso si existe una sola fila del lote en la tabla, en ese caso no tengo que insertar mis filas porque estoy seguro de que todas fueron insertadas.

Para que esto siga siendo cierto, incluso si su programa se interrumpe a mitad de lote, le recomiendo que se asegure de administrar las transacciones de la base de datos de manera adecuada (es decir, que todo el lote se inserta dentro de una sola transacción).

NPE
fuente
11
A veces puede ser programáticamente más fácil "seleccionar recuento (*) de (seleccione 1 ... límite 1)", ya que se garantiza que siempre devolverá una fila con un valor de recuento (*) de 0 o 1.
David Aldridge
@DavidAldridge count (*) todavía significa que todas las filas deben leerse, mientras que el límite 1 se detiene en el primer registro y regresa
Imraan
3
@Imraan Creo que has malinterpretado la consulta. Los COUNTactos en un anidado SELECTque tiene como máximo 1 fila (porque LIMITestá en la subconsulta)
jpmc26
9
INSERT INTO target( userid, rightid, count )
  SELECT userid, rightid, count 
  FROM batch
  WHERE NOT EXISTS (
    SELECT * FROM target t2, batch b2
    WHERE t2.userid = b2.userid
    -- ... other keyfields ...
    )       
    ;

Por cierto: si desea que todo el lote falle en caso de un duplicado, entonces (dada una restricción de clave principal)

INSERT INTO target( userid, rightid, count )
SELECT userid, rightid, count 
FROM batch
    ;

hará exactamente lo que quiere: o tiene éxito o falla.

wildplasser
fuente
Esto verificará cada fila. Quiere hacer un solo chequeo.
JNK
1
No, hace una sola comprobación. La subconsulta no está correlacionada. Se rescatará una vez que se encuentre un par coincidente.
wildplasser
Correcto, pensé que se refería a la consulta externa. +1 para ti
JNK
Por cierto: dado que la consulta está dentro de una transacción, no sucederá nada si se inserta una identificación duplicada, por lo tanto, la subconsulta puede omitirse.
wildplasser
hmm no estoy seguro de entender. Después de insertar los derechos, comienzo a disminuir la columna de conteo. (solo algunos detalles para la imagen) Si ya existen filas y se omite la subconsulta, creo que obtendré errores con una clave única duplicada o? (ID de usuario y forma correcta esa clave única)
Valentin Kuzub
1
select true from tablename where condition limit 1;

Creo que esta es la consulta que utiliza Postgres para verificar las claves externas.

En su caso, también puede hacer esto de una vez:

insert into yourtable select $userid, $rightid, $count where not (select true from yourtable where userid = $userid limit 1);
Royce
fuente
1

como señaló @MikeM.

select exists(select 1 from contact where id=12)

con índice de contacto, generalmente puede reducir el costo de tiempo a 1 ms.

CREATE INDEX index_contact on contact(id);
hcnak
fuente
0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Si su conjunto de resultados contiene una fila, entonces no tiene que insertar. De lo contrario, inserte sus registros.

Fabian Barney
fuente
si el grupo contiene 100 filas, me devolverá 100 filas, ¿crees que es bueno?
Valentin Kuzub
Puedes limitarlo a 1 fila. Debería funcionar mejor. Echa un vistazo a la respuesta editada de @aix para eso.
Fabian Barney
0

Si piensa en el rendimiento, puede usar "PERFORM" en una función como esta:

 PERFORM 1 FROM skytf.test_2 WHERE id=i LIMIT 1;
  IF FOUND THEN
      RAISE NOTICE ' found record id=%', i;  
  ELSE
      RAISE NOTICE ' not found record id=%', i;  
 END IF;
francos
fuente
no funciona conmigo: aparece un error de sintaxis cerca de realizar
Simon
1
eso es pl / pgsql, no SQL, de ahí el error de sintaxis para "PERFORM" si intenta ejecutarlo como SQL
Mark K Cowan
-1

Me gustaría proponer otro pensamiento para abordar específicamente su oración: "Así que quiero verificar si existe una sola fila del lote en la tabla porque sé que todas fueron insertadas ".

¿Estás haciendo las cosas eficientes insertando en "lotes" pero luego haciendo verificaciones de existencia un registro a la vez? Esto me parece contrario a la intuición. Entonces, cuando dices "las inserciones siempre se hacen en lotes ", entiendo que estás insertando múltiples registros con una declaración de inserción . Debe darse cuenta de que Postgres es compatible con ACID. Si está insertando múltiples registros (un lote de datos) con una declaración de inserción , no es necesario verificar si algunos se insertaron o no. La declaración pasa o fallará. Todos los registros serán insertados o ninguno.

Por otro lado, si su código C # simplemente está haciendo un "conjunto" de instrucciones de inserción separadas, por ejemplo, en un bucle, y en su mente, esto es un "lote" ... entonces, de hecho, no debe describirlo como " las inserciones siempre se realizan en lotes ". El hecho de que espere que parte de lo que llama un "lote" en realidad no se pueda insertar y, por lo tanto, sienta la necesidad de una verificación, sugiere que este es el caso, en cuyo caso tiene un problema más fundamental. Necesita cambiar su paradigma para insertar realmente varios registros con una inserción y renunciar a verificar si los registros individuales lo hicieron.

Considere este ejemplo:

CREATE TABLE temp_test (
    id SERIAL PRIMARY KEY,
    sometext TEXT,
    userid INT,
    somethingtomakeitfail INT unique
)
-- insert a batch of 3 rows
;;
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 1, 1),
('bar', 2, 2),
('baz', 3, 3)
;;
-- inspect the data of what we inserted
SELECT * FROM temp_test
;;
-- this entire statement will fail .. no need to check which one made it
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 2, 4),
('bar', 2, 5),
('baz', 3, 3)  -- <<--(deliberately simulate a failure)
;;
-- check it ... everything is the same from the last successful insert ..
-- no need to check which records from the 2nd insert may have made it in
SELECT * FROM temp_test

De hecho, este es el paradigma para cualquier DB compatible con ACID ... no solo Postgresql. En otras palabras, estará mejor si arregla su concepto de "lote" y evita tener que hacer verificaciones fila por fila en primer lugar.

StartupGuy
fuente