Controllo più rapido se la riga esiste in PostgreSQL


177

Ho un sacco di righe che devo inserire nella tabella, ma questi inserimenti vengono sempre eseguiti in batch. Quindi voglio verificare se esiste una sola riga dal batch nella tabella perché poi so che sono stati inseriti tutti.

Quindi non è un controllo chiave primaria, ma non dovrebbe importare troppo. Vorrei controllare solo una riga, quindi count(*)probabilmente non va bene, quindi è qualcosa come existsimmagino.

Ma dato che sono abbastanza nuovo in PostgreSQL, preferirei chiedere alle persone che lo sanno.

Il mio batch contiene righe con la seguente struttura:

userid | rightid | remaining_count

Quindi se la tabella contiene delle righe fornite userid, significa che sono tutte presenti lì.


Vuoi vedere se la tabella ha QUALSIASI riga o qualche riga dal tuo batch?
JNK,

tutte le righe dal mio batch sì. condividono tutti lo stesso campo e modificano un po '.
Valentin Kuzub,

Per favore chiarisci la tua domanda. Vuoi aggiungere una serie di record, tutto o niente? C'è qualcosa di speciale nel conteggio? (A proposito di una parola riservata, impraticabile come un nome di colonna)
wildplasser

ok, stavo cercando di semplificare un po 'la situazione reale ma ci stiamo avvicinando sempre di più alla reale implementazione. Una volta inserite quelle righe (c'è un altro campo for_date) comincio a decrementare i diritti per l'utente specificato mentre usano diritti specifici, una volta che i diritti diventano 0 non possono più eseguire quelle azioni per quella data. questa è la vera storia
Valentin Kuzub,

1
Mostra (la parte rilevante di) le definizioni della tabella e indica cosa intendi fare.
wildplasser,

Risposte:


345

Utilizzare la parola chiave EXISTS per il ritorno TRUE / FALSE:

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

21
Estensione su questo, è possibile nominare la colonna restituita per un facile riferimento. Ad esempioselect exists(select 1 from contact where id=12) AS "exists"
Rowan,

3
Questo è meglio, perché restituirà sempre un valore (vero o falso) anziché talvolta Nessuno (a seconda del linguaggio di programmazione) che potrebbe non espandersi come previsto.
Isaaclw,

1
Ho Seq Scan usando questo metodo. Faccio qualcosa di sbagliato?
FiftiN,

2
@ Michael.MI ho una tabella DB con 30 milioni di righe e quando uso existso limit 1ho un forte calo delle prestazioni perché Postgres utilizza Seq Scan invece di Index Scan. E analyzenon aiuta.
FiftiN

2
@maciek, per favore, capisci che 'id' è una chiave primaria, quindi “LIMIT 1” sarebbe inutile poiché c'è solo un record con
quell'id

34

Che ne dici semplicemente:

select 1 from tbl where userid = 123 limit 1;

dove si 123trova l'ID utente del batch che si sta per inserire.

La query sopra restituirà un set vuoto o una singola riga, a seconda che siano presenti record con l'ID utente specificato.

Se questo risulta troppo lento, potresti cercare di creare un indice tbl.userid.

se nella tabella esiste anche una sola riga dal batch, in tal caso non devo inserire le mie righe perché so per certo che sono state inserite tutte.

Affinché ciò rimanga vero anche se il tuo programma viene interrotto a metà batch, ti consiglio di assicurarti di gestire le transazioni del database in modo appropriato (cioè che l'intero batch venga inserito in una singola transazione).


11
A volte potrebbe essere programmaticamente più semplice "selezionare il conteggio (*) da (selezionare 1 ... limite 1)" poiché è garantito che restituisca sempre una riga con un valore di conteggio (*) pari a 0 o 1.
David Aldridge

Il conteggio di @DavidAldridge (*) significa ancora che tutte le righe devono essere lette, mentre il limite 1 si ferma al primo record e ritorna
Imraan,

3
@Immagina Penso che tu abbia frainteso la domanda. La COUNTagisce su un nidificata SELECTche ha al massimo 1 fila (perché la LIMITè nella sottoquery).
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 ...
    )       
    ;

