NARZĘDZIA INFORMATYCZNE/SQL
Kurs SQL-a dla analityków
Wojciech Gardziński
właściciel firmy AFIN;
Krzysztof Rumiński
właściciel firmy SYSTEM 2001;
Pytania: czytelnicy.controlling@infor.pl
Każdy analityk biznesowy przechodzi ten proces: od zafascynowania intuicyjnym interfejsem i sposobem obsługi Excela aż do… znużenia koniecznością powtarzania tych samych czynności i pokonywania tych samych kłopotów, coraz częstszych i coraz poważniejszych. Nabieramy doświadczenia, wiedzy i umiejętności, model analityczny komplikuje się. A pracochłonność i kłopoty jego obsługi wydają się narastać i nie mieć końca.
Jeżeli nadchodzi czas zmiany podejścia, widzimy konieczność zdobycia wiedzy i umiejętności, których nie da się zdobyć przez klikanie na nieznane ikonki i studiowanie helpa na własną rękę.
Pierwszy etap, trzeba przejść od architektury tradycyjnej (otworzę plik Excela, a potem zobaczę, co się da zrobić) do architektury klient-serwer (nie zaglądam do pliku, pytam się go o interesujące mnie informacje).
Program MS Query, nieodłączny, a rzadko używany przez analityków program zintegrowany z Excelem ma też kilka ikonek, przy pomocy których można zapytać bazę o informacje i stworzyć, imponujące swą formą i łatwością pielęgnacji, raporty. Ale, aby zacząć klikać na te ikonki, trzeba właśnie uzyskać trochę wiedzy nowego rodzaju.
Ci, co dotarli aż tutaj, zapewne znają nasze publikacje na ten temat. Tu już trzeba coś wiedzieć o strukturze danych, sposobach manipulowania tablicami, tworzenia relacji, filtrowania wierszy, wybierania kolumn i tworzeniu nowych, własnych. I, wiedzieć to wszystko właśnie w kontekście Excela.
Jeśli to wiemy, w nagrodę, przy pomocy kilku kliknięć myszą, możemy otrzymać raport, niemożliwy do uzyskania inną metodą. Zawierający wyniki, aktualne dla danych, przed chwilą wprowadzonych do systemu transakcyjnego, odległego o tysiące kilometrów albo do systemu budżetowego, opartego o setki skoroszytów – wszystko po jednym kliknięciu!
Smak pierwszych korzyści innego podejścia do danych – wytwarza potrzebę poznania „języka strukturalnych zapytań” (Structured Query Language, SQL), a, z naszego punktu widzenia, tak naprawdę – języka analizy poprzez zapytania.
Niniejszy artykuł jest skierowany do analityków, którzy dojrzeli już do potrzeby poznania samego języka SQL. To niewątpliwie nowe i trudne, ale możliwe. Zawarta tutaj wiedza jest nieznana zdecydowanej większości analityków, więc, jeśli zdecydujesz się czytać dalej (a może i ćwiczyć), niewątpliwie sam zapisujesz się do elity.
Pobierz plik: http://afin.net/excel/SQL_KompletnyEdytorZapytanPlusDanePrzykladowe.zip. Rozpakuj na dysku, najlepiej bezpośrednio na dysku C:. Powstanie folder SQL_w_Excelu, w którym znajdą się wszystkie niezbędne pliki.
Jeżeli rozpakujesz w innym folderze lub na innym dysku, wszystkie ścieżki będziesz musiał poprawić ręcznie z C:\SQL_w_Excelu\... na TwójDysk:\TwójFolder\SQL_w_Excelu…. Jeżeli chcesz ograniczyć ewentualne problemy ze ścieżkami i błędami ODBC (zdarzają się), radzimy instalację w ścieżce domyślnej, a jeżeli nie jest to możliwe, w jak najprostszej ścieżce bez spacji i „dziwnych” znaków, szczególnie kropek, przecinków, nawiasów itp.
Jeżeli w arkuszu pojawiły się nowe dane, życzymy Ci powodzenia w ćwiczeniach z SQL! Jeśli nie, skontaktuj się z firmowym informatykiem.
Ćwiczymy na bazie Excelowej – pliku Excela jako domyślnym źródle danych. Wszelkie różnice są szczegółowo opisane.
· Wszystkie słowa kluczowe (klauzule, ale też funkcje) języka SQL piszemy wielkimi literami. Nie ma to technicznego znaczenia, ale powoduje czytelność zapytania.
· Nazwy pól piszemy małymi literami (z wyjątkami).
· Aliasy nazw pól piszemy tak, jak potrzebujemy. Pamiętajmy jednak, że alias nazwy pola staje się nazwą pola przy zapytaniu do stworzonej przez nas tabeli.
SELECT *
FROM [faktura$]
WHERE wart_net>50
Klauzula SELECT w innej linii niż FROM, potem WHERE itd. Ma to na celu wyłącznie zwiększenie czytelności zapytania. Jeżeli chcemy dane zapytanie uruchomić, to w implementacji Excelowej, gdy wymagany jest jeden ciąg zapytania, z powyższego należy zrobić tekst: SELECT * FROM [faktura$] WHERE wart_net>50 (znak interlinii należy zastąpić spacją; tekst nie może być przełamany interlinią), a gdy chcemy użyć go w edytorze zapytań w MS Query, możemy pozostawić wieloliniową postać zapytania, ale każda linia (oprócz ostatniej) musi kończyć się spacją (w przykładzie poniżej oznaczoną podkreślnikiem „_”), np.:
SELECT *_
FROM [faktura$]_
WHERE vat>10
Stosujemy ją odpowiednio do sterownika ODBC Excela (czyli dostępu do plików Excela), ale również odpowiednio do systemu, z poziomu którego zapytanie jest realizowane – ma to niekiedy znaczenie. Dla przykładu, symbol wieloznaczny (wildcard) „dowolny ciąg znaków” w SQL to %, ale w SQL, uruchamianym z Accessa – jest to klasyczna, DOS-owa *, operator konkatenacji to &, ale nie w PostgreSQL – tam || itp. Może się zdarzyć, że nie przewidzieliśmy tutaj wszystkich wyjątków – odsyłamy wtedy po wiedzę do wszystkowiedzącej wyszukiwarki internetowej.
· BD – baza danych, SQL – Structered Query Language.
· Znak $ na końcu nazwy tabeli – oznaczenie arkusza w skoroszycie Excela. Jeżeli zapytania odnoszą się do plików Excela – stosujemy Excelowy zapis arkuszowy [faktura$] – wszystkie inne zapytania mają zapis tabelowy – faktura.
· Konkatenacja = łączenie tekstów.
· Iloczyn kartezjański tabel – tzw. CROSS JOIN, zjawisko występujące przy powstaniu zapytania do więcej, niż jednej tabeli, przy czym nie wszystkie tabele są połączone sprzężeniami (relacjami), polegający na zwielokrotnieniu ilości wierszy wyniku zapytania.
· BD MS JET – baza danych Microsoft Jet – „Silnik bazodanowy” Microsoft’a, obejmujący BD jego produkcji: MS SQL Server, MS Access, MS Excel. Poza liberalniejszym traktowaniem złej składni SQL-a, nie ma to większego znaczenia praktycznego.
SELECT *
FROM [faktura$]
SELECT nazwa
FROM [faktura$]
SELECT nazwa, wart_net
FROM [faktura$]
Stosujemy zapis [nazwa pola] lub `nazwa pola` (tzw. lewe apostrofy – nad tabulatorem na klawiaturze). Jedynym znakiem spoza liter i cyfr, traktowanym na równi z nimi, jest ‘_’ (podkreślnik). Wielkość liter, stosowanych w nazwach tabel, nie ma znaczenia.
Stosujemy wówczas gdy chcemy, aby nasze pole nazywało się inaczej, niż w źródle danych.
FROM [faktura$]
SELECT nazwa AS [mój-klient ze spacjami i %$&]
FROM [faktura$]
Uwaga
Nawiasy kwadratowe dotyczą również aliasów tabel.
podwojenie wartości sprzedaży:
SELECT nazwa AS [mój klient], wart_net*2 AS [podwójna sprzedaż]
FROM [faktura$]
pierwsza litera nazwy klienta:
SELECT LEFT(nazwa,1) AS [mój klient], wart_net*2 AS [podwójna sprzedaż]
FROM [faktura$]
pobranie daty w określonym formacie tekstowym:
SELECT data, format(data,'"Y"yyyy" Q"q" M"mm "D"dd') AS 'Data sformatowana'
FROM faktura
Y-Rok, Q-kwartał, M-miesiąc, D-dzień.
Uwaga
Sterownik ODBC Excela nie obsługuje funkcji FORMAT. Działa ona wyłącznie w Accessie i SQLS. W innych bazach ma inną nazwę i składnię).
pobranie fragmentu daty:
SELECT YEAR(data) AS [Rok], MONTH(data) AS [Miesiąc], DAY(data) AS [Dzień]
FROM [faktura$]
Uwaga
Nie należy (choć jest to czasami możliwe) stosować funkcji bez szczegółowego dostosowania typu jej argumentu, np. pobór roku w taki sposób SELECT LEFT(data,4) powiedzie się w BD MS JET, ale stanowczo zostanie odrzucony w innych BD. Nie należy przyzwyczajać się do liberalności BD MS JET.
jedno pole:
SELECT DISTINCT nazwa
FROM [faktura$]
kilka pól – unikatowość dotyczy kombinacji wartości obu pól:
SELECT DISTINCT nazwa, nr_fakt
FROM [faktura$]
faktury klienta o konkretnej nazwie (prosty filtr):
SELECT *
FROM [faktura$]
WHERE nazwa='BRONEX'
Uwaga
Powinniśmy nazwę (wartość tekstową) pisać zawsze tak, jak jest w tabeli (dużymi lub małymi literami). W BD MS JET nie jest to wymagane, ale – ze względu na niezgodność ze standardem SQL – nie jest polecane. Czyli – wielkość znaków w klauzuli WHERE ma znaczenie!
Wybór klientów na literę A w kwerendzie w MS Access:
SELECT *
FROM [faktura$]
WHERE nazwa LIKE 'A*'
Powyższe zapytanie zadziała wyłącznie wówczas gdy zostanie uruchomione w Accessie. W naszym edytorze nie zwróci żadnych rekordów.
Wybór klientów na literę A w Excelu i większości pozostałych edytorów zapytań SQL:
SELECT *
FROM [faktura$]
WHERE nazwa LIKE 'A%'
Uwaga
W standardzie SQL symbole wieloznaczne (wildcard-y) to % (dowolny ciąg dowolnych znaków) i „_” (pojedynczy dowolny znak).
Wybór klienta z literą R na drugim miejscu:
SELECT *
FROM [faktura$]
WHERE nazwa LIKE '_R%'
Wybór zakresu liter:
SELECT *
FROM [faktura$]
WHERE nazwa LIKE '[A-C]%'
Wybór faktur klientów, których nazwa zaczyna się na J, kończy na X i o wartości faktury powyżej 50:
SELECT *
FROM [faktura$]
WHERE nazwa LIKE 'J%X' AND wart_net>50
Wybór klienta, którego nazwa nie zaczyna się na J, nie kończy na X i o wartości faktury poniżej 50 (wszyscy pozostali poza J%X i powyżej 50, negacja poprzedniego):
SELECT *
FROM [faktura$]
WHERE NOT (nazwa LIKE 'J%X' AND wart_net>50)
lub
SELECT *
FROM [faktura$]
WHERE nazwa NOT LIKE 'J%X' OR NOT wart_net>50
zadanej:
SELECT *
FROM [faktura$]
WHERE nazwa IN ('BRONEX', 'CELIMP', 'JUREX')
pobranej z innego zapytania:
wyświetla wszystko z tabeli faktura, gdzie odbiorca jest z Warszawy (dane pobiera z innej tabeli odbiorca – bez zastosowania sprzężenia między tabelami)
SELECT *
FROM [faktura$]
WHERE nazwa IN
(SELECT nazwa FROM [odbiorca$] WHERE miasto='WARSZAWA')
zapytanie do podzapytania:
SELECT *
FROM
(SELECT *
FROM [faktura$]
WHERE nazwa LIKE '%X')
WHERE nazwa LIKE 'A%'
zwraca wynik – zestaw faktur klientów, zaczynających się na A z wyniku pierwszego podzapytania, które zawęziło wynik tylko do klientów, kończących się na X (graficzne wcięcie podzapytania – tylko dla czytelności).
data, podana jako określona wartość (w ‘#’):
SELECT *
FROM [faktura$]
WHERE data = #2004-06-03#
daty z różnych przedziałów czasu:
SELECT *
FROM [faktura$]
WHERE data <= #2004-06-03# OR data >= #2005-01-01#
Uwaga
Sposób zapisu daty jest różny dla różnych BD. W BD Oracle, określoną datę podaje się np. tak: ‘2010-01-31’
SQL używa wszystkich znanych operatorów logicznych AND, OR, NOT oraz matematycznych: =, >, <, >=, <=, <>. W kryteriach można stosować też funkcje SQL, np. NOW, LEFT, MONTH i setki innych.
‘between’, czyli ‘pomiędzy wartościami’:
SELECT *
FROM [faktura$]
WHERE data BETWEEN #2004-06-03# AND #2004-12-31#
SELECT *
FROM [faktura$]
WHERE wart_net BETWEEN 10 AND 50
SELECT *
FROM [faktura$]
WHERE nazwa BETWEEN ‘AR’ AND ‘JU’
Uwaga
‘BETWEEN’ działa nie tylko na polach numerycznych (przedział obustronnie domknięty).
Operator ‘IS [NOT] NULL’ (‘Jest puste’ lub ‘Jest niepuste’):
SELECT *
FROM [faktura$]
WHERE termin IS NOT NULL
Uwaga
Wartość ‘NULL’ nie jest tożsama z wartością 0 (zero) lub ‘’ (ciągiem pustym) lub jakąkolwiek inną. ‘NULL’ to brak wartości, innej, niż ‘NULL’.
+ suma sprzedaży ogółem:
SELECT SUM(wart_net)
FROM [faktura$]
+ zmiana nazwy kolumny:
SELECT SUM(wart_net) AS [sprzedaż ogółem]
FROM [faktura$]
+ grupowanie po polu nieagregowanym (tu: nazwie):
SELECT nazwa, SUM(wart_net) AS [sprzedaż]
FROM [faktura$]
GROUP BY nazwa
Ogólna zasada dotycząca grupowania (używania ‘GROUP BY’): grupujemy zawsze po wszystkich polach nieagregowanych.
+sortowanie po nazwie (sortowanie po wartości agregującej):
SELECT nazwa, SUM(wart_net) AS [sprzedaż]
FROM [faktura$]
GROUP BY nazwa
ORDER BY nazwa
Uwaga
Domyślny porządek sortowania – rosnąco (‘ASC’).
+ ograniczenie rankingu do klientów którzy mają faktury o wartości większej niż 50
(kryterium na wartości surowej):
SELECT nazwa, SUM(wart_net) AS [sprzedaż]
FROM [faktura$]
WHERE wart_net>50
GROUP BY nazwa
ORDER BY SUM(wart_net) DESC
Aby wybierać rekordy po wartościach zagregowanych (agregatach) stosujemy klauzulę HAVING. having jest podobne WHERE, ale dotyczy wyłącznie agregatów i jest wstawiane po Group BY.
Sortowanie po wartości sprzedaży malejąco (sortowanie po agregacie)
Ranking kontrahentów według wielkości sprzedaży:
SELECT nazwa, SUM(wart_net) AS [sprzedaż]
FROM [faktura$]
GROUP BY nazwa
ORDER BY SUM(wart_net) DESC
Ograniczenie rankingu do klientów, którzy mają faktury o wartości większej niż 50
Dla porównania – zobacz podobne zapytanie z kryterium na wartości surowej:
SELECT nazwa, SUM(wart_net) AS [sprzedaż]
FROM [faktura$]
GROUP BY nazwa
HAVING SUM(wart_net)>50
ORDER BY SUM(wart_net) DESC
Łączenie dwóch tabel 1 pod 2-gą z tej samej lub dwóch (lub więcej) różnych baz danych.
Łączenie pionowe – wszystkie rekordy:
(ta sama BD)
SELECT *, 'Tabela1' AS [Indeks tabeli] FROM [faktura$]
UNION ALL
SELECT *, 'Tabela2' AS [Indeks tabeli] FROM [faktura$]
(różne BD)
SELECT*, ‘Access’ AS [Indeks źródła danych] FROM faktura IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.mdb'
UNION ALL
SELECT *, 'Excel' AS [Indeks źródła danych] FROM [faktura$] IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.xls'[Excel 5.0;]
Łączenie pionowe – rekordy unikalne:
Łączenie dwóch tabel 1 pod 2-gą z dwóch różnych baz danych bez powtarzania tych samych rekordów.
SELECT *, ‘Access’ AS [Indeks źródła danych] FROM faktura IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.mdb'
UNION
SELECT *, ‘Excel’ AS [Indeks źródła danych] FROM [faktura$] IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.xls'[Excel 5.0;]
Standardowy zapis relacji – tu: wybór rekordów, spełniających warunek, z iloczynu kartezjańskiego dwóch tabel
(relacja dwóch tabel po polu nazwa z tabel faktura i odbiorca)
SELECT *
FROM [faktura$], [odbiorca$]
WHERE faktura.nazwa=odbiorca.nazwa
Uwaga
Jeśli mamy pola z więcej niż 1 tabeli to, używając nazw pól lub ich aliasów, zawsze piszemy tzw. „nazwę kwalifikowaną”, tj. NazwaTabeli.NazwaPola (nazwa tabeli – kropka – nazwa pola).
Wersja poprawna zapisywania relacji, tzw. „INNER JOIN”:
SELECT *
FROM [faktura$] INNER JOIN [odbiorca$] ON faktura.nazwa=odbiorca.nazwa
+ aliasy tabel:
SELECT *
FROM [faktura$] f INNER JOIN [odbiorca$] o ON f.nazwa=o.nazwa
lub
FROM [faktura$] AS f INNER JOIN [odbiorca$] AS o ON f.nazwa=o.nazwa
Ranking miast według wartości sumarycznej sprzedaży (w etapach):
SELECT o.miasto, f.wart_net
FROM [faktura$] f INNER JOIN [odbiorca$] o ON f.nazwa=o.nazwa
SELECT o.miasto, SUM(f.wart_net)
FROM [faktura$] f INNER JOIN [odbiorca$] o ON f.nazwa=o.nazwa
GROUP BY o.miasto
SELECT o.miasto, SUM(f.wart_net) AS [sprzedaż po miastach]
FROM [faktura$] f INNER JOIN [odbiorca$] o ON f.nazwa=o.nazwa
GROUP BY o.miasto
ORDER BY SUM(f.wart_net) DESC
+ tylko hurtownicy:
SELECT o.miasto, SUM(f.wart_net) AS [sprzedaż po miastach]
FROM [faktura$] f INNER JOIN [odbiorca$] o ON f.nazwa=o.nazwa
WHERE o.gr_odb='HURT'
GROUP BY o.miasto
ORDER BY SUM(f.wart_net) DESC
+ tylko z ogólną wartością sprzedaży powyżej 10:
SELECT o.miasto, SUM(f.wart_net) AS [sprzedaż po miastach]
FROM [faktura$] f INNER JOIN [odbiorca$] o ON f.nazwa=o.nazwa
WHERE o.gr_odb='HURT'
GROUP BY o.miasto
HAVING SUM(f.wart_net)>10
ORDER BY SUM(f.wart_net) DESC
Z tabeli bazy danych Accessa:
SELECT *
FROM odbiorca IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.mdb'
Z Excela (np. z innego pliku Excela):
SELECT *
FROM [odbiorca$] IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.xls'[Excel 5.0;]
Nasz edytor obsługuje tę opcję w arkuszu ExecuteSQL – pierwsze kroki radzimy wykonać pod okiem informatyka.
W tej samej BD:
(w przypadku baz plikowych = w tym samym pliku)
SELECT *
INTO [faktura kopia]
FROM [faktura$]
W innej BD:
(w przypadku baz plikowych = innym pliku tego samego lub innego formatu)
SELECT *
INTO [faktura kopia]
FROM [faktura$] IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.xls'[Excel 5.0;]
Utworzenie kopii tabeli z zaciągnięciem danych z zewnętrznej bazy danych i wklejenie ich do nowej kopii pliku w nowej lokalizacji:
SELECT *
INTO [faktura kopia] IN 'C:\SQL_w_Excelu\Sprzedaz kopia.xls'[Excel 5.0;]
FROM [faktura$] IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.xls'[Excel 5.0;]
+ zmiana formatu BD:
SELECT *
INTO [faktura kopia] IN 'C:\SQL_w_Excelu\Sprzedaz kopia.mdb'
FROM [faktura$] IN 'C:\SQL_w_Excelu\Dane\Sprzedaz.xls'[Excel 5.0;]
SELECT – wybór pól z tabeli
…
INTO wybór tabeli wynikowej
…
IN (po INTO) wybór bazy zewnętrznej wynikowej
…
FROM – wybór tabeli (źródła danych)
…
IN (po FROM) wybór bazy zewnętrznej źródłowej
…
WHERE – warunek na danych surowych, niezagregowanych
LIKE, NOT LIKE
IS NULL, IS NOT NULL
IN (po WHERE) – wybór listy kryteriów
BETWEEN, inne operatory
…
GROUP BY - grupowanie danych według pól nieagregowanych
…
HAVING - warunek na danych zagregowanych
…
ORDER BY – porządkowanie danych (sortowanie)
…
ASC – sortuj rosnąco (domyślnie), DESC – sortuj malejąco