Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Manipulation eines Records in einer FDQuery (https://www.delphipraxis.net/213206-manipulation-eines-records-einer-fdquery.html)

Ykcim 15. Jun 2023 09:24

Datenbank: MsSQL • Version: 14 • Zugriff über: FireDac

Manipulation eines Records in einer FDQuery
 
Guten Morgen Zusammen,

seit einigen Tagen verzweifle ich an dem Versuch, einen Wert in einem Feld in einer FDQuery zu ändern. Der Hintergrund besteht darin, dass ich die Information von einem anderen Datenbank-Server abrufen muss und deshalb nicht alles in der gleichen Query durch führen kann...

Delphi-Quellcode:
MsQuery.SQL.Add('SELECT TOP 1 '+
                                'CASE WHEN ( '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME) is NULL) then '+
                                  'LAG(Concat(rj.JOB_ID, ' + QuotedStr(' ') + ', rj.JOB_NAME)) OVER (ORDER BY re.TIME_LOCAL DESC) ELSE '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ',rj.JOB_NAME) end AS JOB_NAME, '+
                                'CONVERT( varchar( 10 ), re.Time_Local, 104 ) AS Datum, '+
                                'convert(char(5), re.Time_Local, 108) AS Startzeit, '+
                                'rd.device_id, '+
                                'rd.device_name, '+
                                'ro.OPERATION_NAME, '+
                                'concat(ry.FIRST_NAME,' +QuotedStr(' ') + ', ry.FAMILY_NAME) AS Operator, '+
                               
...

                         'ORDER BY re.TIME_LOCAL desc ');
         MsQuery.ParamByName('Device_ID').AsString := Machine_ID;
         MsQuery.Open;
         DB_Modul.Write_LM_Protokoll('Refresh Daten Maschine ' + Machine_ID);


         CDMQuery.SQL.Clear;
         CDMQuery.sql.Add('select * from CDM_SM_DEVICE_OP_STATUS '+
                          'where deviceID = :Device '+
                          'and roleID = ' + QuotedStr('ID_Operator'));
         CDMQuery.ParamByName('Device').AsString := MsQuery.FieldByName('device_id').AsString;
         CDMQuery.Open;

         CDMQuery.First;
         MsQuery.First;
         MsQuery.Edit;
         MsQuery.FieldByName('Operator').AsString := CDMQuery.FieldByName('userID').AsString;
         MsQuery.Post;
Ich bekomme erst die Meldung: "Feld Operator kann nicht geändert werden." Das wird wahrscheinlich daran liegen, dass es sich um ein virtuellen Feld, das aus zwei Feldern zusammengesetzt wird.
Wenn ich es dann mit einem anderen Feld teste, bekomme ich die Meldung: "Invalid Field Columnname 'JOB_NAME'.

Das verstehe ich nicht, denn mit diesem Feld mache ich nichts. Es ist aber das erste Feld der MsQuery, sodass ich davon ausgehe, dass er das bei den anderen Feldern auch sagen würde.

Ich habe aber abgeprüft, und vor dem Post ist das Feld vorhanden und hat einen korrekten Wert.

Sieht jemand, was ich falsch mache?

Vielen Dank Patrick

Jasocul 15. Jun 2023 09:44

AW: Manipulation eines Records in einer FDQuery
 
Bei deinem ersten concat hast du nur JOB_NAME stehen und nicht rj.JOB_NAME.
Ich vermute, dass es daran liegen könnte.

Uwe Raabe 15. Jun 2023 09:49

AW: Manipulation eines Records in einer FDQuery
 
Sowohl Operator als auch JOB_NAME sind keine echten Felder der Tabelle, sondern werden in der Query mit Concat zusammengesetzt. Das existierende Tabellenfeld JOB_NAME wird in der Abfrage nicht aufgeführt. Ein existierendes Feld mit einem virtuellen gleichen Namens zu kaschieren ist eh keine gute Idee. Wenn du JOB_NAME ändern willst, dann musst du dem virtuellen Feld einen anderen Namen geben und JOB_NAME mit in die Feldliste aufnehmen.

Natürlich könntest du das virtuelle Feld so belassen und JOB_NAME mit einem Alias versehen, aber das würde die Verwirrung nur vergrößern.

Ykcim 15. Jun 2023 12:32

AW: Manipulation eines Records in einer FDQuery
 
Hallo Zusammen,

