Sign in Join
Wiki copytemporary

SqlDBHowto/pl: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
1-<div style="font-size:1.88em;margin:0.75em 0;">
2-
31Instrukcja do SqlDB
4-
5-</div>
62
73Ten tekst jest zaplanowany jako instrukcja w stylu „jak to zrobić”. Chcę odpowiedzieć na kilka pytań jedno po drugim i wyjaśnić, jak korzystać z różnych klas. Wszystkie te pytania są stawiane jedno po drugim i tworzą rodzaj samouczka.
84
95Postaram się to sformułować w taki sposób, aby tekst mógł być użyty zarówno dla Lazarusa, jak i Free Pascala. Jednak przykłady dotyczą FreePascala (czyli są to aplikacje konsolowe).
106
117## Gdzie mogę znaleźć oficjalną dokumentację?
128
139Proszę zapoznać się z oficjalną dokumentacją pod adresem [dokumentacja SQLDB](http://www.freepascal.org/docs-html/fcl/sqldb/index.html).
1410
1511## Jak połączyć się z serwerem bazy danych?
1612
1713SqlDB nie łączy się bezpośrednio z serwerem bazy danych, ale używa klienta, który odpowiada używanemu serwerowi bazy danych. SqlDB wysyła polecenia do biblioteki klienta; biblioteka klienta łączy się z bazą danych i przekazuje polecenia. Oznacza to, że aby nawiązać połączenie z bazą danych, na komputerze musi być zainstalowana biblioteka klienta. W systemie Windows klientem jest zwykle biblioteka .dll, w Linuksie .so, a pod OS/X .dylib.
1814
1915Gdy biblioteka klienta jest poprawnie zainstalowana, możesz połączyć się z serwerem bazy danych za pomocą komponentu TSQLConnection. Różne komponenty TSQLConnection są dostępne dla różnych serwerów baz danych (patrz [[SQLdb Package]]):
2016
2117- Firebird/Interbase: [[TIBConnection]]
2218- MS SQL Server: [[TMSSQLConnection]] (dostępny od FPC 2.6.1)
2319- MySQL v4.0: [[TMySQL40Connection]]
2420- MySQL v4.1: [[TMySQL41Connection]]
2521- MySQL v5.0: [[TMySQL50Connection]]
2622- MySQL v5.1: [[TMySQL51Connection]] (dostępny od wersji FPC 2.5.1
2723- MySQL v5.5: [[TMySQL55Connection]] (dostępny od wersji Lazarusa 1.0.8/FPC 2.6.2
2824- MySQL v5.6: [[TMySQL56Connection]] (dostępny w Lazarus 1.2.4/FPC w wersji 2.6.4
2925- ODBC: [[TODBCConnection]] (patrz [[ODBCConn|ODBCConn#TODBCConnection]])
3026- Oracle: [[TOracleConnection]] (patrz [[Oracle]])
3127- PostgreSQL: [[TPQConnection]] (patrz [[postgresql|postgresql#SQLDB]])
3228- Sqlite3: [[TSQLite3Connection]] (dostępne od wersji 2.2.2 FPC, zobacz [[SQLite|SQLite#Built-in_SQLDB]])
3329- Sybase ASE: [[TSybaseConnection]] (dostępne od wersji FPC 2.6.1, zobacz [[Lazarus Database Overview|Lazarus i MSSQL/Sybase]])
3430
3531Uwaga dotycząca MySQL - Istnieje wiele różnic pomiędzy wersjami klienta, w tym zakresie, że klienci i połączenia nie mogą być wymieniane. Jeśli zainstalowana jest biblioteka klienta MySQL w wersji 4.1, musisz użyć połączenia TMySQL41Connection. Nie jest to związane z serwerem MySQL; używając biblioteki klienckiej MySQL 4.1 możesz prawdopodobnie połączyć się z serwerem MySQL 5.0 (zobacz dokumentację MySQL, aby dowiedzieć się, jakie kombinacje są obsługiwane).
3632
3733Chociaż różne bazach danych różnią się w szczegółach, to w praktyce musisz ustawić tylko cztery właściwości, aby połączyć się z serwerem bazy danych:
3834
3935- nazwa serwera lub adres IP
4036- nazwa bazy danych
4137- nazwa użytkownika
4238- hasło
4339
4440Gdy te właściwości są ustawione, możesz utworzyć połączenie za pomocą metody 'open'. Jeśli połączenie nie powiedzie się, zostanie zgłoszony wyjątek EDatabaseError. Użyj właściwości 'connected', aby sprawdzić, czy nawiązano połączenie z serwerem bazy danych. Użyj metody 'close', aby zakończyć połączenie z serwerem.
4541
4642``` pascal
4743Program ConnectDB;
4844
4945{$mode objfpc}{$H+}
5046
5147uses
5248 IBConnection;
5349
5450function CreateConnection: TIBConnection;
5551begin
5652 result := TIBConnection.Create(nil);
5753 result.Hostname := 'localhost';
5854 result.DatabaseName := '/opt/firebird/examples/employee.fdb';
5955 result.UserName := 'sysdba';
6056 result.Password := 'masterkey';
6157end;
6258
6359var
6460 AConnection : TIBConnection;
6561
6662begin
6763 AConnection := CreateConnection;
6864 AConnection.Open;
6965 if Aconnection.Connected then
7066 writeln('Połączenie udało się!')
7167 else
7268 writeln('Wypisanie tego tekstu nie jest to możliwe! ' +
7369 'Ten kod nie zostanie wykonany, ponieważ w przypadku ' +
7470 'niepowodzenia połączenia zgłoszony zostanie wyjątek.');
7571 AConnection.Close;
7672 AConnection.Free;
7773end.
7874```
7975
8076Jeśli zostanie zgłoszony wyjątek, uważnie przeczytaj komunikat o błędzie. Możliwe, że serwer bazy danych nie działa albo nazwa użytkownika, hasło, nazwa bazy danych lub adres IP są wpisane niepoprawnie. Jeśli komunikat o błędzie informuje, że nie można znaleźć biblioteki klienta, to sprawdź, czy klient jest poprawnie zainstalowany. Często komunikat o błędzie zawiera dosłownie nazwę szukanego pliku.
8177
8278## Jak wykonać bezpośrednie zapytania/utworzyć tabelę?
8379
8480SqlDB - nazwa mówi wszystko - działa tylko z serwerem baz danych, który korzysta z SQL. SQL oznacza 'Structured Query Language' (czyli „Strukturalny język zapytań”). SQL to język opracowany w celu umożliwienia pracy z relacyjnymi bazami danych. Praktycznie każdy system baz danych ma swój własny dialekt, ale duża liczba instrukcji SQL jest taka sama dla wszystkich systemów baz danych.
8581
8682W FPC wystepują pewne różnice pomiędzy:
8783
8884- Instrukcjami SQL, które zwracają informacje (zestaw danych - dataset). W tym celu musisz użyć komponentu TSQLQuery; patrz #Jak odczytać dane z tabeli?.
8985- Instrukcjami, które nie zwracają informacji, ale robią coś innego, np. uaktualniają dane. W tym celu możesz również użyć metody 'ExecuteDirect' połączenia TSQLConnection. (Możesz również użyć tego, jeśli otrzymujesz w odpowiedzi zestaw danych, ale nie jesteś zainteresowany wynikami, np. w możliwej do wybrania procedurze składowanej).
9086
9187Większość systemów baz danych wykonuje instrukcje SQL w ramach transakcji. Jeśli chcesz, aby zmiany dokonane w ramach transakcji były dostępne w innych transakcjach lub te zmiany były dostępne nawet po zamknięciu transakcji(!), musisz zatwierdzić transakcję instrukcją 'commit'.
9288
9389Do obsługi transakcji w Sqldb służy komponent TSQLTransaction. Instrukcja SQL wykonywana przez Sqldb zawsze musi być wykonywana w ramach transakcji, nawet jeśli system bazy danych nie obsługuje transakcji. Ponadto istnieją systemy baz danych, które obsługują transakcje, ale dla których TSQLConnection nie obsługuje (jeszcze) transakcji. Nawet wtedy musisz użyć komponentu TSQLTransaction.
9490
9591Aby użyć `TSQLConnection.ExecuteDirect` do wykonania instrukcji SQL, musisz określić, która transakcja ('Transaction') ma być użyta. Z kolei, aby skorzystać z TSQLTransaction musisz określić, który komponent TSQLConnection ma być użyty.
9692
9793Poniższy przykład tworzy tabelę 'TBLNAMES' z polami 'NAME' i 'ID' oraz wstawia dwa rekordy. Tym razem przy użyciu SQLite. Użyte instrukcje SQL nie zostały wyjaśnione. Więcej informacji na temat instrukcji SQL, ich użycia i składni można znaleźć w dokumentacji systemu baz danych. Zauważ, że ten przykład nie próbuje wychwycić żadnych błędów, to bardzo źle! Zajrzyj do [Wyjątki](https://www.freepascal.org/docs-html/current/ref/refch17.html#x226-24800017).
9894
9995``` pascal
10096program CreateTable;
10197{$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
10298uses
10399 sqldb, sqlite3conn;
104100
105101var
106102 AConnection : TSQLite3Connection;
107103 ATransaction : TSQLTransaction;
108104
109105begin
110106 AConnection := TSQLite3Connection.Create(nil);
111107 AConnection.DatabaseName := 'test_dbase';
112108
113109 ATransaction := TSQLTransaction.Create(AConnection);
114110 AConnection.Transaction := ATransaction;
115111 AConnection.Open;
116112 ATransaction.StartTransaction;
117113 AConnection.ExecuteDirect('create table TBLNAMES (ID integer, NAME varchar(40));');
118114
119115 ATransaction.Commit;
120116
121117 ATransaction.StartTransaction;
122118 AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (1,''Name1'');');
123119 AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (2,''Name2'');');
124120 ATransaction.Commit;
125121 AConnection.Close;
126122 ATransaction.Free;
127123 AConnection.Free;
128124end.
129125```
130126
131127## Jak odczytać dane z tabeli?
132128
133129Użyj komponentu TSQLQuery, aby odczytać dane z tabeli. Komponent TSQLQuery musi być połączony z komponentem TSQLConnection i komponentem TSQLTransaction, aby mógł poprawnie wykonać swoją pracę. Ustawienie TSQLConnection i TSQLTransaction zostało omówione w #Jak połączyć się z serwerem bazy danych? i #Jak wykonać bezpośrednie zapytania/utworzyć tabelę?.
134130
135131Gdy TSQLConnection, TSQLTransaction i TSQLQuery są połączone, TSQLQuery wymaga dalszej konfiguracji do pracy. TSQLQuery ma właściwość 'SQL' zawierającą obiekt TStrings. Ta właściwość 'SQL' zawiera instrukcję SQL, która musi zostać wykonana. Jeśli wszystkie dane z tabeli `nazwa_tabeli` muszą zostać odczytane, ustaw właściwość 'SQL' na:
136132
137133``` sql
138134'SELECT * FROM nazwa_tabeli;'
139135```
140136
141137.
142138
143139Użyj metody 'open', aby odczytać tabelę z serwera i umieścić dane w zbiorze danych TSQLQuery. Dostęp do danych można uzyskać za pośrednictwem TSQLQuery, dopóki zapytanie nie zostanie zamknięte za pomocą metody 'close'.
144140
145141TSQLQuery jest podklasą TDataset. TDataset posiada kolekcję 'Fields', która zawiera wszystkie kolumny tabeli. TDataset śledzi również bieżący rekord. Użyj metod 'First', 'Next', 'Prior' i 'Last', aby zmienić bieżący rekord. 'Bof' zwraca 'True', jeśli osiągnięty został pierwszy rekord, a 'Eof' zwraca 'True', jeśli osiągnięty zostanie ostatni rekord. Aby odczytać wartość pola w bieżącym rekordzie, najpierw znajdź odpowiedni obiekt 'TField', a następnie użyj 'AsString', 'AsInteger' itp.
146142
147143### Przykład: odczyt danych z tabeli
148144
149145Poniżej znajduje się przykład, który wyświetla wszystkie wartości tabeli, tak jak została stworzona w powyższym przykładzie CreateTable. Skopiuj plik test_database do katalogu roboczego ShowData. Zauważ, że w tym przykładzie także nie przeprowadzono sprawdzania błędów!
150146
151147``` pascal
152148Program ShowData;
153149 {$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
154150uses
155151 DB, Sysutils, sqldb, sqlite3conn;
156152
157153var
158154 AConnection : TSQLConnection;
159155 ATransaction : TSQLTransaction;
160156 Query : TSQLQuery;
161157
162158begin
163159 AConnection := TSQLite3Connection.Create(nil);
164160 ATransaction := TSQLTransaction.Create(AConnection);
165161 AConnection.Transaction := ATransaction;
166162 AConnection.DatabaseName := 'test_dbase';
167163 Query := TSQLQuery.Create(nil);
168164 Query.SQL.Text := 'select * from tblNames';
169165 Query.Database := AConnection;
170166 Query.Open;
171167 while not Query.Eof do
172168 begin
173169 Writeln('ID: ', Query.FieldByName('ID').AsInteger, 'Name: ' +
174170 Query.FieldByName('Name').AsString);
175171 Query.Next;
176172 end;
177173 Query.Close;
178174 AConnection.Close;
179175 Query.Free;
180176 ATransaction.Free;
181177 AConnection.Free;
182178end.
183179```
184180
185181(Powyższy kod oczywiście nie jest całkiem ukończony, bo brakuje mu bloków 'try...finally'. Jednak powyższy kod ma na celu pokazanie kodu obsługi bazy danych, a zatem pominięto ostatnie poprawki.) Należy pamiętać, że 'TSQLTransaction.StartTransaction' nie jest używany. To nie jest konieczne. Po otwarciu TSQLQuery, wykonywane jest polecenie SQL, a jeśli żadna transakcja nie jest dostępna, to transakcja jest uruchamiana automatycznie. Programista nie musi jawnie rozpoczynać transakcji. To samo dotyczy połączenia utrzymywanego przez TSQLConnection. Połączenie jest otwierane w razie potrzeby, wiersz 'Aconnection.Open' nie jest tak naprawdę wymagany. Jeśli transakcja TSQLTransaction zostanie zniszczona, zostanie wykonane automatyczne wycofanie. **Ewentualne zmiany danych zawartych w transakcji zostaną utracone.**
186182
187183### Dlaczego TSQLQuery.RecordCount zawsze zwraca wartość 10?
188184
189185Aby policzyć rekordy w zbiorze danych, użyj '.RecordCount'. Zauważ jednak, że '.RecordCount' pokazuje liczbę rekordów, które zostały już załadowane z serwera. Ze względu na wydajność, SqlDB domyślnie nie odczytuje wszystkich rekordów podczas otwierania TSQLQuery, a tylko pierwsze 10. Dopiero po uzyskaniu dostępu do jedenastego rekordu zostanie załadowany następny zestaw 10 rekordów itd. Użycie metody '.Last' sprawi, że wszystkie rekordy zostaną załadowane.
190186
191187Jeśli chcesz poznać rzeczywistą liczbę rekordów na serwerze, możesz najpierw wywołać '.Last', a następnie '.RecordCount'.
192188
193189Dostępny jest też sposób alternatywny. Liczbę rekordów zwracanych przez serwer określa właściwość '.PacketRecords'. Wartość domyślna to 10; jeśli ustawisz ją na -1, to wszystkie rekordy zostaną załadowane jednocześnie.
194190
195191W obecnej stabilnej wersji FPC '.RecordCount' nie uwzględnia filtrów, tj. pokazuje sumę niefiltrowanych rekordów.
196192
197193Jeśli potrzebujesz dokładnej liczby rekordów, często lepszym pomysłem jest bezpośrednie zapytanie o liczbę rekordów w zapytaniu za pomocą innego zapytania SQL, ale musisz to zrobić w tej samej transakcji, ponieważ inne transakcje mogły zmienić liczbę rekordów w międzyczasie.
198194
199195### Lazarus
200196
201197Lazarus posiada różne komponenty do wyświetlania danych z zestawu TDataset na formularzu. Zamiast instrukcji While-loop i Writeln, jak pokazano powyżej, można użyć komponentów do wyświetlenia danych w tabeli. Umieść na formularzu odpowiednie komponenty TSQLConnection, TSQLTransaction i TSQLQuery, a następnie połącz je i odpowiednio ustaw. Dodatkowo będziesz potrzebować TDatasource; ustaw właściwość 'TDatasource.Dataset' na używany komponent TSQLQuery. (**Uwaga** nie ustawiaj właściwości 'TSQLQuery.Datasource' na użyty komponent TDatasource. Właściwość 'TSQLQuery.Datasource' jest używana tylko w tabelach master-detail - patrz [[MasterDetail]]). Następnie możesz umieścić TDBGrid na formularzu i ustawić właściwość 'Datasource' siatki na komponent TDatasource, który dodałeś wcześniej.
202198
203199Aby sprawdzić, czy wszystko działa, ustaw właściwość 'Connected' TSQLConnection na 'True' w Lazarus IDE. IDE natychmiast spróbuje połączyć się z serwerem bazy danych. Jeśli to zadziała, możesz ustawić właściwość 'TSQLQuery.Active' na 'True'. Jeśli wszystko jest w porządku, zobaczysz - w IDE - bezpośrednio na ekranie wszystkie dane z tabeli.
204200
205201## Jak zmienić dane w tabeli?
206202
207203Aby zmienić dane w rekordzie (lub rekordach), powrzechną praktyką jest użycie TSQLQuery do pobrania rekordów, które chcesz zmienić, wprowadzienie tam zmian, a następnie przesłanie ich z powrotem do bazy danych. [[TDataSet]] (z którego pochodzi TSQLQuery) musi być ustawiony na tryb edycji. Aby przejść do trybu edycji, wywołaj metody '.Edit', '.Insert' lub '.Append'. Użyj metody '.Edit', aby zmienić bieżący rekord. Użyj '.Insert', aby wstawić nowy rekord przed bieżącym rekordem. Użyj '.Append', aby wstawić nowy rekord na końcu tabeli. W trybie edycji możesz zmieniać wartości pól poprzez właściwość 'Fields'. Użyj metody 'Post', aby sprawdzić poprawność nowych danych, jeśli dane są prawidłowe, tryb edycji zostaje opuszczony. Jeśli przejdziesz do innego rekordu - na przykład używając '.Next' - i zestaw danych jest w trybie edycji, to najpierw zostanie wywołany '.Post'. Użyj metody '.Cancel', aby odrzucić wszystkie zmiany wprowadzone od ostatniego wywołania '.Post' i wyjść z trybu edycji.
208204
209205``` pascal
210206Query.Edit;
211207Query.FieldByName('NAME').AsString := 'Edited name';
212208Query.Post;
213209```
214210
215211Powyższe nie jest jeszcze pełną historią. TSQLQuery wywodzi się z TBufDataset, który wykorzystuje buforowane aktualizacje. Aktualizacja buforowana oznacza, że ​​po wywołaniu 'Post' zmiany w zestawie danych są widoczne od razu, ale nie są wysyłane na serwer bazy danych. Dzieje się tak, że zmiany są utrzymywane w dzienniku zmian. Po wywołaniu metody '.ApplyUpdates' wszystkie zmiany w dzienniku zmian są wysyłane do bazy danych. Dopiero wtedy serwer bazy danych będzie wiedział o zmianach. Zmiany są wysyłane na serwer w ramach transakcji TSQLTransaction. Upewnij się, że poprawnie ustawiłeś transakcję przed wywołaniem 'ApplyUpdates'. Po zastosowaniu aktualizacji należy wykonać zatwierdzenie, aby zapisać zmiany na serwerze bazy danych.
216212
217213Poniżej znajduje się przykład zmiany danych w tabeli, przesłanie zmian na serwer i zatwierdzenie transakcji. Ponownie, bez sprawdzania błędów, więc jest to nadal źle!
218214
219215``` pascal
220216Program EditData;
221217{$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
222218uses
223219 db, sqldb, sqlite3conn;
224220var
225221 AConnection : TSQLConnection;
226222 ATransaction : TSQLTransaction;
227223 Query : TSQLQuery;
228224
229225begin
230226 AConnection := TSQLite3Connection.Create(nil);
231227 ATransaction := TSQLTransaction.Create(AConnection);
232228 AConnection.Transaction := ATransaction;
233229 AConnection.DatabaseName := 'test_dbase';
234230 Query := TSQLQuery.Create(nil);
235231 Query.DataBase := AConnection;
236232 Query.SQL.Text := 'select * from tblNames where ID = 2';
237233 Query.Open;
238234 Query.Edit;
239235 Query.FieldByName('NAME').AsString := 'Name Number 2';
240236 Query.Post;
241237 Query.UpdateMode := upWhereAll; // zdefiniowany w db
242238 Query.ApplyUpdates;
243239 ATransaction.Commit;
244240 Query.Free;
245241 ATransaction.Free;
246242 AConnection.Free;
247243end.
248244```
249245
250246Rzeczywista praca zaczyna się od instrukcji SQL „select * from tblNames where ID = 2” identyfikującej rekord (lub rekordy), który chcesz zmienić. Jeśli pominiesz część "where ID = 2", TSQLQuery pozornie ustawi ID (i inne pola liczb całkowitych?) na 1. I dlatego będzie działać tylko na rekordach, w których ID=1. Aby zapoznać się z właściwością 'UpdateMode', czytaj dalej.
251247
252248## W jaki sposób SqlDB wysyła zmiany do serwera bazy danych?
253249
254250W przykładzie kodu w #Jak zmienić dane w tabeli? znajdziesz linię
255251
256252``` pascal
257253Query.UpdateMode := upWhereAll;
258254```
259255
260256bez wyjaśnienia, co ona robi. Najlepszym sposobem, aby dowiedzieć się, co robi ta linia, jest pominięcie jej. Jeśli pominiesz instrukcję i dokładnie wykonasz ten przykład, to otrzymasz następujący komunikat o błędzie: Nie określono zapytania o aktualizację i nie udało się go wygenerować. (Brak pól do umieszczenia w miejscu, w którym znaleziono oświadczenie) Aby zrozumieć, co poszło nie tak, musisz zrozumieć, w jaki sposób zmiany są wysyłane do serwera bazy danych. Jedynym sposobem uzyskania danych na serwerze SQL jest wykonywanie zapytań SQL. SQL ma trzy typy zapytań dla trzech różnych sposobów manipulowania rekordem. Aby utworzyć nowy rekord, zmienić lub usunąć wstawienie rekordu, instrukcje aktualizacji i usunięcia są wykonywane odpowiednio. Oświadczenie o aktualizacji może wyglądać następująco:
261257
262258``` sql
263259update TBLNAMES set NAME='Edited name' where ID=1;
264260```
265261
266262Aby wysłać zmianę do serwera bazy danych, Sqldb musi złożyć zapytanie aktualizacyjne. Do złożenia zapytania potrzebne są trzy rzeczy:
267263
268264Nazwa tabeli
269265nazwa tabeli jest pobierana podczas analizowania zapytania wybierającego, chociaż nie zawsze to działa.
270266
271267Klauzula `UPDATE` lub `INSERT`
272268zawierają pola, które należy zmienić.
273269
274270Klauzula `WHERE`
275271zawiera pola określające, które rekordy powinny zostać zmienione.
276272
277273Każde pole (każde *TField* w *Fields*) ma właściwość ProviderFlags. Tylko pola z **pfInUpdate** w *ProviderFlags* będą używane w klauzuli aktualizacji lub wstawiania zapytania. Domyślnie wszystkie pola mają ustawione „pfInUpdate” we właściwości „ProviderFlags”.
278274
279275To, które pola są używane w klauzuli `WHERE` zależy od właściwości *UpdateMode* zapytania oraz właściwości *ProviderFlags* pól. Pola, które mają wartość *pfInkey* we właściwości *ProviderFlags* są zawsze używane w klauzuli `WHERE`. Pole będzie miało automatycznie ustawioną flagę *pfInKey*, jeśli pole jest częścią klucza podstawowego tabeli, a 'TSQLQuery.UsePrimaryKeyAsKey' zwraca wartość 'True'.
280276
281277Domyślna wartość *UpdateMode* zapytania to *upWhereKeyOnly*. W tym trybie aktualizacji tylko pola z *pfInkey* we właściwości *ProviderFlags* są używane w klauzuli `WHERE`. Jeśli żadne z pól nie ma ustawionej flagi *pfInKey*, wówczas żadne pola nie są dostępne dla klauzuli `WHERE` i zostanie zwrócony komunikat o błędzie z początku tej sekcji. Możesz rozwiązać problem poprzez:
282278
283279- Dodanie klucza podstawowego do tabeli i ustawienie *TSQLQuery.UsePrimaryKeyAsKey* na 'True' lub
284280- Ustawienie flagi *pfInkey* dla jednego lub więcej pól w kodzie.
285281
286282Właściwość **UpdateMode** zna jeszcze dwie możliwe wartości. 'upWhereAll' może być użyty do dodania wszystkich pól z flagą 'pfInWhere' ustawioną na klauzulę `WHERE`. Domyślnie wszystkie pola mają ustawioną tę flagę. 'upWhereChanged' może służyć do dodawania tylko tych pól, które mają ustawioną flagę 'pfInWhere' **oraz te**, które zostały zmienione w bieżącym rekordzie.
287283
288284## Jak radzić sobie z błędami
289285
290286Błędy w czasie wykonywania są nieuniknione, dyski mogą się zapełniać, niezbędne biblioteki lub aplikacje pomocnicze mogą być niedostępne, coś idzie nie tak i musimy na to pozwolić. FPC dość dobrze wykrywa i radzi sobie z błędami w czasie wykonywania. Zwykle daje zwięzłe i rozsądne wyjaśnienie, co poszło nie tak. Jednakże z pewnością będziesz chciał samodzielnie monitorować i obsługiwać błędy z wielu powodów -
291287
292288- Prawdopodobnie nie chcesz, aby program kończył swoje działanie przy pierwszych oznakach kłopotów.
293289- Jeśli będziemy kontynuować, upewnijmy się, że odzyskana jest jakakolwiek pamięć przydzielona w obszarze problemowym, nie chcemy żadnych wycieków pamięci.
294290- Jeśli jednak zamierzamy zrobić krok wstecz, dajmy użytkownikowi wyjaśnienie kontekstowe.
295291
296292Poniższy fragment kodu jest oparty na powyższych przykładach, ale tym razem sprawdza on błędy w krytycznych miejscach. Kluczem są bloki try...finally...end oraz try...except...end. Możesz to przetestować, wykonując takie czynności jak, odinstalowanie SQLite3, umieszczenie fikcyjnego pliku w miejscu bazy danych test_dbase i tak dalej.
297293
298294``` pascal
299295program DemoDBaseWithErrors;
300296{$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
301297uses
302298 DB, Sysutils, sqldb, sqlite3conn;
303299var
304300 Connect : TSQLite3Connection;
305301 Trans : TSQLTransaction;
306302
307303procedure WriteTable (Command : string);
308304begin
309305 Connect.ExecuteDirect(Command);
310306 Trans.Commit;
311307end;
312308
313309procedure ReadTable ();
314310var
315311 Query : TSQLQuery;
316312 Count : smallint;
317313begin
318314 Count := 0;
319315 try
320316 Query := TSQLQuery.Create(nil);
321317 Query.DataBase := Connect;
322318 Query.SQL.Text:= 'select * from tblNames';
323319 Query.Open; // Spowoduje to również otwarcie Connect
324320 while not Query.EOF do begin
325321 writeln('ID: ', Query.FieldByName('ID').AsInteger, ' Name: ' +
326322 Query.FieldByName('Name').AsString);
327323 Query.Next;
328324 Count := Count + 1;
329325 end;
330326 finally
331327 Query.Close;
332328 Query.Free;
333329 end;
334330 writeln('W sumie znaleziono ' + InttoStr(Count) + ' wierszy.');
335331end;
336332
337333procedure FatalError(ClassName, Message, Suggestion : string);
338334begin
339335 writeln(ClassName);
340336 writeln(Message);
341337 writeln(Suggestion);
342338 Connect.Close; // To prawdopodobnie głupie martwienie się o zwolnienie pamięci,
343339 Trans.free; // skoro zamierzamy wywołać halt(), ale to jest demo, dobrze?
344340 Connect.Free;
345341 halt();
346342end;
347343
348344begin
349345 Connect := TSQLite3Connection.Create(nil);
350346 Trans := TSQLTransaction.Create(Connect);
351347 Connect.Transaction := Trans;
352348 Connect.DatabaseName := 'test_dbase';
353349 try
354350 if not fileexists(Connect.DatabaseName) then begin
355351 Connect.Open; // generuje błąd EInOutError, jeśli (np.) SQLite nie jest zainstalowany
356352 Trans.StartTransaction;
357353 WriteTable('create table TBLNAMES (ID integer Primary Key, NAME varchar(40));');
358354 Trans.Commit;
359355 end;
360356 Connect.open;
361357 Trans.StartTransaction;
362358 WriteTable('insert into TBLNAMES (NAME) values (''AName1'');');
363359 WriteTable('insert into TBLNAMES (NAME) values (''AName2'');');
364360 except
365361 on E : EDatabaseError do
366362 FatalError(E.ClassName, E.Message, 'Czy plik zawiera poprawną bazę danych?');
367363 on E : EInOutError do
368364 FatalError(E.ClassName, E.Message, 'Czy zainstalowałeś SQLite (i pakiet deweloperski)?');
369365 on E : Exception do
370366 FatalError(E.ClassName, E.Message, 'Stało się coś naprawdę złego.');
371367 end;
372368 ReadTable();
373369 Connect.Close;
374370 Trans.Free;
375371 Connect.Free;
376372end.
377373```
378374
379375## Jak wykonać zapytanie za pomocą TSQLQuery?
380376
381377Obok instrukcji zwracających zestaw danych (zobacz #Jak odczytać dane z tabeli?) SQL zawiera instrukcje, które nie zwracają danych. Na przykład instrukcje `INSERT`, `UPDATE` i `DELETE` nie zwracają danych. Instrukcje te można wykonać za pomocą zapytania *TSQLConnection.ExecuteDirect*, ale można również skorzystać z TSQLQuery. Jeśli nie oczekujesz danych zwrotnych, użyj *TSQLQuery.ExecSQL* zamiast *TSQLQuery.Open*. Jak wspomniano wcześniej, użyj "TSQLQuery.Open", aby otworzyć zestaw danych zwrócony przez instrukcję SQL.
382378
383379Poniższa procedura tworzy tabelę i wstawia dwa rekordy za pomocą TSQLQuery.
384380
385381``` pascal
386382procedure CreateTable;
387383
388384var
389385 Query : TSQLQuery;
390386
391387begin
392388 Query := TSQLQuery.Create(nil);
393389 try
394390 Query.Database := AConnection;
395391
396392 Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';
397393 Query.ExecSQL;
398394
399395 Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (1,''Name1'');';
400396 Query.ExecSQL;
401397
402398 Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (2,''Name2'');';
403399 Query.ExecSQL;
404400 finally
405401 Query.Free;
406402 end;
407403end;
408404```
409405
410406## Jak używać parametrów w zapytaniu?
411407
412408W przykładzie kodu #Jak wykonać zapytanie za pomocą TSQLQuery? to samo zapytanie jest używane dwukrotnie, różnią się tylko wartości do wstawienia. Lepszym sposobem na to jest użycie parametrów w zapytaniu.
413409
414410Składnia parametrów w zapytaniach różni się w zależności od systemu baz danych, ale różnice są obsługiwane przez TSQLQuery. Zastąp wartości w zapytaniu dwukropkiem, po którym następuje nazwa parametru, którego chcesz użyć. Na przykład:
415411
416412``` pascal
417413Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';
418414```
419415
420416To zapytanie utworzy dwa parametry: 'ID' i 'NAME'. Aby określić parametry, zapytanie jest parsowane w momencie przypisania lub zmiany tekstu *TSQLQuery.SQL*. Wszystkie istniejące parametry zostaną usunięte, a nowe parametry zostaną dodane do właściwości 'TSQLQuery.Params'. Przypisywanie wartości do parametru jest podobne do przypisywania wartości do pola w zbiorze danych:
421417
422418``` pascal
423419Query.Params.ParamByName('Name').AsString := 'Name1';
424420```
425421
426422W zapytaniu nie można powiedzieć, jakie dane mają być przechowywane w parametrze. Typ danych parametru jest określany w momencie pierwszego przypisania wartości do parametru. Przypisując wartość za pomocą '.AsString', parametrowi przypisywany jest typ danych 'ftString'. Typ danych można określić bezpośrednio, ustawiając właściwość 'DataType'. Jeśli do parametru zostanie przypisany nieprawidłowy typ danych, wystąpią problemy podczas otwierania lub wykonywania zapytania. Zobacz [[Database field type|Typ pola bazy danych]], aby uzyskać więcej informacji na temat typów danych.
427423
428424### Zapytanie select
429425
430426Przykładem zapytania select z parametrami byłaby zmiana czegoś takiego:
431427
432428``` pascal
433429 Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = '''+Edit1.Text+''' ORDER BY NAME ';
434430```
435431
436432na coś takiego:
437433
438434``` pascal
439435 Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = :NAMEPARAM ORDER BY NAME ';
440436 Query.Params.ParamByName('NAMEPARAM').AsString := Edit1.Text;
441437```
442438
443439### Przykład
444440
445441Poniższy przykład tworzy taką samą tabelę jak w poprzednim przykładzie, ale teraz używane są parametry:
446442
447443``` pascal
448444procedure CreateTableUsingParameters;
449445
450446var
451447 Query : TSQLQuery;
452448
453449begin
454450 Query := TSQLQuery.Create(nil);
455451 try
456452 Query.Database := AConnection;
457453
458454 Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';
459455 Query.ExecSQL;
460456
461457 Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';
462458 Query.Prepare;
463459
464460 Query.Params.ParamByName('ID').AsInteger := 1;
465461 Query.Params.ParamByName('NAME').AsString := 'Name1';
466462 Query.ExecSQL;
467463
468464 Query.Params.ParamByName('ID').AsInteger := 2;
469465 Query.Params.ParamByName('NAME').AsString := 'Name2';
470466 Query.ExecSQL;
471467
472468 //Query.UnPrepare; // nie trzeba tego samemu wywoływać; powinien być wywoływany przez Query.Close
473469 Query.Close;
474470 finally
475471 Query.Free;
476472 end;
477473end;
478474```
479475
480476Zauważ, że ten przykład wymaga więcej kodu niż przykład bez parametrów. Jaki jest zatem pożytek z używania parametrów?
481477
482478Jednym z powodów jest szybkość. Przykład z parametrami jest szybszy, ponieważ serwer bazy danych analizuje zapytanie tylko raz (w instrukcji .Prepare lub przy pierwszym uruchomieniu).
483479
484480Innym powodem używania przygotowanych instrukcji jest zapobieganie [wstrzykiwania SQL (SQL-injection)](https://pl.wikipedia.org/wiki/SQL_injection) (zobacz także [[Secure programming/pl|Bezpieczne programowanie]]).
485481
486482Wreszcie w niektórych przypadkach po prostu upraszcza kodowanie.
487483
488484## Rozwiązywanie problemów: rejestrowanie TSQLConnection
489485
490486Możesz pozwolić TSQLConnection rejestrować to, co robi. Może to być przydatne do dokładnego sprawdzenia, co Twój program Lazarus wysyła do bazy danych, do debugowania samych komponentów bazy danych i być może do optymalizacji zapytań. W dodatku, jeśli używasz przygotowanych instrukcji/sparametryzowanych zapytań (patrz sekcja powyżej), parametry są często wysyłane binarnie przez potomka TSQLConnection (np. TIBConnection), więc nie możesz po prostu skopiować/wkleić zalogowanego SQL do narzędzia zapytań do bazy danych. Niezależnie od tego, rejestrowanie połączeń może dać dużo wglądu w to, co robi twój program.
491487
492488Alternatywy to:
493489
4944901. możesz użyć debuggera do przechodzenia przez kod bazy danych, jeśli zbudowałeś FPC (i Lazarusa) z włączonym debugowaniem.
4954912. jeśli używasz sterowników ODBC (przynajmniej w systemie Windows), możesz włączyć wyjście tracelog w panelu sterowania ODBC.
4964923. wiele baz danych pozwala monitorować wszystkie komendy wysyłane do niej z określonego adresu IP/połączenia.
497493
498494Jeśli korzystasz z rejestrowania TSQLConnection, wymagane są dwie rzeczy:
499495
5004961. wskaż typy zdarzeń, które Twoje połączenie TSQLConnection powinno rejestrować
5014972. skojarz TSQLConnection z funkcją, która odbierze zdarzenia i przetworzy je (zapisze je do pliku, wyświetli na ekranie itp.).
502498
503499Ta funkcja musi być typu TDBLogNotifyEvent (patrz sqldb.pp), więc musi mieć sygnaturę typu:
504500
505501``` pascal
506502TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;
507503```
508504
509505### FPC (lub: sposób manualny)
510506
511507Przykładem może być fragment kodu:
512508
513509``` pascal
514510uses
515511...
516512TSQLConnection, //lub obiekt potomny, taki jak TIBConnection, TMSSQLConnection
517513...
518514var
519515type
520516 TMyApplication = class(TCustomApplication); // to jest nasza aplikacja, która korzysta z połączenia
521517...
522518 private
523519 // Ten przykład przechowuje zarejestrowane zdarzenia na tej liście ciągów:
524520 FConnectionLog: TStringList;
525521...
526522 protected
527523 // Ta procedura odbierze zdarzenia, które są rejestrowane przez połączenie:
528524 procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);
529525...
530526 procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;
531527 EventType: TDBEventType; const Msg: String);
532528 // Ta procedura jest wywoływana przez TSQLConnection i zapisuje odebrane komunikaty
533529 // w dzienniku FConnectionLog typu stringlist
534530 var
535531 Source: string;
536532 begin
537533 // Nicely right aligned...
538534 case EventType of
539535 detCustom: Source:='Custom: ';
540536 detPrepare: Source:='Prepare: ';
541537 detExecute: Source:='Execute: ';
542538 detFetch: Source:='Fetch: ';
543539 detCommit: Source:='Commit: ';
544540 detRollBack: Source:='Rollback:';
545541 else Source:='Nieznane zdarzenie. Popraw kod programu.';
546542 end;
547543 FConnectionLog.Add(Źródło + ' ' + Msg);
548544 end;
549545
550546...
551547 // Musimy powiedzieć naszemu TSQLConnection, co ma rejestrować:
552548 FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]
553549 // ... i do jakiej procedury to połączenie powinno wysyłać zdarzenia:
554550 FConnection.OnLog:=@Self.GetLogEvent;
555551...
556552 // teraz możemy użyć połączenia, a lista ciągów FConnectionLog wypełni się komunikatami dziennika.
557553```
558554
559555Możesz również użyć GlobalDBLogHook TSQLConnection zamiast rejestrować wszystko z wielu połączeń.
560556
561557### Lazarus (lub: szybszy sposób)
562558
563559Podsumowując, powyższy opis to sposób rejestrowania komend przez FPC, jak wskazano we wstępie; jeśli używasz Lazarusa, szybszym sposobem jest przypisanie obsługi zdarzenia do zdarzenia OnLog TSQLConnection.
564560
565561## Zobacz także
566562
567563- [[Working With TSQLQuery/pl|Praca z TSQLQuery]]
568564
569565---
570566*From the Free Pascal wiki, page "SqlDBHowto/pl", as it stood on 2022-10-02 after a change by Slawek, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history