A proposito: se vuoi che l' intero batch fallisca in caso di un duplicato, allora (dato un vincolo di chiave primaria)

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

farà esattamente quello che vuoi: o ha successo o fallisce.


Questo controllerà ogni riga. Vuole fare un solo controllo.
JNK,

1
No, esegue un singolo controllo. La subquery non è correlata. Verrà salvato una volta trovata una coppia corrispondente.
wildplasser,

Esatto, ho pensato che si riferisse alla domanda esterna. +1 a te
JNK

A proposito: poiché la query è all'interno di una transazione, non accadrà nulla se si dovesse inserire un ID duplicato, quindi la sottoquery può essere omessa.
wildplasser,

hmm non sono sicuro di aver capito. Dopo aver inserito i diritti, inizio a ridurre la colonna dei conteggi. (solo alcuni dettagli per l'immagine) Se le righe esistono già e la sottoquery viene omessa, penso che avrò errori con la chiave univoca duplicata lanciata o? (ID utente e forma corretta quella chiave univoca)
Valentin Kuzub,

1
select true from tablename where condition limit 1;

Credo che questa sia la query che Postgres utilizza per controllare le chiavi esterne.

Nel tuo caso, potresti farlo anche in una volta sola:

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

1

come ha sottolineato @MikeM.

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

con indice al contatto, di solito può ridurre il costo del tempo a 1 ms.

CREATE INDEX index_contact on contact(id);

0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Se il set di risultati contiene una riga, non è necessario inserire. Altrimenti inserisci i tuoi record.


se il mazzo contiene 100 righe mi restituirà 100 righe, pensi che sia buono?
Valentin Kuzub,

Puoi limitarlo a 1 riga. Dovrebbe funzionare meglio. Dai un'occhiata alla risposta modificata di @aix per questo.
Fabian Barney,

0

Se pensi all'esibizione, puoi usare "PERFORM" in una funzione come questa:

 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;

non funziona con me: ricevo un errore di sintassi vicino a perform
Simon

1
questo è pl / pgsql, non SQL, quindi l'errore di sintassi per "PERFORM" se si tenta di eseguirlo come SQL
Mark K Cowan,

-1

Vorrei proporre un altro pensiero per indirizzare in modo specifico la tua frase: "Quindi voglio verificare se esiste una sola riga dal batch nella tabella perché poi so che sono stati inseriti tutti ".

Stai rendendo le cose efficienti inserendo "batch" ma facendo controlli di esistenza un record alla volta? Questo mi sembra poco intuitivo. Quindi quando dici "gli inserimenti vengono sempre eseguiti in batch " , intendo che intendi inserire più record con un'istruzione insert . È necessario rendersi conto che Postgres è conforme ACID. Se si inseriscono più record (un batch di dati) con un'istruzione insert , non è necessario verificare se alcuni sono stati inseriti o meno. L'istruzione passa o fallisce. Tutti i record verranno inseriti o nessuno.

D'altra parte, se il tuo codice C # sta semplicemente facendo un "set" istruzioni di inserimento separate, ad esempio in un ciclo, e nella tua mente, questo è un "batch" .. allora non dovresti in effetti descriverlo come " gli inserti vengono sempre eseguiti in batch ". Il fatto che ti aspetti che parte di ciò che chiami un "batch", in realtà potrebbe non essere inserito e quindi sentire la necessità di un controllo, suggerisce fortemente che è così, nel qual caso hai un problema più fondamentale. È necessario modificare il paradigma per inserire effettivamente più record con un solo inserimento e rinunciare a verificare se i singoli record lo hanno creato.

Considera questo esempio:

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

Questo è in effetti il ​​paradigma per qualsiasi DB conforme ACID .. non solo Postgresql. In altre parole, è meglio se risolvi il concetto di "batch" ed evita di dover fare controlli riga per riga in primo luogo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.