vielen Dank für Eure Gedanken. Wenn ich die Daten ohne Manipulation ziehe und an die Clients übergebe, funktioniert das wunderbar. Leider sind die Daten aber in der Datenbank nicht korrekt, worauf ich keinen Einfluss habe. Daher ist es erforderlich die korrekten Daten aus der anderen Datenbank zu holen.

Ich habe mal JOB_NAME in JOB_NAME1 umbenannt:
Delphi-Quellcode:
 MsQuery.SQL.Add('SELECT TOP 1 '+
                                'CASE WHEN ( '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME) is NULL) then '+
                                  'LAG(Concat(rj.JOB_ID, ' + QuotedStr(' ') + ', rj.JOB_NAME)) OVER (ORDER BY re.TIME_LOCAL DESC) ELSE '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ',rj.JOB_NAME) end AS JOB_NAME1, '+
                                'CONVERT( varchar( 10 ), re.Time_Local, 104 ) AS Datum, '+
Dann bekomme ich die gleiche Fehlermeldung beim Post.

Ich hatte ja auch schon getestet, ob die Fehlermeldung ausbleibt, wenn ich ein echtes Feld verwende (OPERATION_NAME). Aber das klappt auch nicht.

Wie gesagt, ohne die Manipulation klappt das wunderbar und derzeit nutze ich die Software auch so, aber es arbeiten manchmal die falschen Mitarbeiter gerade an den Maschinen.

Hat jemand eine Idee, warum mit dem Post ein Problem auftritt?

Vielleicht verwende ich die FDQuery falsch, denn diese habe ich ohne Felder angelegt, sondern lasse die mit dem Open anlegen...

Hier mal die ganze Procedure...
Delphi-Quellcode:
procedure TMxSQL.Get_act_MachineData(Machine_ID: string; LStream: TStream; RefreshData, SendData: boolean);
var  Logic: TLogic;
      MsQuery: TFDQuery;
      CDMQuery: TFDQuery;
      Operationslist: string;
