Sagra di Cimpello
Pro Loco Cimpello 1998


Note introduttive

Come ho già abbondantemente spiegato, MySQL è un DBMS SQL ``classicamente'' entry-level, ovvero non supporta chiavi esterne e SELECT annidate.

Quello che è interessante, però, e che il parser è comunque quasi completo, e quindi è possibile scrivere istruzioni in ``full-sql'', che in parte verranno semplicemente ignorate (come per le direttive FOREIGN KEY nelle CREATE TABLE).
Questa secondo me è una ``feature'' interessante e utile...

Sul contatore degli ordini

Dovendo implementare un contatore progressivo per gli ordini, inizialmente mi ero orientato verso le funzioni, proprietarie e quindi non portabili, di generazione di numeri progressivi di MySQL.

Ma questo mi impediva di conoscere il numero d'ordine, che mi serve in una serie di inserimenti successivi.
Ho quindi optato per una soluzione che reputo semplice e robusta, overo una semplice tabella definita come:

CREATE TABLE Progressivo (
	Numero SMALLINT NOT NULL,
	PRIMARY KEY (Numero) )

da cui estraggo un identificatore unico con un algoritmo elementare:

while (``inserimento andato a male'') {

	SELECT MAX(Numero) AS Max FROM Progressivo;
	
	INSERT INTO Progressivo VALUES (Max+1);
}

visto che l'istruzione INSERT INTO è atomica, se l'inserimento va a buon fine ho sicuramente ottenuto un identificativo (Max+1) unico.

Definizioni delle Tabelle

Volontario

CREATE TABLE Volontario (
	IDVolontario CHAR(2) NOT NULL,
	Nome VARCHAR(20),
	Cognome VARCHAR(20),
	Servizio ENUM('C','A','V') NOT NULL,
	DataDiNascita DATE,
	Indirizzo VARCHAR(40),
	PRIMARY KEY (IDVolontario),
	INDEX Aux (Servizio) )

Per un maggiore controllo sull'input, ho utilizzato i tipi enumerati di MySQL, presenti in tutti i DBMS SQL moderni.

Piatto

CREATE TABLE Piatto (
	IDPiatto CHAR(2) NOT NULL,
	NomePiatto VARCHAR(20),
	Descrizione VARCHAR(50),
	Tipo ENUM('C','B') NOT NULL,
	Ord SMALLINT NOT NULL,
	Prezzo SMALLINT UNSIGNED NOT NULL,
	PRIMARY KEY (IDPiatto),
	INDEX Aux (Tipo,Ord) )

Ordine

CREATE TABLE Ordine (
	Nordine SMALLINT NOT NULL,
	DataOrdine DATE NOT NULL,
	Stato ENUM('I','X','A','E','D') NOT NULL,
	PRIMARY KEY (Nordine,DataOrdine),
	INDEX Aux (Stato) )

Asporto

CREATE TABLE Asporto (
	Nordine SMALLINT NOT NULL,
	DataOrdine DATE NOT NULL,
	NomeCliente VARCHAR(20),
	PRIMARY KEY (Nordine,DataOrdine),
	FOREIGN KEY (Nordine,DataOrdine) REFERENCES Ordine (Nordine,DataOrdine)
		ON DELETE CASCADE )

È da notare la definizione (ignorata) di chiave esterna; è anche charo che una cancellazione del'ordine deve comportare la cancelazione delle informazioni sull'asporto.

Tavolo

CREATE TABLE Tavolo (
	Nordine SMALLINT NOT NULL,
	DataOrdine DATE NOT NULL,
	Ntavolo SMALLINT UNSIGNED NOT NULL,
	PRIMARY KEY (Nordine,DataOrdine),
	FOREIGN KEY (Nordine,DataOrdine) REFERENCES Ordine (Nordine,DataOrdine)
		ON DELETE CASCADE )

Stesse note di qui sopra.

Storia

CREATE TABLE Storia (
	Nordine SMALLINT NOT NULL,
	DataOrdine DATE NOT NULL,
	IDVolontario CHAR(2) NOT NULL,
	Tstamp TIMESTAMP,
	PRIMARY KEY (Nordine,DataOrdine,IDVolontario),
	FOREIGN KEY (Nordine,DataOrdine) REFERENCES Ordine (Nordine,DataOrdine)
		ON DELETE CASCADE, \
	FOREIGN KEY (IDVolontario) REFERENCES Volontario (IDVolontario)
		ON DELETE SET NULL )

Qui invece è da notare che se un identificativo di volontario viene eliminato dal database, le informazioni sulla storia degli ordini trattati da quel volontario non vanno cancellate (non vanno ignorate!!!); in questa maniera viene posto un riferimento a NULL.

Composizione

CREATE TABLE Composizione (
	Nordine SMALLINT NOT NULL,
	DataOrdine DATE NOT NULL,
	IDPiatto CHAR(2) NOT NULL,
	Qta SMALLINT UNSIGNED NOT NULL,
	PRIMARY KEY (Nordine,DataOrdine,IDPiatto),
	FOREIGN KEY (Nordine,DataOrdine) REFERENCES Ordine (Nordine,DataOrdine)
		ON DELETE CASCADE,
	FOREIGN KEY (IDPiatto) REFERENCES Piatto (IDPiatto)
		ON DELETE SET NULL )

