SQLite & Delphi 7 - proszę o pomoc w optymalizacji kodu

0

Witam serdecznie.
To mój pierwszy post. Jestem Mariusz - nie jestem zawodowym programistą, programowanie to hobby jak dla mnie.
Stworzyłem pewien program, obsługujący SQLite, jest to baza danych, zawierająca jedynie dane kontaktowe oraz pewne dane liczbowe podane przez użytkownika a później po przeliczeniu generuje raport do pliku Excela. Działa rewelacyjnie, potrafi dla każdego pracownika zaoszczędzić wiele godzin liczenia. Jednak jest pewien problem. Z czasem baza się rozrosła, i nastąpił problem z czasem zapisu. Juz dla 500 pracowników, zapis na 1GB RAM, oraz na procesorze Core 2 Duo około 2Ghz trwa nawet 30-40 sekund. Na szybszych komputerach i tak zapisuje to kilkanaście sekund.

Co ciekawe odczyt jest natychmiastowy.

Oto kod który wykonuje się bardzo długo ( otwieranie, łącznie bazy ze ścieżka jest realizowane w procedurze OnCreate formularza głównego, tak samo zamykanie bazy w OnClose ):

procedure TForm2.zapisz_bazeClick(Sender: TObject);
var
i:integer;
begin
if czy_wczytano_poprawnie = true then
      begin
      SQL_Baza.ExecSQL('DROP TABLE pracownicy');
      SQL_Baza.ExecSQL('CREATE TABLE pracownicy (imie TEXT,nazwisko TEXT,pesel TEXT ,tel TEXT,email  TEXT, miejscowosc TEXT,kod TEXT,poczta TEXT,data_ur TEXT,miejsce_ur TEXT,imie_ojca TEXT,imie_matki TEXT,skarbowy TEXT,skarbowy_adres TEXT,notatki TEXT,czy_student BOOLEAN) ');
     
      end;

      for i := 0 to CheckListBox1.Items.Count-1  do
      begin
      pracownik[i].czy_student:= CheckListBox1.Checked[i] ;
    SQL_Baza.ExecSQL('INSERT INTO pracownicy VALUES ("' + pracownik[i].imie +'", "' + pracownik[i].nazwisko +'","' +  IntToStr(pracownik[i].pesel)+ '","' +      pracownik[i].tel + '","'+ pracownik[i].email + '","' + pracownik[i].miejscowosc
     +'","' + pracownik[i].kod+'","' + pracownik[i].poczta +'","' + pracownik[i].data_ur +'","' + pracownik[i].miejsce_ur
      +'","' + pracownik[i].imie_ojca +'","' + pracownik[i].imie_matki +'","' + pracownik[i].skarbowy +'","' + pracownik[i].skarbowy_adres +'","' + pracownik[i].notatki
       +'","' + BoolToStr(pracownik[i].czy_student) +'")');

      wait(1,CheckListBox1.Items.Count,i);  // jest to wyświetlanie paska postępu w nowym oknie na środku

      end;  // od for

Form5.Close;

end; 

Co ciekawe odczyt jest błyskawiczny oto kod:

