2013-05-14 14 views
11

Ho già esaminato altre risposte e sento ancora che la mia domanda è pertinente e merita una voce separata.Ottieni tutti gli ID inseriti quando si inseriscono più righe utilizzando una singola query

Ho una tabella denominata impostazioni (che memorizza le impostazioni dell'utente) e devo inserire più impostazioni per ogni utente. Inizialmente, avevo eseguito una dichiarazione di inserimento separata per ogni impostazione, ma avendo ritenuto che questo non fosse un modo particolarmente efficace per farlo, ho pensato di inserire più righe con la stessa istruzione di inserimento. Il mio unico problema è che voglio gli ID autoincrementati di ognuna delle nuove righe inserite.

Ho letto le risposte che dicono che questo non è possibile/scalabile ecc., Ma sento che ho trovato la soluzione. Voglio feedback se il mio modo è corretto o meno e quindi questa domanda.

Quello che ho fatto è semplice. Dopo aver inserito le righe multiple, chiamo last_insert_id() per ottenere l'ID della prima riga delle righe inserite simultaneamente. Ho già il conteggio del numero di righe inserite, quindi creo semplicemente un nuovo array e lo popolo con ID che iniziano con last_insert_id() e che terminano con last_insert_id() + n-1 (dove n è il numero di righe inserite).

mi sento questo funzionerà a causa dei seguenti motivi:

1.) Stati documentazione di MySQL che LAST_INSERT_ID() è il collegamento dipendente e se un altro client/connessione inserisce nuovi record, quindi, che non interesserà altri client di LAST_INSERT_ID().

2.) Ritengo che mentre l'inserimento viene eseguito da una singola istruzione SQL, l'intero inserimento deve essere considerato come una singola transazione. Se ciò è vero, dovrebbero essere applicate le regole ACID ei valori auto_incrementati dovrebbero essere sequenziali. Non sono sicuro di questo.

Queste sono le ragioni per cui ritengo che la logica dovrebbe funzionare. Quindi la mia domanda è, la logica sopra funzionerà per TUTTE le condizioni? Posso fare affidamento su di esso per funzionare correttamente in tutte le situazioni? So che sta funzionando per me al momento.

+0

Non sono nemmeno sicuro del punto 2.Se fossi in me, direi di creare una tabella temporanea, aggiungendo ogni last_insert_id ad essa, e poi restituirla. O meglio ancora, avere un altro modo di selezionare ciò che era appena successo, (utente e dire un timestamp datalastchanged) –

+1

Risposta semplice no. Non è robusto e mentre qualsiasi individuo last_insert_id() può essere dipendente dalla connessione, non fornisce una * garanzia * che tutti gli inserti saranno in un blocco simultaneo e non interlacciati con inserimenti da un altro processo concorrente. Nella programmazione è molto meglio affidarsi a una solida ingegneria piuttosto che alla sensazione che dovrebbe essere ok. – Anigel

+0

Sarei d'accordo con le questioni precedenti. Tuttavia potrebbe essere possibile avere un campo extra sulla tabella che stai inserendo e inserire un identificativo univoco. Quindi puoi leggere quella tabella per tutti i record con quell'identificatore univoco per ottenere l'elenco degli ID inseriti. Sarei ancora preoccupato di quale ID si riferisse a quale serie di dati inseriti non fosse definito. – Kickstart

risposta

1

Se vi piace scommettere - poi fare questo :)

per essere sicuri al 99% che avrebbe dovuto bloccare la tabella per la scrittura. Non sei sicuro che le (in futuro) due transazioni non saranno in grado di intrecciarsi.

Per essere sicuri al 100% di leggere questi valori. (Oppure analizza la fonte MySQL) La soluzione migliore sarebbe quella di aggiungere la data alle impostazioni di modifica tablei e leggere le ultime. Se non si desidera modificare la struttura, è possibile utilizzare i trigger http://dev.mysql.com/doc/refman/5.0/en/create-trigger.html.

buona soluzione sarà di aggiornamento tutte le impostazioni o solo le coppie: chiave - Nome impostazione

0

Sono d'accordo con @anigel. Non puoi essere sicuro che alcune transazioni non saranno confuse. È possibile dividere gli inserimenti in query separate, chiamare last_insert_id() per ogni singola query e compilare un array con i risultati.