Si possono fare le stesse note di qui sopra.

Note sugli indici

Come si può notare dalle definizioni qui sopra, oltre agli indici primari ``naturali'', già definiti a livello di progettazione concettuale, sono stati aggiunti ulteriori indici per questioni di efficienza.

In particolare:

  1. è stato aggiunto un indice su (Tipo, Ord) alla relazione Piatto, visto che spesso vengono effettuate scelte (SELECT/sort) di una determinata categoria di piatti
  2. è stato aggiunto un indice su (Servizio) alla relazione Volontario, per gli stessi motivi di qui sopra
  3. è stato aggiunto un indice a (Stato) della relazione Ordine, visto che spesso vengono fatte query che prendono in considerazione condizioni sullo stato.
Tutto questo, chiaramente, per motivi di efficienza.

Alcune note su query notevoli

Tendenzialmente tutte le query in LinuxSagra sono elementari, ma c'è qualche eccezione degna di nota.

Calcolo dei tempi medi

Qui il problema è selezionare gli ordini evasi (informazione data da Ordine.Stato), che non sono da asporto (e quindi presenti in Tavolo) e fare una media dei tempi; il metodo piú semplice, ma coretto, che ho trovato è stato quello di fare una media dei tempi di inserimento, abilitazione e evasione (grazie a Volontario.Servizio) e quindi da questi calcolare le differenze.

SELECT Servizio, AVG(UNIX_TIMESTAMP(Tstamp)) AS Media
FROM Ordine NATURAL LEFT JOIN Storia
	NATURAL LEFT JOIN Volontario,Tavolo
WHERE (Ordine.Nordine=Tavolo.Nordine AND Ordine.DataOrdine=Tavolo.DataOrdine
	AND Stato = 'E')
GROUP BY Servizio

Notare la condizione esplicita tra Ordine e Tavolo, questo perchè una join naturale ritornava un null in corrispondeza degli ordini non presenti in Tavolo; niente di grave, ma non mi piaceva! ;).

Calcolo dei totali a fine serata

Qui il problema era quello di calcolare le somme degli ordini inseriti in un certo intervallo; si deve quindi fare riferimento a Storia.Tstamp inserito in corrispondenza di un Servizio come cassiere.
Inoltre, per avere un output decente, occorre anche ordinare le pietanze nel solito ordine; quindi il join con Composizione e l'ordinamento per Tipo e Ord.

In teoria si dovrebbero controllare solo gli ordini evasi, e non quelli genericamente ``non cancellati''. Pur essendo questo metodo meno robusto, permette di utilizzare solo la gestione cassa di LinuxSagra ma contemporaneamente avere un totale affidabile.

SELECT Composizione.IDPiatto, SUM(Qta) AS Totale
FROM Piatto NATURAL LEFT JOIN Composizione NATURAL LEFT JOIN Ordine
	NATURAL LEFT JOIN Storia NATURAL LEFT JOIN Volontario
WHERE (Servizio = 'C' AND Stato <> 'D' AND
	( ($TS2 - UNIX_TIMESTAMP(Tstamp)) < $diff))
GROUP BY IDPiatto
ORDER BY Tipo,Ord

$TS2 contiene il limite inferiore dell'intervallo, mentre $diff contiene l'estensione dell'intervallo in cui calcolare i totali.

Costruzione della ``matrice'' degli ordini

In Cucina si devono estrarre tutti gli ordini non evasi (quindi da asporto o abilitati) e solo le pietanze che interessano; questa query ritorna una considerevole quantità di dati, che poi devono essere letti per costruire una matrice che elenchi le informazioni utili di almeno una decina di ordini.

SELECT Ordine.Nordine, Ordine.DataOrdine, Stato,IDVolontario, 
	UNIX_TIMESTAMP(Tstamp) AS UTstamp, Composizione.IDPiatto, Qta
FROM Ordine NATURAL LEFT JOIN Storia NATURAL LEFT JOIN Composizione
	NATURAL LEFT JOIN Piatto
WHERE ((Stato='X' OR Stato='A') AND Tipo = 'C')
ORDER BY Ordine.DataOrdine, Ordine.Nordine, Tipo, Ord

calcolo delle porzioni richieste

È piú o meno la query di sopra, solo che con questa calcolo i totali, per ogni voce, delle portate ancora da evadere; questa informazione compare sullo schermo dei vassoisti e può essere comunicata/letta alla cucina.

SELECT IDPiatto, SUM(Qta) AS Qtot
FROM Composizione NATURAL LEFT JOIN Ordine
WHERE (Stato = 'A' OR Stato = 'X')
GROUP BY IDPiatto


2 3 5
LinuxSagra v1.0.0 (C) Marco Gaiarin 12:00:00 Ritorna al Menu