2014-10-20 23 views
6

In una tabella specifica ho un campo (field_order) che servirà come modo per definire un ordine personalizzato per mostrare le righe della tabella. Quando si inserisce un nuovo record vorrei impostare quel particolare campo con il numero di righe in quella tabella più unoValore predefinito per il campo ordine in mysql

Quindi, se la tabella ha 3 righe, al momento di inserire uno nuovo, il valore predefinito per field_order dovrebbe essere 4.

Quale sarebbe l'approccio migliore per impostare tale valore?

Un semplice conteggio di selezione all'interno dell'istruzione di inserimento?

Esiste una costante come CURRENT_TIMESTAMP per il tipo di dati TIMESTAMP che restituisce tale valore?

MODIFICA: La ragione di questo è la possibilità di ordinare la tabella in base a quel particolare campo; e quel campo verrebbe manipolato da un utente sul lato client usando l'ordinabile jQuery

+1

Penso che si desidera un auto_increment chiave primaria. [Prova a dare un'occhiata a questo.] (Http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html) – AdamMc331

+0

@ McAdam331 Posso avere due colonne auto_increment? Ho già una chiave primaria auto_increment, chiamiamola field_id –

+1

Non è possibile avere due autoincrementi, sfortunatamente. A seconda del tuo utilizzo per questo, forse qualcosa deve cambiare a livello di applicazione. Diciamo che nella tua applicazione devi accedere a questa colonna 'field_order'. Invece, puoi accedere a 'field_id' e incrementarlo. Potrebbe essere o non essere una buona soluzione, a seconda del problema. – AdamMc331

risposta

6

Ok, quindi le soluzioni che circondano questa domanda in realtà comportano un po 'di sfumatura. Sono andato avanti e ho deciso di rispondere, ma volevo anche affrontare alcune delle sfumature/dettagli che i commenti non stanno ancora affrontando.

Prima di tutto, vorrei fortemente sconsigliarvi di utilizzare auto_increment sulla chiave primaria, se non altro per il fatto che è molto facile che gli ID di incremento automatico vengano espulsi (ad esempio, le transazioni di rollback interferiranno con loro MySQL AUTO_INCREMENT does not ROLLBACK. Così eliminerà, come menzionato @Sebas).

In secondo luogo, è necessario considerare il motore di archiviazione. Se si utilizza MyISAM, è possibile ottenere molto rapidamente un COUNT (*) della tabella (poiché MyISAM riconosce sempre quante righe sono presenti in ogni tabella). Se stai usando INNODB, non è così. A seconda di cosa ti serve questa tabella, potresti riuscire a cavartela con MyISAM. Non è il motore predefinito, ma è certamente possibile che si possa incontrare un requisito per il quale MyISAM sarebbe una scelta migliore.

La terza cosa che dovresti chiederti è "Perché?" Perché hai bisogno di archiviare i tuoi dati in questo modo? Cosa ti dà effettivamente? Hai davvero bisogno di quelle informazioni in SQL? Nella stessa tabella della stessa tabella SQL?

E se il "Perché" ha una risposta che ne giustifica l'uso, l'ultima cosa che chiedo è "come?" In particolare, come hai intenzione di gestire inserti concomitanti? Come hai intenzione di gestire eliminazioni o rollback?

Dato il requisito che si ha, fare un conteggio stella della tabella è fondamentalmente necessario ... ma anche allora, ci sono alcune sfumature (eliminazioni, rollback, concorrenza) e anche alcune decisioni da prendere (quale motore di archiviazione usi, puoi usare MyISAM, che sarà più veloce per contare le stelle?).

Più di ogni altra cosa, però, mi chiederei perché avevo bisogno di questo in primo luogo. Forse lo fai davvero ... ma è un requisito terribilmente strano.

alla luce della tua EDIT:

EDIT: La ragione di questo è è quello di essere in grado di ordinare la tabella per quel particolare settore; e che il campo sarebbe stato manipolato da un utente nel lato client utilizzando ordinabile di jQuery

Essenzialmente quello che chiediamo è metadati relativi le tabelle. E vorrei raccomandare di memorizzare quei metadati in una tabella separata o in un servizio separato del tutto (ricerca elastica, redis, ecc.). Dovresti aggiornare periodicamente quella tabella separata (o l'archivio dei valori chiave). Se lo facevi in ​​SQL, potresti usare un trigger. O hai usato qualcosa come Elastic Search, potresti inserire i tuoi dati in SQL e ES contemporaneamente. Ad ogni modo, hai alcuni problemi spinosi con cui devi fare i conti (ad esempio, consistenza finale, concorrenza, tutte le cose gloriose che possono ritorcersi contro quando usi i trigger in MySQL).

Se fossi in me, annoterei due cose. Uno, nemmeno Google consegna sempre aggiornato COUNT(*). "Mostra righe 1-10 su circa XYZ." Lo fanno in parte perché hanno più dati che immagino tu faccia, e in parte perché in realtà è poco pratico (e molto rapidamente diventa irrealizzabile e proibitivo) calcolare un esatto COUNT(*) di un tavolo e tenerlo aggiornato in ogni momento .

Quindi, o cambierei completamente il mio requisito e sfruttare una statistica che posso ottenere rapidamente (se si utilizza MyISAM per l'archiviazione, andare avanti e utilizzare count(*) ... sarà molto veloce) o prenderei in considerazione il mantenimento un indice delle stelle di conteggio delle mie tabelle che periodicamente si aggiorna tramite alcuni processi (cron job, trigger, qualunque) ogni due ore o ogni giorno o qualcosa del genere.

In merito alla taglia su questa domanda ... non ci sarà mai una sola risposta canonica a questa domanda. Ci sono dei compromessi da fare indipendentemente da come decidi di gestirlo. Potrebbero essere dei compromessi in termini di coerenza, latenza, scalabilità, soluzioni precise e approssimative, perdere INNODB in cambio di MyISAM ... ma ci saranno dei compromessi. E alla fine la decisione si riduce a ciò che sei disposto a scambiare per ottenere il tuo requisito.

Se fossi in me, probabilmente fletterei la mia richiesta. E se lo facessi, probabilmente finirò per indicizzarlo in Elastic Search e assicurarmi che sia aggiornato fino a un paio d'ore circa. È questo che dovresti fare? Dipende. Certamente non è una "risposta giusta" tanto quanto è una risposta (tra le tante) che funzionerebbe se potessi vivere con il mio count(*) diventando un po 'obsoleto.

Si dovrebbe utilizzare la ricerca elastica per questo? Dipende. Ma avrai a che fare con compromessi che ti porteranno sempre. Questo non dipende. E dovrai decidere cosa sei disposto a rinunciare per ottenere quello che vuoi. Se non è critico, fletti il ​​requisito.

+1

Dovrei avere un riferimento esplicito a un accordo con @ McAddam331 ... non ho visto il suo commento. Ma in sostanza, sì, penso che dovresti davvero considerare l'applicazione stessa e se hai davvero bisogno di questo. Personalmente, non riesco a pensare ad una buona ragione. Ma chi lo sa, è possibile. – evanv

4

Ci può essere un approccio migliore, ma tutto quello che viene in mente in questo momento è quello di creare una seconda tabella che contiene il valore di cui avete bisogno, e utilizzare i trigger per rendere gli inserti appropriati/cancella:

Ecco un esempio :

-- Let's say this is your table 
create table tbl_test(
    id int unsigned not null auto_increment primary key, 
    text varchar(50) 
); 

-- Now, here's the table I propose. 
-- It will be related to your original table using 'Id' 
-- (If you're using InnoDB you can add the appropriate constraint 
create table tbl_incremental_values(
    id int unsigned not null primary key, 
    incremental_value int unsigned not null default 0 
); 

-- The triggers that make this work: 
delimiter $$ 
create trigger trig_add_one after insert on tbl_test for each row 
begin 
    declare n int unsigned default 0; 
    set n = (select count(*) from tbl_test); 
    insert into tbl_incremental_values 
     values (NEW.id, (n)); 
end $$ 

-- If you're using InnoDB tables and you've created a constraint that cascades 
-- delete operations, skip this trigger 
create trigger trig_remove before delete on tbl_test for each row 
begin 
    delete from tbl_incremental_values where id = OLD.id; 
end $$ 
delimiter ; 

Ora, diamo prova che:

insert into tbl_test(text) values ('a'), ('b'); 

select a.*, b.incremental_value 
from tbl_test as a inner join tbl_incremental_values as b using (id); 
-- Result: 
-- id | text | incremental_value 
-- ---+------+------------------ 
-- 1 | a | 1 
-- 2 | b | 2 

delete from tbl_test where text = 'b'; 
select a.*, b.incremental_value 
from tbl_test as a inner join tbl_incremental_values as b using (id); 
-- Result: 
-- id | text | incremental_value 
-- ---+------+------------------ 
-- 1 | a | 1 

insert into tbl_test(text) values ('c'), ('d'); 
select a.*, b.incremental_value 
from tbl_test as a inner join tbl_incremental_values as b using (id); 
-- Result: 
-- id | text | incremental_value 
-- ---+------+------------------ 
-- 1 | a | 1 
-- 3 | c | 2 
-- 4 | d | 3 

Questo funziona bene per i piccoli insiemi di dati, ma come evanv says in his answer:

Perché? "Perché è necessario archiviare i dati in questo modo? Cosa ti dà effettivamente? Hai davvero bisogno di quelle informazioni in SQL? Nella stessa tabella della stessa tabella SQL?

Se tutto ciò che serve è produrre quel risultato, c'è un modo molto più semplice per farlo funzionare: variabili utente.

Vediamo ora dire che il tavolo è qualcosa di simile:

create table tbl_test(
    id int unsigned not null auto_increment primary key, 
    ts timestamp, 
    text varchar(50) 
); 

insert into tbl_test(text) values('a'); 
insert into tbl_test(text) values('b'); 
insert into tbl_test(text) values('c'); 
insert into tbl_test(text) values('d'); 
delete from tbl_test where text = 'b'; 
insert into tbl_test(text) values('e'); 

La colonna ts prenderà il valore della data e l'ora in cui è stata inserita ogni riga, quindi se si ordina che da quella colonna, riceverai le righe nell'ordine in cui sono state inserite. Ma ora: come aggiungere quel "valore incrementale"? Usando un po 'di trucco con le variabili utente è possibile:

select a.* 
    , @n := @n + 1 as incremental_value 
--  ^^^^^^^^^^^^ This will update the value of @n on each row 
from (select @n := 0) as init -- <-- you need to initialize @n to zero 
    , tbl_test as a 
order by a.ts; 
-- Result: 
-- id | ts     | text | incremental_value 
-- ---+---------------------+------+---------------------- 
-- 1 | xxxx-xx-xx xx:xx:xx | a | 1 
-- 3 | xxxx-xx-xx xx:xx:xx | c | 2 
-- 4 | xxxx-xx-xx xx:xx:xx | d | 3 
-- 5 | xxxx-xx-xx xx-xx-xx | e | 4 

Ma ora ... come affrontare grandi insiemi di dati, in cui è probabile che verrà utilizzato LIMIT? Semplicemente inizializzazione @n al valore iniziale di limite:

-- A dull example: 

prepare stmt from 
    "select a.*, @n := @n + 1 as incremental_value 
    from (select @n := ?) as init, tbl_test as a 
    order by a.ts 
    limit ?, ?"; 
-- The question marks work as "place holders" for values. If you're working 
-- directly on MySQL CLI or MySQL workbench, you'll need to create user variables 
-- to hold the values you want to use. 
set @first_row = 2, @nrows = 2; 
execute stmt using @first_row, @first_row, @nrows; 
--     ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^ 
--     Initalizes The "floor" The number 
--     the @n  of the  of rows 
--     value   LIMIT   you want 
-- 
-- Set @first_row to zero if you want to get the first @nrows rows 
-- 
-- Result: 
-- id | ts     | text | incremental_value 
-- ---+---------------------+------+---------------------- 
-- 4 | xxxx-xx-xx xx:xx:xx | d | 3 
-- 5 | xxxx-xx-xx xx-xx-xx | e | 4 
deallocate prepare stmt; 
+0

Sì, e se "perché" ha davvero una buona risposta, sarei curioso di sapere se SQL è il posto giusto per farlo. La ricerca elastica potrebbe essere una buona opzione in realtà, un po 'come un indice di conteggio (*) per un dato record in un determinato momento. Ma quella domanda "Perché" ha bisogno di una risposta prima. – evanv

-1

Che cosa ho fatto per evitare il SELECT COUNT(*) ... nella query di inserimento è di avere un non ordinato stato della colonna field_order, diciamo un valore predefinito di 0 .

Il select-query assomiglia:

SELECT * FROM my_table ... ORDER BY id_primary, field_order 

Finché non si applica un ordine personalizzato, la query si tradurrà in ordine cronologico.

Quando si desidera applicare l'ordinamento personalizzato field_order dovrebbe essere ri-setted da li a contare da -X a 0:

id | sort 
---+----- 
1 | -2 
2 | -1 
3 | 0 

Quando alterazione si verifica l'ordinamento personalizzato rimane, e le nuove righe saranno sempre allineati chronoligicaly alla fine del l'ordinamento personalizzato già in atto:

id | sort 
---+----- 
1 | -2 
3 | 0 
4 | 0 
1

sembra che la domanda iniziale è stato chiesto di un modo semplice per impostare un ordinamento predefinito su un nuovo record. In seguito l'utente può regolare il valore del "campo ordine". Sembra che DELETES e ROLLBACK non abbiano nulla a che fare con questo.

Ecco una soluzione semplice. Per il campo dell'ordine di ordinamento, impostare il valore predefinito su 0 e utilizzare la chiave primaria come ordinamento secondario.Basta cambiare il tuo ordinamento nella query per essere DESC. Se si desidera che la funzionalità di default di essere "visualizzazione più recentemente aggiunto prima", quindi utilizzare:

SELECT * from my_table 
WHERE user_id = :uid 
ORDER BY field_order, primary_id DESC 

Se si vuole "visualizzazione più recentemente aggiunto per ultimo" uso:

SELECT * from my_table 
WHERE user_id = :uid 
ORDER BY field_order DESC, primary_id 
Problemi correlati