Difference between revisions of "SqlDBHowto/pl"

From Lazarus wiki
Jump to navigationJump to search
(tłumaczenie na j. polski (niedokończone))
(tłumaczenie na j. polski, dokończenie)
Line 195: Line 195:
 
Aby 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.
 
Aby 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.
  
== How to change data in a table? ==
+
== Jak zmienić dane w tabeli? ==
To change the data in a record (or records), the general process is get TSQLQuery to search for the records you wish to change, make the changes there and then push them back to the database. The [[TDataSet]] (from which TSQLQuery is derived) must be set to edit mode. To enter edit mode call the '.Edit', '.Insert' or '.Append' methods. Use the '.Edit' method to change the current record. Use '.Insert' to insert a new record before the current record. Use '.Append' to insert a new record at the end of the table. In edit mode you can change field values through the 'Fields' property. Use 'Post' to validate the new data, if the data is valid then the edit mode is left. If you move to another record - for example by using '.Next' - and the dataset is in edit mode, then first '.Post' is called. Use '.Cancel' to discard all changes you made since the last '.Post' call and leave the edit mode.
+
Aby 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.
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 203: Line 203:
 
Query.Post;</syntaxhighlight>
 
Query.Post;</syntaxhighlight>
  
The above is not the complete story yet. TSQLQuery is derived from TBufDataset which makes use of buffered updates. Buffered update means that after you called 'Post' the changes in the dataset are visible immediately, but they are not sent to the database server. What does happen is that the changes are maintained in a change log. When the '.ApplyUpdates' method is called, then all changes in the change log are sent to the database. Only then will database server know of the changes. The changes are sent to the server within a transaction of TSQLTransaction. Make sure to properly set the transaction before 'ApplyUpdates'. After applying the updates, a commit must be executed to save the changes on the database server.
+
Powyż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.
  
The below is an example of changing the data in a table, sending the changes to the server and committing the transaction. Again, no error checking, again, thats bad!
+
Poniż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!
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 229: Line 229:
 
   Query.FieldByName('NAME').AsString := 'Name Number 2';
 
   Query.FieldByName('NAME').AsString := 'Name Number 2';
 
   Query.Post;
 
   Query.Post;
   Query.UpdateMode := upWhereAll;        // defined in db
+
   Query.UpdateMode := upWhereAll;        // zdefiniowany w db
 
   Query.ApplyUpdates;
 
   Query.ApplyUpdates;
 
   ATransaction.Commit;
 
   ATransaction.Commit;
Line 238: Line 238:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The actual works starts with the SQL statement "select * from tblNames where ID = 2" identifying the record (or records) you wish to change. If you leave out the "where ID = 2" bit, the TSQLQuery apparently sets ID (and other integer fields?) to 1. And therefore will operate on lines where ID=1 only. For a discussion of 'UpdateMode' continue reading.
+
Rzeczywista 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.
  
== How does SqlDB send the changes to the database server? ==
+
== W jaki sposób SqlDB wysyła zmiany do serwera bazy danych? ==
  