begin
   if (RefreshData = false) and (DB_Modul.Tmr_LM_RefreshData.Enabled = false) and (SendData = true) then begin
      RefreshData := true;
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert.');
   end;

   if (RefreshData = false) and (DB_Modul.Tmr_LM_RefreshData.Enabled = true) and (SendData = true) then begin
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('OfflineState-Counter zurückgesetzt.');
   end;
   if (DB_Modul.Tmr_LM_RefreshData.Enabled = false) then begin
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert. OfflineState-Counter zurückgesetzt.');
   end;
   Logic := TLogic.create;
   MsQuery := TFDQuery(DB_Modul.FindComponent('Qry_LM_' + Machine_ID));
   Logic.Set_Query(CDMQuery, DB_Modul.PRINECT_CDM);
   try
      if (RefreshData = true) then begin
         MsQuery.Connection:=DB_Modul.PRINECT_APS;
         MsQuery.SQL.Clear;
         MsQuery.FetchOptions.AutoFetchAll;
         MsQuery.FetchOptions.Mode := fmAll;
         OperationsList := DB_Modul.Read_Einstellungswert('Life_Data_OperationList');
         MsQuery.SQL.Add('SELECT TOP 1 '+
                                'CASE WHEN ( '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME) is NULL) then '+
                                  'LAG(Concat(rj.JOB_ID, ' + QuotedStr(' ') + ', rj.JOB_NAME)) OVER (ORDER BY re.TIME_LOCAL DESC) ELSE '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ',rj.JOB_NAME) end AS JOB_NAME, '+
                                'CONVERT( varchar( 10 ), re.Time_Local, 104 ) AS Datum, '+
                                'convert(char(5), re.Time_Local, 108) AS Startzeit, '+
                                'rd.device_id, '+
                                'rd.device_name, '+
                                'ro.OPERATION_NAME, '+
                                'concat(ry.FIRST_NAME,' +QuotedStr(' ') + ', ry.FAMILY_NAME) AS Operator, '+
                                'CASE WHEN ( '+
                                  'DateDiff(minute, re.Time_Local, LAG(re.Time_Local) OVER (ORDER BY re.TIME_LOCAL DESC)) is NULL) then '+
                                  'Concat(DateDiff(minute, re.Time_Local, GETDATE()),' + QuotedStr(':00  (') + ',convert(char(5), re.Time_Local, 108), ' + QuotedStr(')') + ') ELSE '+
                                  'ConCat(DateDiff(minute, re.Time_Local, LAG(re.Time_Local) OVER (ORDER BY re.TIME_LOCAL DESC)),' + QuotedStr(':00  (') + ',convert(char(5), re.Time_Local, 108), ' + QuotedStr(')') + ') end AS Dauer, '+
                                '(SELECT SPEED FROM RBC_DEVICE_BASIC_INTERVALS RDB WHERE RDB.DEVICE_BASIC_INTERVAL_KEY = '+
                                  '(SELECT MAX(Device_Basic_interval_key) FROM RBC_DEVICE_BASIC_INTERVALS RDBI WHERE RDBI.DEVICE_KEY = re.DEVICE_KEY)) AS Speed, '+
                                'Concat(ws.PERCENT_COMPLETED,' + QuotedStr(' %') + ') AS PERCENT_COMPLETED, '+
                                'ws.PLANNED_GOOD_AMOUNT, '+
                                'ws.GOOD_CYCLES, '+
                                'ro.OPERATION_KEY, '+
                                'ws.WORK_STEP_NAME '+
                         'from RPS_EVENTS re '+
                         'LEFT JOIN RPS_WORK_STEPS ws ON ws.WORK_STEP_KEY = re.WORK_STEP_KEY '+
                         'LEFT JOIN RPS_JOBS rj ON rj.JOB_KEY = ws.JOB_KEY '+
                         'LEFT JOIN RPS_EMPLOYEE_ACTIVITIES ea ON ea.WORK_STEP_KEY = ws.WORK_STEP_KEY '+
                         'LEFT JOIN RPS_OPERATIONS ro ON ro.OPERATION_KEY = re.OPERATION_KEY '+
                         'LEFT JOIN RBC_DEVICES rd ON rd.device_key = re.device_key '+
                         'LEFT JOIN RBC_EMPLOYEES ry ON ry.EMPLOYEE_KEY = ea.EMPLOYEE_KEY '+
                         'WHERE rd.DEVICE_ID = :Device_ID '+
                         'AND CONVERT( date, re.TIME_LOCAL) = CONVERT( date, GETDATE()) '+
                         'AND re.OPERATION_KEY IN ( ' + OperationsList + ' ) '+
                         'GROUP BY rj.JOB_NAME, '+
                                  'rj.job_id, '+
                                  're.Time_Local, '+
                                  're.Device_key, '+
                                  'rd.device_id, '+
                                  'rd.device_name, '+
                                  're.OPERATION_KEY, '+
                                  'ro.OPERATION_KEY, '+
                                  'ro.OPERATION_NAME, '+
                                  'ry.FIRST_NAME, '+
                                  'ry.FAMILY_NAME, '+
                                  'ws.PERCENT_COMPLETED, '+
                                  'ws.PLANNED_GOOD_AMOUNT, '+
                                  'ws.GOOD_CYCLES, '+
                                  'ws.WORK_STEP_NAME '+
                         'ORDER BY re.TIME_LOCAL desc ');
         MsQuery.ParamByName('Device_ID').AsString := Machine_ID;
         MsQuery.Open;
         DB_Modul.Write_LM_Protokoll('Refresh Daten Maschine ' + Machine_ID);


         CDMQuery.SQL.Clear; //Ohne den nachfolgenden Teil bis zum Post klappt alles wunderbar. Felder und Werte sind da.
         CDMQuery.sql.Add('select * from CDM_SM_DEVICE_OP_STATUS '+
                          'where deviceID = :Device '+
                          'and roleID = ' + QuotedStr('ID_Operator'));
         CDMQuery.ParamByName('Device').AsString := MsQuery.FieldByName('device_id').AsString;
         CDMQuery.Open;

         CDMQuery.First;
         MsQuery.First;
         MsQuery.Edit;
         MsQuery.FieldByName('Operator').AsString := CDMQuery.FieldByName('userID').AsString;
         MsQuery.Post; //Hier knallt es
      end;



      if SendData then begin
         if Assigned(LStream) then begin
            if MsQuery.State = dsInactive then begin
               DB_Modul.Write_LM_Protokoll('Query('+Machine_ID+') ist inaktiv. Wartezeit 2 Sek.');
               Sleep(2000);
            end;
            if MsQuery.State = dsBrowse then begin
               MsQuery.SaveToStream(LStream, sfJSON);
               DB_Modul.Write_LM_Protokoll('Sent Daten ('+Machine_ID+') LifeMonitor.');
            end
            else begin
               DB_Modul.Write_LM_Protokoll('ERROR: Query nicht aktiv ('+Machine_ID+').');
            end;
         end
         else begin
            DB_Modul.Write_LM_Protokoll('ERROR: No Stream assigned ('+Machine_ID+').');
         end;
      end;
   Finally
      Logic.Free;
      CDMQuery.Free;
   end;