procedure TForm2.wczytaj_bazeClick(Sender: TObject);
var
i : integer;
begin
         CheckListBox1.Clear;//wyczysc liste pracowników
 
        SQL_Tabela := SQL_Baza.GetTable('SELECT * FROM pracownicy');   // pobieranie wszystkich wartosci z tabeli

       //  ShowMessage('liczba wpisów w bazie: ' + INttoStr(SQL_Tabela.RowCount ));
            for i := 0 to SQL_Tabela.RowCount - 1 do
            begin
       
            pracownik[i].imie := SQL_Tabela.FieldByName['imie'] ;
            pracownik[i].nazwisko := SQL_Tabela.FieldByName['nazwisko'] ;
            pracownik[i].pesel := StrTOINt64(SQL_Tabela.FieldByName['pesel']);
            pracownik[i].tel := SQL_Tabela.FieldByName['tel'] ;
            pracownik[i].email := SQL_Tabela.FieldByName['email'] ;
            pracownik[i].miejscowosc := SQL_Tabela.FieldByName['miejscowosc'] ;
            pracownik[i].kod := SQL_Tabela.FieldByName['kod'] ;
            pracownik[i].poczta:= SQL_Tabela.FieldByName['poczta'] ;
            pracownik[i].data_ur:= SQL_Tabela.FieldByName['data_ur'];
            pracownik[i].miejsce_ur:= SQL_Tabela.FieldByName['miejsce_ur'];
            pracownik[i].imie_ojca:= SQL_Tabela.FieldByName['imie_ojca'] ;
            pracownik[i].imie_matki:= SQL_Tabela.FieldByName['imie_matki'];
            pracownik[i].skarbowy:= SQL_Tabela.FieldByName['skarbowy'] ;
            pracownik[i].skarbowy_adres:= SQL_Tabela.FieldByName['skarbowy_adres'] ;
            pracownik[i].notatki:= SQL_Tabela.FieldByName['notatki']   ;
            pracownik[i].czy_student    :=StrToBool( SQL_Tabela.FieldByName['czy_student']);

            CheckListBox1.Items.Add(pracownik[i].nazwisko + ' ' + pracownik[i].imie) ;
            CheckListBox1.Checked[i]:= pracownik[i].czy_student ;
              SQL_Tabela.Next;
              wait(1,SQL_Tabela.RowCount,i);
            end;
       Form5.Close;
        SQL_Tabela.Free;
        CheckListBox1.ItemIndex:= 0;
        Label9.Caption:= 'Pracowników: '+ Inttostr(CheckListBox1.count);
         CheckListBox1.OnClick(self); 

Wrzucam wszystko do tablicy rekordów takiej:

type TPracownicy = record
imie            : string[100];
nazwisko        : string[100];
pesel           : int64;
tel             :string[20];
email           : string[200];
miejscowosc     :string[50];
kod             :string[50];
poczta          :string[50];
data_ur         :string[50];
miejsce_ur      :string[50];
imie_ojca       :string[50];
imie_matki      :string[50];
skarbowy        :string[255];
skarbowy_adres  :string[255];
notatki         :string[255];
czy_student     :Boolean;
end;

 

Niestety wymagane jest częste zamykanie bazy bo trzeba sie przełączać pomiędzy różnymi pracodawcami bo różni pracownicy pracują w różnych firmach, i dla każdego pracownika są tworzone oddzielne bazy godzin i ilości przepracowanych "produktów" które muszą być zliczane a raporty eksportowane do Excela.
Proszę o pomoc, dlaczego odczyt jest taki błyskawiczny a zapis taki powolny??? gdzie robię błąd?

Z góry dziękuję za pomoc. Pozdrawiam Mariusz

0
procedure TForm2.zapisz_bazeClick(Sender: TObject);
var
i:integer;
begin
if czy_wczytano_poprawnie = true then
      begin
      SQL_Baza.ExecSQL('DROP TABLE pracownicy');
      SQL_Baza.ExecSQL('CREATE TABLE pracownicy (imie TEXT,nazwisko TEXT,pesel TEXT ,tel TEXT,email  TEXT, miejscowosc TEXT,kod TEXT,poczta TEXT,data_ur TEXT,miejsce_ur TEXT,imie_ojca TEXT,imie_matki TEXT,skarbowy TEXT,skarbowy_adres TEXT,notatki TEXT,czy_student BOOLEAN) ');
     
      end;

      for i := 0 to CheckListBox1.Items.Count-1  do
      begin
      .......

Przepraszam, czy ja dobrze rozumiem, że Ty za każdym razem "zapisując bazę" niszczysz tabelę i ładujesz w nowo stworzoną dane z programu??? Przecież to bez sensu... poszukaj info o update, wtedy będziesz mógł zapisywać tylko te rekordy, które się zmieniły, a nie wszystko od początku...

