Relacje pomiędzy dwiema tabelami - pobieranie danych z użyciem dbExpress

0

Witam,

To jest mój pierwszy post tutaj na forum - witam wszystkich i proszę o zrozumienie:)

Mam problem z utworzeniem relacji pomiędzy dwoma tablicami.

Pierwsza tablica to:

SQLDataSet1.CommandText:='CREATE TABLE users ('+
  'id INT(11) NOT NULL AUTO_INCREMENT,'+
  'name VARCHAR(255) NOT NULL DEFAULT "",'+
  'password VARCHAR(255) NOT NULL DEFAULT "",'+
  'permissions SMALLINT(2) NOT NULL DEFAULT "0",'+
  'UNIQUE KEY id (id)'+
  ') TYPE=MyISAM;';
  if SQLDataSet1.ExecSQL(True) = 0 then ShowMessage('Table users created');

Druga tablica to:

SQLDataSet1.CommandText:='CREATE TABLE visualcontrol ('+
  'id INT(11) NOT NULL AUTO_INCREMENT,'+
  'number VARCHAR(255) NOT NULL DEFAULT "",'+
  'name VARCHAR(255) NOT NULL DEFAULT "",'+
  'date datetime NOT NULL DEFAULT "0000-00-00 00:00:00",'+
  'result VARCHAR(16) NOT NULL DEFAULT "",'+
  'controller_id INT(11) NOT NULL DEFAULT "0",'+
  'UNIQUE KEY id (id)'+
  ') TYPE=MyISAM;';
  if SQLDataSet1.ExecSQL(True) = 0 then ShowMessage('Table visualcontrol created');

Powstawiałem trochę danych do jednej i drugiej tablicy.

Polecenia:

SQLDataSet1.CommandText:='SELECT * FROM users';
  SQLDataSet1.Open;

oraz:

SQLDataSet1.CommandText:='SELECT * FROM visualcontrol';
  SQLDataSet1.Open;

dają poprawne rezultaty i odczytuję te dane tak jak powinny być.

Jednak, gdy chcę nawiązać relację pomiędzy tymi dwiema tablicami, gdzie visualcontrol.controller_id=users.id, tak jak poniżej:

SQLDataSet1.CommandText:='SELECT visualcontrol.id, visualcontrol.number, visualcontrol.name, visualcontrol.date, visualcontrol.result, visualcontrol.controller_id, users.name FROM (visualcontrol,users) WHERE (visualcontrol.controller_id=users.id)';

wyskakuje błąd, że nieznane jest sformułowanie users.id.

Gdy w klazuli:

FROM (visualcontrol,users)

zamienię miejscami te dwie tablice, błąd dotyczy wówczas visualcontrol.controller_id.

Podejrzewam, że w jakiś sposób TSQLDataSet obsługuje tylko jedną tablicę z klazuli FROM, a o drugiej nie chce słyszeć.
Zaznaczam, że ta komenda wypisana w phpMyAdmin działa całkiem poprawnie.

Czy ktoś może wie, w jaki sposób można zrobić tę relację, czy lepiej będzie użyć innych komponentów do tego celu (np. ADO?)

Pozdrawiam i dziękuję za podpowiedzi,
Wojtek

dodanie znaczników <code class="delphi"> - furious programming

1

Zdaje się że chodzi o jakiś JOIN ale nie wiem co dokładnie chcesz uzyskać.

1

wszystkie nawiasy w tych zapytaniach (select) są zbędne i bardzo możliwe, że to one stanowią problem

0

Witam,

JOIN służy do permanentnego złączenia tablic. Mi chodzi o to, aby kolumnę w tablicy 1 (visualcontrol.controller_id) zastąpić kolumną z tablicy 2 (users.name). Do tego służy relacja pomiędzy tablicami, i chodzi o to, że wpisując to polecenie (nieważne czy z nawiasami czy bez) poprzez phpMyAdmin poprawnie działa, a poprzez dbExpress w Delphi już nie.

0
light.archer napisał(a):

JOIN służy do permanentnego złączenia tablic.
Kto ci takich głupot naopowiadał.
Usuń nawiasy.

1
light.archer napisał(a):

Witam,
JOIN służy do permanentnego złączenia tablic.

W Twojej mózgownicy chyba...

Mi chodzi o to, aby kolumnę w tablicy 1 (visualcontrol.controller_id) zastąpić kolumną z tablicy 2 (users.name). Do tego służy relacja pomiędzy tablicami,

Do tego właśnie służy JOIN, a dokladnie LEFT JOIN. Relacja to jest zupelnie co innego, wg ISO relacja to de-facto tabela (tu kopara w dół, ale zanim coś napiszesz doczytaj...).
Ty pewnie masz na myśli jeszce co innego, a mianowci kluicze obce. I one służą, mniej więcej, do tego co napisałeś...