Concesso, questo può aumentare il tempo di elaborazione ma almeno si può essere certi di evitare conflitti. Inoltre, dal momento che la sua tabella delle impostazioni di una, la sua altamente improbabile che si dovrà eseguire un sacco di transazioni per richiesta del cliente

0

ne dite:

$id = array(); 

$sql = "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; 
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; 
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; 
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; 
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; 
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');"; 

if ($db=new mysqli('host', 'user', 'pass', 'dbase')) 
{ 
    $db->multi_query($sql); 

    if (isset($db->insert_id)) $id[] = $db->insert_id; 

    while ($db->more_results()) 
    { 
     if ($db->next_result()) $id[] = $db->insert_id; 
     else trigger_error($db->error); 
    } 

    $db->close(); 
} 
else trigger_error($db->error); 

if (count($id) == 0) trigger_error('Uh oh! No inserts succeeded!'); 
else print_r($id); 

Forse questo restituirebbe gli ID inserimento come si vogliono .Non l'ho provato, ma forse potresti adattarlo per il tuo scopo e chi lo sa potrebbe funzionare davvero.

+0

L'ultimo ID inserito è una variabile basata sulla sessione, quindi la successiva sovrascrive la precedente. –

+0

@Jack intendi che dopo ogni query viene eseguita e prima di eseguire il looping nell'istruzione while che tutti i precedenti insert_id sono già cronologici e non gestiti con i risultati? – txpeaceofficer09

+0

Sì, è vero :) –

1

Questo comportamento non dovrebbe essere invocato; oltre agli ovvi problemi di blocco, supponiamo di voler impostare master < -> replica master; all'improvviso, l'incremento dell'ID di 2 ogni volta.

Oltre a questo, in realtà invece di scrivere più istruzioni INSERT, forse vale la pena utilizzare le istruzioni preparate:

$db = new PDO(...); 
$db->beginTransaction(); 
$stmt = $db->prepare('INSERT INTO `mytable` (a, b) VALUES (?, ?)'); 

foreach ($entries as $entry) { 
    $stmt->execute(array($entry['a'], $entry['b'])); 
    $id = $db->lastInsertId(); 
} 

$db->commit(); 
+0

Questo causa l'attivazione di db access? E 'meno performante quindi fare l'inserto senza la transazione? – Sangoku

+1

Le prestazioni di @Sokoku non dovrebbero essere la considerazione principale quando si utilizzano le transazioni; la consistenza dei dati è –

+1

In un caso sto lavorando su di esso. E nel caso qualcuno voglia conoscere la performance. è come 4x volte più veloce. :) – Sangoku

0

ho già facendo questo è il mio app. Inserisco i record in loop e quando ogni record è inserito, memorizzerò l'ID di incremento automatico nell'array richiamando last_insert_id(). Quindi posso usare la matrice di id inseriti dove mai ho bisogno.

+0

Quale domanda è questa risposta? –

0

Ho fatto questo per un po 'anche se non ho trovato i dati utili alla fine così fermati.

Traccio il datetime e user_who_altered di ogni voce nella tabella in modo da recuperare l'elenco diventa semplice.

È importante impostare una variabile con il tempo però, piuttosto che basarsi su ORA():

INSERT INTO table (username,privelege,whenadded,whoadded) VALUES ('1','2','$thetime','$theinserter'),('1','5','$thetime','$theinserter'),etc...; 

inserirà i più righe ben.

per recuperare la matrice che cercate:

SELECT idrow FROM table WHERE username='1' AND whenadded='$thetime' AND whoadded='$theinserter'; 

Questa è una buona pratica, come si è in grado di monitorare l'utente che ha modificato il record e che non dipende da serrature o possibilità.

Come per la propria soluzione, funzionerà e salva una query. Mi preoccuperei di come potrebbe rispondere alle dichiarazioni preparate su un tavolo occupato, e forse il metodo usato dovrebbe tenerlo presente. Il metodo che utilizzo è immune a tali problemi.

0

Qualcosa mi ha infastidito su questa domanda ... perché hai bisogno dell'inserzione_id di ogni riga? Mi sembra che se si sta inserendo nella tabella delle impostazioni per correlare le impostazioni all'utente, sarebbe meglio aggiungere una sorta di chiave utente alla tabella. Sto probabilmente solo non vedere l'intera immagine, ma questa è l'idea che sto ricevendo:

