<?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/"
	>

<channel>
	<title>Wojny z materią</title>
	<atom:link href="http://www.oladesign.biz/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.oladesign.biz</link>
	<description>internet, coldfusion i ecommerce okiem programisty...</description>
	<pubDate>Fri, 20 Nov 2009 08:17:10 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.7.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Poddani Mediaamby?</title>
		<link>http://www.oladesign.biz/2009/11/20/poddani-mediaamby/</link>
		<comments>http://www.oladesign.biz/2009/11/20/poddani-mediaamby/#comments</comments>
		<pubDate>Fri, 20 Nov 2009 08:17:10 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[praca]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/?p=34</guid>
		<description><![CDATA[Po zakończeniu projektu znalazłem na swoim biurku elegancko zapakowaną w antyramę laurkę następującej treści:


Tak to jest, jak się dyrekcja dorwie do literatury.
]]></description>
			<content:encoded><![CDATA[<p>Po zakończeniu projektu znalazłem na swoim biurku elegancko zapakowaną w antyramę laurkę następującej treści:</p>
<p style="text-align: center;">
<p style="text-align: center;"><img class="aligncenter" src="http://oladesign.biz/img/ms_serfs_small.jpg" alt="Poddani Microsoftu" width="480" height="640" /></p>
<p>Tak to jest, jak się dyrekcja dorwie do <a title="Poddani Microsoftu" href="http://www.biblionetka.pl/book.aspx?id=978" onclick="pageTracker._trackPageview('/outgoing/www.biblionetka.pl/book.aspx?id=978&referer=');">literatury</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2009/11/20/poddani-mediaamby/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Podzapytania w INSERT</title>
		<link>http://www.oladesign.biz/2009/09/03/podzapytania-w-insert/</link>
		<comments>http://www.oladesign.biz/2009/09/03/podzapytania-w-insert/#comments</comments>
		<pubDate>Thu, 03 Sep 2009 09:14:08 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[php]]></category>

		<category><![CDATA[sql]]></category>

		<category><![CDATA[tips&tricks]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/?p=28</guid>
		<description><![CDATA[Taki typowy problem: tabela zawierająca pole, pozwalające użytkownikowi na dowolne ustalanie kolejności rekordów. Prawie każda aplikacja ma coś tego typu. W jednej z moich aplikacji była to płaska lista kategorii:



CREATE TABLE kategorie &#40;


id INT NOT NULL AUTO_INCREMENT,


nazwa VARCHAR&#40;255&#41;,


position INT,


PRIMARY KEY&#40;id&#41;&#41;;



Dodanie rekordu do tej tablicy ma domyślnie umieścić go na końcu listy.  Często spotykanym (i [...]]]></description>
			<content:encoded><![CDATA[<p>Taki typowy problem: tabela zawierająca pole, pozwalające użytkownikowi na dowolne ustalanie kolejności rekordów. Prawie każda aplikacja ma coś tego typu. W jednej z moich aplikacji była to płaska lista kategorii:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> kategorie <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">id INT <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span>,</div>
</li>
<li class="li1">
<div class="de1">nazwa VARCHAR<span class="br0">&#40;</span><span class="nu0">255</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li1">
<div class="de1">position INT,</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">PRIMARY</span> <span class="kw1">KEY</span><span class="br0">&#40;</span>id<span class="br0">&#41;</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<p>Dodanie rekordu do tej tablicy ma domyślnie umieścić go na końcu listy.  Często spotykanym (i bardzo kiepskim) rozwiązaniem jest użycie dwóch zapytań: SELECT, który odczytuje aktualną wartość pola <em>position </em>oraz INSERT, który dodaje nowy rekord.  A czy da się to zrobić jednym zapytaniem? Pierwszy pomysł jest następujący:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> kategorie<span class="br0">&#40;</span>nazwa, position<span class="br0">&#41;</span> <span class="kw1">VALUES</span> <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">:nazwa, <span class="br0">&#40;</span><span class="kw1">SELECT</span> IFNULL<span class="br0">&#40;</span>MAX<span class="br0">&#40;</span>position<span class="br0">&#41;</span> + <span class="nu0">1</span>, <span class="nu0">1</span><span class="br0">&#41;</span> <span class="kw1">FROM</span> kategorie<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>Fajny pomysł, prawda? Ma tylko tą drobną wadę, że nie działa.. próba jego uruchomienia skończy się komunikatem:</p>
<pre>#1093 - You can't specify target table 'kategorie' for update in FROM clause</pre>
<p>Jak to obejść? Najlepiej podzapytaniem:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> kategorie<span class="br0">&#40;</span>nazwa, position<span class="br0">&#41;</span> <span class="kw1">VALUES</span> <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">:nazwa, <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> IFNULL<span class="br0">&#40;</span>x, <span class="nu0">1</span><span class="br0">&#41;</span> <span class="kw1">FROM</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> MAX<span class="br0">&#40;</span>position<span class="br0">&#41;</span><span class="nu0">+1</span> <span class="kw1">AS</span> x <span class="kw1">FROM</span> kategorie<span class="br0">&#41;</span> <span class="kw1">AS</span> subQuery</div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#41;</span><span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>Ładnie, prawda? Opakowanie podzapytania w następne podzapytanie rozwiązuje zadanie. Niestety - tylko dla MySQL w wersji 5.0+. Użycie go na serwerze 4.1 kończy się tym samym komunikatem, co poprzednie&#8230; Szczerze mówiąc - nie chciało mi się kombinować. Spłodziłem coś takiego:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> kategorie<span class="br0">&#40;</span>nazwa, position<span class="br0">&#41;</span> <span class="kw1">VALUES</span> <span class="br0">&#40;</span>:nazwa, <span class="nu0">0</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">UPDATE</span> kategorie</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INNER</span> <span class="kw1">JOIN</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> MAX<span class="br0">&#40;</span>position<span class="br0">&#41;</span><span class="nu0">+1</span> <span class="kw1">AS</span> x <span class="kw1">FROM</span> kategorie<span class="br0">&#41;</span> <span class="kw1">AS</span> subQuery</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">SET</span> position <span class="sy0">=</span> x</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span> position <span class="sy0">=</span> <span class="nu0">0</span>;</div>
</li>
</ol>
</div>
<p>Proste? Proste.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2009/09/03/podzapytania-w-insert/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Obsługa drzew w SQL - część 2 - Model ścieżki zmaterializowanej</title>
		<link>http://www.oladesign.biz/2009/07/15/obsluga-drzew-w-sql-czesc-2-model-sciezki-zmaterializowanej/</link>
		<comments>http://www.oladesign.biz/2009/07/15/obsluga-drzew-w-sql-czesc-2-model-sciezki-zmaterializowanej/#comments</comments>
		<pubDate>Wed, 15 Jul 2009 09:51:36 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[sql]]></category>

		<category><![CDATA[webdeveloper]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/?p=17</guid>
		<description><![CDATA[W poprzednim wpisie pokazałem typowe podejście do zagadnienia drzew w SQL (notabene: nazywa się on modelem sąsiedztwa), omówiłem też jego podstawowe wady (i zalety) i obiecałem opisać inne podejście - pozbawione wad modelu sąsiedztwa.

Jeśli przyjrzysz się przykładowemu drzewu, który chcemy zapisać w bazie danych, można zauważyć, że położenie w drzewie można także opisać jako listę [...]]]></description>
			<content:encoded><![CDATA[<p>W <a href="http://www.oladesign.biz/2009/06/04/obsluga-drzew-w-sql-czesc-1/" title="model sąsiedztwa">poprzednim wpisie</a> pokazałem typowe podejście do zagadnienia drzew w SQL (notabene: nazywa się on modelem sąsiedztwa), omówiłem też jego podstawowe wady (i zalety) i obiecałem opisać inne podejście - pozbawione wad modelu sąsiedztwa.<br />
<img src="http://oladesign.biz/img/sql-tree.png" border="0" alt="Drzewiasta struktura danych" width="289" height="203" /><br />
Jeśli przyjrzysz się przykładowemu drzewu, który chcemy zapisać w bazie danych, można zauważyć, że położenie w drzewie można także opisać jako listę wszystkich węzłów nadrzęnych danego węzła. Taka lista nosi nazwę &#8220;ścieżki&#8221;, i zapisuje się ją licząc od najstarszego rodzica aż po bieżący węzeł. Przykładowo, ścieżka dla węzła &#8220;4&#8243; ma postać: &#8220;1,2,4&#8243;, a dla węzła &#8220;11&#8243; - &#8220;1,3,10,11&#8243;. Dodatkowo, przyda się kolumna informująca, na jakim poziomie znajduje się dany węzeł względem początku drzewa (teoretycznie można tą informację wyciągnąć z długości ścieżki - ale takie zadanie jest, niestety, nierelacyjne, a co za tym idzie - bardzo obciążające dla bazy)</p>
<p>W bazie danych może to wyglądać tak:</p>
<pre>table tree_mpath:
id	mpath	level	data
1	1	0	...
2	1,2	1	...
3	1,3	1	...
4	1,2,4	2	...
5	1,2,4,5	3	...
6	1,2,4,6	3	...
7	1,2,7	2	...
itd.</pre>
<p>Co daje taki zapis? Przede wszystkim, pozwala z łatwością, pojedynczym zapytaniem, pobieramy fragmenty drzewa:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> id, mpath, level, <span class="kw1">DATA</span> <span class="kw1">FROM</span> tree_mpath</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span> FIND_IN_SET<span class="br0">&#40;</span>id, <span class="br0">&#40;</span><span class="kw1">SELECT</span> mpath <span class="kw1">FROM</span> tree_mpath <span class="kw1">WHERE</span> id <span class="sy0">=</span> <span class="nu0">4</span><span class="br0">&#41;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">ORDER</span> <span class="kw1">BY</span> level;</div>
</li>
</ol>
</div>
<p>zwróci:</p>
<pre>id	mpath	level	data
1	1	0	...
2	1,2	1	...
4	1,2,4	2	...</pre>
<p>czyli wszystkie elementy nadrzędne węzła 4 (wraz z tym węzłem, co akurat łatwo wyfiltrować).</p>
<p>Do pobierania elementów podrzędnych węzła możemy użyć zapytania:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> id, mpath, level, <span class="kw1">DATA</span> <span class="kw1">FROM</span> tree_mpath</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span> mpath <span class="kw1">LIKE</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> CONCAT<span class="br0">&#40;</span>mpath, <span class="st0">&#39;%&#39;</span><span class="br0">&#41;</span> <span class="kw1">FROM</span> tree_mpath <span class="kw1">WHERE</span> id <span class="sy0">=</span> <span class="nu0">4</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">ORDER</span> <span class="kw1">BY</span> level</div>
</li>
</ol>
</div>
<p>które w wyniku da nam:</p>
<pre>id	mpath	level	data
4	1,2,4	2	...
5	1,2,4,5	3	...
6	1,2,4,6	3	...</pre>
<p>Pobieranie fragmentu drzewa też nie jest trudne: powiedzmy, że chcemy pobrać tylko bezpośrednich potomków węzła o id = 2:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> id, mpath, level, <span class="kw1">DATA</span> <span class="kw1">FROM</span> tree_mpath</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span> mpath <span class="kw1">LIKE</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> CONCAT<span class="br0">&#40;</span>mpath, <span class="st0">&#39;%&#39;</span><span class="br0">&#41;</span> <span class="kw1">FROM</span> tree_mpath <span class="kw1">WHERE</span> id <span class="sy0">=</span> <span class="nu0">2</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">AND</span> level <span class="sy0">=</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> level<span class="nu0">+1</span> <span class="kw1">FROM</span> tree_mpath <span class="kw1">WHERE</span> id <span class="sy0">=</span> <span class="nu0">2</span><span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>i w efekcie dostaniemy:</p>
<pre>id	mpath	level	data
4	1,2,4	2	...
7	1,2,7	2	...</pre>
<p>Jak widać - zalety tego modelu są znaczne. Praktycznie dowolny fragment drzewa można czytać pojedynczym zapytaniem, zarówno &#8220;w górę&#8221; jak i &#8220;w dół&#8221; od wskazanego węzła. Oczywiście, dla sprawnego działania na taką tabelę należy nałożyć odpowiednie indeksy: na pole id (to chyba oczywiste), mpath oraz level.</p>
<p>Jakie są wady tego modelu?</p>
<p>1. Wysoki koszt aktualizacji. O ile dodanie nowego rekordu jest całkiem znośne, usunięcie również jest całkiem szybkie (choć wymaga dość złożonych warunków, podobnych do tych używanych w zapytaniach SELECT), to przesuwanie węzła w inne miejsce w drzewie jest drogą przez mękę: nie dość, że trzeba zaktualizować przesuwany rekord, to należy też zmodyfikować wszystkie jego węzły potomne. Biorąc pod uwagę, że aktualizacji podlegają indeksy, narzut czasowy jest poważny.</p>
<p>2. Spójność danych. Konstrukcja tabeli nie pozwala na używanie kaskad przy usuwaniu rekordu. W efekcie błąd może doprowadzić do powstania węzłów-zombie, nie powiązanych z pozostałą strukturą drzewa.</p>
<p>3. Ograniczenia wielkości. Jeśli pole mpath będzie polem typu VARCHAR, trzeba pamiętać, że maksymalna długość tego pola to 255 bajtów. Jeżeli identyfikatory kategorii będą 3-znakowe (100-999), to każdy poziom zagłębienia będzie zabierał 4 bajty (identyfikator + przecinek), co daje maksymalny poziom zagłębienia ok. 60. Przy identyfikatorach 4 znakowych (1000-9999), maksymalny poziom zagłębienia to ok. 50. Przy dłuższych identyfikatorach (np. 36-bajtowy uu_id z MS SQL Server) maksymalna wielkość drzewa drastycznie spada (6 poziomów dla uu_id).</p>
<p>Zmiana typu mpath na TEXT rozwiązuje ten problem, ale może się pojawić problem z indeksowaniem takiego pola - np. MySQL nie pozwala na zakładanie indeksów na pole TEXT w InnoDB.</p>
<p>4. Normalizacja. Konstrukcja pola mpath łamie postać normalną tabeli. Zawartość pola level jest wprost powiązany z zawartością pola mpath (level ma wartość &#8220;długość ścieżki - 1&#8243;), i teoretycznie mógłaby zostać pominięta.</p>
<p>Istnieje rozwiązanie problemów 2-4 - nazywa się znormalizowanym modelem ścieżki zmaterializowanej. Ale o tym innym razem&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2009/07/15/obsluga-drzew-w-sql-czesc-2-model-sciezki-zmaterializowanej/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Zmiany, zmiany, zmiany&#8230;</title>
		<link>http://www.oladesign.biz/2009/06/05/zmiany-zmiany-zmiany/</link>
		<comments>http://www.oladesign.biz/2009/06/05/zmiany-zmiany-zmiany/#comments</comments>
		<pubDate>Fri, 05 Jun 2009 07:28:18 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Bez kategorii]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/?p=14</guid>
		<description><![CDATA[Nie było okazji wspomnieć:

pracuję tam, gdzie pracowałem
ale - z różnych przyczyn (kryzys w US!) już nie mam większego kontaktu z ColdFusion
teraz moim podstawowym językiem jest PHP
a MySQL zastąpił SQL Server
za to zakres produkowanych serwisów drastycznie się rozszerzył: oprócz aplikacji ecommerce tworzę również CMS-y i serwisy społecznościowe
dzięki  temu notki mają szansę traktować o czymś więcej niż [...]]]></description>
			<content:encoded><![CDATA[<p>Nie było okazji wspomnieć:</p>
<ul>
<li>pracuję tam, gdzie pracowałem</li>
<li>ale - z różnych przyczyn (kryzys w US!) już nie mam większego kontaktu z ColdFusion</li>
<li>teraz moim podstawowym językiem jest PHP</li>
<li>a MySQL zastąpił SQL Server</li>
<li>za to zakres produkowanych serwisów drastycznie się rozszerzył: oprócz aplikacji ecommerce tworzę również CMS-y i serwisy społecznościowe</li>
<li>dzięki  temu notki mają szansę traktować o czymś więcej niż sklepy internetowe.</li>
</ul>
<p>I tyle.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2009/06/05/zmiany-zmiany-zmiany/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Obsługa drzew w SQL - część 1</title>
		<link>http://www.oladesign.biz/2009/06/04/obsluga-drzew-w-sql-czesc-1/</link>
		<comments>http://www.oladesign.biz/2009/06/04/obsluga-drzew-w-sql-czesc-1/#comments</comments>
		<pubDate>Thu, 04 Jun 2009 14:43:54 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[sql]]></category>

		<category><![CDATA[programowanie]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/2009/06/04/obsluga-drzew-w-sql-czesc-1/</guid>
		<description><![CDATA[Praktycznie w każdym projekcie informatycznym (a w e-commerce w szczególności)  pojawiają sie jakieś struktury drzewiaste. Ich zapisywanie do bazy danych stanowi problem, ponieważ tabele baz danych są przystosowane do zapisu &#8216;liniowego&#8217; a nie drzewiastego.
Załóżmy, że mam taką strukturę drzewa (cyferki oznaczają identyfikatory węzłów):

Najczęściej używaną strukturą jest wstawianie do rekordu potomnego klucza obcego wskazującego na rekord-rodzic [...]]]></description>
			<content:encoded><![CDATA[<p>Praktycznie w każdym projekcie informatycznym (a w e-commerce w szczególności)  pojawiają sie jakieś struktury drzewiaste. Ich zapisywanie do bazy danych stanowi problem, ponieważ tabele baz danych są przystosowane do zapisu &#8216;liniowego&#8217; a nie drzewiastego.</p>
<p>Załóżmy, że mam taką strukturę drzewa (cyferki oznaczają identyfikatory węzłów):</p>
<p><img src="http://oladesign.biz/img/sql-tree.png" border="0" alt="Drzewiasta struktura danych" width="289" height="203" /></p>
<p>Najczęściej używaną strukturą jest wstawianie do rekordu potomnego klucza obcego wskazującego na rekord-rodzic bieżącego rekordu. Tabela tree będzie zawierała następujące dane:</p>
<pre>id	parent_id		data
1 	NULL 		…
2 	1 		…
3 	1 		…
4 	2		…
5 	4 		…
6 	4 		…
7 	2 		…
itd</pre>
<p>Sens tych wartości jest oczywisty: każdy rekord zawiera referencję na rekord &#8216;nadrzędny&#8217; (rodzica). Rekord od id = 1 nie ma rodzica - w związku z tym parent_id = NULL.</p>
<p>Co daje taki model? Na pewno bardzo proste wstawianie nowych rekordów. Równie proste jest przesuwanie - jednym zapytaniem można przesunąć całą gałąź do innego węzła. Z usuwaniem sprawa nie jest już taka prosta, ale można skorzystać z kaskady (ON DELETE CASCADE), która bardzo ładnie usunie całą gałąź poniżej usuwanego elementu.</p>
<p>Problem w tym, że wstawianie, usuwanie i przesuwanie to nie są operacje najczęściej wykonywane na drzewie. Typowe operacje to: &#8220;<em>pobierz wszystkie węzły potomne oddalone o mniej niż 2 od wskazanego węzła</em>&#8220;, albo &#8220;<em>pobierz wszystkie węzły macierzyste wskazanego węzła</em>&#8220;. W praktycznych zastosowaniach pierwsza operacja odpowiada np. budowaniu nawigacji na podstawie drzewa kategorii, druga zaś - tworzenia ścieżki dostępu (tzw. breadcrumbs) od bieżącej kategorii do strony głównej serwisu.</p>
<p>SQL nie zawiera wydajnych narzędzi czytania drzew zdefiniowanych tak, jak w powyższym przykładzie. W efekcie każda próba odczytania dowolnego fragmentu drzewa wymaga wielokrotnych zapytań do bazy danych - po jednym dla każdego poziomu przy czytaniu &#8220;w górę&#8221;, i co najmniej po jednym na każdy poziom przy czytaniu w dół. Przy serwisach z bardziej złożonym układem kategorii narzut związany z budowaniem nawigacji może być bardzo znaczący. Oczywiście, można użyć jednej z wielu technik cachowania, ale cache tylko maskuje prawdziwy problem.</p>
<p>Na szczęscie jest metoda pozwalająca na odczytanie dowolnego fragmentu drzewa pojedynczym zapytaniem - kosztem bardziej złożonej procedury dodawania i modyfikacji drzewa. Ale o tym w następnym wpisie.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2009/06/04/obsluga-drzew-w-sql-czesc-1/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Zagapiłem się&#8230;</title>
		<link>http://www.oladesign.biz/2009/06/04/zagapilem-sie/</link>
		<comments>http://www.oladesign.biz/2009/06/04/zagapilem-sie/#comments</comments>
		<pubDate>Thu, 04 Jun 2009 13:23:40 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Bez kategorii]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/2009/06/04/zagapilem-sie/</guid>
		<description><![CDATA[Zagapiłem się, i przez wiele miesięcy (ponad rok!) nie zaglądałem na bloga&#8230; aż tu nagle - wirus, trojan, exploit i inne paskudztwa.
Trochę posprzątałem, coś tam naprawiłem i mam nadzieję, że google usunie mnie z indeksu stron niebezpiecznych. Kazali &#8220;sprawdzić za jakiś czas&#8221;&#8230;
]]></description>
			<content:encoded><![CDATA[<p>Zagapiłem się, i przez wiele miesięcy (ponad rok!) nie zaglądałem na bloga&#8230; aż tu nagle - wirus, trojan, exploit i inne paskudztwa.</p>
<p>Trochę posprzątałem, coś tam naprawiłem i mam nadzieję, że google usunie mnie z indeksu stron niebezpiecznych. Kazali &#8220;sprawdzić za jakiś czas&#8221;&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2009/06/04/zagapilem-sie/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Dropshipping - powiedz nienieniearghhhh</title>
		<link>http://www.oladesign.biz/2008/05/15/dropshipping-powiedz-nienieniearghhhh/</link>
		<comments>http://www.oladesign.biz/2008/05/15/dropshipping-powiedz-nienieniearghhhh/#comments</comments>
		<pubDate>Thu, 15 May 2008 10:43:16 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[ecommerce]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/2008/05/15/dropshipping-powiedz-nienieniearghhhh/</guid>
		<description><![CDATA[Przeczytałem sobie taką notkę o dropshippingu. Idea nawet fajna, ale pomija istotną sprawę: firma zarabia na wartości dodanej. Czym więcej kluczowych funkcji biznesowych będzie realizowanych poza firmą, tym mniejszą wartość dodaną generuje. A zamawianie towaru w hurtowni, pakowanie i wysyłanie do klienta jest chyba jedną z kluczowych funkcji sklepu internetowego, czyż nie?
Prędzej czy później dojdzie [...]]]></description>
			<content:encoded><![CDATA[<p>Przeczytałem sobie taką <a href="http://ecommerce.blox.pl/2008/05/Dropshipping-za-i-przeciw.html" onclick="pageTracker._trackPageview('/outgoing/ecommerce.blox.pl/2008/05/Dropshipping-za-i-przeciw.html?referer=');">notkę o dropshippingu</a>. Idea nawet fajna, ale pomija istotną sprawę: firma zarabia na wartości dodanej. Czym więcej kluczowych funkcji biznesowych będzie realizowanych poza firmą, tym mniejszą wartość dodaną generuje. A zamawianie towaru w hurtowni, pakowanie i wysyłanie do klienta jest chyba jedną z kluczowych funkcji sklepu internetowego, czyż nie?</p>
<p>Prędzej czy później dojdzie do nieuniknionego: dostawca uruchomi własny system zamówień, poinformuje wszystkich klientw sklepu, zaoferuje niższą cenę i żegnaj biznesie złoty&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2008/05/15/dropshipping-powiedz-nienieniearghhhh/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Dropdowny, Ajax i usability</title>
		<link>http://www.oladesign.biz/2008/05/15/dropdowny-ajax-i-usability/</link>
		<comments>http://www.oladesign.biz/2008/05/15/dropdowny-ajax-i-usability/#comments</comments>
		<pubDate>Thu, 15 May 2008 10:28:15 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[usability]]></category>

		<category><![CDATA[webdeveloper]]></category>

		<category><![CDATA[ecommerce]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/2008/05/15/dropdowny-ajax-i-usability/</guid>
		<description><![CDATA[Generalną zasadą budowy dostępnego interfejsu jest takie jego przygotowanie, żeby mogły się nim posługiwać także osoby, które posługują się wyłącznie klawiaturą, lub innymi, niestandardowymi urządzeniami do obsługi komputera. Interfejs powinien być także tak przygotowany, żeby korzystanie z niego było oczywiste, a operacje odwracalne. Nie ma nic gorszego dla aplikacji www, niż użytkownik, który nie jest [...]]]></description>
			<content:encoded><![CDATA[<p>Generalną zasadą budowy dostępnego interfejsu jest takie jego przygotowanie, żeby mogły się nim posługiwać także osoby, które posługują się wyłącznie klawiaturą, lub innymi, niestandardowymi urządzeniami do obsługi komputera. Interfejs powinien być także tak przygotowany, żeby korzystanie z niego było oczywiste, a operacje odwracalne. Nie ma nic gorszego dla aplikacji www, niż użytkownik, który nie jest pewien, co zrobić, aby osiągnąć oczekiwany rezultat. <a href="http://helion.pl/ksiazki/niekaz.htm" onclick="pageTracker._trackPageview('/outgoing/helion.pl/ksiazki/niekaz.htm?referer=');pageTracker._trackPageview('/outgoing/helion.pl/ksiazki/niekaz.htm?referer=http://www.oladesign.biz/wp-admin/edit.php');pageTracker._trackPageview('/outgoing/helion.pl/ksiazki/niekaz.htm?referer=http://www.oladesign.biz/wp-admin/post-new.php');pageTracker._trackPageview('/outgoing/helion.pl/ksiazki/niekaz.htm?referer=http://www.oladesign.biz/wp-admin/post-new.php');">&#8220;Don&#8217;t make me think&#8221;</a>, najważniejsza reguła budowy interfejsu.<br />
<span id="more-8"></span><br />
Zanim przejdę do meritum krótkie wprowadzenie: rozwijamy i sprzedajemy system ecommerce. W tym systemie dostępne są różne produkty. Jak wiadomo, produkt, np. koszulka, ma pewne cechy, które są stałe (np. materiał), oraz takie, które mogą się zmieniać bez wpływu na ogólną charakterystykę produktu, np. rozmiar. Te pierwsze cechy nazywamy (tj: w firmie nazywamy) atrybutami, te drugie - opcjami. Koniec teorii.</p>
<p><img src="/img/przelaczanie_wariacji.png" alt="Sposób przełączania wariacji" style="border: 1px solid #333333" align="right" height="145" width="230" />Produkt może mieć więcej niż jedną opcję, a jedna opcja prawie zawsze ma więcej niż jedną dostępną wartość. Koszulki, oprócz rozmiaru, mogą mieć także opcję koloru. Produkt, który ma określoną wartość opcji, nazywamy wariacją. Jeżeli koszulka ma dwa dostępne rozmiary: mały i duży, oraz dwa kolory: zielony i czerwony, to łącznie ma 4 wariacje: mała zielona, mała czerwona, duża zielona, duża czerwona. Oczywistym jest (khe, khe ;)), że nie wszystkie wariacje zawsze występują w sklepie, czy nawet istnieją. Przełączanie wariacji realizowane jest przez zestaw dropdownów (patrz obrazek po prawej). Jeżeli wariacja dla danej kombinacji koloru i rozmiaru nie istnieje, w stosownym miejscu na stronie wyświetlany jest napis &#8220;wariacja niedostępna&#8221;.</p>
<p>Rozwiązanie wydaje się proste i eleganckie, ale tylko wtedy, gdy (A) dostępnych wariacji jest więcej niż niedostępnych, oraz (B) wariacje są niedostępne tylko czasowo. Oba te warunki zawodzą przy sklepach, które mają sezonowe kolekcje i pewność, że towar po wyprzedaniu już nie wróci. Dobrym pomysłem jest więc nie wyświetlać niedostępnych wariacji - czyli uniemożliwiać klientowi takie ustawienie dropdownów, które w wyniku daje &#8220;wariacja niedostępna&#8221;</p>
<p>Od strony technicznej sprawa jest prosta: po zmianie ustawienia pierwszego dropdowna (np.: kolor) badamy, jakie rozmiary są dostępne dla tego koloru, i usuwamy z drugiej listy wszystkie, które są niedostępne. Po wybraniu rozmiaru robimy to samo dla kolorów itp.</p>
<p>I wszystko byłoby super, gdyby nie to, że użytkownik nie ma pojęcia, że za jego plecami odbywają się jakieś podmiany list. Jeżeli np. produkt ma dostępne wariacje: (A, 1), (A, 2), (B, 1), (B, 3) ; wariacje (A, 3) i (B, 2) są niedostępne, a wariacją domyślną (czyli domyślnym ustawieniem dropdownów) jest (A,2), to klient:</p>
<ul>
<li>nie jest świadomy istnienia opcji wariacji B, dopóki nie zmieni drugiej opcji na 1</li>
<li>dojście do wariacji(B, 3) wymaga: przełączenia 2 na 1, przełączenia A na B, przełączenia 1 na 3</li>
<li>i nawet wtedy nie mamy pewności, czy wszystkie kombinacje zostały wyczerpane&#8230; a możemy jeszcze mieć wariację (C, 4), która w ogóle będzie niedostępna - nie z powodów braków magazynowych, ale niemożności ustawienia dropdownów w odpowiedniej pozycji!</li>
</ul>
<p>No to jak to rozwiązać? Przecież klient musi jakoś móc wybrać opcje. Ano, trzeba niestety pożegnać się z klasycznym formularzem i przejść na rozwiązanie w pełni javascriptowe. Na przykład takie:</p>
<p><a href="http://www.gap.com/browse/product.do?cid=15673&amp;pid=431909" onclick="pageTracker._trackPageview('/outgoing/www.gap.com/browse/product.do?cid=15673_amp_pid=431909&referer=');pageTracker._trackPageview('/outgoing/www.gap.com/browse/product.do?cid=15673_amp_pid=431909&#038;referer=http://www.oladesign.biz/wp-admin/edit.php');pageTracker._trackPageview('/outgoing/www.gap.com/browse/product.do?cid=15673_amp_pid=431909&#038;referer=http://www.oladesign.biz/wp-admin/post-new.php');" title="GAP - przykład przełączania wariacji">http://www.gap.com/browse/product.do?cid=15673&amp;pid=431909</a></p>
<p>Prawda, że ładnie i przejrzyście? Szkoda tylko, że nie działa bez JavaScriptu&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2008/05/15/dropdowny-ajax-i-usability/feed/</wfw:commentRss>
		</item>
		<item>
		<title>ColdFusion - utracone hasło</title>
		<link>http://www.oladesign.biz/2008/05/12/coldfusion-utracone-haslo/</link>
		<comments>http://www.oladesign.biz/2008/05/12/coldfusion-utracone-haslo/#comments</comments>
		<pubDate>Mon, 12 May 2008 11:42:25 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[ColdFusion]]></category>

		<category><![CDATA[webdeveloper]]></category>

		<category><![CDATA[tips&tricks]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/2008/05/12/coldfusion-utracone-haslo/</guid>
		<description><![CDATA[Zapisuję to sobie tutaj, bo za każdym razem muszę szukać w sieci  
Metoda
1. Lokalizujemy plik neo-security.xml (jeżeli mamy CF zainstalowane na domyślnej ścieżce, to będzie w c:\CFusionMX7\lib\)
2. Szukamy ciągu:



&#60;var name=&#39;admin.security.enabled&#39;&#62;


  &#160; &#60;boolean value=&#39;true&#39;/&#62;


&#60;/var&#62;



3. Zamieniamy wartość na &#8216;false&#8217;
4. Zapisujemy i restartujemy serwer CF
Voila!
]]></description>
			<content:encoded><![CDATA[<p>Zapisuję to sobie tutaj, bo za każdym razem muszę szukać w sieci <img src='http://www.oladesign.biz/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>Metoda</p>
<p>1. Lokalizujemy plik neo-security.xml (jeżeli mamy CF zainstalowane na domyślnej ścieżce, to będzie w c:\CFusionMX7\lib\)</p>
<p>2. Szukamy ciągu:</p>
<div class="geshi no xml">
<ol>
<li class="li1">
<div class="de1"><span class="sc3"><span class="re1">&lt;var</span> <span class="re0">name</span>=<span class="st0">&#39;admin.security.enabled&#39;</span><span class="re2">&gt;</span></span></div>
</li>
<li class="li1">
<div class="de1">  &nbsp; <span class="sc3"><span class="re1">&lt;boolean</span> <span class="re0">value</span>=<span class="st0">&#39;true&#39;</span><span class="re2">/&gt;</span></span></div>
</li>
<li class="li1">
<div class="de1"><span class="sc3"><span class="re1">&lt;/var<span class="re2">&gt;</span></span></span></div>
</li>
</ol>
</div>
<p>3. Zamieniamy wartość na &#8216;false&#8217;</p>
<p>4. Zapisujemy i restartujemy serwer CF</p>
<p>Voila!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2008/05/12/coldfusion-utracone-haslo/feed/</wfw:commentRss>
		</item>
		<item>
		<title>SQL Subselect - parę słów o wydajności</title>
		<link>http://www.oladesign.biz/2008/05/08/sql-subselect-pare-slow-o-wydajnosci/</link>
		<comments>http://www.oladesign.biz/2008/05/08/sql-subselect-pare-slow-o-wydajnosci/#comments</comments>
		<pubDate>Thu, 08 May 2008 11:38:14 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[sql]]></category>

		<category><![CDATA[benchmark]]></category>

		<category><![CDATA[optymalizacja]]></category>

		<guid isPermaLink="false">http://www.oladesign.biz/2008/05/08/sql-subselect-pare-slow-o-wydajnosci/</guid>
		<description><![CDATA[Trafiłem ostatnio na ciekawy wpis na blogu Krisa, traktujący o problemach związanych z wydajnością SQL subselect w dużych bazach (a przynajmniej dużych jak na aplikacje internetową). Temat dla mnie również ciekawy, ponieważ nasza aplikacja ma poważne problemy z wydajnością pojawiające się przy rozroście bazy danych, i część tych problemów jest związana z bazą danych.

Podejście Krisa - polegające na złamaniu postaci normalnej poprzez utworzenie kolumn pomocniczych z pewnością - poprawiło wydajność, ale budzi we mnie niepokój. Spreparowałem więc sobie zestaw danych wejściowych w celu przeprowadzenia własnego eksperymentu...]]></description>
			<content:encoded><![CDATA[<p>Trafiłem ostatnio na ciekawy <a href="http://kris.biz.pl/2008/04/05/gdy-sql-subselect-to-za-wolno/" onclick="pageTracker._trackPageview('/outgoing/kris.biz.pl/2008/04/05/gdy-sql-subselect-to-za-wolno/?referer=');pageTracker._trackPageview('/outgoing/kris.biz.pl/2008/04/05/gdy-sql-subselect-to-za-wolno/?referer=http://www.oladesign.biz/wp-admin/edit.php');pageTracker._trackPageview('/outgoing/kris.biz.pl/2008/04/05/gdy-sql-subselect-to-za-wolno/?referer=http://www.oladesign.biz/wp-admin/edit.php');pageTracker._trackPageview('/outgoing/kris.biz.pl/2008/04/05/gdy-sql-subselect-to-za-wolno/?referer=http://www.oladesign.biz/wp-admin/edit.php');" title="Gdy sql subselect to za wolno">wpis na blogu Krisa</a>, traktujący o problemach związanych z wydajnością SQL subselect w dużych bazach (a przynajmniej dużych jak na aplikacje internetową). Temat dla mnie również ciekawy, ponieważ nasza aplikacja ma poważne problemy z wydajnością pojawiające się przy rozroście bazy danych, i część tych problemów jest związana z bazą danych.</p>
<p>Podejście Krisa - polegające na złamaniu postaci normalnej poprzez utworzenie kolumn pomocniczych z pewnością - poprawiło wydajność, ale budzi we mnie niepokój. Spreparowałem więc sobie zestaw danych wejściowych w celu przeprowadzenia własnego eksperymentu&#8230;<br />
<span id="more-4"></span></p>
<p>Jako podstawy użyłem prostego zestawu tabel:<a href="/img/subselect_test_schema.png"><img src="/img/subselect_test_schema_tn.png" alt="Schemat tabel" height="111" width="217" /></a></p>
<p>Nie mam dostępu do PosgreSQL-a, ale wszystkie testy wykonałem na MySQL (5.0, InnoDB) i MSSQL 2000.</p>
<p><strong>Indeksy:</strong></p>
<p>users.id (primary)<br />
users.age<br />
user_language.userId<br />
user_language.languageId<br />
user_language.languageId, user_language.userId</p>
<p><strong>Ilość rekordów:</strong></p>
<p>users - 900 000, languages - 12, user_language - 2 700 000 (3 języki na użytkownika)</p>
<p><strong>WAŻNE!</strong> Obie bazy były uruchamiane na różnych maszynach! MySQL pracował na lokalnym komputerze (Pentium 4, 3.2Ghz, 1GB RAM + kupa dodatkowych programów, np. WinAmp w tle), SQL Server na maszynie zdalnej (Xeon 3.2Ghz, 2GB RAMu i w zasadzie tylko MSSQL). Nie można więc porównywać wyników uzyskanych na MySQL i MSSQL.</p>
<p>Druga sprawa: SQL Server był za szybki, żeby sensownie mierzyć czas <img src='http://www.oladesign.biz/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> Ale korzystając z opcji &#8220;Execution Plan&#8221; SQL Query Analyzera mogę mierzyć szybkość względną zapytania, gdzie 100% =  szybkość wykonania wszystkich przedstawionych zapytań.</p>
<p><strong>1.</strong>  Prosty, podstawowy test: wyszukiwanie ilości osób o określonym wieku:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> COUNT<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u <span class="kw1">WHERE</span> age <span class="sy0">&gt;</span> <span class="nu0">56</span>;</div>
</li>
</ol>
</div>
<p>czasy wykonania: MySQL: 0.28, 0.03, 0.03 sekund, SQL Server: 0.35%</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> count<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u <span class="kw1">WHERE</span> age <span class="sy0">&lt;</span> <span class="nu0">42</span>;</div>
</li>
</ol>
</div>
<p>czasy wykonania: MySQL: 2.31, 0.30, 0.30 sekundy, SQL Server: 3.35%</p>
<p><strong>2.</strong> Teraz pora na trochę bardziej złożone zapytanie, wzorowane dokładnie na zapytaniu Krisa:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> COUNT<span class="br0">&#40;</span>*<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">FROM</span> users u</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; age <span class="sy0">&lt;</span> <span class="nu0">50</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">AND</span> <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">SELECT</span> count<span class="br0">&#40;</span>*<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">FROM</span> user_language l</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">WHERE</span> l.languageId<span class="sy0">=</span><span class="nu0">4</span> <span class="kw1">AND</span> l.userId<span class="sy0">=</span>u.id</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="br0">&#41;</span> <span class="sy0">=</span> <span class="nu0">1</span>;</div>
</li>
</ol>
</div>
<p>czasy wykonania: MySQL: 200.66, 186.50, 186.42 sekund, SQL Server: 18.04%</p>
<p>Szczerze mówiąc, trochę to zapytanie dla mnie było dziwne. Po pierwsze, użycie funkcji COUNT() dla sprawdzenia, czy istnieje rekord jest w pewnym sensie nadużycie. Po drugie, złączenie w podzapytaniu z tabelą nadrzędną też nie jest najlepszym pomysłem - wymusza wykonanie zapytania wewnętrznego dla każdego potencjalnie pasującego rekordu.</p>
<p>Moim następnym pomysłem było pozbycie się ww. wad:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> COUNT<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u <span class="kw1">WHERE</span> age <span class="sy0">&lt;</span> <span class="nu0">50</span> <span class="kw1">AND</span> id <span class="kw1">IN</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> userId <span class="kw1">FROM</span> user_language l <span class="kw1">WHERE</span> l.languageId<span class="sy0">=</span><span class="nu0">4</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<p>czasy wykonania: MySQL: 172.88,  190.06, 176.76 sekund, SQL Server: 15.42%</p>
<p>Szczerze mówiąc - zdębiałem. Powinienem dostać  spory przyrost wydajności, a tym czasem MySQL totalnie olał moje modyfikacje (6% szybciej, a w SQL Serverze uzyskałem około 15%)&#8230; zacząłem szukać przyczyny, i wykonałem coś takiego:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> count<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u <span class="kw1">WHERE</span> id <span class="kw1">IN</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> userId <span class="kw1">FROM</span> user_language l <span class="kw1">WHERE</span> l.languageId<span class="sy0">=</span><span class="nu0">4</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<p>czasy wykonania: MySQL: 25.81, 25.72, 25.27 sekund, SQL Server: test nie został przeprowadzony</p>
<p>Usunięcie warunku dla wieku dało tak olbrzymi przyrost wydajności! Zupełnie nie rozumiem, co się wydarzyło, ale zacząłem się zastanawiać, jak w takim razie umieścić warunek wieku, i wymyśliłem coś takiego:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> count<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u <span class="kw1">WHERE</span> id <span class="kw1">IN</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> id <span class="kw1">FROM</span> users <span class="kw1">WHERE</span> age <span class="sy0">&lt;</span> <span class="nu0">50</span><span class="br0">&#41;</span> <span class="kw1">AND</span> id <span class="kw1">IN</span> <span class="br0">&#40;</span><span class="kw1">SELECT</span> userId <span class="kw1">FROM</span> user_language l <span class="kw1">WHERE</span> l.languageId<span class="sy0">=</span><span class="nu0">4</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<p>czasy wykonania: MySQL: 29.97, 30.88, 30.41 sekund, SQL Server: 31.38%</p>
<p>W MySQL użycie dwóch podzapytań działa znacznie lepiej, niż użycie jednego i warunku dla WHERE. Przyczyny są dla mnie na razie tajemnicą ;). SQL Server zadziałał zgodnie z oczekiwaniami, tzn: wydajność poważnie (dwukrotnie!) spadła.</p>
<p><strong>3.</strong>  Trochę zirytowany podzapytaniami, a w szczególności zachowaniem MySQ, na sam koniec zostawiłem sobie testy z wykorzystaniem zwykłych złączeń</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> count<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u, user_languages l <span class="kw1">WHERE</span> l.userId <span class="sy0">=</span> u.id <span class="kw1">AND</span> l.languageId <span class="sy0">=</span> <span class="nu0">4</span> <span class="kw1">AND</span> u.age <span class="sy0">&lt;</span> <span class="nu0">50</span>;</div>
</li>
</ol>
</div>
<p>Czas wykonania: MySQL: 4.08, 4.11, 4.02 sekund. SQL Server: 15.72%</p>
<p>MySQL znacznie szybciej obsługuje zwykłe złączenie niż podzapytania! Przyrost wydajności był imponujący. Natomiast dla SQL Servera użycie złączenia wymagało mniej-więcej tyle samo wysiłku co użycie podzapytania (w najszybciej wersji). Spróbowałem jeszcze odrobinę przyspieszyć zapytanie:</p>
<div class="geshi no sql">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> count<span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">FROM</span> users u <span class="kw1">LEFT</span> <span class="kw1">JOIN</span> user_language l <span class="kw1">ON</span> l.userId <span class="sy0">=</span> u.id <span class="kw1">WHERE</span> l.languageId <span class="sy0">=</span> <span class="nu0">4</span> <span class="kw1">AND</span> u.age <span class="sy0">&lt;</span> <span class="nu0">50</span>;</div>
</li>
</ol>
</div>
<p>Czas wykonania: MySQL:  3.86, 3.75, 3.88.  SQL Server: 15.72%</p>
<p>Zamiana zwykłego złączenia na lewe złączenie przyspieszyła odrobinę zapytanie - o ok. 3% dla MySQL. SQL Server w ogóle nie zarejestrował zmian: plany wykonania obu zapytań nie różnią się.</p>
<p><strong>Wnioski:</strong></p>
<p>po pierwsze, obsługa podzapytań w MySQL jest bardzo wolna. Jeżeli można - należy jej unikać, korzystając ze złączeń.</p>
<p>po drugie -  zapomnij o &#8220;przenaszalnych&#8221; aplikacjach między różnymi silnikami baz danych. SQL może i jest taki sam (lub podobny), ale sposób jego interpretacji i optymalizacji - zupełnie różny. Zupełnie inaczej optymalizuje się zapytanie dla MySQL, a inaczej dla SQL Servera.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.oladesign.biz/2008/05/08/sql-subselect-pare-slow-o-wydajnosci/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