end;
Vielen Dank
Patrick

Uwe Raabe 15. Jun 2023 12:41

Query
 
Zitat:

Zitat von Ykcim (Beitrag 1523435)
Ich habe mal JOB_NAME in JOB_NAME1 umbenannt:
Delphi-Quellcode:
 MsQuery.SQL.Add('SELECT TOP 1 '+
                                'CASE WHEN ( '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME) is NULL) then '+
                                  'LAG(Concat(rj.JOB_ID, ' + QuotedStr(' ') + ', rj.JOB_NAME)) OVER (ORDER BY re.TIME_LOCAL DESC) ELSE '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ',rj.JOB_NAME) end AS JOB_NAME1, '+
                                'CONVERT( varchar( 10 ), re.Time_Local, 104 ) AS Datum, '+
Dann bekomme ich die gleiche Fehlermeldung beim Post.

Ich hatte ja auch schon getestet, ob die Fehlermeldung ausbleibt, wenn ich ein echtes Feld verwende (OPERATION_NAME). Aber das klappt auch nicht.

Das liegt daran, dass weder JOB_NAME noch OPERATION_NAME in der Query explizit in der Feldliste auftauchen. Eine FieldByName auf eines dieser Felder sollte demnach bereits fehlschlagen.

Felder, die du ändern willst, müssen in der Query angegeben sein.

Ykcim 15. Jun 2023 12:50

AW: Manipulation eines Records in einer FDQuery
 
Genau das verstehe ich nicht.

Wenn ich einen Haltepunkt auf MSQuery.Post setze und dann im Debugger die Werte
Delphi-Quellcode:
   MsQuery.FieldByName('Operation_Name').AsString
   MsQuery.FieldByName('Job_Name').AsString
prüfe, dann finde ich sie und sie haben die aktualisierten Werte. Wenn ich einen Haltepunkt bei MSQuery.First setze, finde ich Werte
Delphi-Quellcode:
   MsQuery.FieldByName('Operation_Name').AsString
   MsQuery.FieldByName('Job_Name').AsString
ebenfalls, nur dass sie dann noch die ursprünglichen Werte haben...

Jasocul 15. Jun 2023 12:53

AW: Manipulation eines Records in einer FDQuery
 
group by und dann ein Edit?
Was soll denn in dem Fall geändert werden? Alle Treffer, die in das group by für das Feld fallen?

Ich denke, du wirst ein separates Update-Statement benötigen.

Zu den ganzen True/False vergleichen, spare ich mir an dieser Stelle den Kommentar

Uwe Raabe 15. Jun 2023 13:25

Query
 
Zitat:

Zitat von Jasocul (Beitrag 1523439)
group by und dann ein Edit?
Was soll denn in dem Fall geändert werden? Alle Treffer, die in das group by für das Feld fallen?

Ich denke, du wirst ein separates Update-Statement benötigen.

Das GROUP BY hatte ich noch gar nicht entdeckt. In dem Fall geht das mit dem Post natürlich gar nicht, da der einzelne Datensatz ja nicht identifiziert werden kann. In der Regel braucht FireDAC einen Primary Key Wert für ein Post. Den gibt es bei gruppierten Queries aber nicht.

Mit den passenden Einstellungen kann man auch ohne den Primary Key Datensätze ändern, aber es muss eben immer eine 1:1 Beziehung zischen dem aktuellen Record in der Query und dem Datensatz in der zu ändernden Tabelle geben.

Ykcim 15. Jun 2023 14:28

AW: Manipulation eines Records in einer FDQuery
 
:oops: ich glaube, jetzt muss ich eine ganz blöde Frage stellen...:oops:

Was mache, wenn ich gar nicht die Daten auf der Datenbank ändern möchte, sondern nur die Ergebnis-Daten der Abfrage in der Query selbst?
Die Query nutze ist als Zwischenspeicher und immer, wenn ein Client die Daten abruft, bekommt er sie aus der Query, via Query.SaveToStream. Dabei werden die Daten aber nicht neu von der Datenbank abgefragt. Das passiert unabhängig davon nach einem Zeitintervall.

Also ich möchte das Ergebnis (Query hat in diesem Fall nur einen Datensatz) ändern, ohne das irgendetwas zur Datenbank gespielt wird...

Uwe Raabe 15. Jun 2023 14:52