i chodzi o to, że wpisując to polecenie (nieważne czy z nawiasami czy bez) poprzez phpMyAdmin poprawnie działa, a poprzez dbExpress w Delphi już nie.

I to jest problem bo powinno działać i w dbExpress. Ciekawe po co ktoś jeszcze tego badziewia używa?
No ale nic, może da sie to ogarnąć wpsiując dodatkowe parametry do połączenia, ale na pewno zadziała tak:

SELECT VC.id,
       VC.number,
       VC.name,
       VC.date,
       VC.result,
       VC.controller_id,
       U.name
FROM visualcontrol VC 
lef join users U on U.id = VC.controller_id
0

Witam,

Dziękuję bardzo za odpowiedzi (i mycie głowy)

Poprawny kod wykorzystując komponent TSQLDataSet powinien wyglądać tak:

SQLDataSet1.CommandText:='SELECT VC.id, VC.number, VC.name, VC.date, VC.result, VC.controller_id, U.controller_name FROM visualcontrol VC LEFT JOIN users U ON U.id=VC.controller_id';
SQLDataSet1.Open;

Następnie aby odczytywać kolejne rekordy (do StringGrida), należy:

StringGrid2.RowCount:=SQLDataSet1.RecordCount+1;
  StringGrid2.Cells[0,0]:='Id';
  StringGrid2.Cells[1,0]:='Number';
  StringGrid2.Cells[2,0]:='Name';
  StringGrid2.Cells[3,0]:='Date';
  StringGrid2.Cells[4,0]:='Result';
  StringGrid2.Cells[5,0]:='Controller ID';
  StringGrid2.Cells[6,0]:='Controller name';
  For i:=0 to SQLDataSet1.RecordCount-1 do
  Begin
    StringGrid2.Cells[0,i+1]:=SQLDataSet1.FieldValues['id'];
    StringGrid2.Cells[1,i+1]:=SQLDataSet1.FieldValues['number'];
    StringGrid2.Cells[2,i+1]:=SQLDataSet1.FieldValues['name'];
    StringGrid2.Cells[3,i+1]:=SQLDataSet1.FieldValues['date'];
    StringGrid2.Cells[4,i+1]:=SQLDataSet1.FieldValues['result'];
    StringGrid2.Cells[5,i+1]:=SQLDataSet1.FieldValues['controller_id'];
    StringGrid2.Cells[6,i+1]:=SQLDataSet1.FieldValues['controller_name'];
    SQLDataSet1.Next;
  End;
  SQLDataSet1.Close;

Przy czym jest tutaj pułapka, aby nazwy kolumn (nawet w różnych tabelach) miały inne nazwy (pierwotnie w tabeli visualcontrol i users była ta sama kolumna name, ale w tym przypadku musiałem w tabeli users zmienić ją na controller_name).

Skoro kolega powiedział, że dbexpress to badziew i ja chyba jestem jedynym, który go używa, to czy mógłby zasugerować inny "engine", który pozwoli na komercyjne zastosowanie?

Pozdrawiam i dziękuję bardzo za pomoc.

0

pomijam problem zapytania do bazy , ale czy do pobrania danych z wyniku zapytania nie była by lepsza konstrukcja :

if not SQLDataSet1.isempty then 
begin
   while not SQLDataSet1.eof do 
     begin
        /// tutaj odczyt i podstawienie do zmiennych 
        ///
       SQLDataSet1.next
     end
end
0

Mogłaby być lepsza, ale i tak potrzebowałem zmiennej i aby wstawiać do kolejnych wierszy StringGrida.

0

No to co za problem zwiększać sobie zmienną pomocniczą w pętli while ... do i przypisywać do kolejnych wierszy StringGrid. Musisz się ogarnąć. Przecież już tak proste rzeczy jak operacje na komponencie w pętli, nawet niezwiązane z bazą danych toż to banalne i elementarne podstawy podstaw :/

0
light.archer napisał(a):

Witam,

Dziękuję bardzo za odpowiedzi (i mycie głowy)

Poprawny kod wykorzystując komponent TSQLDataSet powinien wyglądać tak:

SQLDataSet1.CommandText:='SELECT VC.id, VC.number, VC.name, VC.date, VC.result, VC.controller_id, U.controller_name FROM visualcontrol VC LEFT JOIN users U ON U.id=VC.controller_id';
SQLDataSet1.Open;

Następnie aby odczytywać kolejne rekordy (do StringGrida), należy:

StringGrid2.RowCount:=SQLDataSet1.RecordCount+1;
  StringGrid2.Cells[0,0]:='Id';
  StringGrid2.Cells[1,0]:='Number';
  StringGrid2.Cells[2,0]:='Name';
  StringGrid2.Cells[3,0]:='Date';
  StringGrid2.Cells[4,0]:='Result';
  StringGrid2.Cells[5,0]:='Controller ID';
  StringGrid2.Cells[6,0]:='Controller name';
  For i:=0 to SQLDataSet1.RecordCount-1 do
  Begin
    StringGrid2.Cells[0,i+1]:=SQLDataSet1.FieldValues['id'];
    StringGrid2.Cells[1,i+1]:=SQLDataSet1.FieldValues['number'];
    StringGrid2.Cells[2,i+1]:=SQLDataSet1.FieldValues['name'];
    StringGrid2.Cells[3,i+1]:=SQLDataSet1.FieldValues['date'];
    StringGrid2.Cells[4,i+1]:=SQLDataSet1.FieldValues['result'];
    StringGrid2.Cells[5,i+1]:=SQLDataSet1.FieldValues['controller_id'];
    StringGrid2.Cells[6,i+1]:=SQLDataSet1.FieldValues['controller_name'];
    SQLDataSet1.Next;
  End;
  SQLDataSet1.Close;

Nie do końca się z tym zgadzam...
Może tak - jednym z wielu poprawnych rozwiązań ładowania danych z TDataSet do StringGrida jest taka procedura:

procedure LoadData(AStringGrid: TStringGrid; ADS: TDataSet);
var
  lCol  : Integer;
  lField: TField;
begin
  AStringGrid.ColCount  := ADS.FieldCount;
  AStringGrid.RowCount  := 1;

  lCol := 0;
  for lField in ADS.Fields do
  begin
    AStringGrid.Cells[lCol, 0] := lField.DisplayLabel;
    AStringGrid.Cols[lCol].BeginUpdate;
    Inc(lCol);
  end;

  ADS.First;
  ADS.DisableControls;
  try
    while not ADS.Eof do
    begin
      lCol := 0;
      for lField in ADS.Fields do
      begin
        AStringGrid.Cells[lCol, AStringGrid.RowCount] := lField.Text;
        Inc(lCol);
      end;

      ADS.Next;
      AStringGrid.RowCount := AStringGrid.RowCount + 1;
    end;
  finally
    ADS.EnableControls;
  end;

  lCol := 0;
  for lCol := 0 to AStringGrid.Color - 1 do
    AStringGrid.Cols[lCol].EndUpdate;

  AStringGrid.FixedCols := 1;
  AStringGrid.FixedRows := 1;
end;

Zauważ, że to załaduje Ci dane z DataSeta o dowolnej strukturze. Łatwo to uzupełnić o takie rzeczy jak ładowanie tylko widocznych kolumn, lub tylko kolumn które chcemy ładować; Twój kod nie jest ani fajny ani do końca poprawny (DataSet.FieldValues zwraca Variant, wystarczy że pojawi się wartość null i Twój kod leci w kosmos.)

Przy czym jest tutaj pułapka, aby nazwy kolumn (nawet w różnych tabelach) miały inne nazwy (pierwotnie w tabeli visualcontrol i users była ta sama kolumna name, ale w tym przypadku musiałem w tabeli users zmienić ją na controller_name).

A po co mają mieć różne nazwy? Uważam, że ta sama informacja w różnych tabelach powinna mieć tą samą nazwę kolumny.

Skoro kolega powiedział, że dbexpress to badziew i ja chyba jestem jedynym, który go używa, to czy mógłby zasugerować inny "engine", który pozwoli na komercyjne zastosowanie?

FireDAC - na przykład.

0

@wloochacz, dzięki za odpowiedź. Ponieważ ten post odbiega już od głównego tematu tego wątku, czy można np. zmienić temat (dopisać o pobieraniu danych z użyciem dbexpress)?

Swoją drogą, jak piszesz, że jest to jeden z wielu poprawnych sposobów. Jak widzę, podobne rozwiązanie, z wykorzystaniem

 while not ADS.Eof do

jest opisane w MySQL w Delphi, z tym że nie ma nic tam wspomnianego o tym, żeby stosować typ TField. Opisano to tak:

 while not DM.SQLDataSet1.Eof do begin // petla
           id := DM.SQLDataSet1.FieldValues['id']; // przypisujemy wartości rekordu do zmiennych
           imie := DM.SQLDataSet1.FieldValues['imie'];
           nazwisko := DM.SQLDataSet1.FieldValues['nazwisko'];
           gg := DM.SQLDataSet1.FieldValues['gg'];
           telefon := DM.SQLDataSet1.FieldValues['telefon'];

