<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Gabriele Lana &#187; sql</title>
	<atom:link href="http://www.gabrielelana.it/archives/category/sql/feed" rel="self" type="application/rss+xml" />
	<link>http://www.gabrielelana.it</link>
	<description>on Agile Methodologies and Programming</description>
	<lastBuildDate>Fri, 07 May 2010 14:12:28 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>SQL Optimization</title>
		<link>http://www.gabrielelana.it/archives/28</link>
		<comments>http://www.gabrielelana.it/archives/28#comments</comments>
		<pubDate>Sun, 01 Apr 2007 17:57:09 +0000</pubDate>
		<dc:creator>Gabriele Lana</dc:creator>
				<category><![CDATA[programming]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.gabrielelana.it/archives/28</guid>
		<description><![CDATA[Supponiamo abbiate due relazioni di questo tipo

test (id, descrizione, soglia)
storico_test (id, test, studente, punteggio)

La prima relazione raggruppa tutti i dati relativi ai test (stiamo parlando per esempio di un&#8217;applicazione di e-learning), la seconda i dati relativi alle esecuzioni dei test da parte degli studenti. Supponiamo che il nostro scopo sia quello di ottenere un report [...]]]></description>
			<content:encoded><![CDATA[<p>Supponiamo abbiate due relazioni di questo tipo</p>
<pre class="code">
test (id, descrizione, soglia)
storico_test (id, test, studente, punteggio)
</pre>
<p>La prima relazione raggruppa tutti i dati relativi ai test (stiamo parlando per esempio di un&#8217;applicazione di e-learning), la seconda i dati relativi alle esecuzioni dei test da parte degli studenti. Supponiamo che il nostro scopo sia quello di ottenere un report che indichi per ogni tipo di test quanti studenti lo hanno sostenuto, quanti studenti lo hanno superato e quanti no. Da dove iniziamo? Sicuramente il join naturale fra le due relazione deve essere fatto</p>
<p><span id="more-28"></span></p>
<pre class="code">
select * from test, storico_test where test.id = storico_test.test;
</pre>
<p>Il fatto stesso che ci chiedano &#8220;per ogni tipo di test&#8221; ci fa subito venire in mente un raggruppamento</p>
<pre class="code">
select test.id as test
    from test, storico_test
    where test.id = storico_test.test
    group by test.id
</pre>
<p>Quanti studenti lo hanno sostenuto? Anche questo è piuttosto facile</p>
<pre class="code">
select test.id as test, count(*) as sostenuto
    from test, storico_test
    where test.id = storico_test.test
    group by test.id
</pre>
<p>Adesso viene la parte più succosa, ho visto affrontare il problema in diversi modi, uno dei quali è l&#8217;utilizzo di subquery nella <code>select</code></p>
<pre class="code">
select test.id as test, count(*) as sostenuto,
        (select count(*) from storico_test as nested
             where nested.test = test.id and nested.risultato >= test.soglia) as passato,
        (select count(*) from storico_test as nested
             where nested.test = test.id and nested.risultato < test.soglia) as rifiutato
    from test, storico_test
    where test.id = storico_test.test
    group by test.id, test.soglia
</pre>
<p>Corretto, ma ripetere per ben tre volte la stessa join può essere critico per le performance se le tabelle contengono molti dati. Ho visto risolvere lo stesso problema in modi più fantasiosi (ad esempio utilizzando l'operatore insiemistico <code>intersect</code>), ma tutti più o meno con lo stesso peso computazionale. La mia proposta è quella di utilizzare il costrutto (per i database che lo supportano) <code>case</code></p>
<pre class="code">
select test.id as test, count(*) as sostenuto,
        count(case when storico_test.risultato >= test.soglia then 1 else null end) as passato,
        count(case when storico_test.risultato < test.soglia then 1 else null end) as rifiutato
    from test, storico_test
    where test.id = storico_test.test
    group by test.id
</pre>
<p>Grazie all'utilizzo del costrutto <code>case</code> e al fatto che l'operatore <code>count</code> non conta i <code>null</code>, riusciamo a risolvere il problema con una sola join :-)</p>
<p>P.S. Questa ottimizzazione (più altre che tralascio) ha portato una query in produzione in una banca da più di 6 ore a pochi secondi :-)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gabrielelana.it/archives/28/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
