Queer European MD passionate about IT
Davte 4 жил өмнө
parent
commit
4c4ffa048f

+ 3 - 0
.gitignore

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

+ 24 - 5
compitino/main.tex

@@ -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}

+ 72 - 2
compitino/secondo_compitino/dominio.tex

@@ -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.

+ 221 - 21
compitino/secondo_compitino/piani_di_accesso.tex

@@ -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{IndPN} (indice della tabella Persone sull’attributo
+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 sull’attributo
+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}
-Indici necessari: \texttt{IndRC} (indice della tabella R sull’attributo C) e \texttt{IndSF} (indice della tabella S sull'attributo F).
+
+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 sull’attributo
+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.

+ 116 - 8
compitino/secondo_compitino/queries.tex

@@ -1,14 +1,122 @@
 % !TEX root = ../main.tex
 
 \begin{enumerate}[label=\alph*.]
-    \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
+    \item Uso di proiezione, join e restrizione.
+
+    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

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
compitino/secondo_compitino/schema.drawio


BIN
compitino/secondo_compitino/schema_concettuale.pdf


+ 98 - 1
compitino/secondo_compitino/schema_concettuale.tex

@@ -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}

BIN
compitino/secondo_compitino/schema_logico.pdf


+ 49 - 7
compitino/secondo_compitino/schema_logico.tex

@@ -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 l’intera 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}.

+ 5 - 0
compitino/secondo_compitino/source_code.tex

@@ -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}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 8 - 0
compitino/secondo_compitino/test/data.sql


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 108 - 0
compitino/secondo_compitino/test/data_and_schema.sql


+ 192 - 0
compitino/secondo_compitino/test/schema.sql

@@ -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;

+ 86 - 1
compitino/secondo_compitino/testo.tex

@@ -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. L’applicazione deve offrire
+un’interfaccia 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 dell’applicazione}
+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}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно