2010-06-26 4 views
5

Sto costruendo un'applicazione di gestione per aiutare a gestire la mia azienda di dettaglianti auto mobili (e, si spera, altri). Sto lottando per capire come modellare alcuni dati.Appuntamenti e partite singole

Questa domanda è legata ad una precedente interrogazione che ho postato, ma ho riprodotto le informazioni rilevanti di seguito: Database design - google app engine

In questa applicazione, ci sono concetti di "Appuntamenti" e "Voci di linea. "

Gli appuntamenti sono un luogo e un orario in cui ci si aspetta che i dipendenti svolgano un servizio.

Gli elementi pubblicitari sono un servizio, una commissione o uno sconto e le relative informazioni associate. Un esempio di voci che potrebbero andare in un appuntamento:

 
Name:       Price: Commission: Time estimate 
Full Detail, Regular Size:  160  75  3.5 hours 
$10 Off Full Detail Coupon:  -10  0   0 hours 
Premium Detail:     220  110  4.5 hours 
Derived totals(not a line item): $370  $185  8.0 hours 

Nel mio precedente implementazione di questa applicazione, gli elementi pubblicitari sono stati contenuta da un unico appuntamento. Questo ha funzionato bene la maggior parte del tempo, ma a volte ha causato problemi. Un esempio potrebbe essere se un appuntamento venisse interrotto a metà strada a causa della pioggia e il tecnico dovesse tornare fuori il giorno successivo e finire. Questa situazione richiedeva due appuntamenti per lo stesso elemento pubblicitario. In casi come questo, vorrei solo fondere leggermente i dati impostando la "voce" sul secondo appuntamento per leggere qualcosa come "Finish Up" e quindi il costo sarebbe $ 0.

In questa prossima versione, sto considerando consentendo Articoli Linea da abbinare con più di un appuntamento con una struttura di tabella che assomiglia a questo:

Appointment 
start_time 
etc... 

Line_Item 
appointment_Key_List 
name 
price 
etc... 

Un problema generale con questa struttura è che è complicato e non sono nemmeno sicuro se è appropriato abbinare un elemento pubblicitario con più appuntamenti. Se gli elementi pubblicitari possono essere solo parte di un appuntamento, in realtà posso solo inserire un elenco di elementi pubblicitari in ogni appuntamento, quando ricevo gli appuntamenti, ricevo già gli elementi pubblicitari.

Un problema più specifico è che utilizzo il motore di app di google e se desidero eseguire una query per un insieme di appuntamenti e i relativi elementi pubblicitari associati, dovrei prima eseguire una query per l'insieme di appuntamenti e quindi eseguire un secondo eseguire una query per gli elementi pubblicitari utilizzando l'operatore IN per verificare se una qualsiasi delle chiavi dell'appuntamento Line_Item rientra nell'insieme di chiavi di appuntamento che sono state restituite dalla query precedente. La seconda query avrà esito negativo se ho più di 30 chiavi che mi richiedono di dividere la query. Potrei denormalizzare i dati per evitare questa query di lettura complicata ed estesa, e probabilmente dovrò comunque denormalizzare in qualche modo, ma preferirei evitare la complessità laddove appropriato.

La mia domanda è: come viene solitamente modellato questo tipo di situazione? È anche appropriato che un elemento pubblicitario sia abbinato a più di un appuntamento oppure è normale dividere semplicemente gli elementi pubblicitari in singoli per ogni appuntamento, ad esempio "1a metà del lavoro di 2 giorni" e "2a metà del lavoro di due giorni". ". Come fanno le simili applicazioni di successo a fare questo? Quali sono le regole pratiche in questo tipo di situazione? Quali implementazioni si sono rivelate meno problematiche?

Grazie!

risposta

2

L'approccio che stai suggerendo funzionerà bene; puoi modellare 'appuntamento_Key_list' dell'elemento pubblicitario come proprietà di elenco e funzionerà come previsto. Non è necessario utilizzare l'operatore IN: è necessario abbinare un singolo valore nel datastore a un elenco di chiavi (es. "WHERE datastore_column IN ('a', 'b', 'c')), mentre stai facendo il contrario - corrispondenza di un singolo valore con un elenco nel datastore