AW: Manipulation eines Records in einer FDQuery
 
Dann solltest du die Daten in einem TFDMemTable bearbeiten, den du aus der Query füllst.

Alternativ kannst du auch bei der Query CachedUpdates auf True stellen und einfach kein ApplyUpdates aufrufen.

Ykcim 15. Jun 2023 15:26

AW: Manipulation eines Records in einer FDQuery
 
Zitat:

Alternativ kannst du auch bei der Query CachedUpdates auf True stellen und einfach kein ApplyUpdates aufrufen.
Das hat das Problem leider nicht gelöst,

aber basierend auf Deinem Post https://www.delphipraxis.net/1489948-post9.html in der Vergangenheit, habe ich jetzt so einen Erfolg zu verzeichnen:

Delphi-Quellcode:
         CDMQuery.First;
         MsQuery.First;
         MsQuery.Edit;
         MsQuery.FieldByName('Operator').AsString := CDMQuery.FieldByName('userID').AsString;
         MsQuery.Locate('DEVICE_ID', Machine_ID, []);
Ist das noch halbwegs sauber oder gefummelt?

Jasocul 15. Jun 2023 16:05

AW: Manipulation eines Records in einer FDQuery
 
Das ist gefummelt und dürfte auch nicht funktionieren.

Wenn die Query durch das Locate in den Browse-Modus versetzt wird, wird implizit vorher ein Post oder Cancel gemacht.
Das bedeutet, du wirst wieder eine Exception (bei Post) bekommen oder deine Daten werden nicht verändert (Cancel).

Delphi.Narium 15. Jun 2023 16:07

AW: Manipulation eines Records in einer FDQuery
 
Wozu sind die beiden da?
Delphi-Quellcode:
CDMQuery.First;
MsQuery.First;
Vorher stehst Du in beiden Querys auf dem benötigten Datensatz.

Dann änderst Du (vermutlich) in beiden Querys den Datensatzzeiger per First, um dann auf jedenfall den ersten Datensatz in MsQuery per Edit zu ändern.

Das erscheint mir nicht zwingend sinnvoll. Bei zwei Querys mit jeweils nur einem Datensatz mag das unschädlich sein, ist dann aber, da nur ein Datensatz vorhanden ist, unsinnig, da der eine Datensatz ja auch zwingend sowohl der erste, als auch der letzte Datensatz ist. In allen anderen Fällen läuft das auf eine Art Zufallsgenerator hinaus. Abgesehen davon steht der Datensatzzeiger nach 'nem Open sowieso auf dem ersten Datensatz. Ein Konstrukt in der Form
Delphi-Quellcode:
CDMQuery.Open;
CDMQuery.First;
bringt schlicht und einfach keinen Vorteil, sondern ist im Zweifel nur sinnfrei.

Per Edit einen Datensatz zu ändern und vor dem Speichern der Änderung den Datensatz per Locate zu ändern, erscheint mir nicht sinnvoll. Je nach Einstellung dürfte das Locate ein Post implizieren oder ein Cancel. Beim Post steht dann die Änderung in einem anderen Datensatz, als in dem per Locate gesuchten und ggfls. angezeigten. Bei einem impliziten Cancel wird keine Änderung durchgeführt. Damit könnte man sich den Änderung auch sparen, da sie beim Locate sowieso verworfen wird.

Lade Dir die benötigten Daten per SQL. Übertrage das Ergebnis in eine MemTable (s. o.) und arbeite dann ausschließlich mit dieser weiter.

Bei deiner bisherigen Anforderungsbeschreibung erscheint mir alles andere als "Gefrickel", sprich: Nicht zielführend und nicht sicher und zuverlässig implementierbar.
Delphi-Quellcode:
                                'CASE WHEN ( '+
                                  'concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME) is NULL) then '+
Was ich ncht verstehe:
Delphi-Quellcode:
concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME)

Kann ein
Delphi-Quellcode:
concat(Null, ' ', Null)
Null sein oder ist es immer zwingend ein
Delphi-Quellcode:
' '
und damit nicht Null?


Ein paar Stunden später:

Was mir noch auffiel:

Per TOP 1 wird ein Datensatz gesucht, die Ergbenismenge enthält also 0 oder 1 Datensatz. Danach wird per First auf den ersten von einem Datensatz gewechselt und dieser eine Datensatz wird dann erfolgreich(?) in diesem einen Datensatz per Locate gesucht.
Das Ergebnis von Locate wird jedoch nicht überprüft, bei false hieße es z. B., dass der eine Datensatz in der Menge von einem Datensatz nicht gefunden wird, aber der dann zufällig beim Datansatzzeiger stehende Datensatz, also der erste und einzige Datensatz, wird zur weiteren Verarbeitung genutzt.