In the code example in [[#How to change data in a table?]], you will find the line
+
W przykładzie kodu w [[#Jak zmienić dane w tabeli?]] znajdziesz linię
 
<syntaxhighlight lang=pascal>Query.UpdateMode := upWhereAll;</syntaxhighlight>
 
<syntaxhighlight lang=pascal>Query.UpdateMode := upWhereAll;</syntaxhighlight>
without explanation of what it does. The best way to find out what that line does is to leave it out. If you leave out the statement and the followed this howto precisely, then you will receive the following error message:
+
bez 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:
No update query specified and failed to generate one. (No fields for inclusion in where statement found)
+
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)
To understand what went wrong, you must understand how changes are sent to the database server. The only way to get data in a SQL server is by executing SQL queries. SQL has three types of queries for three different ways of manupulating a record. To create a new record, change or delete a record insert, update and delete statements are executed respectively. An update statement may be as follows:
+
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:
 
<syntaxhighlight lang="sql">update TBLNAMES set NAME='Edited name' where ID=1;</syntaxhighlight>
 
<syntaxhighlight lang="sql">update TBLNAMES set NAME='Edited name' where ID=1;</syntaxhighlight>
To send a change to the database server, Sqldb must assemble an update query. To assemble the query, three things are needed:
+
Aby wysłać zmianę do serwera bazy danych, Sqldb musi złożyć zapytanie aktualizacyjne. Do złożenia zapytania potrzebne są trzy rzeczy:
; The name of the table : The table name is retrieved from parsing the select query, although this doesn't always work.  
+
; Nazwa tabeli : nazwa tabeli jest pobierana podczas analizowania zapytania wybierającego, chociaż nie zawsze to działa.
; <tt>UPDATE</tt> or <tt>INSERT</tt> clause : These contain the fields that must be changed.
+
; Klauzula <tt>UPDATE</tt> lub <tt>INSERT</tt> : zawierają pola, które należy zmienić.
; <tt>WHERE</tt> clause : This contains the fields that determine which records should be changed.
+
; Klauzula <tt>WHERE</tt> : zawiera pola określające, które rekordy powinny zostać zmienione.
  
Every field (each ''TField'' in ''Fields'') has a ProviderFlags property. Only fields with '''pfInUpdate''' in ''ProviderFlags'' will be used in the update or insert cluase of a query. By default all fields have ''pfInUpdate'' set in their ''ProviderFlags'' property.
+
Każ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”.
  
Which fields are used in the <tt>WHERE</tt> clause depends on the ''UpdateMode'' property of the query and the ''ProviderFlags'' property of the fields. Fields with ''pfInkey'' in their ''ProviderFlags'' are always used in the <tt>WHERE</tt> clause. A field will have the ''pfInKey'' flag set automatically if the field is part of the primary key of the table and 'TSQLQuery.UsePrimaryKeyAsKey' returns 'True'.
+
To, które pola są używane w klauzuli <tt>WHERE</tt> 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 <tt>WHERE</tt>. Pole będzie miało automatycznie ustawioną flagę ''pfInKey'', jeśli pole jest częścią klucza podstawowego tabeli, a 'TSQLQuery.UsePrimaryKeyAsKey' zwraca wartość 'True'.
  
The default value for ''UpdateMode'' of the query is ''upWhereKeyOnly''. In this update mode only fields with ''pfInkey'' in their ''ProviderFlags'' property are used in the <tt>WHERE</tt> clause. If none of the fields have their ''pfInKey'' flag set, then no fields are available for the <tt>WHERE</tt> clause and the error message from the beginning of this section will be returned. You can solve the issue by:
+
Domyś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 <tt>WHERE</tt>. Jeśli żadne z pól nie ma ustawionej flagi ''pfInKey'', wówczas żadne pola nie są dostępne dla klauzuli <tt>WHERE</tt> i zostanie zwrócony komunikat o błędzie z początku tej sekcji. Możesz rozwiązać problem poprzez:
* Adding a primary key to the table and set ''TSQLQuery.UsePrimaryKeyAsKey'' to 'True', or
+
* Dodanie klucza podstawowego do tabeli i ustawienie ''TSQLQuery.UsePrimaryKeyAsKey'' na 'True' lub
* Setting the ''pfInkey'' flag for one or more fields in code.
+
* Ustawienie flagi ''pfInkey'' dla jednego lub więcej pól w kodzie.
  
The '''UpdateMode''' property knows two more possible values. 'upWhereAll' can be used to add all fields with the 'pfInWhere' flag set to the <tt>WHERE</tt> clause. By default all fields have this flag set. 'upWhereChanged' can be used to add only those fields that have the 'pfInWhere' flag set '''and''' that are changed in the current record.
+
Wł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ę <tt>WHERE</tt>. 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.
  
==How to handle Errors==
+
==Jak radzić sobie z błędami==
Run time errors are unavoidable, disks may fill up, necessary libraries or helper apps may not be available, things go wrong and we need to allow for that.
+
Błę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ć.
The FPC detects and handles run time errors quite well. It usually gives you a concise and reasonable explanation of what went wrong. However, you will want
+
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 -
to monitor and handle errors yourself for a number of reasons -
+
* Prawdopodobnie nie chcesz, aby program kończył swoje działanie przy pierwszych oznakach kłopotów.
* You probably don't want the programme to teminate at the first sign of trouble.
+
* Jeśli będziemy kontynuować, upewnijmy się, że odzyskana jest jakakolwiek pamięć przydzielona w obszarze problemowym, nie chcemy żadnych wycieków pamięci.
* If we do keep going, lets make sure any memory allocated in the problem area is recovered, we don't want any memory leaks.
+
* Jeśli jednak zamierzamy zrobić krok wstecz, dajmy użytkownikowi wyjaśnienie kontekstowe.
* If we are going to go under however, lets give the user a context sensitive explanation.
 
  
The following bit of code is based on the above examples but this time it DOES check for errors in critical places. Key is the try...finally...end and try...except...end blocks. You can test it by doing things like uninstalling SQLite3, putting a dummy file in place of the test_dbase database and so on.
+
Poniż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.
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 299: Line 298:
 
         Query.DataBase := Connect;
 
         Query.DataBase := Connect;
 
         Query.SQL.Text:= 'select * from tblNames';
 
         Query.SQL.Text:= 'select * from tblNames';
         Query.Open;          // This will also open Connect
+
         Query.Open;          // Spowoduje to również otwarcie Connect
 
         while not Query.EOF do begin
 
         while not Query.EOF do begin
 
             writeln('ID: ', Query.FieldByName('ID').AsInteger, '  Name: ' +
 
             writeln('ID: ', Query.FieldByName('ID').AsInteger, '  Name: ' +
Line 310: Line 309:
 
         Query.Free;
 
         Query.Free;
 
     end;
 
     end;
     writeln('Found a total of ' + InttoStr(Count) + ' lines.');
+
     writeln('W sumie znaleziono ' + InttoStr(Count) + ' wierszy.');
 
end;
 
end;
  
Line 318: Line 317:
 
     writeln(Message);
 
     writeln(Message);
 
     writeln(Suggestion);
 
     writeln(Suggestion);
     Connect.Close;              // Its possibly silly worrying about freeing
+
     Connect.Close;              // To prawdopodobnie głupie martwienie się o zwolnienie pamięci,
     Trans.free;                // if we are going to call halt() but its
+
     Trans.free;                // skoro zamierzamy wywołać halt(), ale to jest demo, dobrze?
     Connect.Free;              // a demo, alright ?
+
     Connect.Free;               
 
     halt();
 
     halt();
 
end;
 
end;
Line 331: Line 330:
 
     try
 
     try
 
         if not fileexists(Connect.DatabaseName) then begin
 
         if not fileexists(Connect.DatabaseName) then begin
             Connect.Open;  // give EInOutError if (eg) SQLite not installed
+
             Connect.Open;  // generuje błąd EInOutError, jeśli (np.) SQLite nie jest zainstalowany
 
             Trans.StartTransaction;
 
             Trans.StartTransaction;
 
             WriteTable('create table TBLNAMES (ID integer Primary Key, NAME varchar(40));');
 
             WriteTable('create table TBLNAMES (ID integer Primary Key, NAME varchar(40));');
Line 342: Line 341:
 
     except
 
     except
 
         on E : EDatabaseError do
 
         on E : EDatabaseError do
             FatalError(E.ClassName, E.Message, 'Does the file contain the correct database ?');
+
             FatalError(E.ClassName, E.Message, 'Czy plik zawiera poprawną bazę danych?');
 
         on E : EInOutError do
 
         on E : EInOutError do
             FatalError(E.ClassName, E.Message, 'Have you installed SQLite (and dev package)?');
+
             FatalError(E.ClassName, E.Message, 'Czy zainstalowałeś SQLite (i pakiet deweloperski)?');
 
         on E : Exception do
 
         on E : Exception do
             FatalError(E.ClassName, E.Message, 'Something really really bad happened.');
+
             FatalError(E.ClassName, E.Message, 'Stało się coś naprawdę złego.');
 
     end;
 
     end;
 
     ReadTable();
 
     ReadTable();
Line 355: Line 354:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== How to execute a query using TSQLQuery? ==
+
== Jak wykonać zapytanie za pomocą TSQLQuery? ==
Next to statements that return a dataset (see [[#Jak odczytać dane z tabeli?]]) SQL has statements that do not return data. For example <tt>INSERT</tt>, <tt>UPDATE</tt> and <tt>DELETE</tt> statements do not return data. These statements can be executed using ''[[#Jak wykonać bezpośrednie zapytania/utworzyć tabelę?|TSQLConnection.ExecuteDirect]]'', but TSQLQuery can also be used. If you do not expect return data use ''TSQLQuery.ExecSQL'' instead of ''TSQLQuery.Open''. As mentioned earlier, use ''TSQLQuery.Open'' to open the dataset returned by the SQL statement.  
+
Obok instrukcji zwracających zestaw danych (zobacz [[#Jak odczytać dane z tabeli?]]) SQL zawiera instrukcje, które nie zwracają danych. Na przykład instrukcje <tt>INSERT</tt>, <tt>UPDATE</tt> i <tt>DELETE</tt> nie zwracają danych. Instrukcje te można wykonać za pomocą zapytania ''[[#Jak wykonać bezpośrednie zapytania/utworzyć tabelę?|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.
  
The following procedure creates a table and inserts two records using TSQLQuery.
+
Poniższa procedura tworzy tabelę i wstawia dwa rekordy za pomocą TSQLQuery.
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 384: Line 383:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
== How to use parameters in a query? ==
+
== Jak używać parametrów w zapytaniu? ==
In the code example of [[#How to execute a query using TSQLQuery?]] the same query is used twice, only the values to be inserted differ. A better way to do this is by using parameters in the query.  
+
W 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.
  
The syntax of parameters in queries is different per database system, but the differences are handled by TSQLQuery. Replace the values in the query with a colon followed by the name of the parameter you want to use. For example:
+
Skł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:
 
<syntaxhighlight lang=pascal>Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';</syntaxhighlight>
 
<syntaxhighlight lang=pascal>Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';</syntaxhighlight>
  
This query will create two parameters: 'ID' and 'NAME'.
+
To zapytanie utworzy dwa parametry: 'ID' i 'NAME'.
To determine the parameters, the query is parsed at the moment the text of ''TSQLQuery.SQL'' is assigned or changed. All existing parameters will be removed and the new parameters will be added to the 'TSQLQuery.Params' property. Assigning a value to a parameter is similar to assigning a value to a field in the dataset:
+
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:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 397: Line 396:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
You can't tell from the query what kind of data must be stored in the parameter. The data type of the parameter is determined at the moment a value is first assigned to the parameter. By assigning a value using '.AsString', the parameter is assigned the data type 'ftString'. You can determine the data type directly by setting the 'DataType' property. If an incorrect datatype is assigned to the parameter, then problems will occur during opening or executing the query.
+
W 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.
See [[Database field type]] for more information on data types.
+
Zobacz [[Database field type|Typ pola bazy danych]], aby uzyskać więcej informacji na temat typów danych.
  
=== Select query ===
+
=== Zapytanie select ===
  
An example of a select query with parameters would be to change something like this:
+
Przykładem zapytania select z parametrami byłaby zmiana czegoś takiego:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 408: Line 407:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
to something like this:
+
na coś takiego:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 415: Line 414:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Example ===
+
=== Przykład ===
  
The following example creates the same table as the previous example, but now parameters are used:
+
Poniższy przykład tworzy taką samą tabelę jak w poprzednim przykładzie, ale teraz używane są parametry:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 444: Line 443:
 
     Query.ExecSQL;
 
     Query.ExecSQL;
  
     //Query.UnPrepare; // no need to call this; should be called by Query.Close
+
     //Query.UnPrepare; // nie trzeba tego samemu wywoływać; powinien być wywoływany przez Query.Close
 
     Query.Close;
 
     Query.Close;
 
   finally
 
   finally
Line 452: Line 451:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Notice that this example requires more code than the example without the parameters. Then what is the use of using parameters?  
+
Zauważ, że ten przykład wymaga więcej kodu niż przykład bez parametrów. Jaki jest zatem pożytek z używania parametrów?
  
Speed is one of the reasons. The example with parameters is faster, because the database server parses the query only once (in the .Prepare statement or at first run).  
+
Jednym 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).
  
Another reason to use prepared statements is prevention of [http://en.wikipedia.org/wiki/SQL_injection SQL-injection] (see also [[Secure programming]].  
+
Innym powodem używania przygotowanych instrukcji jest zapobieganie [http://en.wikipedia.org/wiki/SQL_injection wstrzykiwania SQL (SQL-injection)] (zobacz także [[Secure programming|Bezpieczne programowanie]].
  
Finally, in some cases it just simplifies coding.
+
Wreszcie w niektórych przypadkach po prostu upraszcza kodowanie.
  
== Troubleshooting: TSQLConnection logging ==
+
== Rozwiązywanie problemów: rejestrowanie TSQLConnection ==
  
You can let a TSQLConnection log what it is doing. This can be handy to see what your Lazarus program sends to the database exactly, to debug the database components themselves and perhaps to optimize your queries.
+
Moż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ń.
NB: if you use prepared statements/parametrized queries (see section above), the parameters are often sent in binary by the TSQLConnection descendent (e.g. TIBConnection), so you can't just copy/paste the logged SQL into a database query tool.
+
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.
Regardless, connection logging can give a lot of insight in what your program is doing.
+
Niezależnie od tego, rejestrowanie połączeń może dać dużo wglądu w to, co robi twój program.
  
Alternatives are:  
+
Alternatywy to:
  
# you can use the debugger to step through the database code if you have built FPC (and Lazarus) with debugging enabled.  
+
# możesz użyć debuggera do przechodzenia przez kod bazy danych, jeśli zbudowałeś FPC (i Lazarusa) z włączonym debugowaniem.
# if you use ODBC drivers (at least on Windows) you could enable tracelog output in the ODBC control panel.
+
# jeśli używasz sterowników ODBC (przynajmniej w systemie Windows), możesz włączyć wyjście tracelog w panelu sterowania ODBC.
# many databases allow you to monitor all statements sent to it from a certain IP address/connection.
+
# wiele baz danych pozwala monitorować wszystkie komendy wysyłane do niej z określonego adresu IP/połączenia.
  
If you use TSQLConnection logging, two things are required:
+
Jeśli korzystasz z rejestrowania TSQLConnection, wymagane są dwie rzeczy:
  
# indicate which event types your TSQLConnection should log
+
# wskaż typy zdarzeń, które Twoje połączenie TSQLConnection powinno rejestrować
# point TSQLConnection at a function that receives the events and processes them (logs them to file, prints them to screen, etc.).
+
# skojarz TSQLConnection z funkcją, która odbierze zdarzenia i przetworzy je (zapisze je do pliku, wyświetli na ekranie itp.).
That function must be of type TDBLogNotifyEvent (see sqldb.pp), so it needs this signature:
+
Ta funkcja musi być typu TDBLogNotifyEvent (patrz sqldb.pp), więc musi mieć sygnaturę typu:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 482: Line 481:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== FPC (or: the manual way) ===
+
=== FPC (lub: sposób manualny) ===
  
A code snippet can illustrate this:
+
Przykładem może być fragment kodu:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
...
 
...
TSQLConnection, //or a child object like TIBConnection, TMSSQLConnection
+
TSQLConnection, //lub obiekt potomny, taki jak TIBConnection, TMSSQLConnection
 
...
 
...
 
var
 
var
 
type  
 
type  
   TMyApplication = class(TCustomApplication); //this is our application that uses the connection
+
   TMyApplication = class(TCustomApplication); // to jest nasza aplikacja, która korzysta z połączenia
 
...
 
...
 
   private
 
   private
     // This example stores the logged events in this stringlist:
+
     // Ten przykład przechowuje zarejestrowane zdarzenia na tej liście ciągów:
 
     FConnectionLog: TStringList;
 
     FConnectionLog: TStringList;
 
...
 
...
 
   protected
 
   protected
     // This procedure will receive the events that are logged by the connection:
+
     // Ta procedura odbierze zdarzenia, które są rejestrowane przez połączenie:
 
     procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);
 
     procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);
 
...
 
...
 
   procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;
 
   procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;
 
     EventType: TDBEventType; const Msg: String);
 
     EventType: TDBEventType; const Msg: String);
   // The procedure is called by TSQLConnection and saves the received log messages
+
   // Ta procedura jest wywoływana przez TSQLConnection i zapisuje odebrane komunikaty
   // in the FConnectionLog stringlist
+
   // w dzienniku FConnectionLog typu stringlist
 
   var
 
   var
 
     Source: string;
 
     Source: string;
Line 518: Line 517:
 
       detCommit:  Source:='Commit:  ';
 
       detCommit:  Source:='Commit:  ';
 
       detRollBack: Source:='Rollback:';
 
       detRollBack: Source:='Rollback:';
       else Source:='Unknown event. Please fix program code.';
+
       else Source:='Nieznane zdarzenie. Popraw kod programu.';
 
     end;
 
     end;
     FConnectionLog.Add(Source + ' ' + Msg);
+
     FConnectionLog.Add(Źródło + ' ' + Msg);
 
   end;
 
   end;
  
 
...
 
...
   // We do need to tell our TSQLConnection what to log:
+
   // Musimy powiedzieć naszemu TSQLConnection, co ma rejestrować:
 
     FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]
 
     FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]
     // ... and to which procedure the connection should send the events:
+
     // ... i do jakiej procedury to połączenie powinno wysyłać zdarzenia:
 
     FConnection.OnLog:=@Self.GetLogEvent;
 
     FConnection.OnLog:=@Self.GetLogEvent;
 
...
 
...
   // now we can use the connection and the FConnectionLog stringlist will fill with log messages.
+
   // teraz możemy użyć połączenia, a lista ciągów FConnectionLog wypełni się komunikatami dziennika.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
You can also use TSQLConnection's GlobalDBLogHook instead to log everything from multiple connections.
+
Możesz również użyć GlobalDBLogHook TSQLConnection zamiast rejestrować wszystko z wielu połączeń.
  
=== Lazarus (or: the quick way) ===
+
=== Lazarus (lub: szybszy sposób) ===
  
Finally, the description above is the FPC way of doing things as indicated in the introduction; if using Lazarus, a quicker way is to assign an event handler to the TSQLConnection's OnLog event.
+
Podsumowują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.
  
== See also ==
+
== Zobacz także ==
  
* [[Working With TSQLQuery]]
+
* [[Working With TSQLQuery/pl|Praca z TSQLQuery]]

Revision as of 19:44, 2 October 2022

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) Nederlands (nl) polski (pl) 中文(中国大陆)‎ (zh_CN)

Databases portal

References:

Tutorials/practical articles:

Databases

Advantage - MySQL - MSSQL - Postgres - Interbase - Firebird - Oracle - ODBC - Paradox - SQLite - dBASE - MS Access - Zeos
Instrukcja do SqlDB

Ten 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.

Postaram 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).

Gdzie mogę znaleźć oficjalną dokumentację?

Proszę zapoznać się z oficjalną dokumentacją pod adresem dokumentacja SQLDB.

Jak połączyć się z serwerem bazy danych?

SqlDB 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.

Gdy 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):

Uwaga 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).

Chociaż 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:

  • nazwa serwera lub adres IP
  • nazwa bazy danych
  • nazwa użytkownika
  • hasło

Gdy 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.

Program ConnectDB;

{$mode objfpc}{$H+}

uses
  IBConnection;

function CreateConnection: TIBConnection;
begin
  result := TIBConnection.Create(nil);
  result.Hostname := 'localhost';
  result.DatabaseName := '/opt/firebird/examples/employee.fdb';
  result.UserName := 'sysdba';
  result.Password := 'masterkey';
end;
 
var   
  AConnection : TIBConnection;

begin
  AConnection := CreateConnection;
  AConnection.Open;
  if Aconnection.Connected then
    writeln('Połączenie udało się!')
  else
    writeln('Wypisanie tego tekstu nie jest to możliwe! ' +
            'Ten kod nie zostanie wykonany, ponieważ w przypadku ' +
            'niepowodzenia połączenia zgłoszony zostanie wyjątek.');
  AConnection.Close;
  AConnection.Free;
end.

Jeś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.

Jak wykonać bezpośrednie zapytania/utworzyć tabelę?

SqlDB - 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.

W FPC wystepują pewne różnice pomiędzy:

  • Instrukcjami SQL, które zwracają informacje (zestaw danych - dataset). W tym celu musisz użyć komponentu TSQLQuery; patrz #Jak odczytać dane z tabeli?.
  • 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).

Wię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'.

Do 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.

Aby 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.

Poniż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.

program CreateTable;
{$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
uses
  sqldb, sqlite3conn; 
 
var 
  AConnection : TSQLite3Connection;
  ATransaction : TSQLTransaction;
 
begin
  AConnection := TSQLite3Connection.Create(nil);
  AConnection.DatabaseName := 'test_dbase';
 
  ATransaction := TSQLTransaction.Create(AConnection);
  AConnection.Transaction := ATransaction;
  AConnection.Open;
  ATransaction.StartTransaction;
  AConnection.ExecuteDirect('create table TBLNAMES (ID integer, NAME varchar(40));'); 
 
  ATransaction.Commit;
 
  ATransaction.StartTransaction;
  AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (1,''Name1'');'); 
  AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (2,''Name2'');'); 
  ATransaction.Commit; 
  AConnection.Close;
  ATransaction.Free;	
  AConnection.Free;
end.

Jak odczytać dane z tabeli?

Uż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ę?.

Gdy 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:

'SELECT * FROM nazwa_tabeli;'

.

Uż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'.

TSQLQuery 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.

Przykład: odczyt danych z tabeli

Poniż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!

Program ShowData;
 {$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
uses
  DB, Sysutils, sqldb, sqlite3conn;

var
  AConnection  : TSQLConnection;
  ATransaction : TSQLTransaction;
  Query        : TSQLQuery;
 
begin
  AConnection := TSQLite3Connection.Create(nil);
  ATransaction := TSQLTransaction.Create(AConnection);
  AConnection.Transaction := ATransaction;
  AConnection.DatabaseName := 'test_dbase';
  Query := TSQLQuery.Create(nil);
  Query.SQL.Text := 'select * from tblNames';
  Query.Database := AConnection;
  Query.Open;
  while not Query.Eof do
  begin
    Writeln('ID: ', Query.FieldByName('ID').AsInteger, 'Name: ' +
                                  Query.FieldByName('Name').AsString);
    Query.Next;
  end;
  Query.Close;
  AConnection.Close;
  Query.Free;
  ATransaction.Free;
  AConnection.Free;
end.

(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.

Dlaczego TSQLQuery.RecordCount zawsze zwraca wartość 10?

Aby 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.

Jeśli chcesz poznać rzeczywistą liczbę rekordów na serwerze, możesz najpierw wywołać '.Last', a następnie '.RecordCount'.

Dostę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.

W obecnej stabilnej wersji FPC '.RecordCount' nie uwzględnia filtrów, tj. pokazuje sumę niefiltrowanych rekordów.

Jeś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.

Lazarus

Lazarus 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.

Aby 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.

Jak zmienić dane w tabeli?

Aby 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.

Query.Edit;
Query.FieldByName('NAME').AsString := 'Edited name';
Query.Post;

Powyż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.

Poniż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!

Program EditData;
{$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
uses
    db, sqldb, sqlite3conn;
var 
  AConnection : TSQLConnection;
  ATransaction : TSQLTransaction;
  Query : TSQLQuery;
 
begin
  AConnection := TSQLite3Connection.Create(nil);
  ATransaction := TSQLTransaction.Create(AConnection);
  AConnection.Transaction := ATransaction;
  AConnection.DatabaseName := 'test_dbase';
  Query := TSQLQuery.Create(nil);
  Query.DataBase := AConnection;
  Query.SQL.Text := 'select * from tblNames where ID = 2';
  Query.Open;
  Query.Edit;
  Query.FieldByName('NAME').AsString := 'Name Number 2';
  Query.Post;
  Query.UpdateMode := upWhereAll;         // zdefiniowany w db
  Query.ApplyUpdates;
  ATransaction.Commit;
  Query.Free;
  ATransaction.Free;
  AConnection.Free;
end.

Rzeczywista 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.

W jaki sposób SqlDB wysyła zmiany do serwera bazy danych?

W przykładzie kodu w #Jak zmienić dane w tabeli? znajdziesz linię

Query.UpdateMode := upWhereAll;

bez 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:

update TBLNAMES set NAME='Edited name' where ID=1;

Aby wysłać zmianę do serwera bazy danych, Sqldb musi złożyć zapytanie aktualizacyjne. Do złożenia zapytania potrzebne są trzy rzeczy:

Nazwa tabeli
nazwa tabeli jest pobierana podczas analizowania zapytania wybierającego, chociaż nie zawsze to działa.
Klauzula UPDATE lub INSERT
zawierają pola, które należy zmienić.
Klauzula WHERE
zawiera pola określające, które rekordy powinny zostać zmienione.

Każ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”.

To, 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'.

Domyś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:

  • Dodanie klucza podstawowego do tabeli i ustawienie TSQLQuery.UsePrimaryKeyAsKey na 'True' lub
  • Ustawienie flagi pfInkey dla jednego lub więcej pól w kodzie.

Wł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.

Jak radzić sobie z błędami

Błę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 -

  • Prawdopodobnie nie chcesz, aby program kończył swoje działanie przy pierwszych oznakach kłopotów.
  • Jeśli będziemy kontynuować, upewnijmy się, że odzyskana jest jakakolwiek pamięć przydzielona w obszarze problemowym, nie chcemy żadnych wycieków pamięci.
  • Jeśli jednak zamierzamy zrobić krok wstecz, dajmy użytkownikowi wyjaśnienie kontekstowe.

Poniż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.

program DemoDBaseWithErrors;
{$mode objfpc} {$ifdef mswindows}{$apptype console}{$endif}
uses
  DB, Sysutils, sqldb, sqlite3conn;
var
    Connect : TSQLite3Connection;
    Trans : TSQLTransaction;


procedure WriteTable (Command : string);
begin
	Connect.ExecuteDirect(Command);
    	Trans.Commit;
end;

procedure ReadTable ();
var
   Query : TSQLQuery;
   Count : smallint;
begin
    Count := 0;
    try
        Query := TSQLQuery.Create(nil);
        Query.DataBase := Connect;
        Query.SQL.Text:= 'select * from tblNames';
        Query.Open;          // Spowoduje to również otwarcie Connect
        while not Query.EOF do begin
            writeln('ID: ', Query.FieldByName('ID').AsInteger, '  Name: ' +
                              Query.FieldByName('Name').AsString);
            Query.Next;
            Count := Count + 1;
        end;
    finally
        Query.Close;
        Query.Free;
    end;
    writeln('W sumie znaleziono ' + InttoStr(Count) + ' wierszy.');
end;

procedure FatalError(ClassName, Message, Suggestion : string);
begin
    writeln(ClassName);
    writeln(Message);
    writeln(Suggestion);
    Connect.Close;              // To prawdopodobnie głupie martwienie się o zwolnienie pamięci,
    Trans.free;                 // skoro zamierzamy wywołać halt(), ale to jest demo, dobrze?
    Connect.Free;               
    halt();
end;

begin
    Connect := TSQLite3Connection.Create(nil);
    Trans := TSQLTransaction.Create(Connect);
	Connect.Transaction := Trans;
    Connect.DatabaseName := 'test_dbase';
    try
        if not fileexists(Connect.DatabaseName) then begin
            Connect.Open;   // generuje błąd EInOutError, jeśli (np.) SQLite nie jest zainstalowany
            Trans.StartTransaction;
            WriteTable('create table TBLNAMES (ID integer Primary Key, NAME varchar(40));');
            Trans.Commit;
        end;
        Connect.open;
        Trans.StartTransaction;
        WriteTable('insert into TBLNAMES (NAME) values (''AName1'');');
        WriteTable('insert into TBLNAMES (NAME) values (''AName2'');');
    except
        on E : EDatabaseError do
            FatalError(E.ClassName, E.Message, 'Czy plik zawiera poprawną bazę danych?');
        on E : EInOutError do
            FatalError(E.ClassName, E.Message, 'Czy zainstalowałeś SQLite (i pakiet deweloperski)?');
        on E : Exception do
            FatalError(E.ClassName, E.Message, 'Stało się coś naprawdę złego.');
     end;
    ReadTable();
    Connect.Close;
    Trans.Free;
    Connect.Free;
end.

Jak wykonać zapytanie za pomocą TSQLQuery?

Obok 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.

Poniższa procedura tworzy tabelę i wstawia dwa rekordy za pomocą TSQLQuery.

procedure CreateTable;
  
var 
  Query : TSQLQuery;
  
begin
  Query := TSQLQuery.Create(nil);
  try
    Query.Database := AConnection;

    Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';
    Query.ExecSQL;
 
    Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (1,''Name1'');';
    Query.ExecSQL;
  
    Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (2,''Name2'');';
    Query.ExecSQL;
  finally
    Query.Free;
  end;
end;

Jak używać parametrów w zapytaniu?

W 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.

Skł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:

Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';

To 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:

Query.Params.ParamByName('Name').AsString := 'Name1';

W 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 Typ pola bazy danych, aby uzyskać więcej informacji na temat typów danych.

Zapytanie select

Przykładem zapytania select z parametrami byłaby zmiana czegoś takiego:

  Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = '''+Edit1.Text+''' ORDER BY NAME ';

na coś takiego:

  Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = :NAMEPARAM ORDER BY NAME ';
  Query.Params.ParamByName('NAMEPARAM').AsString := Edit1.Text;

Przykład

Poniższy przykład tworzy taką samą tabelę jak w poprzednim przykładzie, ale teraz używane są parametry:

procedure CreateTableUsingParameters;
  
var 
  Query : TSQLQuery;
  
begin
  Query := TSQLQuery.Create(nil);
  try
    Query.Database := AConnection;

    Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';
    Query.ExecSQL;

    Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';
    Query.Prepare;
  
    Query.Params.ParamByName('ID').AsInteger := 1;
    Query.Params.ParamByName('NAME').AsString := 'Name1';
    Query.ExecSQL;
  
    Query.Params.ParamByName('ID').AsInteger := 2;
    Query.Params.ParamByName('NAME').AsString := 'Name2';
    Query.ExecSQL;

    //Query.UnPrepare; // nie trzeba tego samemu wywoływać; powinien być wywoływany przez Query.Close
    Query.Close;
  finally
    Query.Free;
  end;
end;

Zauważ, że ten przykład wymaga więcej kodu niż przykład bez parametrów. Jaki jest zatem pożytek z używania parametrów?

Jednym 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).

Innym powodem używania przygotowanych instrukcji jest zapobieganie wstrzykiwania SQL (SQL-injection) (zobacz także Bezpieczne programowanie.

Wreszcie w niektórych przypadkach po prostu upraszcza kodowanie.

Rozwiązywanie problemów: rejestrowanie TSQLConnection

Moż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.

Alternatywy to:

  1. możesz użyć debuggera do przechodzenia przez kod bazy danych, jeśli zbudowałeś FPC (i Lazarusa) z włączonym debugowaniem.
  2. jeśli używasz sterowników ODBC (przynajmniej w systemie Windows), możesz włączyć wyjście tracelog w panelu sterowania ODBC.
  3. wiele baz danych pozwala monitorować wszystkie komendy wysyłane do niej z określonego adresu IP/połączenia.

Jeśli korzystasz z rejestrowania TSQLConnection, wymagane są dwie rzeczy:

  1. wskaż typy zdarzeń, które Twoje połączenie TSQLConnection powinno rejestrować
  2. skojarz TSQLConnection z funkcją, która odbierze zdarzenia i przetworzy je (zapisze je do pliku, wyświetli na ekranie itp.).

Ta funkcja musi być typu TDBLogNotifyEvent (patrz sqldb.pp), więc musi mieć sygnaturę typu:

TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;

FPC (lub: sposób manualny)

Przykładem może być fragment kodu:

uses
...
TSQLConnection, //lub obiekt potomny, taki jak TIBConnection, TMSSQLConnection
...
var
type 
  TMyApplication = class(TCustomApplication); // to jest nasza aplikacja, która korzysta z połączenia
...
  private
    // Ten przykład przechowuje zarejestrowane zdarzenia na tej liście ciągów:
    FConnectionLog: TStringList;
...
  protected
    // Ta procedura odbierze zdarzenia, które są rejestrowane przez połączenie:
    procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);
...
  procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;
    EventType: TDBEventType; const Msg: String);
  // Ta procedura jest wywoływana przez TSQLConnection i zapisuje odebrane komunikaty
  // w dzienniku FConnectionLog typu stringlist
  var
    Source: string;
  begin
    // Nicely right aligned...
    case EventType of
      detCustom:   Source:='Custom:  ';
      detPrepare:  Source:='Prepare: ';
      detExecute:  Source:='Execute: ';
      detFetch:    Source:='Fetch:   ';
      detCommit:   Source:='Commit:  ';
      detRollBack: Source:='Rollback:';
      else Source:='Nieznane zdarzenie. Popraw kod programu.';
    end;
    FConnectionLog.Add(Źródło + ' ' + Msg);
  end;

...
  // Musimy powiedzieć naszemu TSQLConnection, co ma rejestrować:
    FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]
    // ... i do jakiej procedury to połączenie powinno wysyłać zdarzenia:
    FConnection.OnLog:=@Self.GetLogEvent;
...
  // teraz możemy użyć połączenia, a lista ciągów FConnectionLog wypełni się komunikatami dziennika.

Możesz również użyć GlobalDBLogHook TSQLConnection zamiast rejestrować wszystko z wielu połączeń.

Lazarus (lub: szybszy sposób)

Podsumowują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.

Zobacz także