Suggerirei, tuttavia, che il contrario potrebbe essere più adatto al tuo compito: avere ogni appuntamento con un elenco di chiavi di elementi pubblicitari. Funziona più o meno allo stesso modo, ma per recuperare tutti i dati su un appuntamento, è prima necessario recuperare l'appuntamento, quindi eseguire un rinvio di massa sugli elementi, utilizzando le chiavi dall'entità di appuntamento.Se si conosce la chiave dell'appuntamento , eviti così la necessità di fare qualsiasi domanda.

Ho cercato di spiegare a Pindatjuh perché interrogare una proprietà di lista non è meno efficiente di uno a valore singolo, ma apparentemente è necessaria una descrizione più dettagliata, quindi senza ulteriori indugi, ecco ...

una breve primer su App Engine datastore indicizzazione

Sebbene Python e Java forniscono varie interfacce di alto livello per il datastore, il datastore per sé la dice un'astrazione di livello inferiore, chiamato entità. Un'entità consiste delle seguenti operazioni:

  1. Una unica chiave primaria
  2. un elenco di (nome, valore) coppie

La chiave primaria è la chiave Datastore sei già familiarità con. L'elenco di coppie (nome, valore) è la rappresentazione di App Engine per i dati nell'entità. Fin qui tutto molto semplice. Un'entità con i seguenti valori:

a_string = "Hello, world" 
an_int = 123 

sarebbe serializzato a qualcosa di simile a questo:

[('a_string', 'Hello, world'), ('an_int', 123)] 

Ma come funziona questo interagire con gli elenchi? Bene, le liste sono trattate come proprietà a "valore moltiplicato". Cioè, una lista con n elementi è memorizzata come n proprietà separate. Un esempio rende probabilmente più chiaro:

a_string = "Hello, world" 
an_int = 123 
a_list_of_ints = [42, 314, 9] 

verrà serializzato come:

[('a_string', 'Hello, world'), ('an_int', 123), ('a_list_of_ints', 42), ('a_list_of_ints', 314), ('a_list_of_ints', 9)] 

Come si può vedere, la lista ottiene rappresentato una serie di valori, tutti con lo stesso nome. Quando carichi dati dal datastore, l'SDK vede il valore ripetuto e lo trasforma in un elenco.

Dove questo diventa importante quando interagisce con l'indicizzazione. Supponiamo di avere un indice su "a_string" e "an_int". Quando si inserisce o modifica un valore, App Engine genera una serie di voci di indice per esso; per l'indice al di sopra e l'entità di cui sopra, si genera una singola riga nella indice che sembra qualcosa di simile:

('Hello, world', 123, a_key) 

('a_key' qui è un segnaposto per la chiave del soggetto originale.) Quando si esegue una query che utilizza questo indice, ha solo bisogno di fare una ricerca sull'indice per trovare le righe con il prefisso appropriato (ad esempio, 'SELECT * FROM Kind WHERE a_string = "Hello, world" ORDER BY an_int').

Quando si indice un elenco, tuttavia, App Engine inserisce più righe di indice. Un indice su 'an_int' e 'a_list_of_ints' genererebbe queste righe per l'entità di cui sopra:

(123, 42, a_key) 
(123, 314, a_key) 
(123, 9, a_key) 

Anche in questo caso, l'interrogazione funziona lo stesso come ha fatto in precedenza - App Engine deve solo cercare la riga con il prefisso corretto nell'indice Il numero di voci nell'elenco non ha alcun impatto sulla velocità della query, ma solo sul tempo impiegato per generare e scrivere le voci dell'indice. In effetti, il pianificatore di query non ha idea che 'a_list_of_ints' sia una proprietà con valore moltiplicato - lo considera semplicemente come qualsiasi altra voce di indice.

Quindi, in poche parole:

  1. Non c'è alcuna differenza pratica tra un elenco con un elemento in esso e una proprietà individuale, in indicizzazione e termini interrogazione
  2. Le dimensioni di un elenco indicizzato influisce sul tempo e spazio richiesto per l'indicizzazione, ma non per l'interrogazione.
  3. È possibile eseguire una query che corrisponde a qualsiasi entità con un valore determinato in un elenco utilizzando un filtro di uguaglianza semplice.
+0

