Second assignment.

This commit is contained in:
Davte 2020-05-30 15:28:21 +02:00
parent 796cbd7140
commit 4c4ffa048f
Signed by: Davte
GPG Key ID: D848081D6F892DA9
15 changed files with 1131 additions and 45 deletions

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
# ---> TeX
out/
# SQLite database files
*.db
## Core latex/pdflatex auxiliary files:
*.aux
*.lof

View File

@ -4,6 +4,7 @@
\usepackage{enumitem}
\usepackage{forest}
\usepackage{graphicx}
\usepackage[hidelinks]{hyperref}
\usepackage[utf8]{inputenc}
\usepackage{listings}
\usepackage{pxfonts}
@ -11,6 +12,8 @@
\usepackage[normalem]{ulem}
\usepackage{geometry}
\AfterEndEnvironment{figure}{\noindent}
\geometry{
a4paper,
top=30mm,
@ -18,7 +21,13 @@ top=30mm,
\forestset{qtree/.style={for tree={parent anchor=south,
child anchor=north,align=center,inner sep=0pt}}}
\lstset{upquote=true,showstringspaces=false}
\lstset{
upquote=true,
inputencoding=utf8,
extendedchars=true,
literate={à}{{\`a}}1, % Accept à in `lstlisting` environments
showstringspaces=false
}
\lstdefinestyle{SQLu}{
language=SQL,
basicstyle=\small\ttfamily,
@ -34,17 +43,22 @@ top=30mm,
% First page information
\title{\textbf{Basi di dati prof. Ghelli}\linebreak\textit{``Sempre sul pezzo"}}
\title{\textbf{Basi di dati prof. Ghelli}\linebreak\textit{``Una cervecita fresca"}}
\author{Davide Testa 613565}
\date{2020-05-15}
\date{2020-06-01}
\begin{document}
\maketitle % Insert the title, author and date
\input{\folder/source_code.tex}
\section{Descrizione di massima del dominio (testo)}\label{sec:testo}
\input{\folder/testo.tex}
\clearpage
\section{Descrizione del dominio}\label{sec:dominio}
\input{\folder/dominio.tex}
% \clearpage
\section{Schema concettuale}\label{sec:schema-concettuale}
La figura~\ref{fig:schema-concettuale} mostra lo schema concettuale in formato
grafico.
\begin{figure}[hb]
\centering
\includegraphics[width=\linewidth]{\folder/schema_concettuale.pdf}
@ -52,11 +66,14 @@ top=30mm,
\label{fig:schema-concettuale}
\end{figure}
\input{\folder/schema_concettuale.tex}
\section{Schema logico}\label{sec:schema-logico}
\clearpage
\section{Schema logico relazionale}\label{sec:schema-logico}
La figura~\ref{fig:schema-logico} mostra lo schema logico relazionale in
formato grafico.\\
\begin{figure}[htb]
\centering
\includegraphics[width=\linewidth]{\folder/schema_logico.pdf}
\caption{Schema logico in formato grafico}
\caption{Schema logico relazionale in formato grafico}
\label{fig:schema-logico}
\end{figure}
\input{\folder/schema_logico.tex}
@ -66,4 +83,6 @@ top=30mm,
\vskip2pc
\section{Piani di accesso}\label{sec:piani}
\input{\folder/piani_di_accesso.tex}
\vfill
\input{\folder/source_code.tex}
\end{document}

View File

@ -1,5 +1,75 @@
% !TEX root = ../main.tex
Descrizione del dominio.
L'applicazione ``Una cervecita fresca" deve fornire supporto ai birrai e alle
birraie artigianali nella produzione delle loro birre fatte in casa con il
metodo all-grain.
Nominare le classi di interesse, specificarne gli attributi e indicarne le relazioni con le altre classi.
Ogni utente dell'app (birraio o birraia) può lavorare per uno o più birrifici, e può
visualizzare le ricette dei birrifici per cui lavora.
Del birraio o birraia sono rilevanti il nome, il cognome, il soprannome,
l'indirizzo email, il codice fiscale.
Ogni birrificio ha un nome, un anno di fondazione, un motto e uno stemma.
Il birrificio ha inoltre una capacità produttiva, cioè il numero massimo di
litri che può produrre in un singolo ciclo produttivo.
Per ogni birrificio possono lavorare più persone.
Ogni birrificio può lavorare a una sola produzione per volta, mettendo a
disposizione tutta la sua capacità produttiva, o solo una parte di questa.
Ogni birrificio ha accesso a una o più ricette: ogni ricetta ha un nome, un
creatore o creatrice, una data di creazione, una eventuale ricetta madre
(ovvero la ricetta che è stata modificata per elaborarla) e la quantità
relativa di ciascun ingrediente.
Oltre alla creatrice o creatore di una ricetta, anche le birraie e i birrai di
un birrificio possono vedere le ricette del birrificio in cui lavorano.
La ricetta può essere archiviata o eliminata dal creatore o creatrice: lo stato
della ricetta può essere attiva, archiviata, eliminata.
Gli ingredienti sono moltissimi, ma di solo 5 tipi: malti, luppoli, lieviti,
zuccheri e additivi.
Ciascun tipo ha un nome e la sua unità di misura appropriata (per esempio, i
luppoli vengono espressi in \textit{mash}, ovvero grammi per litro di
miscuglio, mentre i malti sono espressi in peso percentuale sugli ingredienti
secchi).
Ciascun ingrediente ha un tipo e una descrizione.
In ogni ricetta, è indicata la quantità di ciascun ingrediente come numero
puro: l'unità di misura (mash, peso percentuale, \textellipsis) dipende dal
tipo di ingrediente.
La quantità di acqua è ricavabile dagli altri ingredienti e non occorre quindi
memorizzarla: si miscelano in proporzione gli ingredienti secchi, dal
\textit{mash} si capisce quale volume finale deve raggiungere la soluzione.
La visualizzazione delle ricette terrà conto della quantità di prodotto che si
vuole produrre (che dev'essere inferiore o uguale alla capacità produttiva del
birrificio) per mostrare le quantità assolute dei vari ingredienti in kg e L;
nel database invece le quantità verranno memorizzate in termini relativi come
descritto sopra.
Una produzione è caratterizzata da una data di produzione, un numero di lotto
che la identifica univocamente, il numero di bottiglie da 500 mL prodotte
(questo è l'unico possibile formato di produzione) e uno stato di preparazione
(in corso, completa, annullata).
È prodotta seguendo una ricetta di un birrificio.
A ogni produzione si possono accompagnare alcune note.
Ogni nota ha un testo.
Esistono particolari note, dette di degustazione, che esprimono anche un
giudizio da 1 a 10 sulla qualità del prodotto.
Il birrificio tiene un registro degli acquisti, conservando i dati della
fattura e specificando per ogni ingrediente acquistato la quantità.
Ogni fattura registrata dal birrificio è caratterizzata da una data, un numero
di fattura, un importo e un fornitore.
I fornitori hanno una ragione sociale, una partita IVA e un indirizzo.
L'inventario mostra, per ogni ingrediente, la quantità disponibile e quella
totale (compresi cioè gli ingredienti ``prenotati" da preparazioni in corso).
Oltre che come birrario o birraia, ci si può anche registrare come cliente,
specificando un indirizzo di spedizione.
I clienti sono caratterizzati, come chi produce birra, da nome, cognome, email
e codice fiscale, ma non hanno un soprannome.
La clientela può effettuare prenotazioni per una quantità di bottiglie di un
dato lotto di produzione.
Ogni prenotazione ha uno stato, che rimane in sospeso fino al termine della
produzione, quando il birrificio può impostarlo su `confermato' se intende
procedere alla vendita oppure `annullato' se il prodotto non è soddisfacente.

View File

@ -3,33 +3,35 @@
\paragraph{Piano di accesso logico della query a}
\begin{center}
\begin{forest}
[{$\pi^{b}$ R.C, S.D}
[{$\bowtie$ R.E = S.F}
[{$\sigma$ C $>=$ 10}
[Tabella1 R]
\begin{forest}, baseline, qtree
[{$\pi^{b}$ r.IdRicetta , r.Nome}
[{$\bowtie$ p.IdPersona = r.IdCreatrice}
[{$\sigma$ p.Nome = 'Giovanni'}
[Persone p]
]
[Tabella2 S]
[Ricette r]
]
]
\end{forest}
\end{center}
Non c'è in questo caso differenza tra $\pi^{b}$ e $\pi$: non ci possono essere
duplicati.
\paragraph{Piano di accesso fisico della query a senza indici}
\begin{center}
\begin{forest}
[{Project(\{C, D\})}
[{SortMerge(R.E = S.F)}
[{Sort(\{E\})}
[{Project(\{E, C\})}
[{Filter(C $>=$ 10)}
[{TableScan(Tabella R)}]
\begin{forest}, baseline, qtree
[{Project(\{r.IdRicetta , r.Nome\})}
[{SortMerge(p.IdPersona = r.IdCreatrice)}
[{Sort([p.IdPersona])}
[{Project(\{p.IdPersona\})}
[{Filter(p.Nome = 'Giovanni')}
[{TableScan(Persone p)}]
]
]
]
[{Sort(\{F\})}
[{Project(\{C, F\})}
[{TableScan(Tabella S)}]
[{Sort([r.IdCreatrice])}
[{Project(\{r.IdRicetta, r.Nome, r.IdCreatrice\})}
[{TableScan(Ricette r)}]
]
]
]
@ -40,12 +42,210 @@
\paragraph{Piano di accesso fisico della query a con due indici}
\begin{center}
\begin{forest}, baseline, qtree
[{Project(\{C, D\})}
[{IndexNestedLoop(R.E = S.F)}
[{IndexFilter(Tabella R,\\ IndRC, C $>=$ 10)}]
[{IndexFilter(Tabella S,\\IndSF, S.F = R.E)}]
[{Project(\{r.IdRicetta , r.Nome\})}
[{IndexNestedLoop(p.IdPersona = r.IdCreatrice)}
[{IndexFilter(Persone p,\\ IndPN, p.Nome = 'Giovanni')}]
[{IndexFilter(Ricette r,\\IndRC, r.IdCreatrice = p.IdPersona)}]
]
]
\end{forest}
\end{center}
Indici necessari: \texttt{IndRC} (indice della tabella R sullattributo C) e \texttt{IndSF} (indice della tabella S sull'attributo F).
Indici necessari: \texttt{IndPN} (indice della tabella Persone sullattributo
Nome) e \texttt{IndRC} (indice della tabella Ricette sull'attributo Creatrice).
\clearpage
\subsection{Query b}
\paragraph{Piano di accesso logico della query b}
\begin{center}
\begin{forest}, baseline, qtree
[{$\tau$[-DiversiFornitori]}
[{$\pi^{b}$ fa.IdBirrificio, COUNT(DISTINCT fa.IdFornitore) DiversiFornitori}
[{$\sigma$ COUNT(DISTINCT fa.IdFornitore) $>=$ 3}
[\{fa.IdBirrificio\} {$\gamma$ \{COUNT(DISTINCT fa.IdFornitore)\}}
[{$\sigma$ fa.Data $>=$ '2020-01-01'}
[Fatture fa]
]
]
]
]
]
\end{forest}
\end{center}
Non c'è in questo caso differenza tra $\pi^{b}$ e $\pi$: non ci possono essere
duplicati, in quanto la GROUP BY raggruppa per IdBirrificio.
\paragraph{Piano di accesso fisico della query b senza indici}
\begin{center}
\begin{forest}, baseline, qtree
[{Sort[-DiversiFornitori]}
[{Project(\{fa.IdBirrificio, COUNT(DISTINCT fa.IdFornitore) DiversiFornitori\})}
[{Filter(COUNT(DISTINCT fa.IdFornitore) $>=$ 3)}
[{GroupBy(\{fa.IdBirrificio\}, \{COUNT(DISTINCT fa.IdFornitore)\})}
[{Sort([fa.IdBirrificio])}
[{Filter(fa.Data $>=$ '2020-01-01')}
[{TableScan(Fatture fa)}]
]
]
]
]
]
]
\end{forest}
\end{center}
Il sort sull'attributo dimensione di analisi prima della GroupBy è necessario,
in quanto non è garantito che i record della tabella Fatture siano raggruppati
per IdBirrificio.
Lo sarebbero se l'organizzazione primaria della tabella fosse sequenziale
proprio su questo attributo, il che è estremamente poco probabile.
\clearpage
\paragraph{Piano di accesso fisico della query b con un indice}
\begin{center}
\begin{forest}, baseline, qtree
[{Sort[-DiversiFornitori]}
[{Project(\{fa.IdBirrificio, COUNT(DISTINCT fa.IdFornitore) DiversiFornitori\})}
[{Filter(COUNT(DISTINCT fa.IdFornitore) $>=$ 3)}
[{GroupBy(\{fa.IdBirrificio\}, \{COUNT(DISTINCT fa.IdFornitore)\})}
[{Sort([fa.IdBirrificio])}
[{IndexFilter(Fatture fa, IndFD, fa.Data $>=$ '2020-01-01')}]
]
]
]
]
]
\end{forest}
\end{center}
Indice necessario: \texttt{IndFD} (indice della tabella Fatture sullattributo
Data).
Il sort sull'attributo IdBirrificio prima della GroupBy è necessario, in quanto
i record in input sono ordinati per data, il che non ci garantisce che siano
raggruppati per IdBirrificio (che è dimensione di analisi).
\subsection{Query c}
\paragraph{Piano di accesso logico della query c}
\begin{center}
\begin{forest}, baseline, qtree
[{$\pi^{b}$ fo.RagioneSociale, SUM(fa.Importo) ImportoTotale, AVG(fa.Importo) ImportoMedio}
[$\sigma$ SUM(fa.Importo) $>$ 10
[{\{fo.IdFornitore, fo.RagioneSociale\} $\gamma$ \{SUM(fa.Importo), AVG(fa.Importo)\}}
[{$\bowtie$ fa.IdFornitore = fo.IdFornitore}
[{Fornitori fo}]
[{$\bowtie$ fa.IdBirrificio = b.IdBirrificio}
[{$\sigma$ b.Nome = 'Pirati Rossi'}
[{Birrifici b}]
]
[{Fatture fa}]
]
]
]
]
]
\end{forest}
\end{center}
In questo caso non ci dovrebbe essere differenza tra $\pi^{b}$ e $\pi$: non ci
devono essere due fornitori con la stessa ragione sociale (la ragione sociale
è chiave naturale); è comunque possibile un errore di inserimento se non ho
impostato un vincolo di unicità anche su questo attributo, che non ho scelto
come chiave primaria della tabella: ecco perché ho raggruppato anche per
IdFornitore e non solo per RagioneSociale.
Ho scelto l'ordine di giunzione in modo da avere la restrizione il più distale
possibile.
\clearpage
\paragraph{Piano di accesso fisico della query c senza indici}
\begin{center}
\begin{forest}, baseline, qtree
[{Project(\{fo.RagioneSociale,\\SUM(fa.Importo) ImportoTotale, AVG(fa.Importo) ImportoMedio\})}
[{Filter(SUM(fa.Importo) $>$ 10)}
[{GroupBy(\{fo.IdFornitore,fo.RagioneSociale\}, \{SUM(fa.Importo), AVG(fa.Importo)\})}
[{MergeSort(fa.IdFornitore = fo.IdFornitore)}
[{Sort([fo.IdFornitore])}
[{Project(\{fo.IdFornitore,\\fo.RagioneSociale\})}
[{Fornitori fo}]
]
]
[{Sort([fa.IdFornitore])}
[{Project(\{fa.IdFornitore, fa.Importo\})}
[{MergeSort(fa.IdBirrificio = b.IdBirrificio)}
[{Sort([b.IdBirrificio])}
[{Project(\{b.IdBirrificio\})}
[{Filter(b.Nome = 'Pirati Rossi')}
[{TableScan(Birrifici b)}]
]
]
]
[{Sort([fa.IdBirrificio])}
[{Project(\{fa.IdBirrificio,\\fa.IdFornitore fa.Importo\})}
[{Fatture fa}]
]
]
]
]
]
]
]
]
]
\end{forest}
\end{center}
Non è necessario ordinare per \texttt{[fo.IdFornitore, fo.RagioneSociale]} prima
della GroupBy: per costruzione, l'ordine dell'operatore esterno della SortMerge
viene mantenuto nell'output, e questo ordine è sull'attributo fo.IdFornitore,
che a sua volta determina funzionalmente l'altra dimensione di analisi, fo.RagioneSociale.
Pertanto, è garantito che l'input della GroupBy sarà già raggruppato per gli
attributi che sono dimensione di analisi e non occorre un ordinamento
preventivo.
\clearpage
\paragraph{Piano di accesso fisico della query c con tre indici}
\begin{center}
\begin{forest}, baseline, qtree
[{Project(\{fo.RagioneSociale,\\SUM(fa.Importo) ImportoTotale, AVG(fa.Importo) ImportoMedio\})}
[{Filter(SUM(fa.Importo) $>$ 10)}
[{GroupBy(\{fo.IdFornitore, fo.RagioneSociale\},\\\{SUM(fa.Importo), AVG(fa.Importo)\})}
[{Sorted([fo.IdFornitore])}
[{Project(\{fo.IdFornitore, fo.RagioneSociale, fa.Importo\})}
[{IndexNestedLoop(fa.IdFornitore = fo.IdFornitore)}
[{IndexNestedLoop\\(fa.IdBirrificio = b.IdBirrificio)}
[{IndexFilter(Birrifici b, IndBN,\\b.Nome = 'Pirati Rossi')}]
[{IndexFilter(Fatture fa, IndFaIdB,\\fa.IdBirrificio = b.IdBirrificio)}]
]
[{IndexFilter(Fornitori fo, IndFoIdF,\\fo.IdFornitore = fa.IdFornitore)}]
]
]
]
]
]
]
\end{forest}
\end{center}
Indici necessari: \texttt{IndBN} (indice della tabella Birrifici sullattributo
Nome), \texttt{IndFaIdB} (indice della tabella Fatture sull'attributo
IdBirrificio) e \texttt{IndFoIdF} (indice della tabella Fornitori sull'attributo
IdFornitore).
Occorre ordinare per IdFornitore prima della \texttt{GroupBy}, in quanto
l'output della IndexNestedLoop è ordinato come l'operatore esterno, ovvero
per nome del birrificio.
Potrei spostare l'ordinamento tra le due giunzioni con IndexNestedLoop, tanto
ogni fattura ha un fornitore e l'output non andrà a decrecere dopo la seconda
giunzione (anzi, si arricchirà di campi).
Il sort andrebbe fatto con il minor numero possibile di dati, dato l'alto costo
dell'algoritmo, eliminando i campi superflui con una project prima.
Il vantaggio dell'IndexNestedLoop sul SortMerge si ha solo se la condizione è
sufficientemente restrittiva da essere soddisfatta da una piccola minoranza
di record.
In questo caso, la restrizione sul nome del birrificio dovrebbe essere
abbastanza restrittiva (se ci sono abbastanza birrifici, il numero di
birrifici con il nome `Pirati Rossi' sarà trascurabile rispetto al totale) ed
è ragionevole che le fatture che riguardano quel birrificio siano una esigua
minoranza rispetto al totale delle fatture.
Se così non fosse, pur avendo i tre indici a disposizione, converrebbe
utilizzare comunque il SortMerge.

View File

@ -1,14 +1,122 @@
% !TEX root = ../main.tex
\begin{enumerate}[label=\alph*.]
\item Uso di proiezione, join e restrizione
\item Uso di proiezione, join e restrizione.
Per ogni record di R con valore di C maggiore o uguale a 10 e che ha un valore di E uguale a un valore di F nella tabella S, riportare R.C e S.D.
\begin{lstlisting}[style=SQLu]
SELECT R.C, S.D
FROM Tabella1 R
JOIN Tabella2 S ON R.E = S.F
WHERE R.C >= 10
Mostrare l'IdRicetta e il Nome delle ricette create da birrai di nome
Giovanni.
\begin{lstlisting}[style=SQLu,escapechar=@]
SELECT r.IdRicetta, r.Nome
FROM Ricette r
JOIN Persone p ON p.IdPersona = r.IdCreatrice
WHERE p.Nome = 'Giovanni'
\end{lstlisting}
\item Uso di group by con having, where e sort.
Per ogni birrificio che abbia fatto almeno un acquisto quest'anno,
riportare l'IdBirrificio e il numero di diversi fornitori da cui ha
acquistato quest'anno, se questo numero è almeno di 3.
Ordinare il risultato dal birrificio che ha avuto più fornitori a quello
che ne ha avuti meno.
\begin{lstlisting}[style=SQLu,escapechar=@]
SELECT fa.IdBirrificio,
COUNT(DISTINCT fa.IdFornitore) DiversiFornitori
FROM Fatture fa
WHERE fa.Data >= '2020-01-01'
GROUP BY fa.IdBirrificio
HAVING COUNT(DISTINCT fa.IdFornitore) >= 3
ORDER BY COUNT(DISTINCT fa.IdFornitore) DESC
\end{lstlisting}
\item Uso di join, group by con having e where.
Dei fornitori da cui ha ordinato il birrificio `Pirati Rossi', mostrare la
ragione sociale, l'importo totale e l'importo medio delle fatture, purché
l'importo totale sia superiore a 10 euro.
\begin{lstlisting}[style=SQLu,escapechar=@]
SELECT fo.RagioneSociale, SUM(fa.Importo) ImportoTotale,
AVG(fa.Importo) ImportoMedio
FROM Fornitori fo
JOIN Fatture fa ON fa.IdFornitore = fo.IdFornitore
JOIN Birrifici b ON b.IdBirrificio = fa.IdBirrificio
WHERE b.Nome = 'Pirati Rossi'
GROUP BY fo.IdFornitore, fo.RagioneSociale
HAVING SUM(fa.Importo) > 10
\end{lstlisting}
\item Uso di select annidata con quantificazione esistenziale.
Mostrare il soprannome de* birrai* che siano aut*r* di almeno una ricetta.
\begin{lstlisting}[style=SQLu,escapechar=@]
SELECT b.Soprannome
FROM Birraie b
WHERE EXISTS (SELECT *
FROM Ricette r
WHERE r.IdCreatrice = b.IdPersona)
\end{lstlisting}
\clearpage
\item Uso di select annidata con quantificazione universale.
Mostrare il nome e il cognome de* clienti che hanno ordinato da un solo
birrificio.
\textbf{Traduco in notazione insiemistica:}
\begin{lstlisting}[style=SQLu,escapechar=@]
{p1.Nome, p1.Cognome | (p1 @$\in$@ Persone, pre1 @$\in$@ Prenotazioni,
pre1.IdCliente = p1.IdPersona,
pro1 @$\in$@ Produzioni,
pro1.IdProduzione = pre1.IdProduzione,
r1 @$\in$@ Ricette,
r1.IdRicetta = pro1.IdRicetta) .
@$\forall$@ (pre2 @$\in$@ Prenotazioni, pre2.IdCliente = pre1.IdCliente
pro2 @$\in$@ Produzioni, pro2.IdProduzione = pre2.IdProduzione,
r2 @$\in$@ Ricette, r2.IdRicetta = pro2.IdRicetta) .
(r2.IdBirrificio = r1.IdBirrificio)}
\end{lstlisting}
\textbf{Sostituisco il $\forall x . P$ con $\neg\exists x . \neg P$}
\begin{lstlisting}[style=SQLu,escapechar=@]
{p1.Nome, p1.Cognome | (p1 @$\in$@ Persone, pre1 @$\in$@ Prenotazioni,
pre1.IdCliente = p1.IdPersona,
pro1 @$\in$@ Produzioni,
pro1.IdProduzione = pre1.IdProduzione,
r1 @$\in$@ Ricette,
r1.IdRicetta = pro1.IdRicetta) .
@$\neg\exists$@ (pre2 @$\in$@ Prenotazioni, pre2.IdCliente = pre1.IdCliente
pro2 @$\in$@ Produzioni, pro2.IdProduzione = pre2.IdProduzione,
r2 @$\in$@ Ricette, r2.IdRicetta = pro2.IdRicetta) .
(r2.IdBirrificio @$\neq$@ r1.IdBirrificio)}
\end{lstlisting}
\textbf{Scrivo quindi la query}, inserendo l'IdPersona e la parola chiave
\texttt{DISTINCT} per rimuovere i duplicati (ma non le persone omonime).
\begin{lstlisting}[style=SQLu,escapechar=@]
SELECT DISTINCT p1.IdPersona, p1.Nome, p1.Cognome
FROM Persone p1
JOIN Prenotazioni pre1 ON pre1.IdCliente = p1.IdPersona
JOIN Produzioni pro1 ON pro1.IdProduzione = pre1.IdProduzione
JOIN Ricette r1 ON r1.IdRicetta = pro1.IdRicetta
WHERE NOT EXISTS (SELECT *
FROM Prenotazioni pre2
JOIN Produzioni pro2
ON pro2.IdProduzione = pre2.IdProduzione
JOIN Ricette r2 ON r2.IdRicetta = pro2.IdRicetta
WHERE pre2.IdCliente = pre1.IdCliente
AND r2.IdBirrificio <> r1.IdBirrificio)
\end{lstlisting}
\item Uso di subquery di confronto quantificato.
Per ogni birrificio, mostrare l'IdBirrificio e l'ultimo NumeroLotto
prodotto in quel birrificio (sapendo che il NumeroLotto è progressivo).
\begin{lstlisting}[style=SQLu,escapechar=@]
SELECT r1.IdBirrificio, pro1.NumeroLotto
FROM Produzioni pro1
JOIN Ricette r1 ON r1.IdRicetta = pro1.IdRicetta
WHERE pro1.NumeroLotto >= ANY (SELECT pro2.NumeroLotto
FROM Produzioni pro2
JOIN Ricette r2
ON r2.IdRicetta = pro2.IdRicetta
WHERE r2.IdBirrificio = r1.IdBirrificio)
\end{lstlisting}
\end{enumerate}
\clearpage

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,100 @@
% !TEX root = ../main.tex
Vincoli non catturati graficamente: \textellipsis
\paragraph{Elenco degli ingredienti disponibili}
*l* committente ha chiesto espressamente l'elenco degli ingredienti disponibili.
Tuttavia, questo è calcolabile a partire dagli acquisti e dalle produzioni.
Ho convinto *l* committente a non memorizzare separatamente l'inventario degli
ingredienti, rassicurandol* che avrei fornito una vista logica ``Inventario".
\begin{lstlisting}[style=SQLu][float,floatplacement=H]
CREATE VIEW IngredientiAcquistatiTotali (IdIngrediente, Ingrediente,
Totale)
AS SELECT i.IdIngrediente IdIngrediente, i.Descrizione Ingrediente,
SUM(a.Quantità) Totale
FROM Acquisti a
JOIN Ingredienti i ON i.IdIngrediente = a.IdIngrediente
GROUP BY i.IdIngrediente, i.Descrizione;
CREATE VIEW IngredientiInUso (IdIngrediente, Ingrediente, InUso)
AS SELECT i.IdIngrediente IdIngrediente, i.Descrizione Ingrediente,
SUM(ir.Quantità) InUso
FROM IngredientiRicette ir
JOIN Ingredienti i ON i.IdIngrediente = ir.IdIngrediente
JOIN Produzioni p ON p.IdRicetta = ir.IdRicetta
WHERE p.Stato IS NULL
GROUP BY i.IdIngrediente, i.Descrizione;
CREATE VIEW IngredientiUsati (IdIngrediente, Ingrediente,
Usati)
AS SELECT i.IdIngrediente IdIngrediente, i.Descrizione Ingrediente,
SUM(ir.Quantità) Usati
FROM IngredientiRicette ir
JOIN Ingredienti i ON i.IdIngrediente = ir.IdIngrediente
JOIN Produzioni p ON p.IdRicetta = ir.IdRicetta
WHERE p.Stato = 0
GROUP BY i.IdIngrediente, i.Descrizione;
CREATE VIEW Inventario (IdIngrediente, Ingrediente, QuantitàTotale,
QuantitàDisponibile)
AS SELECT iat.IdIngrediente IdIngrediente,
iat.Ingrediente Ingrediente,
(iat.Totale - COALESCE(iu.Usati, 0)) QuantitàTotale,
(iat.Totale - COALESCE(iu.Usati, 0)
- COALESCE(iiu.InUso, 0)) QuantitàDisponibile
FROM IngredientiAcquistatiTotali iat
LEFT JOIN IngredientiUsati iu
ON iu.idIngrediente = iat.IdIngrediente
LEFT JOIN IngredientiInUso iiu
ON iiu.idIngrediente = iat.IdIngrediente
WHERE iat.Totale - COALESCE(iu.Usati, 0) > 0;
\end{lstlisting}
Ogni volta che si inizia una produzione, l'applicazione controlla che la
quantità di ingredienti disponibili superi la quantità degli ingredienti
necessari alla preparazione.
L'applicazione può anche mostrare una ``lista della spesa" basandosi su ricette
che si vogliono preparare e sulla vista inventario.
L'applicazione mostrerà ad ogni birrai* solo le ricette di cui è aut*r* o di un
birrificio per cui lavora.
\begin{minipage}{\linewidth}
\paragraph{Vincoli intra-relazionali}
\begin{itemize}
\itemsep0em
\item Non possono esistere due persone con lo stesso codice fiscale.
\item Non possono esistere due birrai* con lo stesso soprannome.
\item Non possono esistere due fornitori con la stessa partita IVA né
con la stessa ragione sociale.
\item Il tipo di ingrediente determina l'unità di misura.
Esiste un breve elenco di tipi ingredienti disponibili con la relativa unità di
misura.
Non ho creato una classe ``TipiIngredienti" per contenere il numero di classi,
ma in effetti il tipo determina funzionalmente l'unità di misura ed esistono
pochi tipi, mentre ci sono molti ingredienti per ogni tipo.
\end{itemize}
\paragraph{Vincoli inter-relazionali}
\begin{itemize}
\itemsep0em
\item Alla registrazione, l'utente deve inserire un soprannome e/o un indirizzo
di spedizione: il vincolo di copertura impone che l'unione di Clienti e
Birraie sia Persone, non devono esistere persone che non sono né clienti
né birrai*.
\item In ogni produzione, il NumeroBottiglie diviso per 0.5 non deve superare la
CapacitàProduttiva del birrificio.
\item Ogni produzione deve iniziare con stato `in corso';
non può iniziare una produzione se un altra è `in corso' nello stesso
birrificio.
\item Ogni nota deve fare riferimento a una produzione.
\item Ogni prenotazione deve fare riferimento a una produzione.
\item Ogni prenotazione deve fare riferimento ad un* cliente.
\item Ogni produzione deve seguire una ricetta.
\item Due produzioni di uno stesso birrificio non devono avere lo stesso lotto.
\item Ogni ricetta deve avere un* creat*r* e un birrificio di riferimento.
\item Ogni fattura deve fare riferimento ad un birrificio e un fornitore.
\item Ogni acquisto deve riferirsi a una fattura e un ingrediente.
\item Ogni ricetta deve avere almeno un ingrediente per ciascuno dei
seguenti tipi: malto, luppolo, lievito.
\end{itemize}
\end{minipage}

View File

@ -1,21 +1,63 @@
% !TEX root = ../main.tex
\\
\textbf{Schema logico relazionale in formato testuale}
\begin{lstlisting}[style=SQLu,escapechar=@]
Tabella(@\underline{ChiavePrimaria}@, B, C, D)
AltraTabella(@\underline{ChiavePrimariaEdEsterna*}@, E)
@\textellipsis@
Persone(@\underline{IdPersona}@, Nome, Cognome, Email, CodiceFiscale)
Clienti(@\underline{IdPersona*}@, IndirizzoSpedizione)
Birraie(@\underline{IdPersona*}@, Soprannome)
Birrifici(@\underline{IdBirrificio}@, Nome, AnnoFondazione, Motto, Stemma,
CapacitàProduttiva)
BirrificiBirraie(@\underline{IdBirrificio*, IdBirraia*}@)
Fornitori(@\underline{IdFornitore}@, RagioneSociale, PartitaIva, Indirizzo)
Fatture(@\underline{IdFattura}@, IdBirrificio*, IdFornitore*, Data,
NumeroFattura, Importo)
TipiIngredienti(@\underline{IdTipo}@, Tipo, UnitàDiMisura)
Ingredienti(@\underline{IdIngrediente}@, IdTipo*, Descrizione)
Acquisti(@\underline{IdFattura*, IdIngrediente*}@, Quantità)
Ricette(@\underline{IdRicetta}@, IdBirrificio*, IdCreatrice*, IdRicettaMadre*,
Nome, DataCreazione, Stato)
IngredientiRicette(@\underline{IdRicetta*, IdIngrediente*}@, Quantità)
Produzioni(@\underline{IdProduzione}@, IdRicetta*, DataProduzione, NumeroLotto,
Stato, NumeroBottiglie)
Prenotazioni(@\underline{IdCliente*, IdProduzione*}@, Stato, Quantità)
Note(@\underline{IdNota}@, IdProduzione*, Testo)
NoteDegustazione(@\underline{IdNota*}@, Giudizio)
\end{lstlisting}
\paragraph{Dipendenze funzionali}
\begin{itemize}
\item Per ogni tabella la chiave primaria (sottolineata) determina ciascuno degli attributi della tabella
\item Altre eventuali dipendenze
\itemsep0em
\item Per ogni tabella la chiave primaria (sottolineata) determina ciascuno
degli attributi della tabella.
\begin{lstlisting}[style=SQLu,escapechar=@]
IdPersona @$\to$@ Nome, IdPersona @$\to$@ Cognome, IdPersona @$\to$@ Email,
IdPersona @$\to$@ CodiceFiscale, @$\textellipsis$@
\end{lstlisting}
\item Nella tabella \texttt{Persone}, \texttt{CodiceFiscale} è chiave
naturale e determina tutti gli altri attributi.
Ho ritenuto prudente aggiungere una chiave artificiale perché, se è vero
che due persone diverse non avranno mai lo stesso codice fiscale, è vero
anche che ci possono essere errori umani nell'inserimento di un CF e voglio
riservarmi la possibilità di correggere un CF senza minare l'affidabiltà
della base di dati.
\item Stesso discorso per la RagioneSociale e la PartitaIva nella tabella
\texttt{Fornitori}: ciascuno è chiave separatamente.
\item Nella tabella \texttt{Fatture}, la coppia di attributi \texttt{\{IdFornitore, NumeroFattura\}}
è chiave.
\item Nella tabella \texttt{Produzioni}, il NumeroLotto \underline{non} è chiave, in
quanto birrifici diversi possono avere lotti uguali, è solo all'interno del
birrificio che il lotto identifica univocamente la produzione.
\end{itemize}
Uno schema R, avente insieme di attributi T e insieme di dipendenze funzionali F, (\lstinline{R<T, F>}) è
in forma normale di Boyce-Codd (BCNF) se ogni dipendenza funzionale della chiusura di F o è
banale o ha come determinante una superchiave di T.
Esiste un teorema che semplifica il calcolo, asserendo che se la condizione di cui sopra vale per
una qualsiasi copertura di F allora vale per lintera chiusura di F.
Nella copertura di F che ho descritto sopra (che peraltro è canonica: ogni dipendenza ha un
solo attributo come determinato, nessuna dipendenza è ridondante e non sono presenti
attributi estranei, in quanto ogni determinante è chiave), ogni dipendenza funzionale ha
come determinante o la chiave primaria o una chiave naturale che non è stata scelta come
primaria, in ogni caso una superchiave. \underline{La BCNF è pertanto rispettata}.

View File

@ -0,0 +1,5 @@
% !TEX root = ../main.tex
\begin{center}
Codice sorgente e test: \href{https://gogs.davte.it/Davte/basi_di_dati}{https://gogs.davte.it/Davte/basi\_di\_dati}
\end{center}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,192 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "Acquisti" (
"IdFattura" INTEGER NOT NULL,
"IdIngrediente" INTEGER NOT NULL,
"Quantità" INTEGER NOT NULL,
PRIMARY KEY("IdFattura","IdIngrediente")
);
CREATE TABLE IF NOT EXISTS "Birraie" (
"IdPersona" INTEGER NOT NULL,
"Soprannome" TEXT NOT NULL UNIQUE,
PRIMARY KEY("IdPersona")
);
CREATE TABLE IF NOT EXISTS "Birrifici" (
"IdBirrificio" INTEGER PRIMARY KEY AUTOINCREMENT,
"Nome" TEXT,
"AnnoFondazione" INTEGER,
"Motto" TEXT,
"Stemma" BLOB,
"CapacitàProduttiva" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "BirrificiBirraie" (
"IdBirrificio" INTEGER NOT NULL,
"IdBirraia" INTEGER NOT NULL,
PRIMARY KEY("IdBirrificio","IdBirraia")
);
CREATE TABLE IF NOT EXISTS "Clienti" (
"IdPersona" INTEGER NOT NULL,
"IndirizzoSpedizione" TEXT NOT NULL,
PRIMARY KEY("IdPersona")
);
CREATE TABLE IF NOT EXISTS "Fatture" (
"IdFattura" INTEGER PRIMARY KEY AUTOINCREMENT,
"IdBirrificio" INTEGER NOT NULL,
"IdFornitore" INTEGER NOT NULL,
"Data" TEXT NOT NULL,
"NumeroFattura" INTEGER NOT NULL UNIQUE,
"Importo" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "Fornitori" (
"IdFornitore" INTEGER PRIMARY KEY AUTOINCREMENT,
"RagioneSociale" TEXT NOT NULL UNIQUE,
"PartitaIva" INTEGER NOT NULL UNIQUE,
"Indirizzo" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "Ingredienti" (
"IdIngrediente" INTEGER PRIMARY KEY AUTOINCREMENT,
"IdTipo" INTEGER NOT NULL,
"Descrizione" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "IngredientiRicette" (
"IdRicetta" INTEGER NOT NULL,
"IdIngrediente" INTEGER NOT NULL,
"Quantità" INTEGER NOT NULL,
PRIMARY KEY("IdRicetta","IdIngrediente")
);
CREATE TABLE IF NOT EXISTS "Note" (
"IdNota" INTEGER PRIMARY KEY AUTOINCREMENT,
"IdProduzione" INTEGER NOT NULL,
"Testo" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "NoteDegustazione" (
"IdNota" INTEGER NOT NULL,
"Giudizio" INTEGER NOT NULL,
PRIMARY KEY("IdNota")
);
CREATE TABLE IF NOT EXISTS "Persone" (
"IdPersona" INTEGER PRIMARY KEY AUTOINCREMENT,
"Nome" TEXT NOT NULL,
"Cognome" TEXT NOT NULL,
"Email" TEXT NOT NULL,
"CodiceFiscale" TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS "Prenotazioni" (
"IdCliente" INTEGER NOT NULL,
"IdProduzione" INTEGER NOT NULL,
"Stato" INTEGER,
"Quantità" INTEGER NOT NULL,
PRIMARY KEY("IdProduzione","IdCliente")
);
CREATE TABLE IF NOT EXISTS "Produzioni" (
"IdProduzione" INTEGER PRIMARY KEY AUTOINCREMENT,
"IdRicetta" INTEGER NOT NULL,
"DataProduzione" TEXT NOT NULL,
"NumeroLotto" INTEGER NOT NULL,
"Stato" INTEGER,
"NumeroBottiglie" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "Ricette" (
"IdRicetta" INTEGER PRIMARY KEY AUTOINCREMENT,
"IdBirrificio" INTEGER NOT NULL,
"IdCreatrice" INTEGER NOT NULL,
"IdRicettaMadre" INTEGER,
"Nome" TEXT NOT NULL,
"DataCreazione" TEXT NOT NULL,
"Stato" INTEGER
);
CREATE TABLE IF NOT EXISTS "TipiIngredienti" (
"IdTipo" INTEGER PRIMARY KEY AUTOINCREMENT,
"Tipo" TEXT NOT NULL,
"UnitàDiMisura" TEXT NOT NULL
);
CREATE VIEW Query_a
AS SELECT r.IdRicetta, r.Nome
FROM Ricette r
JOIN Persone p ON p.IdPersona = r.IdCreatrice
WHERE p.Nome = 'Giovanni';
CREATE VIEW Query_b
AS SELECT fa.IdBirrificio,
COUNT(DISTINCT fa.IdFornitore) DiversiFornitori
FROM Fatture fa
WHERE fa.Data >= '2020-01-01'
GROUP BY fa.IdBirrificio
HAVING COUNT(DISTINCT fa.IdFornitore) >= 3
ORDER BY COUNT(DISTINCT fa.IdFornitore) DESC;
CREATE VIEW Query_c
AS SELECT fo.RagioneSociale, SUM(fa.Importo) ImportoTotale,
AVG(fa.Importo) ImportoMedio
FROM Fornitori fo
JOIN Fatture fa ON fa.IdFornitore = fo.IdFornitore
JOIN Birrifici b ON b.IdBirrificio = fa.IdBirrificio
WHERE b.Nome = 'Pirati Rossi'
GROUP BY fo.IdFornitore, fo.RagioneSociale
HAVING SUM(fa.Importo) > 10;
CREATE VIEW Query_d
AS SELECT b.Soprannome
FROM Birraie b
WHERE EXISTS (SELECT *
FROM Ricette r
WHERE r.IdCreatrice = b.IdPersona);
CREATE VIEW Query_e
AS SELECT DISTINCT p1.IdPersona, p1.Nome, p1.Cognome
FROM Persone p1
JOIN Prenotazioni pre1
ON pre1.IdCliente = p1.IdPersona
JOIN Produzioni pro1
ON pro1.IdProduzione = pre1.IdProduzione
JOIN Ricette r1
ON r1.IdRicetta = pro1.IdRicetta
WHERE NOT EXISTS (SELECT *
FROM Prenotazioni pre2
JOIN Produzioni pro2
ON pro2.IdProduzione = pre2.IdProduzione
JOIN Ricette r2 ON r2.IdRicetta = pro2.IdRicetta
WHERE pre2.IdCliente = pre1.IdCliente
AND r2.IdBirrificio <> r1.IdBirrificio);
CREATE VIEW Query_f
AS SELECT r1.IdBirrificio, pro1.NumeroLotto
FROM Produzioni pro1
JOIN Ricette r1 ON r1.IdRicetta = pro1.IdRicetta
WHERE pro1.NumeroLotto = (SELECT MAX(pro2.NumeroLotto)
FROM Produzioni pro2
JOIN Ricette r2
ON r2.IdRicetta = pro2.IdRicetta
WHERE r2.IdBirrificio = r1.IdBirrificio);
CREATE VIEW IngredientiUsati (IdIngrediente, Ingrediente,
Usati)
AS SELECT i.IdIngrediente IdIngrediente, i.Descrizione Ingrediente,
SUM(ir.Quantità) Usati
FROM IngredientiRicette ir
JOIN Ingredienti i ON i.IdIngrediente = ir.IdIngrediente
JOIN Produzioni p ON p.IdRicetta = ir.IdRicetta
WHERE p.Stato = 0
GROUP BY i.IdIngrediente, i.Descrizione;
CREATE VIEW IngredientiInUso (IdIngrediente, Ingrediente, InUso)
AS SELECT i.IdIngrediente IdIngrediente, i.Descrizione Ingrediente,
SUM(ir.Quantità) InUso
FROM IngredientiRicette ir
JOIN Ingredienti i ON i.IdIngrediente = ir.IdIngrediente
JOIN Produzioni p ON p.IdRicetta = ir.IdRicetta
WHERE p.Stato IS NULL
GROUP BY i.IdIngrediente, i.Descrizione;
CREATE VIEW IngredientiAcquistatiTotali (IdIngrediente, Ingrediente,
Totale)
AS SELECT i.IdIngrediente IdIngrediente, i.Descrizione Ingrediente,
SUM(a.Quantità) Totale
FROM Acquisti a
JOIN Ingredienti i ON i.IdIngrediente = a.IdIngrediente
GROUP BY i.IdIngrediente, i.Descrizione;
CREATE VIEW Inventario (IdIngrediente, Ingrediente, QuantitàTotale,
QuantitàDisponibile)
AS SELECT iat.IdIngrediente IdIngrediente,
iat.Ingrediente Ingrediente,
(iat.Totale - COALESCE(iu.Usati, 0)) QuantitàTotale,
(iat.Totale - COALESCE(iu.Usati, 0)
- COALESCE(iiu.InUso, 0)) QuantitàDisponibile
FROM IngredientiAcquistatiTotali iat
LEFT JOIN IngredientiUsati iu
ON iu.idIngrediente = iat.IdIngrediente
LEFT JOIN IngredientiInUso iiu
ON iiu.idIngrediente = iat.IdIngrediente
WHERE iat.Totale - COALESCE(iu.Usati, 0) > 0;
COMMIT;

View File

@ -1,3 +1,88 @@
% !TEX root = ../main.tex
Il testo viene assegnato dal Professore.
\paragraph{Introduzione}
La birra fatta in casa è un'attività che riceve crescente attenzione da parte degli appassionati.
Ogni birraio amatoriale possiede un'attrezzatura per il processo di produzione della birra su
piccola scala (bollitori, fermentatori, tubi, ecc.) con una certa capacità massima di
fermentazione: il numero di litri che l'attrezzatura è in grado di gestire in un unico ``lotto". La
preparazione della birra richiede anche ingredienti, le cui quantità effettive variano da una
ricetta all'altra, questi sono vari tipi di malto, luppolo, lieviti e zuccheri (e, naturalmente, acqua).
Ai birrai piace registrare le proprie ricette per riferimento futuro e mantenere un elenco
aggiornato degli ingredienti disponibili per fare acquisti prima della successiva produzione.
L'obiettivo di questo progetto è quello di sviluppare un'applicazione per i birrai domestici che
consenta loro di mantenere un elenco di ricette e adattare quelle esistenti. L'applicazione deve
anche:
\begin{itemize}
\itemsep0em
\item mantenere un elenco di ingredienti disponibili;
\item aggiornare questo elenco dopo un ciclo di produzione e quando vengono acquistati nuovi ingredienti;
\item produrre liste della spesa per il lotto successivo;
\item guidare il birraio nel processo di produzione.
\end{itemize}
\paragraph{Descrizione del progetto}
``Una cervecita fresca" è un'applicazione che consente ai produttori amatoriali di birra di
mantenere un database organizzato delle loro ricette di birra. L'applicazione consente agli
utenti di creare, archiviare e modificare ricette, e successivamente eliminarle, se l'utente
desidera farlo. L'applicazione è destinata solo ai produttori di birra con metodo
\href{https://www.birradegliamici.com/fare-la-birra/all-grain/}{all-grain}, e
quindi tutte le ricette sono per questo tipo di birre (le birre ``estratto" non
sono supportate).
Ogni birrificio domestico dispone di un'attrezzatura specifica, le cui caratteristiche portano a
una particolare ``dimensione del lotto": il numero massimo di litri che possono essere prodotti
in una singola produzione.
Le ricette prevedono, oltre all'acqua:
\begin{itemize}
\itemsep0em
\item malti
\item luppolo
\item lieviti
\item zuccheri
\item additivi
\end{itemize}
Mentre i produttori di birra preferiscono creare ricette riferendosi a valori concreti, come
chilogrammi di un particolare malto o grammi di un particolare luppolo, l'applicazione deve
memorizzare queste ricette in una misura ``assoluta", che consente una conversione diretta
della ricetta quando l'apparecchiatura, e di conseguenza la dimensione del lotto, è diversa.
Ad esempio, una possibilità è esprimere la quantità di malto in percentuale del totale e usare
i grammi per litro di miscuglio (mash) per il luppolo.
Oltre alle ricette, l'applicazione deve conservare le \textbf{istanze} della ricetta, ovvero singole
produzioni basate su una ricetta; queste istanze possono essere accompagnate da note per
fare riferimento a problemi che possono influire sulla birra risultante, note che i produttori di
birra vorrebbero rimanessero memorizzate. Un particolare tipo di nota sono le note di
degustazione, che consentono ai birrai di tenere traccia delle opinioni su una birra di un dato
lotto.
Oltre a queste funzionalità più tradizionali, l'applicazione “Una cervecita fresca”, mantiene un
elenco di ingredienti disponibili. Ciò consente ai birrai di avere la lista degli ingredienti
mancanti per la prossima produzione. Un'istanza della ricetta, ovvero una produzione di birra,
dovrebbe consentire agli utenti di aggiornare l'elenco degli ingredienti disponibili, sottraendo
gli ingredienti usati da quelli disponibili.
Sarà inoltre possibile per i birrai vendere la birra prodotta. Lapplicazione deve offrire
uninterfaccia web per la prenotazione e la vendita. Un cliente registrato può prenotare un lotto
di birra in produzione, oppure parte di esso. Quando il lotto è stato prodotto, il birraio può
confermare le prenotazioni e procedere con la vendita oppure, se non è soddisfatto del
prodotto, cancellarle, per non danneggiare il proprio buon nome. La birra non prenotata può
essere messa in vendita e comprata da utenti registrati.
\paragraph{Scopo dellapplicazione}
Il sistema deve implementare le funzionalità sopra descritte, ovvero creazione, modifica e
cancellazione di ricette, creazione di istanze di ricette (birre), supporto per le note sulle birre,
controllo degli ingredienti disponibili, supporto alla produzione con allarmi, supporto alla
vendita.
\paragraph{Scopo del progetto per quanto riguarda Basi di Dati}
Si integrano i requisiti già specificati con le seguenti ulteriori informazioni:
\begin{itemize}
\itemsep0em
\item le ricette sono relative ad un solo birrificio ma possono essere condivise tra
diversi birrai che sono autorizzati al loro utilizzo;
\item gli ingredienti possono essere acquistati da più fornitori (registrati).
\end{itemize}