+---------------------------------------+ 
| `settings`       | 
+------+--------+-----------+-----------+ 
| id | user | setting | value | 
+------+--------+-----------+-----------+ 
| `id` INT(11) PRIMARY AUTO_INCREMENT | 
+---------------------------------------+ 
| `user` INT(11)      | 
+---------------------------------------+ 
| `setting` VARCHAR(15)    | 
+---------------------------------------+ 
| `value` VARCHAR(25)     | 
+---------------------------------------+ 

+---------------------------------------+ 
| `users`        | 
+------+--------+--------+--------------+ 
| id | name | email | etc   | 
+------+--------+--------+--------------+ 
| `id` INT(11) PRIMARY AUTO_INCREMENT | 
+---------------------------------------+ 
| `name` VARCHAR(32)     | 
+---------------------------------------+ 
| `email` VARCHAR(64)     | 
+---------------------------------------+ 
| `etc` VARCHAR(64)     | 
+---------------------------------------+ 

Il mio pensiero di base è che ciò che si sta facendo con l'insert_id (s) è quindi in qualche modo cercando per creare un riferimento tra gli utenti e le impostazioni in modo da sapere chi imposta cosa e restituirgli le impostazioni in un secondo momento.

Se ho completamente perso il punto, per favore, mi guida indietro sul soggetto e cercherò di trovare un'altra soluzione, ma se sto colpendo ovunque vicino a dove si sta tentando di andare:

Se si sta tentando di correlare le due tabelle utilizzando insert_id (s), non sarebbe più opportuno qualcosa di simile al codice seguente (presupponendo che si abbia una struttura tabella simile a quella precedente):

I'll supponiamo che $ user sia un riferimento a un particolare utente (users. id).

Suppongo anche che tu abbia un array associativo chiamato $ settings che stai cercando di inserire nel database.

$mysqli = new mysqli('host', 'username', 'password', 'database') or trigger_error('[MYSQLI]: Could not connect.'); 

if ($mysqli) 
{ 
    foreach ($settings AS $setting_name=>$setting_value) 
    { 
     // I'll do individual queries here so error checking between is easy, but you could join them into a single query string and use a $mysqli->multi_query(); 
     // I'll give two examples of how to make the multi_query string below. 
     $sql = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')"; 
     $mysqli->query($sql) or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']'; 
    } 
    $mysqli->close() // Since we know in this scope that $mysqli was created we need to close it when we are finished with it. 
} 

Ora, quando hai bisogno di impostazioni dell'utente si potrebbe fare una SELECT con un JOIN di mettere tutte le impostazioni e le informazioni utente insieme e mi perdoni se mi pidocchio questo po 'su, perché io sono di gran lunga non un mysql (i) esperto, quindi non sono sicuro che dovrai fare qualcosa di speciale in quanto ci sono più voci nella tabella delle impostazioni e una singola voce nella tabella degli utenti, ma sono sicuro che qualcuno può impostarci entrambi se io rovinare tutto:

$mysqli = new mysqli('host', 'username', 'password', 'database') or trigger_error('[MYSQLI]: Unable to connect.'); 

if ($mysqli) 
{ 

    $sql = "SELECT `users`.`name`, `users`.`email`, `users`.`id`, `users`.`etc`, `settings`.`setting`, `settings`.`value` FROM `settings` JOIN (`users`) ON (`users`.`id`=`settings`.`user`) WHERE `settings`.`user`=$user GROUP BY `settings`.`user` ORDER BY `settings`.`user` ASC"; 

    if ($result=$mysqli->query($sql)) 
    { 
     if ($result->num_rows == 0) 
      echo "Uh oh! $user has no settings or doesn't exist!"; 
     else 
     { 
      // Not sure if my sql would get multiple results or if it would get it all in one row like I want so I'll assume multiple which will work either way. 
      while ($row=$result->fetch_array()) 
       print_r($row); // Just print the array of settings to see that it worked. 
     } 
     $result->free(); // We are done with our results so we release it back into the wild. 
    } else trigger_error('[MYSQLI]: '.$mysqli->error . '['.$sql.']'); 
    $mysqli->close(); // Our if ($mysqli) tells us that in this scope $mysqli exists, so we need to close it since we are finished. 
} 

