Rozdział 17
Delphi a Interbase
W tym rozdziale przedstawimy Interbase RDBMS. Omówimy elementy SQL oraz
niektóre zagadnienia dotyczące wspólnej pracy InterBase i Delphi. Opiszemy także
podstawy administrowania serwerami InterBase.
Zaczniemy oczywiście od uruchomienia serwera.
Uruchamianie serwera InterBase
Sposób uruchomienia serwera w dużym stopniu zależy od systemu operacyjnego.
Mając na uwadze fakt, że nie jest to książka poświęcona wyłącznie systemowi
InterBase, ograniczymy nasze rozważania tylko do najpopularniejszych - Windows
95 i Windows NT.
Po przeprowadzeniu standardowej instalacji, serwer InterBase uruchamia się
automatycznie przy każdym starcie Windows. Na pasku zadań w Windows 95/NT
pojawia się wtedy jego ikona ( lub ikona na pulpicie, jeśli używamy Windows NT
3.51 ).
Klikając ikonę na pasku zadań prawym przyciskiem myszy, uzyskujemy dostęp do
podstawowych ustawień serwera. Jeśli np. wybierzemy - z menu rozwijalnego -
pozycję
Startup Configuration
, serwer będzie startował równolegle z Windows. Po
wybraniu z kolei
Properties
, będziemy mogli odczytać dopuszczalną i aktualną
liczbę połączeń z serwerem.
Łączenie się z serwerem
W przeciwieństwie do omówionych dotychczas serwerów - aby uzyskać dostęp do
serwerów InterBase, nie musimy definiować oddzielnego połączenia. Jeśli
korzystamy z lokalnego serwera InterBase, oprogramowanie klienta (WISQL,
BDE, itp. ) odwołuje się bezpośrednio do pliku bazy danych (nie ma potrzeby
podawania jakichkolwiek danych odnoszących się do serwera).
W przypadku odległego serwera (InterBase), niezbędne do połączenia informacje
zależą od wyboru protokołu sieciowego. Jeśli łączymy się poprzez TCP/IP, wtedy
plik HOSTS musi zawierać dane dotyczące naszego serwera, np.:
100.10.15.12 marketing
506
Część III
Także plik TCP SERVICES powinien zostać uzupełniony o linię definiującą
protokół dostępu InterBase:
gds_db 3050/tcp
Oczywiście odpowiednie zmiany w
plikach dokonywane są automatycznie
w czasie instalacji serwera. Gdy tylko skonfigurujemy dostęp poprzez TCP/IP,
możemy korzystać z WISQL i innych narzędzi InterBase. Należy jeszcze raz
podkreślić, że podane wymagania dotyczące plików inicjacyjnych odnoszą się
tylko do TCP/IP. NetBEUI ani IPX/SPX nie nakładają tego typu wymagań.
Konfigurowanie Aliasów BDE
Teraz utworzymy alias BDE, który umożliwi aplikacjom w Delphi dostęp do
serwera. Ten temat był już omawiany wcześniej, dlatego teraz przedstawimy tylko
kolejne kroki, które pozwolą poprawnie go skonfigurować.
Chcąc zbudować alias BDE powinniśmy wybrać jedno z
narzędzi: BDE
Administrator lub Delphi Database Explorer. Opisany poniżej sposób
postępowania odnosi się do programu BDE Administrator.
1. Uruchamiamy BDE Administrator, klikamy prawym przyciskiem myszy fiszkę
Databases
i wybieramy
New
. W oknie dialogowym
New Database Alias
wybieramy, z listy typów aliasów,
INTRBASE
i klikamy
OK
.
2. Wpisujemy nazwę nadaną tworzonemu aliasowi .
3. Konfigurujemy go przez odpowiednie ustawienie parametrów na stronie
Definition
4. Klikamy parametr
SERVER NAME
i wprowadzamy nazwę serwera oraz plik
bazy danych, z którym chcemy się łączyć. Przyjęto konwencję, że ten plik ma
rozszerzenie
GDB
. Wprowadzane dane powinny przyjmować postać:
MIS:/data/interbase/accounting.gdb
gdzie
MIS
jest nazwą twojego serwera a
/data/interbase/
accounting.gdb
jest pełną ścieżką do pliku zawierającego bazę danych.
WSKAZÓWKA
Jeśli korzystamy z lokalnego serwera InterBase, pole
SERVER NAME
może
pozostać niewypełnione. Wystarczy podać tylko pełną ścieżkę dostępu do pliku
zawierającego bazę danych., np.
C:\DATA\INTERBASE\ACCOUNT.GDB
Rozdział 17 Delphi w Interbase
507
5. Jeśli podamy nazwę użytkownika (parametr
USER NAME
), to będzie on
domyślnie przyłączany do serwera. Podana nazwa będzie też umieszczana we
wbudowanym w Delphi oknie dialogowym logowania.
Jak widać jest kilka innych parametrów, którym możemy nadać odpowiednie
wartości. W szczególności jeden z nich powinien być zawsze ustawiony dla
aliasów InterBase -
ENABLE BCD. D
ecyduje on bowiem o tym, jak BDE będzie
interpretował pewne typy danych numerycznych. Chociaż paramatr ma w swej
nazwie BCD, to jednak jego zasięg nie ogranicza się tylko do danych BCD
(Binary-Coded Decimal), ale dotyczy także typów zmiennoprzecinkowych:
DECIMAL i
NUMERIC. Gdy
ENABLE BCD
jest ustawiony na
FALSE
(ustawienie domyślne), pola DECIMAL lub NUMERIC będą traktowane przez
Delphi jako integers (całkowite). W konsekwencji elementy rozpoznające rodzaj
danych uniemożliwią nam wprowadzanie części dziesiętnych liczb. Dlatego
powinniśmy ustawiać
ENABLE BCD
na
TRUE
we wszystkich aliasach.
Po wybraniu
Apply
wprowadzone zmiany zostają zapamiętane.
Rozwiązywanie problemów połączeniowych z serwerem InterBase
Poniżej zebraliśmy kilka praktycznych porad, które mogą okazać się przydatne
w
przypadku napotkania problemów połączeniowych w naszych aplikacjach
w Delphi.
1. Skorzystamy najpierw z
narzędzia Interbase Communication Diagnostic,
umieszczonego w folderze InterBase 4.2. Klikamy przycisk
Test
ze strony
DB
Connection
. Brak połączenia może być spowodowany złą konfiguracją aliasu
BDE. W takiej sytuacji powinniśmy powrócić do programu BDE Administrator
i upewnić się, czy podane ustawienia są poprawne. Jeśli nadal nie będziemy
uzyskiwali połączenia z DB Connection, powinniśmy przejść na stronę
NetBEUI
lub
Winsock
( wybierając odpowiednią fiszkę) i kliknąć
Test
. Jeśli
choć jeden będzie funkcjonował poprawnie, to prawdopodobną przyczyną
braku połączenia są złe ustawienia parametrów. Jeśli żadna z prób się nie
powiedzie (a mamy pewność, że serwer jest uruchomiony), proponujemy
kontynuowanie diagnostyki według zamieszczonego poniżej opisu.
2. Jeśli InterBase WISQL umożliwia nawiązanie komunikacji z serwerem,
natomiast nadal występują problemy połączeniowe w naszej aplikacji, to
prawdopodobnie konfiguracja aliasu DBE jest niewłaściwa. W takiej sytuacji
powinniśmy powrócić do programu BDE Administrator i upewnić się, czy
podane ustawienia są poprawne ( w szczególności nazwa serwera).
3. Brak połączenia poprzez WISQL świadczy o wadliwym funkcjonowaniu
protokołu sieciowego. Jeśli korzystamy z TCP/IP, łączność z serwerem można
sprawdzić za pomocą programu PING, który jest dostarczany z Windows 95 /
508
Część III
NT. Powinniśmy spróbować łączenia poprzez nazwę komputera zapisaną
w pliku HOSTS lub poprzez jego adres IP.
4. Jeśli korzystamy z nazwanych potoków (ang. named pipes) - do komunikacji
z serwerem pracującym w Windows NT powinniśmy użyć polecenia
net
view \\ servername
, gdzie
servername
jest nazwą komputera w sieci
NT, na którym funkcjonuje serwer SQL. Po poprawnej realizacji
net view
posłużymy się instrukcją
netuse \\servername\IPC$,
zamieniając
servername
na nazwę naszego serwera. Wystąpienie jakichkolwiek
nieprawidłowości wskazuje na brak połączenia z komputerem serwera w sieci.
W takim przypadku proponujemy zwrócić się do administratora systemu NT.
Źródłem zakłóceń może być też wadliwa obsługa potoków na serwerze.
5. W sytuacji, gdy uzyskaliśmy połączenie poprzez adres IP, co nie powiodło się
za pośrednictwem nazwy serwera, powinniśmy dokładnie sprawdzić dane
w pliku
HOSTS
, bowiem oprogramowanie klienta InterBase’a korzysta z niego
przy wyszukiwaniu serwera. W pliku
HOSTS
musi się znaleźć odpowiednia
linia z informacjami o każdym serwerze, z którym chcemy się połączyć np:
100.10.15.12 marketing
6. Gdy nie udało się nam połączyć z serwerem (ani poprzez nazwę ani adres IP),
to prawdopodobną przyczyną może być uszkodzenie sieci. Oczywiście
powinniśmy jeszcze raz sprawdzić adres IP serwera oraz uruchomić
PING
(podając adres
127.0.0.1
) - aby przekonać się, czy stos protokołu TCP/IP
funkcjonuje poprawnie. Jeśli test się nie powiedzie, to prawdopodobnie źle
skonfigurowano sam protokół. W takim przypadku proponujemy zwrócić się do
administratora sieci.
7. Może się też zdarzyć, że nie uzyskujemy połączenia poprzez WISQL, natomiast
PING umożliwia nawiązanie komunikacji. W takim przypadku możemy
skorzystać z Telnetu (dostarczanego wraz z Windows95/NT). Składnia wywołania
wygląda następująco:
TELNET 100.100.100.100
lub
TELNET hostname
gdzie
100.100.100.100
jest adresem TCP/IP komputera serwera a hostname
jego nazwą. Przyczyną braku połączenia poprzez Telnet może być wadliwe
działanie demona inet na serwerze.
8. Jeśli połączyliśmy się poprzez Telnet przy wciąż nie funkcjonującym WISQL,
powinniśmy się upewnić, czy oprogramowanie serwera InterBase’a zostało
poprawnie zainstalowane i uruchomione. Istotne jest także, czy plik
TCP
SERVICES
zawiera niezbędne informacje. Powinny one przyjmować postać:
Rozdział 17 Delphi w Interbase
509
gds_db 3050/tcp
9. Jeśli nasze działania nie przyniosły pożądanego rezultatu, powinniśmy
skonsultować się z administratorem sieci lub bazy danych.
Wstęp do SQL
Język SQL należy do podstawowych środków komunikowania się z serwerami baz
danych. Niniejszy rozdział jest wprowadzeniem do własnej, bogatej odmiany SQL,
wykorzystywanej przez InterBase. Zwrócimy tutaj uwagę na kilka właściwości,
które różnią tę implementację SQL od dostarczanych przez innych sprzedawców.
Aby móc poznawać w praktyce prezentowane elementy języka, wystarczy
zapewnić sobie dostęp do uruchomionego serwera i umieć się z nim komunikować
poprzez WISQL.
Tworzenie bazy danych
Zaczniemy od utworzenia bazy danych i umieszczenia w niej tymczasowych tabel,
które zostaną wykorzystane w podanych dalej przykładach. W tym celu posłużymy
się instrukcją
CREATE DATABASE
. Jej składnia zależy od konkretnej
implementacji SQL. Poniższy przykład przedstawia podstawową składnię
InterBase’a:
CREATE DATABASE "C:\DATA\IB\SALES" USER SYSDBA PASSWORD
➥
masterkey
Aby utworzyć bazę danych używając InterBase WISQL, korzystamy z polecenia
Create
Database
z menu
File
. A oto sposób postępowania:
1. Uruchamiamy program interaktywnej obsługi SQL (WISQL) - albo z grupy
programów InterBase’a albo z paska zadań z InterBase Server Manager.
2. Wybieramy polecenie
Create
Database
z menu
File
.
3. Wpisujemy pełną ścieżkę dostępu do bazy, którą chcemy utworzyć (np.
C:\DATA\IB\SALES.GB
) w
polu
Database
okna dialogowego
Create
Database
.
4. Podajemy nazwę użytkownika i hasło. Domyślne wartości tych parametrów to:
nazwa użytkownika-
SYSDBA
; hasło -
masterkey
.
5. Klikamy przycisk
OK
. InterBase powinien utworzyć bazę danych i podłączyć
nas do niej. Później - do łączenia się z istniejącą bazą - korzystamy z polecenia
Connect to
Database
z menu
File
.
W oknie dialogowym
Create DataBase
,w części
Database Options
, można ustawić
pewne parametry bazy, między innymi rozmiar stron (w bajtach) tworzących bazę.
510
Część III
Domyślnie jest przyjmowane 1024 (1K), a dopuszczalne są jeszcze wartości 2048,
4096 lub 8192 ( np.
PAGE_SIZE=2048
).
Można też określić dodatkowe pliki, które zostaną utworzone wraz
z podstawowym plikiem bazy danych oraz podać dla nich początkowe wielkości.
WSKAZÓWKA
Utworzenie bazy w WISQL jest możliwe tylko przez wybór odpowiedniej pozycji
z menu. Mamy jednak do dyspozycji program ISQL (umieszczany domyślnie
w kartotece
C:\Program Files\Borland\IntrBase\Bin
), umożliwia-
jący wydawanie w linii poleceń instrukcji SQL, dostępnych w WISQL tylko za
pośrednictwem odpowiednich pozycji menu.
Cienie (shadows)
Serwer InterBase pozwala tworzyć specjalny obiekt nazwany cieniem, do
utrzymywania lustrzanego odbicia bazy danych na innym dysku (lub dyskach).
Dzięki temu może być ona nadal dostępna - nawet w przypadku jej uszkodzenia na
podstawowym dysku lub uszkodzenia samego dysku.
Wydanie instrukcji
CREATE SHADOW
spowoduje stworzenie cienia dla aktywnej
bazy danych. Składnia polecenia jest następująca:
CREATE SHADOW 1 "sales.shd" LENGTH 5000
Podajemy unikalny numer cienia jako pierwszy parametr instrukcji. Jeśli numer
zostanie pominięty lub będzie się powtarzał, InterBase nie utworzy cienia.
Dublowanie bazy wiąże się z podwojeniem liczby zapisów, które są wykonywane
w czasie przeprowadzanych zmian. Należy się z tym liczyć przy wyborze
lokalizacji dla cienia. Praca z powolnym dyskiem może istotnie obniżyć wydajność
systemu
Instrukcja
CREATE SHADOW
ma specjalny parametr pozwalający ustalić
zachowanie się systemu, gdy cień będzie niedostępny. Po wybraniu wartości
AUTO
aplikacje klienta będą nadal przyłączone do bazy, a cień zostanie skasowany.
Ustawienie
MANUAL
zablokuje wszystkie połączenia - aż do chwili, gdy cień
będzie ponownie dostępny lub zostanie usunięty (instrukcją
DROP SHADOW).
CONDITIONAL
pozwoli kontynuować połączenia i
spowoduje utworzenie
nowego cienia. Jeśli parametr jest ustawiony na
CONDITIONAL,
wtedy
w przypadku uszkodzenia bazy jej miejsce zajmie cień i jednocześnie zostanie
utworzony cień nowo promowanego cienia. A oto jeszcze jeden przykład:
Rozdział 17 Delphi w Interbase
511
CREATE SHADOW 1 CONDITIONAL "d:\shadows\sales.shd" LENGTH
➥
5000 ;
Oczywiście zastosowane słowo kluczowe
CONDITIONAL
określa zachowanie się
bazy, gdy cień będzie niedostępny.
Instrukcja CONNECT
Instrukcja InterBase SQL - CONNECT - umożliwia łączenie się z istniejącą bazą
danych. Posiada ona następującą składnię:
CONNECT ServerAndDatabasePath USER "ValidUser" PASSWORD
➥
"UserPassword"
ServerAndDatabasePath
oznacza pełną ścieżkę do serwera i bazy danych,
z którą chcemy się połączyć. Jeśli serwer jest uruchomiony lokalnie, wystarczy
podać tylko ścieżkę do bazy danych.
ValidUser
i
UserPassword
to
odpowiednio: nazwa użytkownika i hasło na naszym serwerze bazy danych.
W przypadku lokalnej bazy danych instrukcja
CONNECT
może mieć postać:
CONNECT "C:/DATA/IB/SALES.GDB" USER "SYSDBA" PASSWORD
➥
"masterkey"
Każdorazowo, gdy łączymy się z inną bazą danych, jesteśmy odłączani od
bieżącej.
DISCONNECT
umożliwia zerwanie połączenia (ustalonego wcześniej poleceniem
CONNECT)
. Typowy przykład:
DISCONNECT ALL;
Ten sam efekt osiągniemy zastępując
ALL
. przez
DEFAULT
.W WISQL jesteśmy
zmuszeni do korzystania z poleceń
Connect to DataBase
i
Disconnect
from Database
z menu
File
(
CONNECT
i
DISCONNECT
nie działają bowiem
w WISQL bezpośrednio, ale można ich użyć w linii poleceń w ISQL).
Tworzenie tabel
Gdy jesteśmy już podłączeni do bazy, możemy rozpocząć definiowanie obiektów.
Prawie każde pojęcie odnoszące się do relacyjnych baz danych może być
zaprezentowane za pomocą trzech tabel. Utwórzmy więc na początek trzy tabele,
dzięki którym będziemy mogli zrozumieć istotę omawianych zagadnień.
Posłużymy się poleceniem SQL:
CREATE TABLE
. Wprowadźmy odpowiednią
instrukcję korzystając z programu WISQL, aby utworzyć tabelę CUSTOMER:
512
Część III
CREATE TABLE CUSTOMER
(
CustomerNumber int
NOT
NULL,
LastName Char(30),
FirstName char(30),
StreetAddress char(30),
City char(20),
State char(2),
Zip char(10),
)
Dalej utwórzmy tabelę SALE :
CREATE TABLE SALE
(
SaleNumber int NOT
NULL,
SaleDate date,
CustomerNumber int
NOT
NULL,
ItemNumber int NOT
NULL,
Amount float
)
Po zbudowaniu tabeli SALE, pozostała do utworzenia tylko tablica ITEM:
CREATE TABLE ITEM
(
ItemNumber int
NOT
NULL,
Description char(30),
Price float
)
Tabele zewnętrzne
InterBase wykorzystuje pewne interesujące odmiany instrukcji
CREATE TABLE
.
Jedna z takich alternatywnych postaci umożliwia tworzenie tabel zewnętrznych
w stosunku do bazy, np.:
CREATE TABLE SALE_HISTORY EXTERNAL FILE
➥
C:\DATA\IB\SALEHIST.DAT
(
SaleNumber int
NOT
NULL,
SaleDate date,
CustomerNumber int
NOT
NULL,
ItemNumber int
NOT
NULL,
Amount float
)
Jeśli określony w poleceniu plik zewnętrzny nie istnieje, wtedy InterBase go
utworzy, w przeciwnym wypadku pozostanie nie zmieniony. Dzięki tej instrukcji
można odwoływać się do danych utworzonych poza InterBase - np. tabeli, w której
przechowuje się dane z innego systemu DBMS. Jeśli wyspecyfikowana struktura
Rozdział 17 Delphi w Interbase
513
tabeli w InterBase będzie zgodna ze strukturą danych zewnętrznych, można
skopiować wiersze z pliku zewnętrznego do wewnętrznej tabeli poleceniem:
INSERT...SELECT.
Kolumny obliczane (Computed columns)
Instrukcja
CREATE TABLE
w InterBase umożliwia także definiowanie kolumn
obliczanych. Poniżej przedstawiamy tabelę ITEMS, rozszerzoną o dodatkową
kolumnę obliczaną - GovernmentPrice :
CREATE TABLE ITEM
(
ItemNumber int
NOT
NULL,
Description char(30),
Price float
GovernmentPrice COMPUTED BY (Price-Price* .15)
)
W tym przypadku w kolumnie
GovermentPrice
uwzględniono 15% zniżki (w
stosunku do ceny w kolumnie
PRICE
) dla klientów rządowych. Ponieważ nie
został w niej podany typ danych, Interbase ustala go na podstawie typu z kolumn
wykorzystywanych do obliczeń. Definicje kolumn wykorzystywanych przez
kolumnę obliczaną muszą oczywiście - na liście kolumn polecenia
CREATE
TABLE -
wystąpić przed nią.
Podobne możliwości daje oczywiście instrukcja SELECT czy perspektywa.
Jednak w tym wypadku wpisujemy obliczenia bezpośrednio do definicji tabeli.
Jest to dobry sposób na ustalenie pewnych zależności obliczeniowych. Wtedy
bowiem aplikacje i inne obiekty SQL mogą po prostu pobierać obliczone wartości
z kolumny obliczanej.
Kolumny tablicowe (Array Columns )
W uzupełnieniu prostych typów danych InterBase pozwala na zdefiniowanie
kolumn, które są tablicami danych zadanego typu. Zdefiniowanie kolumny jako
tablicy pozwala traktować naturalnie zgrupowane elementy danych jako zbiór.
Przedstawiamy przykład zmodyfikowanej tabeli CUSTOMER, która zawiera
kolumnę tablicową:
CREATE TABLE CUSTOMERARRAY
(
CustomerNumber int
NOT
NULL,
LastName Char(30),
FirstName char(30),
StreetAddress char(30)[3],
City char(20),
514
Część III
State char(2),
Zip char(10),
)
Należy zwrócić uwagę na oznaczenie "[3]" po prawej stronie w definicji kolumny
StreetAddress. Zgodnie z podanym opisem będzie ona interpretowana jako
trzyelementowa tablica elementów typu char (30). Umożliwia więc zapisanie
poszczególnych części adresu w trzech osobnych liniach.
Oczywiście możemy też podać zakres indeksów tablicy, np.
CREATE TABLE CUSTOMERARRAY
(
CustomerNumber int
NOT
NULL,
LastName Char(30),
FirstName char(30),
StreetAddress char(30)[1:3],
City char(30),
State char(2),
Zip char(10),
)
Także w tym przypadku StreetAddress jest jednowymiarową, trójelementową
tablicą lecz indeksowaną liczbami od 1 do 3, a nie - jak w poprzednim przykładzie
- domyślnie od 0 do 2.
Tablice w kolumnach mogą być także wielowymiarowe ( największy wymiar 16).
Prezentuje to prosty przykład:
CREATE TABLE HOURLY_SAMPLES
(
SampleNo int
NOT
NULL,
SampleDate DATE
NOT
NULL,
WeeklySampleValues int[6,23]
)
Zdefiniowana powyżej kolumna
WeeklySampleValues
jest dwuwymiarową
tablicą, w której można gromadzić dane typu int, odnoszące się do każdej godziny,
przez cały tydzień.
Rozdział 17 Delphi w Interbase
515
OSTRZEŻENIE
Zanim wykorzystamy kolumny tablicowe, w naszych bazach danych warto
przeanalizować pewne konsekwencje implementacyjne. Kolumny tablicowe są
istotnie powtarzającymi się grupami. Mówiąc formalnie naruszają pierwszą postać
normalną. Wymagają także dodatkowego nakładu pracy przy podstawianiu (nie
można podstawić wartości kolumny tablicowej używając SQL), formatowaniu (np.
przy raportach) oraz przy zapytaniach. Jest także wiele ograniczeń nałożonych na
kolumny tablicowe, które nie dotyczą innych typów danych ( np. nie mogą być one
przekazywane lub zwracane przez procedury pamiętane). Warto ostrożnie
wyważyć wszystkie za i przeciw wprowadzaniu do struktury swojej bazy danych
takich, utrudniających manipulowanie danymi, elementów.
Specjalne wartości domyślne kolumn (special column defaults)
Oprócz definiowania stałych jako wartości domyślnych kolumn, InterBase
umożliwia użycie trzech specjalnych słów kluczowych, definiujących wartość
domyślną w kolumnie:
USER,TODAY and NOW
.
USER
oznacza nazwę
aktualnego użytkownika;
TODAY
- aktualną datę (analogicznie do funkcji Delphi -
Date
), natomiast
NOW
- aktualną datę i czas ( zgodna z funkcją
Now
w Delphi).
Poniżej przedstawiamy przykład zastosowania omówionych elementów:
CREATE TABLE REPORTLOG
(ReportUser VARCHAR(20) DEFAULT USER,
ReportData DATE DEFAULT "TODAY",
ReportDateTime DEFAULT "Now"
Warto zaznaczyć, że
"TODAY"
oraz
"NOW
" muszą być podane w cudzysłowie,
natomiast
USER
nie. Tekst
USER
podany w cudzysłowie będzie oznaczał wartość
"USER"
a nie nazwę aktualnego użytkownika.
Dodawanie i usuwanie kolumn
Dodając lub usuwając kolumnę z istniejącej tabeli korzystamy z instrukcji
ALTER
TABLE
. W przeciwieństwie do niektórych serwerów baz danych (np. Microsoft
SQL Server) InterBase Server umożliwia usuwanie kolumn. Składnia dla
dodawania kolumn wygląda następująco :
ALTER TABLE CUSTOMER
ADD PhoneNumber char(10)
natomiast dla usuwania:
ALTER TABLE CUSTOMER
DROP PhoneNumber
516
Część III
Oczywiście nie można rozszerzyć niepustej tabeli o nową kolumnę z zastrzeżeniem
NOT NULL.
Wtedy bowiem nie można byłoby odpowiednio uzupełnić
umieszczonych w niej wierszy
.
Więzy (constrains)
Więzy to mechanizmy, dzięki którym możemy ograniczyć rodzaj danych
umieszczanych w kolumnie lub powiązać je ze sobą. Pozwalają także określić
wartości domyślne dla kolumn. Definiuje się je dołączając odpowiednie instrukcje
do
CREATE TABLE
albo
ALTER TABLE
. Jednym z przykładów nakładania
więzów jest tworzenie klucza głównego:
ALTER TABLE CUSTOMER
ADD PRIMARY KEY (CustomerNumber)
W tym przykładzie zdefiniowaliśmy klucz główny dla tabeli CUSTOMER jako
pole
CustomerNumber
. Dzięki temu, na bazie pola
CustomerNumber
zostanie utworzony unikalny indeks. Oczywiście do zdefiniowania klucza
głównego tabeli nie można użyć kolumny, która zezwala na użycie wartości
NULL
.
Klucz obcy definiuje kolumnę w jednej tabeli, której wartości muszą znajdować
się innej tabeli. Nie określa jednoznacznie wierszy, tak jak jest to w przypadku
klucza głównego. Musi być jednak kluczem głównym lub unikalnym w tabeli, do
której się odnosi. Dodanie klucza obcego powoduje, że SQL Server buduje wtórny
indeks, bazujący na polu kluczowym. Oto odpowiedni przykład:
ALTER TABLE SALE
ADD CONSTRAINT INVALID_CUSTOMER_NUMBER FOREIGN KEY
➥
(CustomerNumber)REFERENCES CUSTOMER
W przykładzie zdefiniowano pole
CustomerNumber
w tabeli SALE, jako klucz
obcy odnoszący się do tej samej kolumny w tabeli CUSTOMER. Nałożone więzy
powodują, że aby numer klienta ( ang. customer number) mógł być wprowadzony
do tabeli SALE, musi najpierw istnieć w tabeli CUSTOMER. Także numery
używane w tabeli SALE nie mogą zostać usunięte z tabeli CUSTOMER.
Możliwość wymuszenia zależności pomiędzy dwiema tabelami przez
zadeklarowanie ich relacji w SQL jest nazywana deklaratywną spójnością
referencyjną (ang. declarative referential integrity). Ten termin znaczy po prostu,
że spójność danych w tabelach związanych relacją jest zapewniana przez
zdefiniowanie (lub zadeklarowanie) tej relacji w bazie, a nie przez kod programu.
Trzeci typ więzów pozwala ustalić zakres dozwolonych wartości dla
wprowadzanych danych np.
Rozdział 17 Delphi w Interbase
517
ALTER TABLE CUSTOMER
ADD CONSTRAINT INVALID_STATE CHECK (State in ('OK', 'AR',
➥
'MO'))
Zwracamy uwagę na celowość użycia elementów negacji w
konwencji
nazewniczej dotyczącej więzów. Oprogramowanie pracujące po stronie
użytkownika, które zapobiega naruszeniu więzów, ma dostarczać użytkownikowi
łatwą do poprawnego zinterpretowania informację. Gdyby informacja o błędzie
była sygnowana
VALID_STATE,
użytkownik mógłby się nie zorientować, co jest
przyczyną problemu. Dobrze dobrana nazwa ograniczenia, która służy jako
informacja zwrotna, będzie dla użytkownika wystarczającą wskazówką, w jaki
sposób próbowano naruszyć więzy. Dzięki temu nie musimy zastępować, swoimi
własnymi, komunikatów generowanych przez wyjątki obsługiwane w Delphi.
Testowanie funkcjonowania więzów
Każde więzy nałożone na bazę powinny zostać sprawdzone. Najlepszym testem
będzie próba ich naruszenia. Np. aby sprawdzić więzy
INVALID_STATE,
zdefiniowane powyżej, wprowadzamy następującą instrukcję w
programie
WISQL:
INSERT INTO CUSTOMER (CustomerNumber,State)
VALUES(123,'CA')
Ponieważ istniejące więzy pozwalają tylko na wprowadzenie wartości
'OK',
'AR' i 'MO',
to próba umieszczenia tego wiersza w bazie powinna wywołać
komunikat błędu.
Jeśli wprowadzone więzy nie funkcjonują, należy przede wszystkim sprawdzić,
czy zostały one poprawnie zapisane w bazie oraz przeanalizować ich definicje.
Tworzenie indeksów
Do budowania indeksów w InterBase SQL służy instrukcja
CREATE INDEX.
Jej
podstawowa składnia wygląda następująco:
CREATE INDEX SALE02 ON SALE (SaleDate)
SALE02
jest nazwą nowego indeksu,
SALE
- tabelą, dla której budujemy indeks
a
SaleDate -
kluczem indeksu. Należy zaznaczyć, że nazwa indeksu w systemie
InterBase musi być unikalna w bazie, w której jest on umieszczony.
Instrukcja
CREATE UNIQE INDEX
tworzy indeks zapewniający unikalność
klucza np:
CREATE UNIQE INDEX SALE01 ON SALE (SaleNumber)
518
Część III
Domyślnie indeks jest uporządkowany według rosnących wartości klucza.
InterBase pozwala także na uporządkowanie malejące, jeśli przy tworzeniu
indeksu użyjemy słowa kluczowego
DESCENDING
np.
CREATE DESCENDING INDEX SALE03 ON SALE (Amount)
Tak utworzony indeks przyspiesza wykonanie niektórych zapytań, np.:
SELECT * FROM SALE
ORDER BY Amount DESCENDING
Włączanie i wyłączanie indeksu
InterBase dysponuje użytecznym mechanizmem wyłączania indeksu. Przyspiesza
to wprowadzanie zmian do tabeli podstawowej. Wyłączony indeks włącza się (
i przy tym przebudowuje) dopiero po wprowadzeniu całej partii nowych danych.
Dzięki temu można szybko dodać do tabeli dużą liczbę wierszy, unikając
aktualizacji indeksu przy każdym nowym wierszu. Oto przykład wyłączenia
indeksu:
ALTER INDEX SALE02 INACTIVE
i ponownego włączenia:
ALTER INDEX SALE02 ACTIVE
Ponowne uaktywnienie indeksu wymusza jego przebudowę. Należy zaznaczyć, że
wyłączenie indeksu jest możliwe dopiero po jego całkowitym zwolnieniu - nie
może on być wykorzystywany przez klucz główny lub obcy. Aby wyłączyć indeks
używany przez więzy należy najpierw je usunąć.
Wprowadzanie danych
Instrukcja
INSERT
służy do wprowadzania danych do tabeli InterBase. Każde
wystąpienie klauzuli
VALUES
w instrukcji
INSERT
umożliwia dodanie jednego
wiersza danych. Można też wprowadzić od razu kilka wierszy, wybierając je
z innej tabeli. W poniższym przykładzie dodano dane do wszystkich naszych tabel.
Najpierw trzy wiersze do tabeli CUSTOMER (posłużymy się WISQL):
INSERT INTO CUSTOMER (CustomerNumber, LastName, FirstName,
➥
StreetAddress, City, State, Zip)
VALUES(1,'Doe','John','123 Sunnylane','Anywhere','MO',
➥
'73115')
INSERT INTO CUSTOMER (CustomerNumber, LastName, FirstName,
➥
StreetAddress, City, State, Zip)
Rozdział 17 Delphi w Interbase
519
VALUES(2,'Doe','Jane','123 Sunnylane','Anywhere','MO',
➥
'73115')
INSERT INTO CUSTOMER (CustomerNumber, LastName, FirstName,
➥
StreetAddress, City, State, Zip)
VALUES(3,'Philgates','Buck','57 Riverside','Reo','AR',
➥
'65803')
Teraz dodamy trzy wiersze do tabeli ITEM i:
INSERT INTO ITEM(ItemNumber, Description, Price)
VALUES(1001,'Zoso LP',13.45)
INSERT INTO ITEM(ItemNumber, Description, Price)
VALUES(1002,'White LP',67.90)
INSERT INTO ITEM(ItemNumber, Description, Price)
VALUES(1003,'Bad Co. LP',11.45)
W końcu dodamy cztery wiersze do tabeli SALE:
INSERT INTO SALE (SaleNumber, SaleDate, CustomerNumber,
➥
ItemNumber, Amount)
VALUES(101,'10/18/90',1,1001,13.45)
INSERT INTO SALE (SaleNumber, SaleDate, CustomerNumber,
➥
ItemNumber, Amount)
VALUES(102,'02/27/92',2,100,67.90)
INSERT INTO SALE (SaleNumber, SaleDate, CustomerNumber,
➥
ItemNumber, Amount)
VALUES(103,'05/20/95',3,1003,11.45)
INSERT INTO SALE (SaleNumber, SaleDate, CustomerNumber,
➥
ItemNumber, Amount)
VALUES(104,'11/27/97',4,1004,67.90)
Warto zauważyć, że nie trzeba uwzględniać wszystkich kolumn ani ich porządku
ustalonego przy tworzeniu tabeli ale lista podanych wartości musi być zgodna pod
względem liczby elementów i ich uporządkowania z listą kolumn w instrukcji
INSERT
, np.:
INSERT INTO ITEM (Price, ItemNumber)
VALUES(13.45, 1001)
520
Część III
Wartości specjalne
InterBase umożliwia korzystanie z domyślnych wartości specjalnych kolumn
USER
,
TODAY
i
NOW
, jak to zaprezentowano w poniższym przykładzie:
INSERT INTO REPORTLOG VALUES(USER, "TODAY", "NOW")
Instrukcja UPDATE
Instrukcja SQL
UPDATE
służy do modyfikacji danych w tabeli. Jej klauzula
WHERE
umożliwia wybór modyfikowanych wierszy. Oto odpowiedni przykład:
UPDATE CUSTOMER
SET Zip='65803'
WHERE City='SpringField'
Klauzula
WHERE
w
instrukcji
UPDATE
pozwala ograniczyć liczbę
modyfikowanych wierszy nawet do jednego ( zależnie od danych i warunku),
natomiast pomijając ją domyślnie zmodyfikujemy wszystkie rekordy:
UPDATE CUSTOMER
SET State='MO'
Poprawiając dane w kolumnie można korzystać z wartości innych kolumn danej
tabeli (także z tej kolumny). Załóżmy, że chcemy zwiększyć cenę każdego
produktu z tabeli ITEM o 7 %. Modyfikację tę można wykonać następująco:
UPDATE ITEM
SET Price=Price+(Price*.07)
Podobnie jak
INSERT
, instrukcja
UPDATE
umożliwia wykorzystanie specjalnych
wartości stałych kolumnowych InterBase, jak pokazano w przykładzie:
UPDATE SALE
SET Amount=Amount-(Amount*.25)
WHERE SaleDate>="TODAY"-30
Dzięki
TODAY
dokonano wyboru aktualizowanych wierszy. W ten sposób zostaną
zmodyfikowane wszystkie transakcje sprzedaży z ostatnich 30 dni.
Instrukcja DELETE
Dzięki instrukcji SQL
DELETE
mamy możliwość usuwania wierszy z tabeli. Np. -
aby opróżnić tabelę CUSTOMER wystarczy napisać:
DELETE FROM CUSTOMER
Rozdział 17 Delphi w Interbase
521
Instrukcja
DELETE
może też zawierać klauzulę
WHERE,
ograniczającą zakres
usuwanych wierszy. Oto odpowiedni przykład:
DELETE FROM CUSTOMER
WHERE LastName<>'Doe'
Kontrola Transakcji
Grupa powiązanych zmian w bazie nazywa się formalnie transakcją. Własne
transakcje inicjujemy za instrukcją
BEGIN TRANSACTION
. Instrukcja
COMMIT
służy do zachowania zmian dokonywanych w czasie transakcji, a instrukcja
ROLLBACK
pozwala na ich wycofanie. Obie odnoszą się tylko do zmian
dokonanych od chwili wywołania ostatniej instrukcji
COMMIT
.
ROLLBACK
nie
spowoduje wycofania zmian przyjętych poprzedzającymi ją instrukcjami
COMMIT.
InterBase WISQL otwiera transakcję automatycznie (używając odpowiednika
instrukcji
SET TRANSACTION
) przy pierwszym załadowaniu. Przy opuszczaniu
programu użytkownik jest proszony o
zatwierdzenie dokonanych zmian.
W każdym momencie pracy z WISQL można zapisać lub wycofać wprowadzone
zmiany, posługując się odpowiednio poleceniami
Commit Work
i
Rollback Work
z menu
File
.
UWAGA:
Oprócz zwykłej modyfikacji danych kontrola transakcji w InterBase uwzględnia
także instrukcje DDL (Data Definition Language). Za ich pomocą można
definiować i tworzyć obiekty bazy danych - takie, jak tabele i indeksy. Jeśli obiekt
zostanie utworzony wewnątrz transakcji, to w przypadku jej wycofania jest
usuwany. Ta zmiana jest natychmiast widoczna w całej bazie i na każdym
poziomie izolacji transakcji (Transaction Isolation Level-TIL), aktywnym w danej
chwili.
Instrukcja SELECT
Instrukcja SQL
SELECT
pobiera dane z określonych kolumn tabeli. Pozwoli nam
więc sprawdzić zawartość trzech naszych tabel. W
tym celu wykonajmy
trzykrotnie instrukcję o składni
SELECT * FROM tablename
, zamieniając za
każdym razem
tablename
na nazwę odpowiedniej tabeli (CUSTOMER
,
SALE
oraz ITEM). Jeśli wcześniej dodaliśmy proponowane dane, to każda z powstałych
w ten sposób tabel roboczych powinna mieć co najmniej trzy wiersze.
522
Część III
SELECT
* zwraca cała tabelę. Jeśli gwiazdkę zastąpimy listą nazw pól
oddzielonych przecinkami, to otrzymamy dane tylko z wybranych pól np.:
SELECT CustomerNumber, LastName, State FROM CUSTOMER
Wyrażenia kolumnowe
Instrukcja
SELECT
w SQL Server umożliwia podanie na liście kolumn nie tylko
samych nazw, ale także wyrażeń arytmetycznych, zbudowanych z wartości
kolumn, stałych i funkcji. Podajemy tutaj taki przykład zastosowania instrukcji
SELECT
, zwracającej rekordy z tabeli SALE, z wartością sprzedaży powiększoną
o $15 (opłatę za dostarczenie towaru):
SELECT SaleNumber, SaleDate, Amount+15 AmountPlusShipping
FROM SALE
Funkcje sumaryczne
Funkcje sumaryczne wykonują pewne obliczenia na zbiorach danych. Przykładami
takich funkcji są
COUNT, SUM, AVG, MIN
oraz
MAX
. Podamy teraz kilka
przykładów ich zastosowania:
SELECT COUNT(*) FROM CUSTOMER
To zapytanie daje w wyniku liczbę klientów w pliku.
SELECT MAX(Amount) FROM SALE
To z kolei podaje największą wartość sprzedaży w dolarach.
SELECT SUM(Amount) FROM SALE
Natomiast wynikiem tego zapytania jest całkowita wartość sprzedaży w dolarach.
Klauzula WHERE
Klauzula SQL
WHERE
umożliwia wybranie wierszy zwracanych przez instrukcję
SELECT
. Oto przykład:
SELECT * FROM CUSTOMER
WHERE State='MO'
W wyniku otrzymujemy tylko tych klientów, którzy mieszkają w Missouri (symbol
'MO').
SELECT * FROM CUSTOMER
WHERE StreetAddress LIKE '%Sunny%'
Rozdział 17 Delphi w Interbase
523
Rezultatem tego zapytania będą dane klientów, u których w
polu
StreetAddress
występuje słowo
Sunny
. Należy pamiętać, że porównanie
znaków uwzględnia różnice pomiędzy wielkimi i małymi literami. Zawsze jednak
można - do zamiany wszystkich liter w kolumnie i poszukiwanym wzorcu na duże
- zastosować funkcję
UPPER
.
Oto dalsze przykłady:
SELECT * FROM SALE
WHERE Amount>500
W wyniku otrzymujemy listę wszystkich transakcji, których kwota przekroczyła
500$.
SELECT
* FROM SALE
WHERE SaleDate BETWEEN '10/18/90' AND '05/20/95'
To zapytanie zwraca dane o wszystkich transakcjach dokonanych pomiędzy 18
października 1990 r. a 20. maja 1995 r. włącznie.
Złączenia (Joins)
Klauzula
WHERE
jest także wykorzystywana do łączenia tabel. Przy złączeniu
składnia klauzuli
WHERE
jest inna niż w podstawowej instrukcji
SELECT
.
Formułując odpowiednie warunki w klauzuli
WHERE
określamy dodatkowe tabele
w klauzuli
SELECT FROM
i łączymy pola będące ze sobą w relacji
.
Dobrze
ilustruje to następujący przykład:
SELECT CUSTOMER.CustomerNumber, SALE.Amount
FROM CUSTOMER, SALE
WHERE CUSTOMER.CustomerNumber=SALE.CustomerNumber
Zauważmy, że do klauzuli
FROM
włączono tabelę SALE oraz - do złączenia tabel
CUSTOMER i SALE poprzez pole
CustomerNumber
- użyto znaku równości.
Tabela wymieniona po lewej stronie znaku równości nazywana jest tabelą
zewnętrzną, a po prawej - wewnętrzną. Ze względu na ich pozycję w stosunku do
znaku równości często używa się określenia prawa i lewa. O złączeniu mówi się
lewa do prawej lub złączenie lewostronne. Jest to najczęściej używane złączenie
w zapytaniach SQL
Złączenia wewnętrzne(inner)a zewnętrzne (outer)
Wspomniane powyżej złączenie lewostronne formalnie nazywa się złączeniem
wewnętrznym. Złączenie wewnętrzne daje w wyniku tylko wiersze, dla których
spełniony jest warunek złączenia. Natomiast złączenie zewnętrzne uwzględnia też
wiersze, dla których warunek nie został spełniony. W złączeniu zewnętrznym, jeśli
nie znajdziemy pasujących wierszy w tabeli wewnętrznej, to kolumny z tabeli
524
Część III
wewnętrznej dołączane będą z wartością NULL. W zależności od rodzaju
złączenia zewnętrznego - tzn. left (lewostronne) lub right (prawostronne) -
wartościami
NULL
są odpowiednio uzupełniane wszystkie wiersze lewej albo
prawej tabeli, dla których nie był spełniony warunek złączenia.
Składnia złączenia w ANSI
Składnia ANSI definiuje specjalne operatory złączeń, umieszczane w klauzuli
FROM
. Składnię dla lewostronnego wewnętrznego złączenia zaprezentujemy na
przykładzie:
SELECT CUSTOMER.CustomerNumber, SALE.Amount
FROM CUSTOMER LEFT JOIN SALE
ON CUSTOMER.CustomerNumber=SALE.CustomerNumber
Należy zaznaczyć, że domyślnie przyjmuje się słowo kluczowe
INNER
(złączenie
wewnętrzne). Jak pokazano wcześniej, InterBase także używa takiej składni do
zdefiniowania lewostronnego wewnętrznego złączenia :
SELECT CUSTOMER.CustomerNumber, SALE.Amount
FROM CUSTOMER, SALE
WHERE CUSTOMER.CustomerNumber=SALE.CustomerNumber
Składnię dla lewostronnego złączenia zewnętrznego przedstawiamy w poniższym
przykładzie:
SELECT CUSTOMER.CustomerNumber, SALE.Amount
FROM CUSTOMER LEFT OUTER JOIN SALE
ON CUSTOMER.CustomerNumber=SALE.CustomerNumber
Dla określenia prawostronnego złączenia zewnętrznego wystarczy po prostu
zamienić
LEFT
na
RIGHT
. Oczywiście nie ma potrzeby rozróżniania
prawostronnego i
lewostronnego złączenia wewnętrznego, bowiem zgodnie
z podaną zasadą dają one ten sam wynik.
Złączenia wielopoziomowe (Multi-Tier Joins)
Złączenia wielopoziomowe korzystają z więcej niż dwóch tabel. Tabela A jest
złączana z tabelą B, a ta z kolei jest - z tabelą C. Rozważmy następujące zapytanie:
SELECT C.LastName, C.FirstName, I.Description, S.Amount
FROM CUSTOMER C,
SALE
S,
ITEM
I
WHERE C.CustomerNumber=S.CustomerNumber
and S.ItemNumber=I.ItemNumber
Rozdział 17 Delphi w Interbase
525
W tym zapytaniu
CUSTOMER
i
SALE
złączone są przez wspólny klucz - pole
CustomerNumber
a
SALE
i
ITEM
przez
ItemNumber
. W efekcie wszystkie
trzy tabele utworzą jeden zbiór wynikowy.
Samozłączenia (Self-Joins)
Oprócz złączenia z innymi tabelami, tabela może być złączana sama ze sobą. Ten
rodzaj złączenia nazywa się samozłączeniem. Rozważmy następujące zapytanie:
SELECT S.CustomerNumber, S.Amount,
(S.Amount/SUM(S2.Amount))*100 Precentage
FROM SALE S,
SALE
S2
WHERE S.CustomerNumber=S2.CustomerNumber
GROUP BY S.CustomerNumber, S.Amount
To zapytanie podaje kwoty wszystkich zakupów dokonanych przez klientów wraz
z rozbiciem procentowym w stosunku do całej kwoty zapłaconej przez danego
klienta. Budując takie zapytanie w ramach jednej instrukcji
SELECT
musimy
posłużyć się samozłączeniem. Najpierw jest tworzona i grupowana indywidualna
statystyka, a potem tabela SALE złączana jest ze sobą dla uzyskania łącznej kwoty
zakupów każdego klienta. To pozwala już obliczyć odpowiednią wartość
procentową, która wystąpi w wyniku zapytania.
Złączenia z użyciem innych operatorów (Theta Joins)
W warunku złączenia oprócz najczęściej stosowanego operatora porównania „jest
równe” (=) mogą wystąpić inne - zwykle jest to operator " jest różne" (<>).Podany
przykład prezentuje takie złączenie, pokazując jednocześnie wykorzystanie
samozłączenia:
SELECT C.CustomerNumber, S.Amount, (S2.Amount) OTHERS
FROM CUSTOMER C,
SALE
S,
SALE
S2
WHERE C.CustomerNumber=S.CustomerNumber
AND C.CustomerNumber<>S2.CustomerNumber
GROUP BY C.CustomerNumber, S.Amount
Faktycznie to zapytanie zawiera dwa złączenia. Pierwsze - pomiędzy
CUSTOMER
i
SALE
- aby uzyskać kwotę zakupu dla każdego klienta. Następne zaś (theta join)
- do obliczenia sumy kwot wszystkich zakupów, które nie zostały zrobione przez
klienta. Ponieważ stosuje się dwa różne typy złączenia do tej samej tabeli, więc
zapytanie, dla SALE, korzysta z dwóch różnych aliasów.
526
Część III
Złączenia pełne (Full Joins)
Pełne złączenie pozwala łączyć wiersze z dwóch tabel bez klauzuli
WHERE
. Jest
ono przydatne w przypadku łączenia tabel powiązanych relacjami. Jeśli żadna
z tabel nie zawiera wszystkich interesujących nas wierszy, wtedy łącząc je poprzez
zewnętrzne pełne złączenie otrzymamy wszystkie wiersze. Jest to po prostu
kombinacja złączenia prawo i lewostronnego. Oto przykład:
SELECT DISTINCT C.CustomerNumber, S.SaleNumber
FROM CUSTOMER C FULL JOIN SALE S
ON 1=1
UWAGA
Powyższy prosty kod zawiera tożsamość 1=1, bowiem analizator składniowy
InterBase wymaga klauzuli ON we wszystkich złączeniach, nawet pełnych, dla
których praktycznie nie jest to konieczne.
Iloczyn kartezjański
Iloczyn kartezjański jest wynikiem połączenia wszystkich wierszy z jednej tabeli
ze wszystkimi wierszami innej. Zwykle taki zbiór otrzymujemy przypadkowo, gdy
opuścimy lub niewłaściwie sformułujemy warunek złączenia. Oto odpowiedni
przykład:
SELECT SALE.SaleNumber, ITEM.ItemNumber
FROM SALE, ITEM
ORDER BY SaleNumber, ItemNumber
Dla tabel z dużą ilością danych obliczanie iloczynu kartezjańskiego może
zablokować serwer na tak długo, że konieczne będzie zamknięcie połączenia albo
zatrzymanie realizacji zapytania. W niektórych systemach zatrzymanie procesu
jest możliwe tylko przez zrestarowanie komputera serwera. Należy więc unikać
tworzenia iloczynu kartezjańskiego - zwłaszcza wtedy, gdy pracujemy z dużymi
tabelami.
Podzapytania (Subqueries)
Podzapytanie to instrukcja
SELECT
umieszczona w klauzuli
WHERE
innej
instrukcji
SELECT
. Ogólnie podzapytania to zapytania zwracające dane, które nie
są końcowym wynikiem, lecz będą wykorzystywane w dalszej części instrukcji.
Oto przykład:
SELECT * FROM CUSTOMER
WHERE CustomerNumber IN (SELECT CustomerNumber FROM SALE)
Rozdział 17 Delphi w Interbase
527
Pewne operatory funkcjonują tylko w podzapytaniach. S¹ to
ANY, ALL, SOME,
EXISTS i SINGULAR
. Chociaż słowo kluczowe
ALL
jest także używane
w instrukcji
SELECT,
to jako operator występuje tylko w podzapytaniach.
GROUP BY
Ponieważ SQL jest językiem zapytań zorientowanym na przetwarzanie zbiorów
(set - oriented), instrukcje grupujące dane są jego integralną częścią. Często osoby
tworzące bazy danych i pracujące z innymi systemami DBMS uważają takie
podejście za nietypowe, bowiem są przyzwyczajone do procedur przetwarzających
dane wiersz po wierszu. W wielu programach działających na komputerach PC,
aby zebrać dane sumaryczne iteruje się pętlę przez całą tabelę. Podejście SQL jest
zupełnie inne. Niekiedy pojedyncza instrukcja SQL zastępuje 10 lub nawet 50 linii
kodu programu napisanego w dBase. Jest to możliwe dzięki instrukcji
SELECT
,
klauzuli
GROUP BY
oraz funkcjom sumarycznym SQL. Klauzula
GROUP BY
pozwala definiować grupy wyjściowe wierszy, do których odnoszą się funkcje
sumaryczne użyte w klauzuli
SELECT
. Następny przykład pokazuje zastosowanie
klauzuli
GROUP BY
:
SELECT CUSTOMER.CustomerNumber, SUM(SALE.Amount) TotalSale
FROM CUSTOMER, SALE
WHERE CUSTOMER.CustomerNumber=SALE.CustomerNumber
GROUP BY CUSTOMER.CustomerNumber
Rezultatem tego zapytania jest lista wszystkich klientów wraz z sumaryczną
wartością transakcji każdego klienta.
Oczywiście nasuwa się pytanie, które pola połączyć klauzulą
GROUP BY
. InterBase
Server oraz ANSI SQL wymagają, aby klauzula
GROUP BY
zawierała wszystkie
kolumny wymienione na liście kolumn instrukcji
SELECT
, które nie są funkcjami
sumarycznymi. W przypadku korzystania z klauzuli
GROUP BY
na liście kolumn
instrukcji
SELECT
powinna znaleźć się choć jedna funkcja sumaryczna. Jeśli ten
warunek nie jest spełniony, InterBase Server uniemożliwia zastosowanie klauzuli
GROUP BY.
HAVING
Klauzula
HAVING
w instrukcji
SELECT
służy do selekcji wierszy zwracanych
przez klauzulę
GROUP BY
. Zależność między klauzulami
GROUP BY
oraz
HAVING
przypomina relację pomiędzy instrukcją
SELECT
a klauzulą
WHERE
.
Klauzula
HAVING
działa podobnie jak klauzula
WHERE,
ale na zbiorze
wynikowym, a nie na wierszach w tabelach zapytania.
Ogólnie
HAVING
jest mniej efektywne niż
WHERE
, ponieważ selekcjonuje zbiór
wynikowy, po tym jak zostanie on zgrupowany, zaś
WHERE
czyni to najpierw. Są
528
Część III
jednak sytuacje, w których korzystamy z klauzuli
HAVING
. Przeanalizujmy
poniższy przykład:
SELECT CUSTOMER.LastName, COUNT(*) NumberWithName
FROM CUSTOMER
GROUP BY CUSTOMER.LastName
HAVING COUNT(*)>1
HAVING
wykorzystujemy przy selekcji wierszy otrzymywanych przez zapytanie
bazujące na funkcji sumarycznej. Zastosowanie
WHERE
jest niemożliwe, bowiem
potrzebna informacja nie istnieje - aż do momentu wykonania zapytania
i obliczenia odpowiednich wartości.
ORDER BY
Klauzula ta służy do odpowiedniego posortowania zbioru wynikowego. Oto
przykład:
SELECT * FROM CUSTOMER
ORDER BY State
Bez
ORDER BY
nie ma żadnej gwarancji, że wiersze zostaną odpowiednio
posortowane. Wtedy nawet ta sama instrukcja
SELECT,
wywołana dwukrotnie,
może stworzyć zbiory wynikowe, za każdym razem uporządkowane inaczej.
Aliasy kolumn
W kilku prezentowanych wcześniej przykładach używaliśmy logicznych nazw
kolumn, w których umieszczaliśmy wyniki obliczeń funkcji sumarycznych, jak
COUNT()
czy
SUM().
Etykiety tego typu nazywamy aliasami kolumn. Dzięki
nim zapytanie i jego wynik są czytelniejsze. W InterBase SQL alias kolumny
umieszcza się bezpośrednio po prawej stronie odpowiadającej mu kolumny - na
liście pól instrukcji
SELECT
. Na przykład w podanym poniżej zapytaniu aliasem
wartości funkcji sumarycznej
COUNT()
jest etykieta
NumberWithName:
SELECT CUSTOMER.LastName, COUNT(*) NumberWithName
FROM CUSTOMER
GROUP BY CUSTOMER.LastName
HAVING COUNT(*)>1
Aliasy można stosować nie tylko dla funkcji sumarycznych, lecz dla każdej
pozycji w zbiorze wynikowym, np.:
SELECT CUSTOMER.LastName LName, COUNT(*) NumberWithName
FROM CUSTOMER
GROUP BY CUSTOMER.LastName
Rozdział 17 Delphi w Interbase
529
W tym zapytaniu zastąpiliśmy w zbiorze wynikowym nazwę kolumny
LastName
przez jej alias
LName
. Aliasów nie można jednak stosować w innych częściach
zapytania, takich jak klauzula
WHERE
czy
GROUP BY
. Wtedy musimy posłużyć
się nazwą kolumny lub wartością.
Aliasy tabel
Składnia instrukcji
SELECT
umożliwia korzystanie ze skrótu, zamiast podawania
pełnej nazwy tabeli. Nazywamy go aliasem tabeli. Jest definiowany w klauzuli
FROM
instrukcji
SELECT
i
umieszczany bezpośrednio po prawej stronie
właściwej nazwy, tak jak w poniższym przykładzie:
SELECT C.LastName, COUNT(*) NumberWithName
FROM CUSTOMER C
GROUP BY C.LastName
Może dziwić fakt, że alias występuje na liście pól instrukcji
SELECT,
czyli zanim
go zdefiniowano. Powinniśmy jednak pamiętać, że odwołanie się do obiektów
bazy danych musi nastąpić przed wykonaniem zapytania
Perspektywy (Views)
Perspektywa SQL składa się z instrukcji
SELECT
, którą można traktować jak
tabelę i, w
dalszej kolejności, zapytania z
innymi instrukcjami
SELECT
.
W pewnych sytuacjach może być ona parametrem instrukcji
INSERT, DELETE
i UPDATE
. Perspektywa nie zapamiętuje jednak żadnych danych. Jest ona tylko
pewną konstrukcją logiczną. Można o niej myśleć jako o małym programie
w języku SQL, uruchamianym przy każdym zapytaniu stworzonym na jej bazie.
Jest podobna do procedury wyboru - omówionej w następnej sekcji " Procedury
pamiętane". Gdy jest wykonywane zapytanie posługujące się perspektywą,
optymalizator zapytań tworzy najpierw perspektywę, łączy ją z właściwym
zapytaniem i optymalizuje obie czynności wykonując je jako jedno zapytanie.
Perspektywy w SQL tworzy się instrukcją
CREATE VIEW,
np.:
CREATE VIEW MOCUSTOMERS AS
SELECT *
FROM CUSTOMER
WHERE State='MO'
Perspektywa może zostać wykorzystana w zapytaniu jak zwykła tabela - np.:
SELECT * FROM MOCUSTOMERS
Mimo braku klauzuli
WHERE w
zapytaniu, zbiór wynikowy uwzględnia
oczywiście klauzulę
WHERE,
podaną w definicji perspektywy.
530
Część III
Instrukcja
SELECT,
tworząca perspektywę, ma prawie takie same możliwości
jak podstawowa instrukcja
SELECT
. Nie może tylko korzystać z klauzuli
ORDER
BY
. To ograniczenie odnosi się do wszystkich omawianych w tym rozdziale
serwerów baz danych.
Dodatkowe ograniczenia obowiązują przy tworzeniu perspektyw
modyfikowalnych.
Instrukcja
SELECT
może dotyczyć tylko jednej tabeli lub innej
modyfikowalnej perspektywy.
Kolumny, które nie zostały włączone do perspektywy, muszą dopuszczać
wartość
NULL
, aby było możliwe wykonanie instrukcji
INSERT.
Instrukcja
SELECT
użyta w
definicji perspektywy nie może zawierać
podzapytań, predykatu
DISTINCT
, klauzuli
HAVING
, funkcji sumarycznych,
złączeń, funkcji użytkownika oraz procedur pamiętanych.
Tworząc modyfikowalną perspektywę, można ograniczyć poprawianie lub
dodawanie wierszy do wartości spełniających zadane przez nią warunki. Serwer
blokuje wtedy wszelkie zmiany wykraczające poza perspektywę. W tym celu
wystarczy - w instrukcji
CREATE VIEW
- dodać klauzulę
WITH CHECK
OPTION
, np.:
CREATE VIEW MOCUSTOMERS AS
SELECT *
FROM CUSTOMER
WHERE State='MO'
WITH CHECK OPTION
W ramach zdefiniowanej perspektywy będzie można dodawać tylko takie rekordy,
dla których
State
=
'MO'
. Nie ma także możliwości zmiany wartości pola
State
Procedury pamiętane (Stored Procedures)
Procedury pamiętane są kompilowanymi programami SQL (często zawierającymi
wiele instrukcji SQL), przechowywanymi wraz z innymi obiektami bazy danych.
W InterBase występują dwa podstawowe rodzaje tych procedur: procedury
wyboru (ang. select procedures) i procedury wykonywalne (ang. executable
procedures). Procedur wyboru można użyć zamiast tabel lub perspektyw
w instrukcji SELECT. Ponadto muszą one zwracać wynik ( jedną lub wiele
wartości). Natomiast w przypadku procedur wykonywalnych nie jest to konieczne.
Procedury pamiętane tworzy się instrukcją
CREATE PROCEDURE
. Poniżej
przedstawiamy przykład procedury w InterBase:
Rozdział 17 Delphi w Interbase
531
CREATE PROCEDURE listcustomers RETURNS (LastName CHAR(30),
➥
FirstName CHAR(30))
AS BEGIN
FOR SELECT LastName, FirstName FROM CUSTOMER
INTO :LastName, :FirstName
DO SUSPEND;
END
Dla procedury z parametrami składnia instrukcji ulega drobnej modyfikacji:
CREATE PROCEDURE listcustomersbystate (LastName CHAR(30),
➥
FirstName CHAR(30))
RETURNS (LastName CHAR(30), FirstName CHAR(30))
AS BEGIN
FOR SELECT LastName, FirstName FROM CUSTOMER
WHERE State= :State and LastName like :LastNameMask
INTO :LastName, :FirstName
DO SUSPEND;
END
Procedury wyboru (Select Procedures)
W procedurze wyboru rodzaj zwracanych danych definiuje się po słowie
kluczowym
RETURNS
(jak to pokazano w poniższym przykładzie):
CREATE PROCEDURE listcustomers (State CHAR(2))
RETURNS (LastName CHAR(30))
AS
BEGIN
FOR SELECT LastName
FROM CUSTOMER
WHERE State= :State
INTO :LastName
DO
SUSPEND;
END
Warto zwrócić uwagę, że w powyższym przykładzie konstrukcja
FOR
SELECT ... DO
powoduje przekazanie wierszy wynikowych do miejsca
wywołania. Instrukcja
SUSPEND
przerywa wykonanie procedury - aż do chwili,
gdy wywołanie zażąda następnego wiersza. Wartości przypisane parametrom
wyjściowym zostają przekazane przed przerwaniem realizacji procedury.
Procedury wykonywalne
Procedury wykonywalne różnią się od procedur wyboru tym, że nie wymagają
użycia instrukcji
RETURNS
. Oto przykład procedury wykonywalnej w InterBase
SQL:
532
Część III
CREATE PROCEDURE insertitem (ItemNumber Integer, Description
➥
Char(30), Price Float)
AS
BEGIN
INSERT INTO ITEM (ItemNumber, Description, Price)
VALUES (:ItemNUmber, :Description, :Price);
END
Skrypty
Instrukcje Data Definition Language (DDL), zawierające procedury pamiętane,
warto umieszczać w specjalnych plikach zwanych skryptami. Są to zwykłe pliki
tekstowe, zawierające ciągi poleceń SQL, więc można ich tworzyć korzystając
z dowolnego edytora tekstów. Warto tu przypomnieć, że powinny one zawierać
wszystkie niezbędne instrukcje
CONNECT i SET TERM
. Skrypt SQL można
wykonać, wybierając polecenie
Run an
SQL Script
z menu
File
programu
WISQL. Przykład zawiera listing 17.1
Listing 17.1. Procedura pamiętana w
skrypcie SQL.
CONNECT "C:\DATA\IB\SALES.GDB" USER "SYSDBA" PASSWORD
➥
"masterkey";
SET TERM ^ ;
CREATE PROCEDURE GET_CUSTOMER_BY _STATE (State CHAR(2))
RETURNS (LastName CHAR(30))
AS
BEGIN
FOR SELECT LastName
FROM
CUSTOMER
WHERE State= :State
INTO
:LastName
DO
SUSPEND;
END^
SET TERM ; ^
EXIT;
Instrukcja
CONNECT
na początku pliku ustala połączenie z bazą danych.
Następnie
SET TERM
zmienia znak kończący instrukcje SQL - z domyślnego
średnika (;) na (^). Zmiana ta powoduje, że instrukcje zawarte w przechowywanej
procedurze nie są wykonywane, gdy jest ona dopiero tworzona - za pomocą
CREATE PROCEDURE
. Instrukcja
SET TERM
przywraca domyślny znak
zakończenia instrukcji.
Rozdział 17 Delphi w Interbase
533
Uruchamianie procedur pamiętanych
Procedury pamiętane InterBase uruchamiamy instrukcją
EXECUTE PROCEDURE
.
Jej składnia przedstawia się następująco:
EXECUTE PROCEDURE procedurename parameters
W miejsce
procedurename
należy podać nazwę uruchamianej procedury,
a w miejsce
parameters
jej parametry np.:
EXECUTE PROCEDURE GET_CUSTOMER_BY_STATE "MO"
Ta instrukcja uruchamia procedurę pamiętaną
GET_CUSTOMER_BY_STATE
z parametrem "MO".
Wyjątki (Exceptions)
Wyjątki są mechanizmami przekazującymi aplikacji określone przez programistę
sygnały o błędach, jakie powstały w czasie wykonania procedur pamiętanych
i procedur zdarzeń. W
InterBase wyjątki definiujemy instrukcją
CREATE
EXCEPTION,
natomiast instrukcja
EXCEPTION
służy do ich przechwycenia
i wygenerowania odpowiedniego komunikatu. W listingu 17.2 pokazano sposób
definiowania wyjątku i jego wykorzystania w procedurze przechowywanej.
Listing 17.2. Wyjątek InterBase w
procedurze pamiętanej.
CONNECT "C:\DATA\IB\SALES.GDB" USER "SYSDBA" PASSWORD
➥
"masterkey";
CREATE EXCEPTIONSALE_TOO_LOW;
"The sale amount is to low. Only purchases of $1 or more are
allowed";
SET TERM ^ ;
CREATE PROCEDURE insertsale(SaleNumber int, SaleDate date,
➥
CustomerNumber int, ItemNumber int, Amount float)
AS
BEGIN
If
(:Amount<1)THEN
EXCEPTION
SALE_TOO_LOW;
ELSE
INSERT
INTO
SALE
VALUES (:SaleNumber, :SaleDate, :CustomerNumber,
➥
:ItemNumber, :Amount);
END^
SET TERM ;
EXIT;
534
Część III
Ta procedura ustala minimalną kwotę sprzedaży - 1$. Instrukcja wyboru
IF...THEN
sprawdza, czy kolumna
Amount
w wierszu przygotowanym do
wprowadzenia zawiera kwotę mniejszą niż 1$. Natomiast instrukcja
EXCEPTION
została użyta do zgłoszenia wyjątku
SALE_TOO_LOW
, gdy kwota ta jest mniejsza
niż 1 $.
Procedury zdarzeń (Triggers)
Procedury zdarzeń są to (podobnie jak procedury pamiętane) podprogramy SQL,
uruchamiane gdy dane z zadanej tabeli są wprowadzane, modyfikowane lub
usuwane. Procedura zdarzenia jest skojarzona ze specyficzną operacją
dokonywaną na tabeli (zdarzeniem): wstawianiem wiersza, modyfikacją lub
usuwaniem. Oto odpowiedni przykład w InterBase SQL:
CREATE TRIGGER SALEDelete FOR CUSTOMER
BEFORE DELETE
AS
BEGIN
DELETE FROM SALE
WHERE
CustomerNumber=OLD.CustomerNUmber;
END
Ta procedura zdarzenia likwiduje transakcje danego klienta w tabeli SALE, gdy
jego rekord jest usuwany z tabeli CUSTOMER (usuwanie kaskadowe).Operacja
usuwania z tabeli uruchamia "kaskadowy" proces eliminacji odpowiednich danych
w innych tabelach, połączonych z nią za pomocą wspólnego klucza.
Należy zwrócić uwagę na użycie zmiennej kontekstowej
OLD. O
dwołuje się ona
do bieżącej wartości kolumny w danym wierszu przed wykonaniem operacji
UPDATE
lub
DELETE
. Zmienna kontekstowa
NEW
odwołuje się do nowej
wartości, która ma być wprowadzona realizacją
INSERT
lub
UPDATE
.
Warto też odnotować wystąpienie słowa kluczowego
BEFORE
. Procedura
zdarzenia może być uruchomiona przed (before) lub po (after)
INSERT,
UPDATE, DELETE
.
W InterBase z danym zdarzeniem można skojarzyć do 32768 procedur zdarzenia.
Dlatego w sytuacji, gdy jedno zdarzenie aktywuje wiele procedur obsługi,
powinno się ustalić kolejność ich uruchamiania. Służy do tego słowo kluczowe
POSITION,
które zastosowano w poniższym przykładzie:
CREATE TRIGGER SALEDelete FOR CUSTOMER
BEFORE DELETE
POSITION 0
AS
BEGIN
DELETE FROM SALE
Rozdział 17 Delphi w Interbase
535
WHERE
CustomerNumber=OLD.CustomerNUmber;
END
Numery mogą przyjmować wartości od 0 do 32767, a procedura o niższym
numerze wykonywana jest najpierw. Procedury zdarzeń z tym samym numerem
uruchamiane są w porządku losowym.
Funkcje
InterBase umożliwia rozszerzanie swojej odmiany SQL - przez dołączenie
własnych funkcji. Zasadniczo można zdefiniować własne funkcje w zewnętrznej,
współdzielonej bibliotece funkcji ( w Windows jest to DLL) i udostępniać je za
pomocą instrukcji
DECLARE EXTERNAL FUNCTION
. Przy takim podejściu są
one przechowywane w
naszej bazie danych i
zachowują się jak funkcje
wbudowane. Oto przykład prostej biblioteki DLL, w której zdefiniowano dwie
funkcje.
library IBUDFLIB;
uses
SysUtils,
Classes;
function MaxInt(var Int1, Int2 :Integer) : Integer; far cdecl
➥
export;
begin
if (Int1>Int2) then Result:=Int1
else
Result:=Int2;
end;
function MinInt(var Int1, Int2 :Integer) : Integer; far cdecl
➥
export;
begin
if (Int1<Int2) then Result:=Int1
else
Result:=Int2;
end;
exports
MaxInt,
MinInt;
begin
end.
A teraz przedstawimy przykład skryptu SQL, który udostępni te funkcje dla
InterBase:
536
Część III
CONNECT "C:\DATA\IB\SALES.GDB" USER SYSDBA PASSWORD
➥
masterkey;
DECLARE EXTERNAL FUNCTION MAXINT
INTEGER, INTEGER
RETURNS INTEGER BY VALUE
ENTRY_POINT "MaxInt" MODULE_NAME "IBUDFLIB.DLL";
DECLARE EXTERNAL FUNCTION MININT
INTEGER, INTEGER
RETURNS INTEGER BY VALUE
ENTRY_POINT "MinInt" MODULE_NAME "IBUDFLIB.DLL";
EXIT;
Teraz podamy kilka uwag praktycznych dla osób chcących wzbogacać InterBase
SQL o własne funkcje,:
InterBase pobiera funkcje z biblioteki DLL, posługując się nazwą funkcji
i rozróżniając w niej wielkie i małe litery. Jeśli nie zadba się o poprawne użycie
wielkich i małych liter w nazwie funkcji, w instrukcji
DECLARE EXTERNAL
FUNCTION
InterBase nie będzie w stanie jej zlokalizować.
Parametry są przekazywane przez zmienną (zwracamy uwagę na słowo
kluczowe VAR na liście parametrów funkcji MaxInt i MinInt) a nie przez
wartość. Dlatego zadeklarowane funkcje powinny spodziewać się wskaźników
do zmiennych, a nie ich wartości.
InterBase przyjmuje, że funkcja jest zdefiniowana z wywołaniem
cdecl
. Nie
należy więc używać wywołań
far pascal
,
stdcall
,
or
oraz
safecall,
bowiem nie będą funkcjonowały poprawnie.
Wyniki mogą być zwracane przez zmienną lub przez wartość. Jeśli wybierzemy
przekazanie przez zmienną, należy pamiętać, aby zwrócić wskaźnik do wartości
a nie samą wartość, inaczej bowiem InterBase zatrzyma się próbując wykonać
funkcję.
Zdefiniowane funkcje
MinInit
i
MaxInit
możemy przetestować, wykonując
następującą instrukcję w WISQL:
SELECT MaxInt(3,5) FROM CUSTOMER;
Składnia InterBase SQL wymaga użycia nazwy co najmniej jednej tabeli
w instrukcji
SELECT
. W
podanym przykładzie wykorzystaliśmy tabelę
CUSTOMER. Funkcja
MaxInt
będzie wykonywana dla każdego wiersza tabeli,
więc uruchamiając podany kod możemy otrzymać wielokrotnie powtarzaną
wartość.
Rozdział 17 Delphi w Interbase
537
WSKAZÓWKA
Możemy utworzyć jednowierszową tabelę InterBase, którą wykorzystamy do
tworzenia zapytań nie odnoszących się do tabel. Tabela taka może być
wykorzystana w instrukcji
SELECT
wtedy, gdy praktycznie nie korzystamy
z danych z żadnej tabeli. Oto przykład utworzenia i użycia takiej tabeli (każde
polecenie należy wykonać osobno w WISQL):
CREATE TABLE DUAL
(A CHAR(1));
INSERT INTO DUAL (A) VALUES ('A');
SELECT 22/7 AS ApproximatePI
FROM DUAL;
Deklarację zewnętrznej funkcji można usunąć z bazy za pomocą instrukcji
DROP
EXTERNAL FUNCTION.
Kursory (Cursors)
Koncepcja kursorów jest wynikiem zastosowania zorientowanej na zbiory
koncepcji SQL w odniesieniu do przetwarzania wierszowego. Pozwalają one
pracować z jednym wierszem w danej chwili. Ponieważ ich tworzenie i obsługa
jest w BDE zautomatyzowana, więc w zasadzie nie ma potrzeby tworzenia
własnych kursorów. Jednak niekiedy mogą się one okazać użyteczne
w procedurach pamiętanych.
Są cztery podstawowe operacje wykonywane na kursorze: deklarowanie (ang.
declare), otwarcie (ang. open), pobranie (ang. fetch), zamknięcie (ang. close).
Można go też wykorzystać do zmodyfikowania lub usunięcia pojedynczego
wiersza tabeli.
UWAGA
Nie można wykonywać tych instrukcji samodzielnie w programie WISQL. Są one
przeznaczone do użycia tylko w procedurach pamiętanych.
Deklaracja kursora składa się z
instrukcji
SELECT
i
(dla kursorów
modyfikowalnych) - z listy modyfikowalnych kolumn. Oto przykład:
DECLARE CUSTOMER_SELECT CURSOR
FOR SELECT * FROM CUSTOMER
Zanim uzyskamy dostęp do wierszy przy użyciu kursora, musi on być najpierw
otwarty. Aby przygotować zapytanie zapisane w definicji kursora należy użyć
instrukcji
OPEN
:
538
Część III
OPEN CUSTOMER_
SELECT
Samo
OPEN
nie wprowadza jednak wierszy do aplikacji klienta. Niezbędna jest
instrukcja
FETCH
:
FETCH CUSTOMER_SELECT
W ten sposób otrzymamy jeden wiersz ze zbioru wynikowego kursora. Każde
następne
FETCH
pozwala uzyskać następny wiersz w zbiorze. InterBase posiada
tylko kursory jednokierunkowe. Aby przejść do wierszy poprzednich należy
zamknąć (
CLOSE
) i ponownie otworzyć (
OPEN
) kursor.
UWAGA
Mimo że InterBase nie obsługuje kursorów dwukierunkowych, można ich użyć
w aplikacji stworzonej w Delphi. BDE emuluje bowiem dwukierunkowy kursor na
poziomie programowym - bez względu na to, czy serwer baz danych go obsługuje.
Dlatego można przewijać w obie strony w obiektach
TDataSets
takich jak
TQuery i TTable
.
Wiersze zwracane przez modyfikowalny kursor mogą być zmieniane za pomocą
specjalnych wersji instrukcji
UPDATE i DELETE
jak np.:
DECLARE CUSTOMER_UPDATE CURSOR
FOR SELECT *FROM CUSTOMER
FOR UPDATE OF LastName
UWAGA
Należy się upewnić, czy na liście kolumn klauzuli
FOR UPDATE OF
umieszczono tylko te kolumny, które mają być rzeczywiście aktualizowane.
Umieszczenie na tej liście innych pól niepotrzebnie wiąże zasoby serwera.
Aby zmodyfikować lub usunąć bieżący wiersz modyfikowalnego kursora, należy
posłużyć się wyrażeniem
WHERE CURRENT OF
cursorname
, jak to pokazano
w poniższym przykładzie:
UPDATE CUSTOMER
SET LastName="Cane"
WHERE CURRENT OF CUSTOMER_UPDATE
lub:
DELETE FROM CURSOR
WHERE CURRENT OF CUSTOMER_UPDATE
Rozdział 17 Delphi w Interbase
539
Gdy kończymy pracę z kursorem należy go zamknąć (instrukcją
CLOSE)
.
Zamknięcie kursora zwalnia wszystkie zasoby systemowe, których używał. Oto
przykład:
CLOSE CUSTOMER_UPDATE
Administrowanie bazą Interbase
Zwykle osoby tworzące bazy danych wykonują tylko najprostsze czynności
administracyjne. W naszych rozważaniach ograniczymy się do podstawowych,
niezbędnych narzędzi administrowania bazą. Oczywiście skomplikowane
problemy najlepiej pozostawić do rozwiązania administratorom bazy danych.
Archiwizacja i odzyskiwanie danych
Podstawową troską projektantów, administratorów i użytkowników baz danych
jest bezpieczeństwo danych. Musimy więc przede wszystkim zabezpieczyć bazę na
wypadek uszkodzenia dysków, na których jest umieszczona. W tym celu tworzymy
kopie bazy danych. Nie wystarczy jednak po prostu skopiować pliki z danymi.
Część z nich może być bowiem akurat w użyciu i znajdować się częściowo
w pamięci serwera. Dlatego zwykła kopia, sporządzona narzędziami systemu
operacyjnego, nie daje prawdziwego obrazu stanu bazy w danym momencie i nie
gwarantuje jej spójności. Dopiero Server Manager zapewni nam właściwą
archiwizację danych. Postępujemy następująco :
1. Uruchamiamy program Server Manager
2. Klikamy
File\Server Login
i łączymy się z serwerem.
3. Wybieramy
Tasks\Backup
- aby wyświetlić okno dialogowe do archwizacji
4. W lini
Database Path
wpisujemy pełną ścieżkę dostępu do danych, które
chcemy archiwizować. Gdy archiwizujemy odległą bazę na platformie innej niż
NetWare, wybieramy
Remote
.
5. Wpisujemy nazwę pliku lub urządzenia, którego chcemy użyć, w linii
Backup
File or
Device
. W Windows NT można wpisać domyślne urządzenie taśmowe
jako
\\.\tape0
.
6. Aby rozpocząć archiwizację klikamy
OK
. Sytuację ilustruje rys 17.1.
Odzyskiwanie zarchiwizowanych danych jest po prostu odwróceniem poprzednio
opisanego procesu. Wybieramy opcję
Restore
z menu
Tasks
, określając swoją
kopię jako
Restore Source
, a swoją bazę jako
Restore Destination
540
Część III
Zarządzanie kontami użytkowników
Aby móc łączyć się z bazą danych, trzeba być zarejestrowanym jako użytkownik
i znać swoje hasło. Do utworzenia konta nowego użytkownika można użyć
polecenia
Task\User Security
z menu programu Server Manager. Postępujemy
wtedy zgodnie z poniższą procedurą:
1. Uruchamiamy program Serwer Manager.
2. Klikamy
File\Sever Login
i łączymy się z serwerem
3. Wybieramy
Tasks\User Security
, aby wyświetlić okienko dialogowe InterBase
Security.
4. Klikamy przycisk
Add User
.
5. Wprowadzamy nazwę i hasło dla nowego użytkownika.
6. Klikamy
OK
, aby dodać nowego użytkownika. Ilustruje to rysunek 17.2
Rysunek 17.1
Wykorzystanie
Server Managera
do archiwizacji
bazy danych
Rysunek 17.2
Dodawanie nowego
uzytkownika
InterBase
w programie Server
Manger. Okno
dialogowe User
Configuration
Rozdział 17 Delphi w Interbase
541
Po utworzeniu konta użytkownika możemy się łączyć z dowolną bazą danych
InterBase’a. Jednak korzystanie z bazy jest możliwe dopiero po nadaniu praw
dostępu do poszczególnych obiektów bazy.
Konfiguracja serwera
Jest parę sposobów dojścia do okna dialogowego
Interbase Server Configuration
.
Umożliwia to Server Manager poleceniem
Config
z menu
Task\Server
. Możemy
też kliknąć prawym przyciskiem myszy ikonę
InterBase Server
na pasku zadań
Windows i wybrać polecenie
Properties
.
W oknie dialogowym
Server Configuration
parametry serwera InterBase zostały
podzielone na dwie klasy: IB (InterBase) Settings i OS (Operating system)
Settings. Ustawienia IB składają się z dwóch parametrów:
Database Cache
i
Client map size
.
Database cache
określa liczbę stron
zarezerwowanych na pamięć cache dla baz. W zasadzie wszystkie bazy można by
umieścić w pamięci RAM, jeśli cache będzie odpowiednio duży. Przyspieszyłoby
to znacznie wszelkie operacje na bazie. Jednak ustalając zbyt duży cache
w stosunku do zasobów komputera powodujemy, że system operacyjny przenosi
część pamięci cache na dysk, zmniejszając w ten sposób wydajność serwera.
Client map size
określa rozmiar bufora rezerwowanego dla każdego
połączonego z bazą klienta. Zwykle nie ma potrzeby zmieniania tego parametru -
można go zwiększyć przy transmisjach dużych porcji danych (np. BLOB) do i
z aplikacji klienta.
Ustawienia OS zawierają trzy pozycje:
Min Process Working Set
,
Max Process
working
Set
i
Process
Priority
Class
.
Min
i
Max Process working Set
kontrolują
wielkość oddanej do dyspozycji serwera bazy danych pamięci fizycznej.
Podwyższając wartość maksymalną pozwalamy na zwiększenie ustawień Database
Cache, jednak zbyt duża wartość tego parametru może powodować błędy
stronnicowania (ang. page faults). W Windows NT można sprawdzić - poprzez NT
Performance Monitor - czy parametry pracy serwera wymagają korekty. Aby
określić, czy ustawienia
Min
i
Max
process
Working
Set
są właściwe, wykonujemy
następujące kroki:
1. Jeśli InterBase server jest już uruchomiony, włączamy program NT
Performance Monitor (najlepiej na tym samym komputerze)
2. Wybieramy nowy wykres i naciskamy CTRL+I - aby dodać nowy licznik do
wykresu.
3. Wybieramy
Object Process, Instance
:
IBSERVER, Counter:Page File Bytes
i klikamy
Add
.
4. Dodajemy licznik dla
IBSERVER
Page
faults/sec
i
% Processor
Time
. (patrz
rysunek 17.3)
542
Część III
Jeśli stwierdzimy dużą ilość błędów stronicowania (Page Faults) lub % Processor
Time jest mniejszy niż 50%, można zwiększyć zakres Process Working Set, co
powinno poprawić wydajność serwera. Wprowadzone zmiany zostaną
uwzględnione przy następnym starcie serwera.
Process Priority Class określa priorytet procesów inicjowanych przez serwer
InterBase wśród innych procesów aktualnie uruchomionych na komputerze
serwera. Zmieniając priorytet na High można uzyskać znaczny wzrost wydajności
- szczególnie na komputerach wydzielonych (ang. dedicated) wyłącznie do pracy
z serwerem InterBase, oraz w sytuacjach, gdy duża ilość aplikacji jest aktywna na
komputerze serwera. Zmiany tego ustawienia realizowane są natychmiast po
kliknięciu
Apply
.
Przeglądanie statystyki blokad
Systemy klient/serwer wykorzystują pesymistyczny mechanizm sterowania
współbieżnością, więc są wyposażane w różnorodne narzędzia obsługujące
blokady i rywalizację o dostęp. InterBase, jak większość wielodostępnych DBMS,
odpowiednio blokuje zasoby - aby zapobiec jednoczesnemu, niekontrolowanemu
procesowi wprowadzania zmian przez wielu użytkowników. Oczywiście niezbędne
są narzędzia do kontrolowania i
usuwania problemów związanych
z wielodostępem. Blokady zakładane przez serwer InterBase’a można obejrzeć
wybierając polecenie
Tasks\Lock Manager Statistics
w programie Server Manager
(patrz rysunek 17.4).
Rysunek 17.3
Użycie NT
Performabce
Monitor do
określenia potrzeb
serwera
InterBaze’a
w zakresie RAM.
Rozdział 17 Delphi w Interbase
543
Program zarządzający blokadami w InterBase korzysta ze specjalnej tablicy do ich
śledzenia. Są w
niej informacje na temat statusu wszystkich blokad.
LOCK_HEADER BLOCK podaje szeroki zakres danych podsumowujących - jak
np. aktualny i maksymalny rozmiar zablokowanych tablic, liczbę zakleszczeń itp.
W części szczegółowej dotyczącej poszczególnych procesów możemy sprawdzić,
który proces zarządził daną blokadę, które blokady zostały zwolnione, a które
czekają itd. Wszystkie te informacje mogą się okazać pomocne w rozwiązywaniu
problemów związanych z blokadami, jak np. zakleszczenie (ang. deadlock)
Sprawdzanie integralnośći bazy
Oprócz regularnej archiwizacji danych należy - dla zapewnienia sobie
wiarygodności danych w bazie - podjąć inne środki. Powinno się regularnie
sprawdzać ich poprawność, najlepiej bezpośrednio przed lub po dokonaniu
archiwizacji. Umo¿liwia to polecenie menu
Maintance\Database Validation
w InterBase Server Manager. Rysunek 17.5 pokazuje okno dialogowe
Database
Validation
.
Database Validation wykonuje trzy podstawowe funkcje: inwentaryzuje
uszkodzone struktury na dysku, pokazuje strony z błędną alokacją i zwalnia
miejsce na dysku po "osieroconych" stronach. Standardowo korekty w strukturze,
RYSUNEK 17.4
Okno Lock
Manager Statistics
wyświetla ważne
informacje
dotyczące blokad
InterBase.
Rysunek 17.5
Sprawdzanie bazy -
okienko dialogowe
Database
Validation
544
Część III
które nie dotyczą danych użytkownika wykonywane są automatycznie. Można
jednak zablokować przeprowadzanie zmian przez wybranie opcji
Read-only
validation
.
Każdy błąd wykryty przez program sprawdzający wyświetlany jest w okienku. Po
kliknięciu przycisku
Repair
program podejmie próbę ich naprawy. Program
sprawdzający może także warunkowo pomijać wykryty błąd sumy kontrolnej. Aby
naprawić uszkodzoną bazę wykonujemy kroki opisane poniżej
1. Kopiujemy bazę z pominięciem programu ServerManager Backup. Można użyć
zwykłego polecenia
Copy
albo Eksploratora Windows. Oprogramowanie
InterBase nie archiwizuje bowiem uszkodzonych baz.
2. Dla sprawdzenia uszkodzeń w bazie wybieramy polecenie
Maintenance\
Database
Validation
.
3. Po zakończeniu weryfikacji klikamy przycisk
Repair
- aby zaznaczyć lub
naprawić uszkodzone struktury
4. Jeśli powstały błędy sumy kontrolnej ponownie wybieramy polecenie
Database
Validation
, tym razem jednak z opcją
Ignore checksum errors
ustawioną na
TRUE
.
5. Kiedy proces się zakończy klikamy
Repair
- aby naprawić nowo wykryte
uszkodzenia.
6. Uruchamiamy Database Validation, wybierając tym razem opcję
Read
-
only
validation
. Warto zwrócić uwagę na fakt, że zwolnione stronice nie są już
wyświetlane, a niepełne rekordy zaznaczone jako uszkodzone. Każdy rekord
zaznaczony jako uszkodzony nie będzie archiwizowany przez program Backup
z Server Managera.
7. Archiwizujemy naprawioną bazę programem archiwującym z Server Managera.
Uszkodzone rekordy zostaną oczywiście wyłączone z archiwizacji.
8. Odzyskujemy zarchiwizowaną bazę. Ta operacja pozwoli na przebudowanie
pewnych struktur wewnętrznych i usunięcie uszkodzonych części z bazy.
9. Uruchamiamy Database Validation po raz ostatni - aby sprawdzić, czy
rzeczywiście baza została naprawiona. Tym razem wybieramy opcję
Read
-
only
validation
.
Statystyki bazy danych
Opisywane tutaj polecenia Server Managera wymagają aktywnego połączenia
z daną bazą. Musimy być więc zarejestrowani na serwerze oraz połączeni z bazą
(polecenie
File\Database Connect
w Server Manager).
Rozdział 17 Delphi w Interbase
545
Polecenie
Task\Database Statistics
wyświetla różnorodne informacje odnoszące
się do bazy. Są wśród nich między innymi: data utworzenia bazy, najstarsza
aktywna transakcja, rozmiar strony, liczba zdefiniowanych cieni. Niektóre z tych
danych są także dostępne poprzez polecenia
Maintance\Database Properties
.
Rysunek 17.6 pokazuje okno utworzone przez
Database
Statistics
.
Gdy okno
Database Statistics
jest na ekranie, można wybrać polecenie
Database
Analysis
z menu
View
- aby zobaczyć ilość stron alokowanych dla bazy i ich
wypełnienie. Ułatwi to nam zrozumienie, jak struktura logiczna bazy wpływa na
fizyczne rozmieszczenie danych.
Czyszczenie bazy (Database sweeping)
Polecenie
Maintenance\Database Sweep
zwalnia miejsce po starych wersjach
rekordów z odrzuconych transakcji. Chociaż nie jest to niezbędne, należy zamknąć
bazę na czas jej oczyszczenia - ze względu na wpływ tej operacji na działanie
programów użytkowników. Oczyszczanie dużych baz może bowiem drastycznie
spowolnić realizację innych procesów.
Odzyskiwanie transakcji (Transaction Recovery)
Każda transakcja, obejmująca zakresem wiele baz, jest w InterBase zatwierdzana
dwufazowo. Zapewnia to zachowanie niepodzielności transakcji dla wszystkich
baz składowych. Dwufazowe zatwierdzanie może się nie udać ze względu na
zerwanie połączenia sieciowego lub awarii dysku, która uniemożliwi dostęp do
części baz. Polecenie
Maintaince\Transaction Recovery
ma za zadanie obsługę
transakcji pozostających w „zawieszeniu” po nieudanym procesie dwufazowego
zatwierdzania. Pozwala ono odzyskać te transakcje i - w zależności od sytuacji -
wycofać lub zatwierdzić.
Rysunek 17.6
Okno Database
Statistics
z podstawowymi
informacjami
o bazie.