Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

[Delphi] Odczyt rekordu bazy SQL.

papajondro 27 Sep 2010 15:01 6965 27
  • #1
    papajondro
    Level 11  
    Witam, pozwolę sobie odświeżyć ten temat. arnoldziq gdybyś mógł mi poradzić jak odczytać wartość zapytania do labela lub edita? Gdy wpisuje coś takiego, dostaję w wyniku 0.

    
    IBQuery1.SQL.Clear;
    Label1.Caption:=IBQuery1.SQL.add('select first 1 skip 3 kolumna_1 from tabela');
    IBQuery1.Open;
    


    Metodą prób i błędów zwróciłem tekst zapytania, ale wciąż nie wiem jak odwołać się do wartości.

    
    IBQuery1.SQL.Clear;
    IBQuery1.SQL.add('select first 1 skip 3 kolumna_1 from tabela');
    Label2.Caption:=IBQuery1.SQL.Text;
    IBQuery1.Open;
    


    Proszę o sugestie.

    Posty oddzieliłem. - arnoldziq
  • #2
    arnoldziq
    Moderator of Programming
    Spróbuj czegoś takiego :
    if IBQuery1.Active then IBQuery1.Close;
     IBQuery1.SQL.Clear;
     IBQuery1.SQL.Add('select * from users where id=10');
     IBQuery1.Open;
     if not IBQuery1.Eof then
      begin
        label1.caption:=IBQuery1.FieldByName('ID').AsString;
        label2.caption:=IBQuery1.FieldByName('NAME').AsString;
        label3.caption:=IBQuery1.FieldByName('LOGIN').AsString;    
      end;
     if IBQuery1.Active then IBQuery1.Close;

    Oczywiście sam SQL i nazwy pól ID,NAME,LOGIN występują w jednej z moich baz danych. Musisz to przystosować do swojej bazy.
  • #3
    papajondro
    Level 11  
    Dzięki za pomoc, to mnie nakierowało na takie rozwiązanie:

    
    IBQuery1.SQL.Clear;
    IBQuery1.SQL.add('select first 1 skip 3 kolumna from tabela');
    IBQuery1.Open;
    Label2.Caption:=IBQuery1.Fields[0].AsString;
    


    Działa świetnie.
  • #4
    arnoldziq
    Moderator of Programming
    papajondro wrote:
    Działa świetnie.

    Tak, pod warunkiem że chcesz przeczytać pierwsze pole w rekordzie.
    Jeżeli będzie tych pól kilka/kilkanaście, to odwoływanie się do niech poprzez ich numer, jest po prostu niewygodne i jest to prosta droga do popełnienia błędu.
    Jeżeli możesz, to zawsze używaj nazw pól. Będziesz miał pełną kontrolę nad rekordem.
  • #5
    papajondro
    Level 11  
    Dokładnie na tym mi zależało- chciałem odczytywać wartości z konkretnych wierszy w kolumnie.

    Dzięki za szybką odpowiedź.
  • #6
    papajondro
    Level 11  
    arnoldziq, wiem że masz doświadczenie w delphi/sql, dlatego też liczę na parę rad z twojej strony.

    W jaki sposób pod jednym przyciskiem wykonać kilka zapytań do bazy,np. najpierw update wartości w kolumnie a potem select tej wartości z bazy.
    Próbowałem wstawiać pomiędzy nie ExecSQL, ale to też nic nie dało.

    Form2.IBQuery1.SQL.Clear;
      Form2.IBQuery1.SQL.Add('update nastawy set kasuj_p1 = 1');
      Form2.IBQuery1.SQL.Add('select kasuj_p1 from nastawy');
      Form2.IBQuery1.Open;
  • #7
    arnoldziq
    Moderator of Programming
    Możesz to zrobić pod jednym przyciskiem. Ale trochę inaczej.
    W tym samym czasie, TQuery, może przetwarzać tylko jedno polecenie.
    Poza tym, uruchomienie skryptu SLQ/UPDATE oraz SQL/SELECT odrobinkę się różni.
    Warto by, także, zapisać zmiany do bazy danych.
    procedure TForm2.Button1Click(Sender: TObject);
    begin
     if form2.IBTransaction1.InTransaction then Form2.IBTransaction1.Commit;
     Form2.IBTransaction1.StartTransaction;
     if Form2.IBQuery1.Active then Form2.IBQuery1.Close;
     Form2.IBQuery1.SQL.Clear;
     Form2.IBQuery1.SQL.Add('update nastawy set kasuj_p1 = 1');
     Form2.IBQuery1.ExecSQL;
     if form2.IBTransaction1.InTransaction then Form2.IBTransaction1.Commit;
    
     Form2.IBTransaction1.StartTransaction;
     if Form2.IBQuery1.Active then Form2.IBQuery1.Close;
     Form2.IBQuery1.SQL.Clear;
     Form2.IBQuery1.SQL.Add('select kasuj_p1 from nastawy');
     Form2.IBQuery1.Open;
     if form2.IBTransaction1.InTransaction then Form2.IBTransaction1.Commit;
     Form2.IBTransaction1.StartTransaction;
    end;
  • #8
    papajondro
    Level 11  
    Wielkie dzięki, właśnie tego mi brakowało. Jeszcze chciałem Cię zapytać dlaczego jak wpisuję w polu Memo zapytanie update to dostaję informację "list index out of bounds(0)", a samo zapytanie zostaje wykonane.

    
    IBQuery1.SQL.Clear;
    IBQuery1.SQL.text:=Memo1.Text;
      if pos('select ',Memo1.text)<>0 then
        IBQuery1.Open
      else
        IBQuery1.ExecSQL;
    Label2.Caption:=IBQuery1.Fields[0].AsString;
    
  • #9
    arnoldziq
    Moderator of Programming
    papajondro wrote:
    "list index out of bounds(0)", a samo zapytanie zostaje wykonane.

    Nawet gdy zapytanie zostanie wykonane poprawnie, z różnych przyczyn, wynik zapytania może być pusty.
    W takim przypadku, przy próbie odczytu w w/w sposób, otrzymasz właśnie taki błąd, bo próbujesz odczytać element 0 z listy Fields, która po prostu nie istnieje.
    Można się przed tym zabezpieczyć. Np. w ten sposób :

    if NOT IBQuery1.EOF then
       Label2.Caption:=IBQuery1.Fields[0].AsString
    else
       Label2.Caption:='Brak szukanych danych.';
    
  • #10
    papajondro
    Level 11  
    Ok, wprowadzę zmiany. Mam pytanie dotyczące stricte sql'a w firebird, jest jakiś sposób aby śledzić zmiany na serwerze w komórkach, zamiast permanentnego odczytywania za pomocą aplikacji? Można to jakoś monitorować ?
  • #11
    arnoldziq
    Moderator of Programming
    Można, przynajmniej na kilka sposobów.
    Pierwszy i najprostszy, to dodać do rekordu znacznik modyfikacji.
    Drugi, to tak zwany Trigger. Jest to skrypt wykonywany automatycznie przez serwer, w przypadku zauważenia przez serwer zmian w zawartości bazy danych.
  • #12
    papajondro
    Level 11  
    Quote:
    Można, przynajmniej na kilka sposobów.
    Pierwszy i najprostszy, to dodać do rekordu znacznik modyfikacji.
    Drugi, to tak zwany Trigger. Jest to skrypt wykonywany automatycznie przez serwer, w przypadku zauważenia przez serwer zmian w zawartości bazy danych.


    Czy mógłbyś szerzej na ten temat? Ewentualnie mógłbym prosić linki do wiedzy ?
  • #13
    arnoldziq
    Moderator of Programming
    W pierwszym przypadku dodajesz tylko dodatkowe pole do tabeli. Chyba wiesz jak to zrobić :). Trzeba je tylko potem ustawić w trakcie zapisywania/zmiany danych.
    Drugi przypadek, tzn. Trigger, jest ładnie opisany np. tutaj lub tutaj .

    -=Edit=-
    Ale wiesz co? Napisz dokładnie o co ci chodzi z tym sprawdzaniem, bo chyba nie do końca chodzi nam o to samo.
  • #14
    papajondro
    Level 11  
    Mam tabelę, w której mam zapisane stany wejść i kilka rejestrów ze sterownika PLC. Chciałbym zmieniać wejścia zdalnie poprzez bazę. W momencie kiedy nastąpiła zmiana stanu któregoś wejścia 0->1, 1->0, chciałbym wykonać odpowiedni kod w delphi, który prześle mi to do sterownika. Mogę odpytywać bazę co jakiś czas, ale wydaje mi się to tylko niepotrzebnie spowalniać aplikację.
  • #15
    arnoldziq
    Moderator of Programming
    Możesz coś takiego zrobić właśnie dzięki Trigger-owi i kontrolce TIBEvents.
    Uruchamiasz Trigger w przypadku zmiany zawartości rekordu i wysyłasz event, który informuje aplikację, że nastąpiła jakaś zmiana w bazie danych.
    W ten sposób, nie musisz odpytywać co chwila bazy danych, czy zaszły w niej jakieś zmiany. Serwer sam cię o tym powiadomi.
  • #16
    papajondro
    Level 11  
    Dzięki za pomoc. Utworzyłem taki trigger:
    create trigger p1_kasuj for nastawy after update position 0 
    as
    begin
       if (new.kasuj_p1<>old.kasuj_p1 and new.kasuj_p1=1) then post_event 'p1_kasuj_1';
       if (new.kasuj_p1<>old.kasuj_p1 and new.kasuj_p1=0) then post_event 'p1_kasuj_0';
    end


    w delphi odwołuję się poprzez:
    if EventName='p1_kasuj_0' then wykonaj kod dla 0
    else wykonaj kod dla 1;


    i działa pięknie :D
  • #17
    papajondro
    Level 11  
    arnoldziq, czy możliwe jest zmiana formatowania czasu zapisywanego do bazy. Mam kolumnę czas, do której zapisuję aktualny czas- current_time w formacie hh:mm:ss, jednak chciałbym jeszcze dołożyć do tego milisekundy.

    Do bazy są zapisywane zmienne co 1s, jednakże jak to bywa z wątkami w delphi, dostaję w efekcie kilka wpisów z tym samym czasem lub z czasem różniącym się między wpisami o sekundę.

    [Delphi] Odczyt rekordu bazy SQL.

    Tego ustrojstwa już chyba nie zwalczę, więc chciałem zwiększyć dokładność logowania czasu.
  • #18
    arnoldziq
    Moderator of Programming
    Jest to jak najbardziej możliwe. Można zapisywać do bazy czas z dokładnością do milisekund.
    Musisz jednak ustawić precyzję zapisu CURRENT_TIME lub zacząć używać CURRENT_TIMESTAMP.
    Standardowa (default) precyzja CURRENT_TIME to 0, co oznacza rozdzielczość 1 sekundy. Standardowa precyzja CURRENT_TIMESTAMP to 3, co oznacza dokładność zapisu 1 tysięczna sekundy.
    Przygotuj się także, że większość komponentów, będzie takie pole bazy danych, traktować jako zwykłe pole zawierające datę i czas. Co za tym idzie, nie będę pokazywać milisekund. Sam musisz je odczytać i zinterpretować.
  • #19
    papajondro
    Level 11  
    Dzięki wielkie, nareszcie mam to o co chodzi :D
  • #20
    papajondro
    Level 11  
    Walczę teraz z zapytaniem between, mianowicie zapytanie w IBExpert:

    select * from scada where data between '07.10.2010' and '08.10.2010';


    zwraca poprawne wyniki- no nie ma cudów, inaczej być nie może. Walczyłem z delphi, aż doszedłem do tego i działa:

    Form2.IBQuery1.SQL.Add('select * from scada where data between ''07.10.2010'' and ''08.10.2010''');


    ale gdy podstawię zmienne to już cyrk się dzieje:

    od_data:='07.10.2010';
    do_data:='08.10.2010';
    if Form2.IBQuery1.Active then
    Form2.IBQuery1.Close;
    Form2.IBQuery1.SQL.Clear;
    Form2.IBQuery1.SQL.Add('select * from scada where data between ''07.10.2010'' and ''08.10.2010''');
    Form2.IBQuery1.Open;

    błąd: "conversion error from string 'od daty'"

    próbowałem to zrobić inaczej:

    
    if Form2.IBQuery1.Active then
        Form2.IBQuery1.Close;
      Form2.IBQuery1.SQL.Clear;
      Form2.IBQuery1.SQL.Add('select * from scada where data between :od_daty and :do_daty');
      Form2.IBQuery1.ParamByName('od_daty').AsDate:=StrToDate(Edit1.Text);
      Form2.IBQuery1.ParamByName('do_daty').AsDate:=StrToDate(Edit2.Text);
      Form2.IBQuery1.Open;
    


    błąd:"07.10.2010 is not a valid date"
    ________________________________________________

    Chociaż sobie poradziłem z tym, nadal mnie nurtuje dlaczego nie działa powyższy sposób z ParamByName?

    Ja poradziłem sobie tak:
    
    od_data:=Edit1.Text;
    do_data:=Edit2.Text;
    if Form2.IBQuery1.Active then
      Form2.IBQuery1.Close;
    Form2.IBQuery1.SQL.Clear;
    Form2.IBQuery1.SQL.Add('select * from scada where data between '''+od_data+''' and '''+do_data+'''');
    Form2.IBQuery1.Open;
    
  • #21
    arnoldziq
    Moderator of Programming
    Wychodzisz z błędnego założenia !
    Błąd "07.10.2010 is not a valid date" nie jest błędem parametru TQuery, tylko błędem konwersi StrToDate.
    Pomysł z parametrem zapytania SQL był bardzo dobry, tylko format zapisu daty który próbujesz wymusić, nie bardzo jest zgodny z twoimi ustawieniami systemowymi :)

    Odpal sobie w swoim programie taki kod :
    showmessage(fornatDateTime('dd.mm.yyyy',StrToDate('2010-10-09')));
    a potem :
    showmessage(fornatDateTime('dd.mm.yyyy',StrToDate('09/10/2010')));
    i na końcu :
    showmessage(fornatDateTime('dd.mm.yyyy',StrToDate('09.10.2010')));

    Zobaczysz co się stanie.
  • #22
    papajondro
    Level 11  
    Dzięki już wiem o co chodzi :D.

    Że tak jeszcze pozwolę sobie o coś spytać. Próbowałem odczytać liczbę wierszy z DBGrid i już nie wiem o co chodzi.

    label3.Caption:=IntToStr(Form2.IBQuery1.RecordCount);

    wynik 18, czyli tyle ile DBGrid mi wyświetla jednorazowo, wystarczy przesunąć scrollbar'a żeby zobaczyć, że rekordów jest więcej niż 18.

    type
      TGrid = class(TDBGrid);
    label3.Caption:=IntToStr(TGrid(DBGrid1).RowCount);

    wynik 19

    type
      TGrid = class(TDBGrid);
    label3.Caption:=IntToStr(TGrid(DBGrid1).VisibleRowCount);

    wynik 18

    Czy jest jakiś inny sposób na odczytanie liczby rekordów w DBGrid, albo chociaż zaznaczenie ostatniego rekordu bo wtedy mógłbym odczytać jego nr poprzez:

    label3.Caption:=IntToStr(Form2.IBQuery1.RecNo);
  • #23
    arnoldziq
    Moderator of Programming
    Każdy obiekt klasy TGrid liczy rzędy (Rows) zaczynając od rzędu 0, czyli od nagłówków kolumn.
    Jeżeli nie podasz do TGrid żadnych danych, to i tak zwrócona ilość rzędów będzie 1, ponieważ są liczone nagłówki.

    Natomiast połączenie TDBGrid i TSQLQuery, jest dość "nieszczęśliwe". TDBGrid zostało stworzone do współpracy z bazami danych z dostępem opartym na BDE, i łączenie go z komponentami IB/FB nie ma większego sensu.
    Zrób sobie raczej własną obsługę i wypełniaj normalnego TStringGrid-a.
  • #24
    papajondro
    Level 11  
    Arnoldziq, zważywszy na fakt, iż nie chciałem słuchać kolegów starszych stażem, teraz walczę z zapytaniem mającym zwracać mi wartości z podanego zakresu czasu. Mam dwie kolumny: data, czas. Potrzebuję wyszukać wartości pomiędzy konkretnymi datami i godzinami. Ponieważ nie stworzyłem jednego pola timestamp, musiałem złączyć te dwa pola w jedno:

    select cast(data+czas as timestamp) from scada;


    I teraz problem, bo przy zapytaniu BETWEEN dostaję błąd: "Unsupported field type specified in BETWEEN predicate".
    select * from scada where (select cast(data+czas as timestamp) from scada) between '07.10.2010 00:00:00' and '08.10.2010 12:00:00';

    gdy zamienię to na zwykłą nierówność:
    select * from scada where ((select cast(data+czas as timestamp) from scada) >= '07.10.2010 00:00:00') and ((select cast(data+czas as timestamp) from scada) <= '08.10.2010 12:00:00');

    dostaję błąd: "multiple rows in singleton selection"
  • #25
    arnoldziq
    Moderator of Programming
    Utworzenie dwóch osobnych pól na datę i czas, od początku było chyba złym pomysłem.
    Ale to zapytanie ma ciągle szansę zadziałać :)
    W zależności, jak masz zadeklarowane te pola CZAS i DATA, możesz spróbować czegoś takiego:
    select * from scada 
    where CAST(DATA ||' '|| CZAS as TIMESTAMP) between '07.10.2010 00:00:00' and '08.10.2010 12:00:00';
    Widziałem już taki kod i "jakoś to działało", chociaż ja , osobiście nie polecam.

    Jeżeli naprawdę nie możesz już użyć jednej z tych kolumn, do przechowania pełnej daty wraz z czasem, to pozostaje ci jedno, bezpieczne i skuteczne rozwiązanie : dwa osobne parametry opisujące czas i dwa opisyjace datę. Coś takiego jak :
    select * from scada 
    where (DATA between ?Start_Data and ?Koniec_Data) and 
          (CZAS between ?Start_Czas and ?Koniec_Czas)

    Możesz także pokusić się o konwersję bezpośrednią, ale nie jestem przekonany do tego pomysłu :
    select * from scada 
    where (DATA between '07.10.2010' and '07.10.2010') and 
          (CZAS between '00:00:00' and '01:01:01')


    Zamieść może definicję tej swojej tabeli, to wymyślimy jeszcze coś innego.
  • #26
    papajondro
    Level 11  
    Próbowałem z
    CAST(DATA ||' '|| CZAS as TIMESTAMP)
    , ale ciągle dostaję błąd : "Unsuccessful execution caused by an unavailable resource. Unsupported field type specified in BETWEEN predicate."

    Wcześniej szukałem w ten sposób:

    select * from scada where (data between '07.10.2010' and '08.10.2010') and (czas between '12:00:00' and '14:00:00')


    ale dostaję dane pomiędzy godziną 12-14 z daty 07.10.2010 i 12-14 z daty 08.10.2010.

    Ja potrzebuję wydobyć dane z bazy z godziny 12 dnia 07.10.2010 do godziny 14 dnia 08.10.2010. Wiem, że timestamp by to załatwił za mnie od razu ale nie mogę teraz zmienić już typu pól. I dlatego chciałem je połączyć w pole timestamp. Ale to znowuż owocuje błędem.
  • #27
    arnoldziq
    Moderator of Programming
    Jak masz zadeklarowane tablicę scada ?
    Możesz podać jej zapis tekstowy ?

    Dodano po 21 [minuty]:

    Wiesz co, w między-czasie zadeklarowałem sobie tablicę, tak jak myślę że ty masz ją zadeklarowaną :
    CREATE TABLE SCADA (
      DATA DATE,
      CZAS TIME,
      DANE CHAR(1));

    Wprowadziłem kilka czasów i dat.. i okazało się, ze żadne CAST nie jest kompletnie potrzebne. :)
    Bez żadnych problemów działa takie zapytanie :
    SELECT CZAS,DANE,DATA FROM SCADA 
    where ((scada.DATA + scada.CZAS) >= '2010-10-18 12:00:00')
    and ((scada.DATA + scada.CZAS) <= '2010-10-19 12:05:00')
    
  • #28
    papajondro
    Level 11  
    Powinienem Cię ozłocić, lub chociażby wziąć na piwo. Dzięki za pomoc przy tym. Chociaż nadal zastanawia mnie dlaczego gdy jest BETWEEN dostaję komunikat błędu. Ważne, że teraz jest ok.