ambrosius napisał(a):

Niestety wymagane jest częste zamykanie bazy bo trzeba sie przełączać pomiędzy różnymi pracodawcami bo różni pracownicy pracują w różnych firmach, i dla każdego pracownika są tworzone oddzielne bazy godzin i ilości przepracowanych "produktów" które muszą być zliczane a raporty eksportowane do Excela.

Ech, przemyśl sobie samą konstrukcję bazy, przecież to wszystko można zawrzeć w jednej bazie przy mądrze utworzonych tabelach.

1

To co powiedział @madmike + jeszcze:

if czy_wczytano_poprawnie = true then

Po co to = true?
Dlaczego nie od razu if (((czy_wczytano_poprawnie = true) = true) = true) = true then albo po prostu if (czy_wczytano_poprawnie) then?

1

Nie będę ingerował w sam program, w końcu to Twoja zabawa, ale z tym co masz najpościej to przerobić tak:

  1. Zmień sobie ten sql na:
'CREATE TABLE pracownicy (id INTEGER NOT NULL, imie TEXT,nazwisko TEXT,pesel TEXT ,tel TEXT,email  TEXT, miejscowosc TEXT,kod TEXT,poczta TEXT,data_ur TEXT,miejsce_ur TEXT,imie_ojca TEXT,imie_matki TEXT,skarbowy TEXT,skarbowy_adres TEXT,notatki TEXT,czy_student BOOLEAN, primary key(id)) '

TAKĄ TABELĘ TWORZYSZ RAZ PRZY TWORZENIU BAZY!!! NIE NISZCZYSZ JEJ!!!

  1. dodaj sobie do type TPracownicy = record pole id : Integer - tam wczytujesz id z bazy
  2. dodaj sobie do type TPracownicy = record pole modyfikacja : Boolean;, przy odczycie z bazy ustaw sobie na False.
  3. Jedyny fragment musisz dorobić, żeby przy zmianie danych dla tego użytkownika zmieniało to pole na True
  4. Przy zapisie nie usuwasz tabeli, ale sprawdzasz czy modyfikacja = True i jeśli tak to robisz update dla rekordu w bazie o zapisanym przy tym pracowniku id - poszukaj składni update, jeśli dalej jest False to nic nie robisz

Zobaczysz jak życie przyśpieszy ;)

ps. żeby nie było niejasności :] - nowego pracownika dodajesz do tabeli używając insert, istniejącemu w bazie zmieniasz dane używając update

0

Dziękuję koledzy za pomoc, wiem że ta baza nie jest poprawna, ale tak to jest jak się na początku nie wie jak ma to wyglądać i dopisuje się kolejne rzeczy to wtedy wychodzi niezły bałagan. Teraz pewnie zabrałbym się do tego zupełnie inaczej. Ale pisać od początku coś co już ma około 2500 linijek kodu, tym bardziej że już działa i liczy poprawnie, i jest to aplikacja pod jedno konkretne działanie to chyba warto tylko ją trochę dopieścić....

Dziękuję za podpowiedzi, dokonam takich zmian jak koledzy pisali. Faktycznie pewne rzeczy robiłem bez sensu....

Poprawię i dam znać czy pomogło. Jeszcze raz dzięki!

0

BTW: pracownik[i].pesel := StrTOINt64(SQL_Tabela.FieldByName['pesel']); - troszkę ryzykowne, nie uważasz? Pesel osoby urodzonej 1 stycznia 2000 roku (już za kilka lat będzie mógł pracować) to np 00210109614, a co Twój program z niego zrobi? :]

0

hmm... czyli wszystkie pesele trzymać jako stringi?

usunięcie cytowania całego poprzedniego posta - fp

0