Wenn nun diese Konstrukt so funktioniert und vorhandene Probleme behebt, ist das absoluter Zufall.

Bitte überprüfe dein Vorgehen noch einmal, ggfls. liefere uns eine (möglichst) konkrete Beschreibung der Aufgabenstellung, damit wir zielgerichtete Hilfestellung geben können.

Ykcim 16. Jun 2023 12:37

AW: Manipulation eines Records in einer FDQuery
 
Hallo,

vielen Dank für den ausführlichen Post! Ich arbeite nicht oft mit datensensitiven Komponenten, sondern befülle Grids und andere Komponenten meistens von Hand. Auch insert / updates Statements schreibe ich für gewöhnlich manuell und verwende eine Query nur als Schnittstelle zur Datenbank.

Von daher war der Befehl Query.First die Hosenträger zum Gürtel und weil ich testen wollte, ob ich damit irgendwie weiterkomme. Ich bin mir absolut bewusst, dass ich die in diesem Anwendungsfall nicht benötige.

Was will ich gerade machen: Ich habe eine Client / Server App, die den Life-Zustand der Maschinen anzeigt. Dabei laufen mehrere Clients hier im Unternehmen. Diese fordern alle 60 Sekunden neue Daten von der Server App an (also nicht ganz Life:wink:).

Wenn aber 5 Clients, die zu unterschiedlichen Uhrzeiten gestartet werden, immer die Daten anfordern, wären das ziemlich viele unnötige Abfragen. Deshalb lasse ich die Server App alle 60 Sekunden die Daten von der DB abfragen, die dann in der Query stehen. Immer wenn ein Client nach frischen Daten fragt, werden diese via Query.SaveToStream an den Client geschickt.
Wenn 120 Sekunden kein Client nach neuen Daten gefragt hat, stoppt auch der Server seine Abfragen und startet erst wieder mit einer neuen Anfrage.

Ich nutze also die Query als Datenspeicher aus dem die Clients bedient werden.

Jetzt habe ich aber leider das Problem, dass die Daten nicht von einem Datenbankserver kommen, sondern von zweien. Das liegt daran, dass das ERP-System so aufgebaut ist, denn die Datenbanken gehören zum ERP-System.

Deshalb habe ich eine zweite Query created, die Daten aus der Tabelle auf dem zweiten Datenbank-Server abgefragt und wollte anschließend ein Datenfeld in der ersten Query durch Daten aus der zweiten Query ersetzen.

Deshalb ist ein Post an der Stelle gar nicht gewünscht, denn ich will gar keine Daten auf der Datenbank ändern, sondern nur den aktuellen Datensatz in der Query. Und dafür brauche ich eine Möglichkeit, den Editiermodus zu beenden.

Ich hatte zwischendurch einen anderen Weg mit einem JSON-Result, welches ich über die API bekommen kann getestet. Hatte dabei dann die Daten manuell in eine FDMemTable geschrieben und versucht, diese via MEMTable.SaveToStream an den Client zu übergeben. Aber dann funktionierte das LoadFromStream leider nicht, weil der Stream nicht interpretiert werden konnte.

Die Lösung, die oben steht, funktioniert bis hierhin ohne Zicken, denn ich habe sie heute den ganzen Vormittag parallel laufen lassen. Aber ich hätte gerne eine saubere Lösung, auch wenn die App für den Eigengebrauch ist...

Vielen Dank und lieben Gruß
Patrick

Delphi.Narium 16. Jun 2023 22:51

AW: Manipulation eines Records in einer FDQuery
 
Hm, weiß nicht so recht, ob ich das richtig verstanden habe.

Von einem Server bekommst du sehr viele Daten per SQL. Nun muss du aus einer zweiten Datenbank einen anderen Wert holen und damit ein Feld der ersten Daten ändern.

Das Ergebnis muss nicht gespeichert werden, sondern nur solange im Arbeitsspeicher für die Weitergabe an die Clients zur Verfügung stehen, bis eine bestimmte Zeitspanne abgelaufen ist oder über einen längeren Zeitraum keine Clientanfrage beim Server ankommt.