Come ho già detto, io sono di gran lunga non un mysql (i) esperto e probabilmente ci sono modi per ottimizzare le cose e forse ho fatto alcuni errori con la Synta x sulla mia dichiarazione JOIN o semplicemente usato clausole superflue come il GRUPPO BY. Ho fatto il SELECT un pull da settings e mi sono unito a users perché non ero sicuro che unire le impostazioni agli utenti avrebbe prodotto un singolo risultato contenente utenti e tutti i valori possibili dalle impostazioni che corrispondevano alla nostra clausola WHERE. Mi sono mai unito a A con B dove c'era 1 risultato in ciascuno, ma sono fiducioso che un JOIN è nella giusta area generale.

alternativa alle molteplici domande, ho detto che avrei dare esempi di costruzione di una singola stringa di query per un multi_query, quindi:

$arr = array(); 
foreach($settings AS $setting_name=>$setting_value) 
{ 
    $arr[] = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')"; 
} 
$sql = join('; ', $arr); 

o

foreach($settings AS $setting_name=>$setting_value) 
{ 
    $sql = (isset($sql)) ? $sql.'; '."INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')" : "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')"; 
} 

Un'ultima cosa voglio sottolineare : A meno che tu non stia specificatamente richiedendo più voci della stessa impostazione per l'utente, allora potresti fare un UPDATE prima della tua query INSERT e fare solo l'INSERT se il risultante $ mysqli-> insert_id == 0. Se stai tentando di mantenere un cronologia delle modifiche alle impostazioni per gli utenti ed è per questo avete bisogno di più voci poi vorrei suggerire la creazione di un tavolo separato con un log che contiene una struttura di tabella del tipo:

+--------------------------------------------------+ 
| `logs`           | 
+------+--------+--------+-----------+-------------+ 
| id | time | user | setting | new_value | 
+------+--------+--------+-----------+-------------+ 
| `id` INT(11) PRIMARY AUTO_INCREMENT    | 
+--------------------------------------------------+ 
| `time` TIMESTAMP        | 
+--------------------------------------------------+ 
| `user` INT(11)         | 
+--------------------------------------------------+ 
| `setting` INT(11)        | 
+--------------------------------------------------+ 
| `new_value` VARCHAR(25)       | 
+--------------------------------------------------+ 

Se si effettua il default del time del CURRENT_TIMESTAMP o semplicemente inserire la data ('Ymd G: i: s') in quel campo, quindi è possibile tenere traccia delle modifiche alle impostazioni inserendo nel registro quando vengono create o modificate nuove impostazioni.

Per eseguire gli INSERTI se l'AGGIORNAMENTO non ha modificato nulla, è possibile utilizzare due istruzioni separate. È anche possibile farlo con "INSERT VALUES() ON DUPLICATE KEY UPDATE ...", ma questo metodo non è apparentemente raccomandato su tabelle con più campi UNIQUE. Utilizzare il secondo metodo significherebbe una singola query, ma dovresti fare la combinazione di user e setting UNICA (o forse DISTINCT?). Se hai fatto setting UNICO allora saresti in grado di avere uno solo setting = 'pippo' nella tabella a meno che non mi sbagli. Per farlo con due affermazioni si dovrebbe fare qualcosa di simile:

$sql = "UPDATE `settings` SET `value`='bar' WHERE `user`=$user AND `setting`='foo' LIMIT 1"; 
$mysqli->query() or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']'); 
if ($mysqli->affected_rows == 0) 
{ 
    $sql = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, 'foo', 'bar')"; 
    $mysqli->query($sql) or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']'); 
} 

Nel codice di cui sopra si potrebbe sostituire $ mysqli-> insert_id a $ mysqli-> affected_rows se preferite, ma il risultato finale è lo stesso. L'istruzione INSERT viene chiamata solo se l'UPDATE non ha modificato nulla nella tabella (il che indicherebbe che non c'erano record per quell'impostazione e quell'utente).

Mi scuso che questa è una risposta molto lunga e si è allontanata dalla tua domanda iniziale, ma spero che sia relavent al tuo vero scopo/obiettivo. Attendo con ansia la vostra risposta e i commenti sui modi per migliorare le mie istruzioni SQL dai veri master SQL.

Problemi correlati