Pomimo, że to się nazywa "numer pesel" to jest de facto ciąg cyfr o stałej długości, gdzie na pierwszym miejscu ma prawo wystąpić zero. Pokazałem Ci powyżej jaki to może nieść za sobą błąd.
Trzymaj jako string. Nawet jeśli będziesz kiedyś sprawdzał poprawność tego numeru łatwiej jest to robić z poziomu stringa niż "liczby".

Zresztą, patrząc na Twój kod tak na szybko miałbym jeszcze ze dwie uwagi, ale najpierw pytanie: jakiej biblioteki używasz do sqlite?

0

No dobra... zakładam, że używasz tego: http://www.itwriting.com/blog/?page_id=659

Tego typu kod:

SQL_Baza.ExecSQL('INSERT INTO pracownicy VALUES ("' + pracownik[i].imie +'", "' + pracownik[i].nazwisko +'","' +  IntToStr(pracownik[i].pesel)+ '","' +      pracownik[i].tel + '","'+ pracownik[i].email + '","' + pracownik[i].miejscowosc
     +'","' + pracownik[i].kod+'","' + pracownik[i].poczta +'","' + pracownik[i].data_ur +'","' + pracownik[i].miejsce_ur
      +'","' + pracownik[i].imie_ojca +'","' + pracownik[i].imie_matki +'","' + pracownik[i].skarbowy +'","' + pracownik[i].skarbowy_adres +'","' + pracownik[i].notatki
       +'","' + BoolToStr(pracownik[i].czy_student) +'")');

Jest nie dość, że brzydki i bałaganiarski, to niesie za sobą możliwość popełnienia masy błędów. Wspomniana wyżej biblioteka posiada polecenie AddParamText, większość bibliotek do obsługi baz danych coś takiego posiada. Pozwala to na spokojne wpisanie polecenia sql w czytelnej postaci i podania parametrów do tego sql-a. W Twoim przypadku ten paskudny zapis wyglądałby wtedy tak (pomijam tu, że ta tabela i tak powinna być inaczej zbudowana):

var
      sql : String;
begin
sql := 'INSERT INTO pracownicy VALUES (:imie, :nazwisko, :pesel, :tel, :email, :miejscowosc, :kod, :poczta, :data_ur, :imie_ojca, :imie_matki, :skarbowy, :skarbowy_adres, :notatki, :czy_student)';

SQL_Baza.AddParamText(':imie', pracownik[i].imie);
SQL_Baza.AddParamText(':nazwisko', pracownik[i].nazwisko);
//tu reszta parametrów

SQL_Baza.ExecSQL(sql);

Po prostu wpisujesz sobie cały sql, w miejscach gdzie coś wstawiasz dajesz np. :imie, a potem podstawiasz taki parametr poprzez SQL_Baza.AddParamText(':imie', pracownik[i].imie); - ładne, czytelne i nie daje okazji do popełnienia błędów... I łatwe do modyfikacji, gdyby przyszła taka potrzeba.

To oczywiście jest tylko przykład (nie uwzględnia pętli, czy innych zasad Twojego programu), ale pokazuje jak sobie ułatwić życie...
Jeśli nie ma takiej funkcji w bibliotece, którą używasz, to to jest akurat bardzo łatwe do napisania sobie samemu ;)

Ps. Oczywiście, żeby nie było, że tylko text... AddParamInt, AddParamFloat, AddParamNull - zależnie do typu parametru jaki podstawiasz ;)


I kolejne pytanie, w pętli odczytujesz pracowników do struktury, zapisujesz ich też do CheckListBox'a:

CheckListBox1.Items.Add(pracownik[i].nazwisko + ' ' + pracownik[i].imie) ;
CheckListBox1.Checked[i]:= pracownik[i].czy_student ;

Jak wczytujesz dane wybranego pracownika do edycji czy wyświetlenia po kliknięciu na listę??? To tak z ciekawości, bo od razu powiem, że to powinno być zrobione inaczej, a na pewno bez ładowania wszystkich dostępnych danych do pamięci :]

1 użytkowników online, w tym zalogowanych: 0, gości: 1