Grob also sowas:
Delphi-Quellcode:
// In einem Timerereignis oder wie auch immer die "Zeitschleife" gesteuert wird.
begin
  // Cancel und Close funktionieren auch fehlerfrei, wenn die Abfrage weder im Editiermodus noch geöffnet ist.
  MsQuery.Cancel; // Letzte Änderung verwerfen.
  MsQuery.Close; // Letzte Abfrage schließen.
  MsQuery.SQL.Add('große Abfrage von oben'); // Neue Abfragen erstellen.
  CDMQuery.SQL.Add('kleine Abfrage von oben');
  MsQuery.Open; // Erste Abfrage öffnen
  CDMQuery.ParamByName('Device').AsString := MsQuery.FieldByName('device_id').AsString; // und in zweiter Abfrage den Parameter setzen.
  CDMQuery.Open; // Die zweite Abfrage öffnen,
  MsQuery.Edit; // die erste Abfrage editieren und den Wert aus der zweiten Abfrage übernehmen.
  MsQuery.FieldByName('Operator').AsString := CDMQuery.FieldByName('userID').AsString;
  // Die Abfrage bleibt im Editiermodus.
  // Bei Anforderungen durch einen Client müssten dann die bereits editierten, aber nicht gespeicherten Daten,
  // per SaveToStream ausgeliefert werden.
end;

Delphi.Narium 17. Jun 2023 23:52

AW: Manipulation eines Records in einer FDQuery
 
Eventuell ginge aber auch sowas:
Delphi-Quellcode:
procedure TMxSQL.Get_act_MachineData(Machine_ID: string; LStream: TStream; RefreshData, SendData: boolean);
...
begin
...
  OperationsList := DB_Modul.Read_Einstellungswert('Life_Data_OperationList');
  MsQuery.SQL.Add('große Abfrage von oben'); // Neue Abfragen erstellen.
  CDMQuery.SQL.Add('kleine Abfrage von oben');
  MsQuery.Open; // Erste Abfrage öffnen
  CDMQuery.ParamByName('Device').AsString := MsQuery.FieldByName('device_id').AsString; // und in zweiter Abfrage den Parameter setzen.
  CDMQuery.Open; // Die zweite Abfrage öffnen,
  MsQuery.Edit; // die erste Abfrage editieren und den Wert aus der zweiten Abfrage übernehmen.
  MsQuery.FieldByName('Operator').AsString := CDMQuery.FieldByName('userID').AsString;
  MsQuery.SaveToStream(LStream, sfJSON); // unmittelbar nach der Änderung in den Stream schreiben,
  MsQuery.Cancel; // die letzte Änderung verwerfen
  MsQuery.Close; // und die Abfragen schließen.
  CDMQuery.Close;
...
end;
Der Stream enthält immer die aktuellen Daten, so dass bei einer Anforderung durch einen Client nicht jedesmal auf die Query zugegriffen werden muss. Der Inhalt des Streams bleibt ja erhalten und kann beliebig oft an die Clients ausgeliefert werden.

Eigentlich wird nur die Stelle, an der die Query in den Stream geschrieben wird, in deiner ursprünglichen Prozedur an eine andere Stelle verschoben. Der Teil ab
Delphi-Quellcode:
 if SendData then begin
kann (vermutlich) entfallen.

Hoffentlich liege ich damit nicht allzuweit daneben ;-)

Ykcim 19. Jun 2023 18:56

AW: Manipulation eines Records in einer FDQuery
 
Zitat:

Hoffentlich liege ich damit nicht allzuweit daneben
Damit liegst Du genau richtig. Ich habe es so gelöst, als ich eine Procedure unter Verwendung der API-Schnittstelle gebaut habe:
Delphi-Quellcode:
procedure TMxSQL.Get_act_MachineJSON(Machine_ID: string; RefreshData, SendData: boolean; var LStream: TMemoryStream);
var  Logic: TLogic;
      API_URL: string;
      IdHTTP_MStatus: TIdHTTP;
      RStream: TMemoryStream;