Risposta molto informativa! Grazie per aver condiviso queste informazioni con SO. @DutrowLLC si prega di contrassegnare questa risposta come quella corretta, in quanto è, a mio parere, una risposta molto migliore alla tua domanda. @ Nick Johnson Le mie scuse per credere alle cose sbagliate. Grazie per aver spiegato e fornito questa bellissima risposta con ottime informazioni per tutti! – Pindatjuh

+0

@Pindatjuh - È molto da vedere. Questo video contiene anche alcuni dettagli su come gli elenchi sono indicizzati e ricercati. Ho trovato estremamente utile la seconda parte di unire-join. Era un pdf con diapositive che puoi guardare mentre guardi il video: http://code.google.com/events/io/2009/sessions/BuildingScalableComplexApps.html –

+0

Grazie per aver trovato il tempo di rispondere a questa domanda così accuratamente Spero che anche altre persone possano trovare la tua risposta e trarne beneficio. –

1

La soluzione usuale per questo tipo di problemi è la normalizzazione del modello, ovvero lo First Normal Form.

Il modello, in forma normalizzata, avrebbe una terza tabella, con riferimenti alle Appointment e Line_Item righe:

Appointment 
start_time 
... 

Line_Item 
name 
price 
... 

Appointment_Line_Item 
appointment_key 
line_item_key 

C'è un problema però! Poiché utilizzi Google App Engine e il relativo archivio dati è limitato a ("GQL cannot perform an SQL-like JOIN") e richiede in gran parte una denormalizzazione.

Hai suggerito di utilizzare un campo elenco. È possibile usarlo, ma è molto difficile indicizzarlo. La ricerca di un tasto (lo appointment_key) in un elenco per riga nel database non si sta effettivamente comportando. Propongo due possibilità:

  1. Duplicato Line_Item.

    Line_Item 
    appointment_key 
    name 
    price 
    finished 
    ... 
    

    Un Line_Item dovrebbe avere uno stato finished, quando la voce è finito o no dal dipendente. Se un dipendente non ha terminato tutti gli elementi pubblicitari, contrassegnali come incompiuti, crea un nuovo appuntamento e copia tutti gli elementi incompleti. Puoi indicizzare il campo appointment_key su tutto il Line_Items, che è una buona cosa. Tuttavia, i dati duplicati potrebbero essere un problema.

  2. campi dinamici per Line_Item:

    Line_Item 
    duplicate_key 
    appointment_key 
    name 
    price 
    finished 
    ... 
    

    creare un nuovo campo, duplicate_key, per Line_Item che punta ad un altro Line_Item o per nulla (riservare questa chiave!). Null significa che Line_Item è originale, qualsiasi altro valore indica che questo Line_Item è un duplicato di Line_Item al quale punta il campo. Tutti i campi di Line_Item contrassegnati come duplicati ereditano i campi dell'originale Line_Item, ad eccezione dello appointment_key: quindi richiederà meno spazio di archiviazione. Anche questa soluzione dovrebbe avere appointment_key indicizzato, per accelerare i tempi di ricerca. Ciò richiede una query aggiuntiva per duplicato Line_Item, che potrebbe essere un problema.

Ora, è una scelta chiara: migliore velocità o migliore conservazione. Vorrei andare per primo, in quanto riduce la complessità del modello, e la memorizzazione non è mai un problema con i sistemi moderni. Meno complessità generalmente significa meno errori e meno costi di sviluppo/test, che giustificano il costo del requisito di archiviazione.

+0

Grazie per la risposta. Non ho mai pensato all'approccio chiave duplicato, questa è una soluzione davvero interessante. Una cosa da tenere a mente con il motore di app è che fanno liste di indici e ti permettono di cercare su di loro. Lo chiamano un "merge-join" e sembra espandere le loro capacità al di là di un semplice archivio di valori-chiave: http://code.google.com/events/io/2009/sessions/BuildingScalableComplexApps.html –

+1

"Ricerca di una chiave (il tasto_appuntamento) in una lista per riga nel database non si sta effettivamente esibendo. " - non vero. Puoi filtrare le proprietà degli elenchi in App Engine con la stessa efficienza con cui non sono presenti elenchi. –

+0

@Nick Johnson - Grazie per avermi contattato. Penso che questo sia un importante cambiamento di gioco con il motore dell'app che è inaspettato e poco conosciuto. –