W artykule Adama Boducha, o czym wspomniałem wcześniej w komentarzu pod postem (Rozdział 17),, jest napisane dokładnie tak jak sugerowałem:

 for I := 1 to SQL.RecordCount do
  begin
  { dodaj kolejne wartości }
    ListItem := ListView.Items.Add;
    ListItem.Caption := IntToStr(SQL.FieldValues['id']);
    ListItem.SubItems.Add(SQL.FieldValues['name']);
    ListItem.SubItems.Add(SQL.FieldValues['mail']);
    SQL.Next;
  end;

W każdym razie bardzo dziękuję za pomoc, Twoja propozycja jest bardzo ciekawa. Czy mógłbyś mi powiedzieć, skąd o tym się dowiedziałeś/nauczyłeś?

0
light.archer napisał(a):

@wloochacz, dzięki za odpowiedź. Ponieważ ten post odbiega już od głównego tematu tego wątku, czy można np. zmienić temat (dopisać o pobieraniu danych z użyciem dbexpress)?

Swoją drogą, jak piszesz, że jest to jeden z wielu poprawnych sposobów. Jak widzę, podobne rozwiązanie, z wykorzystaniem

 while not ADS.Eof do

jest opisane w MySQL w Delphi, z tym że nie ma nic tam wspomnianego o tym, żeby stosować typ TField. Opisano to tak:

Spójrz w kod TDataSet.FieldValues, a zobaczysz że używany jest tam TField i to dodatkowo dość naokoło... Po prostu FieldValues napisano po to, aby za jednym zamachem zwrócić wiele wartości z wielu pól jako wektor (tu - jednowymiarowa tablica Variantów).

 while not DM.SQLDataSet1.Eof do begin // petla
           id := DM.SQLDataSet1.FieldValues['id']; // przypisujemy wartości rekordu do zmiennych
           imie := DM.SQLDataSet1.FieldValues['imie'];
           nazwisko := DM.SQLDataSet1.FieldValues['nazwisko'];
           gg := DM.SQLDataSet1.FieldValues['gg'];
           telefon := DM.SQLDataSet1.FieldValues['telefon'];

W artykule Adama Boducha, o czym wspomniałem wcześniej w komentarzu pod postem (Rozdział 17),, jest napisane dokładnie tak jak sugerowałem:

 for I := 1 to SQL.RecordCount do
  begin
  { dodaj kolejne wartości }
    ListItem := ListView.Items.Add;
    ListItem.Caption := IntToStr(SQL.FieldValues['id']);
    ListItem.SubItems.Add(SQL.FieldValues['name']);
    ListItem.SubItems.Add(SQL.FieldValues['mail']);
    SQL.Next;
  end;

Tyle, że ten kod w poradniku jest nie tyle błędny, co nie do końca bezpieczny (korzystanie z Variant bez testowania czy jest null/unassigned, opieranie się na RecordCount, i pewnie wiele innych - nie czytałem całego), a cały poradnik powinien być... przepisany ;-)

W każdym razie bardzo dziękuję za pomoc, Twoja propozycja jest bardzo ciekawa. Czy mógłbyś mi powiedzieć, skąd o tym się dowiedziałeś/nauczyłeś?

Skąd wiem? Po prostu, programuję od dość dawna i coś tam, zwłaszcza o bazach danych w Delphi, wiem ;-)

0

Dzięki za szybką odpowiedź.

Jeśli chodzi o pytanie, czemu nadałem inne nazwy tych kolumn, to wynikało z tego:

    StringGrid2.Cells[0,i+1]:=SQLDataSet1.FieldValues['id'];
    StringGrid2.Cells[1,i+1]:=SQLDataSet1.FieldValues['number'];
    StringGrid2.Cells[2,i+1]:=SQLDataSet1.FieldValues['name'];
    StringGrid2.Cells[3,i+1]:=SQLDataSet1.FieldValues['date'];
    StringGrid2.Cells[4,i+1]:=SQLDataSet1.FieldValues['result'];
    StringGrid2.Cells[5,i+1]:=SQLDataSet1.FieldValues['controller_id'];
    StringGrid2.Cells[6,i+1]:=SQLDataSet1.FieldValues['controller_name'];

Nie dało się po prostu rozróżnić w tym przypadku tych kolumn, nie da się użyć visualcontrol.name czy users.name...

0
light.archer napisał(a):

Nie dało się po prostu rozróżnić w tym przypadku tych kolumn, nie da się użyć visualcontrol.name czy users.name...

OK, ale chodzi o to, że zmiana nazwy kolumny w tabeli jest niepotrzebna. Przecież zawsze możesz w SQL skorzystać z operatora AS, aby zmienić nazwę zwracanej kolumny dla danego zapytania, a więc:

SELECT VC.id,
       VC.number,
       VC.name,
       VC.date,
       VC.result,
       VC.controller_id,
       U.name as controller_name
FROM visualcontrol VC
LEFT JOIN users U ON (U.id = VC.controller_id)
0

Dzięki :)

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