begin
   if (RefreshData = false) and (DB_Modul.Tmr_LM_RefreshData.Enabled = false) and (SendData = true) then begin
      RefreshData := true;
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert.');
   end;

   if (RefreshData = false) and (DB_Modul.Tmr_LM_RefreshData.Enabled = true) and (SendData = true) then begin
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('OfflineState-Counter zurückgesetzt.');
   end;
   if (DB_Modul.Tmr_LM_RefreshData.Enabled = false) then begin
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert. OfflineState-Counter zurückgesetzt.');
   end;
   Logic := TLogic.create;
   IdHTTP_MStatus := TIdHTTP.Create;
   API_URL := DB_Modul.Read_Einstellungswert('MData_API_URL');
   API_URL := API_URL + Machine_ID;

   RStream := TMemoryStream.Create;
   try
      if (RefreshData = true) then begin
         IdHTTP_MStatus.Request.BasicAuthentication := true;
         IdHTTP_MStatus.Request.Username := DB_Modul.Read_Einstellungswert('API_User');
         IdHTTP_MStatus.Request.Password := DB_Modul.Read_Einstellungswert('API_Passwort');
         IdHTTP_MStatus.Get(API_URL, RStream);
         //RStream in den FStream kopieren
         DB_Modul.CopyStream('LStream_',Machine_ID,'', RStream);
         DB_Modul.Write_LM_Protokoll('Refresh Daten Maschine ' + Machine_ID);
      end;
      //FStream in den LStream kopieren
      DB_Modul.CopyStreamBack('LStream_',Machine_ID,'', LStream);
   Finally
      Logic.Free;
      IdHTTP_MStatus.Free;
      RStream.Free;
   end;
end;
Aber ehrlich gesagt, fühlt sich das auch irgendwie gefuckelt an... Schreibe etwas in die Query (es geht nur um ein Feld, was geändert werden muss), speichere in Stream und verwirf die Änderung wieder...

Mal gucken, vielleicht baue ich es noch um, aber jetzt geht es kurzum erst einmal in den Urlaub.

Vielen Dank
LG Patrick

P.S.: Bevor das Fragezeichen aufwirft, ich hatte ziemlich Probleme, dass die Daten irgendwie zerstört wurden (also der Stream), bevor sie an den Client gingen, daher habe ich testweise mit unterschiedlichen Streams gearbeitet und von einem in den nächsten kopiert... Geht mit Sicherheit besser!

Delphi.Narium 19. Jun 2023 19:18

AW: Manipulation eines Records in einer FDQuery
 
Habe mal noch ein bisserl rumgefrickelt:
Delphi-Quellcode:
// procedure TMxSQL.Get_act_MachineJSON(Machine_ID: string; RefreshData, SendData: boolean; var LStream: TMemoryStream);
procedure TMxSQL.Get_act_MachineJSON(Machine_ID: string; RefreshData, SendData: boolean; LStream: TMemoryStream);
var Logic: TLogic;
      API_URL: string;
      IdHTTP_MStatus: TIdHTTP;
      RStream: TMemoryStream;
begin
   if (not RefreshData) and (not DB_Modul.Tmr_LM_RefreshData.Enabled) and (SendData) then begin
      RefreshData := true;
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert.');
   end else
   if (not RefreshData) and (DB_Modul.Tmr_LM_RefreshData.Enabled) and (SendData) then begin
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('OfflineState-Counter zurückgesetzt.');
   end else
   if (not DB_Modul.Tmr_LM_RefreshData.Enabled) then begin
      DB_Modul.Tmr_LM_RefreshData.Enabled := true;
      DB_Modul.FLM_OfflineState := 0;
      DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert. OfflineState-Counter zurückgesetzt.');
   end;
   Logic := TLogic.create;
   IdHTTP_MStatus := TIdHTTP.Create;
   API_URL := Format('%s%s',[DB_Modul.Read_Einstellungswert('MData_API_URL'),Machine_ID]);

   RStream := TMemoryStream.Create;
   try
      if (RefreshData) then begin
         IdHTTP_MStatus.Request.BasicAuthentication := true;
         IdHTTP_MStatus.Request.Username := DB_Modul.Read_Einstellungswert('API_User');
         IdHTTP_MStatus.Request.Password := DB_Modul.Read_Einstellungswert('API_Passwort');
         IdHTTP_MStatus.Get(API_URL, RStream);
         //RStream in den FStream kopieren
         DB_Modul.CopyStream('LStream_',Machine_ID,'', RStream);
         DB_Modul.Write_LM_Protokoll(Format('Refresh Daten Maschine %s',[Machine_ID]));
      end;
      //FStream in den LStream kopieren
      DB_Modul.CopyStreamBack('LStream_',Machine_ID,'', LStream);
   Finally
      Logic.Free;
      IdHTTP_MStatus.Free;
      RStream.Free;
   end;
end;
Werden die Daten im Stream auch noch zerstört, wenn Du im Prozeduraufruf das Var vor dem LStream weglässt?


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:47 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz