# Database

> Werte, die ein System des Werks in einer Datenbanktabelle hält (SQL Server, PostgreSQL, MySQL und MariaDB, SQLite oder jede Datenbank über ODBC), als Tags gelesen und dorthin zurückgeschrieben, wo ein Tag es verlangt: die Karten Datenbank und Anmeldung, ein Tag, das eine Zelle über Tabelle, Spalte und Zeile liest oder eine eigene Abfrage ausführt, die zwei Arten, wie ein Tag schreibt, der Suchlauf, der ein Tag je Spalte anbietet, vier vollständige Beispiele und was der Treiber nicht tut.

Der Treiber Database liest die Werte, die ein System des Werks in einer Datenbanktabelle hält: den
Fertigungsauftrag, den das MES führt, das Rezept oder den Sollwert, den das ERP hält, das Ergebnis, das
ein anderes Prüfsystem aufgezeichnet hat. Jedes Tag liest einen Wert einer Tabelle, und seine Werte
landen dort, wo die jedes anderen Tags landen: in Logic, auf Dashboards, in den Läufen eines Prüfstands
von [Process](process). Wo ein Tag es verlangt, schreibt der Treiber auch zurück: in die Zelle, die das
Tag liest, oder über einen eigenen Befehl des Tags.

SQL Server, PostgreSQL, MySQL und MariaDB sowie SQLite werden erreicht, ohne dass etwas auf dem
Computer installiert wird. Jede andere Datenbank (Oracle, Access, Firebird und andere) wird über ihren
eigenen ODBC-Treiber erreicht, der in der 64-Bit-Version auf dem Computer installiert sein muss.

Lesen ist der Hauptzweck, und der Weg dahin ist kurz: Ein Gerät gibt an, wo seine Datenbank ist und wie
sich die Station anmeldet, der Suchlauf bietet ein Tag je Spalte seiner Tabellen an, und jedes Tag
entsteht mit Nur lesen. Was zum Schreiben gehört, erscheint an einem Tag erst, wenn sein Zugriff die
Station schreiben lässt.

Ein Gerät dieses Treibers ist eine Datenbank: Zwei Datenbanken desselben Servers sind zwei Geräte. Die
Geräte stehen unter dem Treiber und können in Ordnern abgelegt werden,
**Treiber → Ordner → Gerät → Tag**, wie unter jedem Treiber ohne serielle Leitung und ohne Broker.

## Die Seite des Treibers {#the-drivers-page}

Die Zeile unter der Kopfzeile lautet "Jedes Gerät liest eine Datenbank. Legen Sie ein Gerät an, tragen Sie ein, wo seine Datenbank ist und wie sich die Station anmeldet, und durchsuchen Sie es dann, um seine Spalten als Tags hinzuzufügen."
Mit Connector konfigurieren trägt die Seite den eigenen Suchbereich des Treibers, mit dem Gerät, dessen
Datenbank der Suchlauf liest (siehe [Gerätesuche](#discovery)). Darunter steht die Tabelle **Geräte**
jedes Treibers (siehe [Die Gerätetabelle](connector-drivers#the-device-table)), die mit
**Gerät hinzufügen** rechts neben ihrem Titel wächst; leer zeigt sie "Noch keine Geräte." Die Leiste
des Treibers trägt **Geräte suchen**, ausgegraut, bis die Karte Suchbereich vollständig ist. Der
Treiber steht unter **Dateien und Datenbanken**, neben dem Treiber [File](driver-file).

## Gerät {#device}

Ein Gerät dieses Treibers hat keine Karte Verbindung: An ihrer Stelle steht die Karte **Datenbank**,
gefolgt von der Karte **Anmeldung** bei jeder Datenbank außer SQLite, das keine Anmeldung hat. Das
**Abfrageintervall (ms)** der Karte Gerät, Standard 1000, gibt an, wie oft die Tags die Datenbank
lesen, wobei jedes Tag es wie bei jedem Treiber überschreiben kann.

Ein neues Gerät ist ein SQL Server ohne Server: Es zeigt "Server noch nicht eingetragen", seine Seite sagt "Die Station erreicht dieses Gerät nicht, solange es nicht angibt, wo seine Datenbank ist. Deshalb wird noch nichts aus ihm gelesen, und es gibt keinen Fehler anzuzeigen. Tragen Sie es unten in der Karte Datenbank ein (den Server und den Datenbanknamen, die Datei oder die Datenquelle), dann verbindet sich die Station damit."
und nichts wird gelesen, bis es das angibt. Die Basis-URI, die die Kopfzeile zeigt, ist die
Laufzeitadresse, die nie den Benutzer oder das Passwort enthält:
`database://sqlserver?server=mes-sql&instance=SQLEXPRESS&database=MES` für eine SQL-Server-Instanz,
`database://sqlite?file=C%3A%5CData%5Crecipes.db` für eine SQLite-Datei,
`database://odbc?source=ERP` für eine ODBC-Datenquelle.

### Die Karte Datenbank {#the-database-card}

| Feld | Angezeigt | Bedeutung | Standard |
| --- | --- | --- | --- |
| **Datenbanktyp** | immer | **SQL Server**, **PostgreSQL**, **MySQL / MariaDB**, **SQLite** oder **Andere (ODBC)**, das sagt "Verwendet den 64-Bit-ODBC-Treiber, der auf diesem Computer für diese Datenbank installiert ist (Oracle, Access, Firebird und andere)." | **SQL Server** |
| **Datenbankserver** | SQL Server, PostgreSQL, MySQL / MariaDB | Bei SQL Server: "Ein Hostname oder eine IP-Adresse. SERVER\INSTANZ und SERVER,PORT werden auf ihre Felder aufgeteilt." Bei den beiden anderen: "Ein Hostname oder eine IP-Adresse. Ein Port nach einem Doppelpunkt kommt in das Feld Port." | leer |
| **Port** | dieselben | Leer für den eigenen Port der Datenbank, den das leere Feld anzeigt: 1433, 5432 oder 3306. Bei einer SQL-Server-Instanz ohne Port zeigt das Feld nichts, und der SQL Server Browser des Servers nennt den Port. | leer |
| **Instanzname** | SQL Server | "Leer für die Standardinstanz. Eine benannte Instanz ohne Port wird über den SQL Server Browser gefunden." Bis zu 16 Buchstaben, Ziffern, `_` und `$`, beginnend mit einem Buchstaben oder `_`. | leer |
| **Datenbankname** | SQL Server, PostgreSQL, MySQL / MariaDB | Die Datenbank des Servers, die die Tabellen enthält, bis zu 128 Zeichen. | leer |
| **Datenbankdatei** | SQLite | "Der vollständige Pfad der SQLite-Datei, erreicht mit den Windows-Rechten des Kontos, unter dem die Station läuft. Sie wird nur lesend geöffnet, solange kein Tag schreibt." Das Feld zeigt `C:\Data\recipes.db` als Hinweis. Die Datei wird nie angelegt. | leer |
| **Datenquelle oder Verbindungstext** | Andere (ODBC) | "Der Name einer Datenquelle in den ODBC-Datenquellen von Windows (64 Bit) oder ein Verbindungstext wie Driver={...};Dbq=C:\Data\plant.accdb. Das Passwort gehört in das Passwortfeld, nie in den Text." | leer |

Ein Feld, das der gewählte Typ nicht liest, wird nicht angezeigt und behält seinen Wert für den Fall,
dass er es wieder liest. Die Wahl eines anderen Typs leert einen Port, der auf dem eigenen Port der
bisherigen Datenbank stand, damit die neue auf ihrem eigenen erreicht wird. Das Feld Datenbankserver
teilt auf, was Datenbankwerkzeuge zusammen schreiben:

| In Datenbankserver eingetragen | Wird zu |
| --- | --- |
| `mes-sql\SQLEXPRESS` (SQL Server) | Datenbankserver `mes-sql`, Instanzname `SQLEXPRESS` |
| `mes-sql,1433` (SQL Server) | Datenbankserver `mes-sql`, Port 1433 |
| `10.0.0.5:5433` | Datenbankserver `10.0.0.5`, Port 5433 |

Solange die Karte nicht angibt, wo die Datenbank ist, wird das Gerät gespeichert, wie es ist, und
wartet, mit grauem Punkt, ohne etwas zu lesen und ohne einen Fehler anzuzeigen:

| Datenbanktyp | Wartet auf | Das Gerät zeigt |
| --- | --- | --- |
| SQL Server, PostgreSQL, MySQL / MariaDB | Den Server, dann den Datenbanknamen | "Server noch nicht eingetragen", dann "Datenbankname noch nicht eingetragen" |
| SQLite | Die Datei | "Datenbankdatei noch nicht eingetragen" |
| Andere (ODBC) | Die Datenquelle oder den Verbindungstext | "Datenquelle noch nicht eingetragen" |

Eine Einstellung, die die Karte nicht annehmen kann, wird unter den Karten genannt, und das Gerät wird
nicht gespeichert, bis sie korrigiert ist:

| Sie tragen ein | Die Karte sagt |
| --- | --- |
| Einen Server mit Schema, Pfad oder Leerzeichen, etwa `http://mes-sql` | "Der Server ist nur ein Hostname oder eine IP-Adresse: kein Schema, kein Pfad und kein Leerzeichen. Eine SQL-Server-Instanz und ein Port, die mit ihm geschrieben werden, kommen in ihre eigenen Felder." |
| Einen Instanznamen mit Leerzeichen oder einem anderen Zeichen als `_` und `$` | "Der Instanzname besteht aus bis zu 16 Buchstaben, Ziffern, Unterstrichen und Dollarzeichen." |
| Einen Datenbanknamen mit mehr als 128 Zeichen | "Der Datenbankname hat höchstens 128 Zeichen, ohne Steuerzeichen." |
| Eine Datei, die kein vollständiger Pfad ist, etwa `recipes.db` | "Die Datenbankdatei ist ein vollständiger Pfad: ein Laufwerk, ein Ordner und die Datei (C:\Data\Rezepte.db) oder eine Netzwerkfreigabe (\\server\data\Rezepte.db)." |
| Einen Ordner oder einen Pfad mit `*`, `?`, `<`, `>` oder einem Anführungszeichen | "Tragen Sie den Pfad der Datenbankdatei selbst ein, nicht den eines Ordners, ohne * ? \| < > oder Anführungszeichen." |
| Einen Datenquellennamen mit einer eckigen, geschweiften oder runden Klammer, einem Komma, einem Semikolon oder einem von `? * ! @ \` | Die Karte verlangt den Namen, den die Quelle in den ODBC-Datenquellen hat, oder einen Verbindungstext. |
| Einen Verbindungstext, der nicht aus Paaren `key=value` besteht, etwa mit einer offenen geschweiften Klammer | "Ein Verbindungstext besteht aus Paaren key=value, getrennt durch Semikolons; ein Wert mit einem Semikolon steht in geschweiften Klammern." |
| Einen Verbindungstext mit `PWD=` oder `Password=` | "Nehmen Sie das Passwort aus dem Verbindungstext und tragen Sie es im Passwortfeld ein: Der Text wird angezeigt und ins Journal geschrieben, das Passwortfeld hält es geschützt." |

Das Speichern hält dieselben Regeln ein, und der Feed sagt es, wenn es eine ablehnt:
"Das Gerät 'MES' hat eine Datenbankeinstellung, die sein Treiber nicht verwenden kann. Öffnen Sie es und korrigieren Sie das Feld, das sein Panel markiert."

### Die Karte Anmeldung {#the-sign-in-card}

Jede Datenbank außer SQLite trägt die Karte **Anmeldung**, mit dem Benutzer, mit dem sich die Station
anmeldet.

| Feld | Angezeigt | Bedeutung | Standard |
| --- | --- | --- | --- |
| **Mit dem Windows-Konto der Station anmelden** | SQL Server | Ein: "Die Anmeldung bei SQL Server erfolgt mit dem Windows-Konto, unter dem die Station läuft; es werden weder Benutzer noch Passwort gespeichert." Aus: "Die Anmeldung bei SQL Server erfolgt mit einem Datenbankbenutzer und seinem Passwort." | aus |
| **Benutzername** | ein Server, bei dem man sich mit einem Benutzer anmeldet, und ODBC, wo er optional ist | Der Datenbankbenutzer. | leer |
| **Passwort** | dieselben | Das Passwort des Benutzers, geschützt gespeichert wie jede Anmeldeinformation eines Treibers: Es wird nie wieder angezeigt, nie in die Adresse oder ins Journal geschrieben und nie an einen Agenten zurückgegeben. | leer |
| **Verschlüsselung** | SQL Server | **Verschlüsselt, Zertifikat geprüft**: "Die Verbindung ist verschlüsselt, und das Zertifikat des Servers wird gegen die Zertifikate geprüft, denen dieser Computer vertraut." **Verschlüsselt, jedes Zertifikat akzeptiert**: "Die Verbindung ist verschlüsselt, und jedes Zertifikat wird akzeptiert, wie es ein Fabrikserver mit eigenem Zertifikat braucht. Die Identität des Servers wird nicht geprüft." **Nicht verschlüsselt**: "Die Werte und die Befehle werden unverschlüsselt übertragen. Nur für einen alten Server, der keine Verschlüsselung anbietet." | **Verschlüsselt, Zertifikat geprüft** |

PostgreSQL und MySQL haben kein Feld Verschlüsselung: Ihre Verbindung ist verschlüsselt, sobald der
Server es anbietet. Ein SQL Server mit eigenem Zertifikat, wie es ein Server im Werk oft hat, lehnt die
Verbindung mit den Worten der Datenbank über eine Zertifikatskette ab, die von einer nicht
vertrauenswürdigen Stelle ausgestellt wurde: **Verschlüsselt, jedes Zertifikat akzeptiert** ist die
Wahl, die dieser Server verlangt.

Unter ihren Feldern sagt die Karte, um welchen Benutzer man bitten soll:
"Bitten Sie den Administrator der Datenbank um einen Benutzer mit nur den Rechten, die die Station braucht: Lesen, und Schreiben nur in die Tabellen, die ihre Tags ändern."
Die Station liest und schreibt die Daten eines anderen Systems mit allem, was ihr Benutzer darf; dieser
Benutzer ist deshalb die eigentliche Grenze dessen, was die Station ändern kann. Jeder Schreibvorgang
wird ins Journal geschrieben, wie bei jedem Treiber.

Ein Server, bei dem man sich mit einem Benutzer anmeldet und noch keiner eingetragen ist, sagt
stattdessen
"Tragen Sie den Benutzer ein, mit dem sich die Station anmeldet. Bis dahin verbindet die Station dieses Gerät nicht."
Das ist keine Ablehnung: Das Gerät wird gespeichert, und sein Fehler sagt, dass noch kein
Datenbankbenutzer eingetragen ist, bis der Benutzer da ist.

### Online, wartend und offline {#online-waiting-and-offline}

| Situation | Was das Gerät und seine Tags tun |
| --- | --- |
| Die Karte Datenbank gibt nicht an, wo die Datenbank ist | Das Gerät wartet, grauer Punkt, nichts wird gelesen und kein Fehler (siehe oben). |
| Ein Server, bei dem man sich mit einem Benutzer anmeldet, und keiner ist eingetragen | Die Station verbindet das Gerät nicht; sein Fehler sagt, dass noch kein Datenbankbenutzer eingetragen ist. |
| Der Server antwortet nicht, lehnt die Anmeldung ab, oder die SQLite-Datei existiert nicht | Das Gerät ist offline mit der Ursache, die die Datenbank selbst nennt, und die Station verbindet es, sobald sie kann. Auf eine Verbindung wird höchstens 10 s gewartet. |
| Die Verbindung geht verloren (der Server hat neu gestartet, das Netz ist unterbrochen) | Das Gerät geht offline und verbindet sich erneut. |
| Eine Anweisung, die die Datenbank ablehnt oder die nach 10 s noch läuft | Ein fehlgeschlagener Lesevorgang dieses Tags, mit den Worten der Datenbank selbst; das Gerät liest seine anderen Tags weiter. |
| Ein Tag liest die neueste Zeile einer Tabelle, die noch keine Zeile hat | Das Tag wartet ohne Fehler, und der Zustand seines Geräts lässt es außen vor, bis die erste Zeile geschrieben ist: Die anderen Tags des Geräts sagen, ob es online ist. Eine Tabelle, die geleert wird, nachdem das Tag aus ihr gelesen hat, ist nicht dieses Warten: Das Tag behält seinen letzten Messwert, der als fehlgeschlagen zählt, sobald er älter ist als sein Abfrageintervall plus 10 s, wie bei jedem Treiber. |

Jedes Gerät hat eine eigene Verbindung, die geöffnet wird, wenn es sich verbindet, und nie geteilt oder
in einem Pool gehalten wird; eine Verbindung, die ein Server getrennt hat, wird deshalb nie wieder
verwendet.

### Den Server vorbereiten {#prepare-the-server}

Die drei Netzwerkdatenbanken sind in üblichen Installationen für einen anderen Rechner
geschlossen. Jede dieser Einstellungen wird auf dem Server vorgenommen, von dem, der ihn
verwaltet; die Station ändert keine davon.

| Datenbank | Was die Station aussperrt | Wo es geändert wird |
| --- | --- | --- |
| SQL Server | TCP/IP ausgeschaltet (SQL Server Express und Developer kommen so) | SQL Server Configuration Manager > SQL Server Network Configuration > Protocols for die Instanz: TCP/IP aktivieren, dann SQL Server (die Instanz) unter SQL Server Services neu starten |
| SQL Server | Eine benannte Instanz hört auf einem Port, den sie selbst wählt | TCP/IP > IP Addresses > IPAll: TCP Dynamic Ports leeren und den Port unter TCP Port eintragen, dann neu starten; oder das Feld Port des Geräts leer lassen und den Instanznamen angeben |
| SQL Server | Eine benannte Instanz ohne Port wird vom SQL Server Browser gefunden, der gestoppt oder blockiert ist (Fehler 26) | SQL Server Services: SQL Server Browser gestartet, Start Mode Automatic; UDP 1434 in der Firewall des Servers erlaubt |
| PostgreSQL | listen_addresses steht auf localhost, dem Standardwert | postgresql.conf: listen_addresses = '*', dann PostgreSQL neu starten |
| PostgreSQL | Keine Zeile der pg_hba.conf lässt die Adresse dieser Station herein (SQLSTATE 28000) | pg_hba.conf: eine host-Zeile für die Datenbank, den Benutzer und die Adresse dieser Station, mit scram-sha-256 (md5 für ein mit MD5 gespeichertes Passwort), dann SELECT pg_reload_conf(); |
| MySQL, MariaDB | bind-address steht auf 127.0.0.1, wie viele Pakete es setzen, oder skip-networking ist eingeschaltet | Der Abschnitt [mysqld] der Konfigurationsdatei: bind-address = 0.0.0.0, skip-networking entfernt, dann neu starten |
| MySQL, MariaDB | Kein Konto für die Adresse dieser Station (Fehler 1130) | CREATE USER für den Benutzer an der Adresse dieser Station und GRANT dessen, was die Tags brauchen |

Jede braucht außerdem den Port der Datenbank in der Firewall des Server-Rechners erlaubt.
Antwortet der Server auf eine dieser Arten, sagt die Seite des Geräts, welche Einstellung es
ist, mit diesen Schritten und den Werten dieses Geräts (der Port, die Instanz, die Adresse
dieser Station, wie der Server sie gesehen hat, die Zeile der pg_hba.conf und das MySQL-Konto,
jeweils mit einer Schaltfläche zum Kopieren), anstelle des englischen Fehlers der Datenbank. Der
Hinweis verschwindet von selbst, sobald sich das Gerät verbindet oder der Server anders
antwortet. Das Protokoll und der Agent lesen dieselben Schritte.

## Adressierung des Tags {#tag-addressing}

### Wie ein Tag liest {#how-a-tag-reads}

Ein Tag dieses Treibers hat kein eigenes Adressfeld: Seine Karte **Quelle** fragt, wie es liest.

| **Liest** | Was das Tag zeigt | Seine Felder |
| --- | --- | --- |
| **Tabelle und Spalte** | Eine Zelle: die Wertspalte einer Zeile einer Tabelle. | **Tabelle**, **Wertspalte**, **Zu lesende Zeile** und die Felder der Zeile |
| **Eigene Abfrage** | Die erste Spalte der ersten Zeile, die ein eigenes SELECT zurückgibt. | **Abfragetext** |

Ein neues Tag liest **Tabelle und Spalte**, über seinen Schlüssel, als Double. Angeboten werden die
Typen Boolean, Int32, Int64, Float, Double, String und DateTime. Beim Lesen einer Zelle lehnt ein
Ganzzahl-Tag einen Wert mit Nachkommastellen ab, statt ihn zu runden (beim Schreiben ist es umgekehrt,
siehe [Schreiben](#writing)), und ein Text wird nur als Zahl gelesen, wenn er eine ist.
Jedes Tag entsteht mit **Nur lesen**; was das Schreiben hinzufügt, steht unter [Schreiben](#writing).

### Tabelle und Spalte {#table-and-column}

| Feld | Angezeigt | Bedeutung | Standard |
| --- | --- | --- | --- |
| **Tabelle** | Tabelle und Spalte | "Ein Name oder ein Schema und ein Name (dbo.Orders). Ein Name mit Leerzeichen oder Großbuchstaben steht in Anführungszeichen oder eckigen Klammern, wie [Order Lines]." | leer |
| **Wertspalte** | Tabelle und Spalte | Die Spalte, deren Zelle der Wert des Tags ist. | leer |
| **Zu lesende Zeile** | Tabelle und Spalte | **Über ihren Schlüssel**: "Die Zeile, deren Schlüsselspalte den Schlüsselwert enthält. Tags derselben Tabelle und Zeile werden zusammen in einer Abfrage gelesen." **Neueste**: "Die Zeile mit dem höchsten Wert der Datumsspalte: ein Datum mit Uhrzeit oder eine Zahl, die nur wächst. Tags derselben Tabelle und Zeile werden zusammen in einer Abfrage gelesen." | **Über ihren Schlüssel** |
| **Schlüsselspalte** | Über ihren Schlüssel | Die Spalte, die die Zeile auswählt, etwa `OrderNo`. | leer |
| **Schlüsselwert** | Über ihren Schlüssel | Was die Schlüsselspalte in der zu lesenden Zeile enthält, etwa `4711`, als Text eingetragen: Die Datenbank liest ihn, wie sie einen Wert dieser Spalte liest. | leer |
| **Datumsspalte** | Neueste | Die Spalte, deren höchster Wert die neueste Zeile ist, etwa `updated_at`. | leer |

Ein Name wird so gelesen, wie die Datenbank ihn liest:

| Geschrieben | Gelesen als |
| --- | --- |
| `Orders` | Ein einfacher Name, gelesen, wie die Datenbank jeden einfachen Namen liest: PostgreSQL in Kleinbuchstaben, Oracle in Großbuchstaben. |
| `"Order Lines"`, `[Order Lines]` | Der Name genau, welche Anführungszeichen die Datenbank selbst auch nimmt. |
| `dbo.Orders` | Ein Schema und eine Tabelle: Ein Punkt außerhalb der Anführungszeichen trennt sie. |

Der Treiber setzt jeden Teil eines Namens in die Anführungszeichen, die seine Datenbank nimmt; ein Name
wird also nie als Text in eine Anweisung eingefügt. Ein Tag, dessen Felder nicht funktionieren können,
wird nicht gespeichert, und seine Seite sagt, was fehlt:

| Sie lassen leer oder tragen ein | Die Seite des Tags sagt |
| --- | --- |
| Keine Tabelle | "Tragen Sie die Tabelle ein, in der der Wert steht." |
| Eine Tabelle aus drei Teilen oder ein nicht geschlossenes Anführungszeichen | "Die Tabelle ist ein Name oder ein Schema und ein Name (dbo.Orders); setzen Sie einen Namen mit Leerzeichen in Anführungszeichen oder eckige Klammern. Jeder Teil hat höchstens 128 Zeichen." |
| Keine Wertspalte | "Tragen Sie die Spalte des Werts ein." |
| Eine Wertspalte aus zwei Teilen | "Die Wertspalte ist ein einzelner Name; setzen Sie einen Namen mit Leerzeichen in Anführungszeichen oder eckige Klammern." |
| Über ihren Schlüssel, ohne Schlüsselspalte | "Tragen Sie die Schlüsselspalte ein, die die Zeile auswählt." |
| Über ihren Schlüssel, ohne Schlüsselwert | "Tragen Sie den Schlüsselwert ein: was die Schlüsselspalte in der zu lesenden Zeile enthält." |
| Neueste, ohne Datumsspalte | "Tragen Sie die Datumsspalte ein, deren höchster Wert die neueste Zeile ist." |

Die schreibgeschützte Zeile **Quelladresse (Bus)** zeigt, was der Treiber erhält:
`row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711` für eine Zeile über ihren
Schlüssel, `newest?table=recipe_setpoints&column=oven_temperature&by=updated_at` für die neueste Zeile.

### Eigene Abfrage {#own-query}

| Feld | Bedeutung | Standard |
| --- | --- | --- |
| **Abfragetext** | "Ein SELECT, das nur liest; der Wert ist die erste Spalte der ersten Zeile. @now ist Datum und Uhrzeit der Station." Eine Abfrage, die mit WITH beginnt und nur liest, wird ebenfalls angenommen. | leer |

Die Abfrage wird so gelesen, wie die Datenbank sie liest: Was in einem Text in Anführungszeichen, einem
Namen in Anführungszeichen oder einem Kommentar steht, ist kein Wort der Anweisung. Eine Abfrage, die
mehr als lesen will, wird dort abgelehnt, wo sie eingetragen wird:

| Sie tragen ein | Die Seite des Tags sagt |
| --- | --- |
| Nichts | "Tragen Sie die Abfrage ein: ein SELECT, dessen erste Spalte der ersten Zeile der Wert ist." |
| Eine Anweisung, die kein SELECT ist, etwa `UPDATE` oder `DELETE` | "Die Abfrage beginnt mit SELECT oder WITH. Zum Schreiben lassen Sie den Zugriff des Tags schreiben und verwenden einen Schreibbefehl." |
| Ein SELECT, das die Datenbank ändert: `SELECT ... INTO`, `FOR UPDATE` oder eines mit `INSERT`, `UPDATE`, `EXEC` | "Die Abfrage würde die Datenbank ändern (INSERT, UPDATE, SELECT INTO, FOR UPDATE, EXEC und Ähnliches). Eine Abfrage liest nur." |
| Zwei Anweisungen | "Die Abfrage enthält mehr als eine Anweisung. Behalten Sie ein SELECT." |
| Ein nicht geschlossenes Anführungszeichen, eine Klammer oder ein Kommentar | "Ein Anführungszeichen, eine Klammer oder ein Kommentar der Abfrage ist nicht geschlossen." |
| Einen anderen Wert als `@now`, etwa `@order` | "Eine Abfrage nimmt nur @now, Datum und Uhrzeit der Station. Schreiben Sie jeden anderen Wert in den Text." |

Die Busadresse eines Abfrage-Tags ist `query?sql=`, gefolgt von der prozentkodierten Abfrage.

## Lesen {#reading}

| Ein Tag, das liest | Wie es gelesen wird |
| --- | --- |
| Über ihren Schlüssel | Seine Spalte, in der Zeile, deren Schlüsselspalte gleich dem Schlüsselwert ist; der Schlüsselwert wird der Datenbank als Parameter übergeben. |
| Die neueste Zeile | Seine Spalte, in der Zeile, deren Datumsspalte den höchsten Wert dieser Spalte hat (`WHERE ... = (SELECT MAX(...) FROM ...)`), eine Form, die jede Datenbank und ODBC gleich lesen; bei zwei gleichen Zeilen wird die erste gelesen. |
| Mehrere Tags derselben Tabelle und Zeile | Eine Abfrage je Durchlauf für alle ihre Spalten, damit die Datenbank des Werks nicht einmal je Tag gefragt wird. Lehnt die Datenbank diese Abfrage ab (eine Spalte, die die Tabelle nicht hat), wird die Spalte des Tags selbst allein gelesen und diese Zeile von da an Spalte für Spalte: Eine falsche Spalte kostet nur ihr eigenes Tag. |
| Seine eigene Abfrage | Die Abfrage, wie sie geschrieben ist, mit `@now` als Parameter übergeben. |

| Die Datenbank antwortet | Das Tag |
| --- | --- |
| Ein Wert eines Typs, den das Tag liest | Liest ihn. |
| Eine leere Zelle (NULL) | Sein Lesevorgang schlägt fehl. |
| Keine Zeile mit dem Schlüsselwert, oder mehr als eine | Sein Lesevorgang schlägt fehl: Ein Tag liest eine Zeile. |
| Die neueste Zeile einer Tabelle, die noch keine Zeile hat | Wartet, ohne Fehler. |
| Eine Abfrage, die keine Zeile zurückgibt | Sein Lesevorgang schlägt fehl. |
| Ein Wert, den der Typ des Tags nicht liest (ein Text in einem Int32-Tag, Nachkommastellen in einem Ganzzahl-Tag) | Sein Lesevorgang schlägt fehl, mit dem Grund. |

Ein fehlgeschlagener Lesevorgang behält den letzten Messwert des Tags, der als fehlgeschlagen zählt,
sobald er älter ist als sein Abfrageintervall plus 10 s, wie bei jedem Treiber. Die Datenbank wird bei
jedem Lesevorgang gefragt: Eine Änderung, die ein anderes Programm macht, wird beim nächsten
Lesevorgang gesehen, nie in dem Moment, in dem sie gemacht wird.

## Schreiben {#writing}

Ein Tag schreibt erst, wenn sein **Zugriff** **Lesen und schreiben** oder **Nur schreiben** ist. Seine
Karte Quelle fügt dann hinzu, was ein Schreibvorgang tut:

| Feld | Angezeigt | Bedeutung | Standard |
| --- | --- | --- | --- |
| **Ein Schreibvorgang** | das Tag schreibt | **Ändert die Zelle, die es liest**: "Ändert die Zelle, die dieses Tag liest, und zwar in nur einer Zeile. Ein neuerer Wert, der noch wartet, ersetzt einen älteren." **Führt seinen Schreibbefehl aus**: "Führt den Schreibbefehl unten aus. Jeder Schreibvorgang zählt: Keiner, der noch wartet, wird für einen neueren verworfen." Ein Tag, das seine eigene Abfrage liest, hat keine Zelle zu ändern und bekommt nur **Führt seinen Schreibbefehl aus** angeboten. | **Ändert die Zelle, die es liest** |
| **Schreibbefehl** | Führt seinen Schreibbefehl aus | "Ein UPDATE mit seinem WHERE, ein INSERT oder der Aufruf einer gespeicherten Prozedur. @value ist der geschriebene Wert und @now Datum und Uhrzeit der Station; der Wert ist immer ein Parameter und wird nie in den Text eingefügt." Das leere Feld zeigt `INSERT INTO results (result, recorded_at) VALUES (@value, @now)` als Hinweis. | leer |

Ein Tag mit **Nur schreiben**, das seinen Schreibbefehl ausführt, liest nichts; seine Karte Quelle
enthält deshalb nur das Schreiben.

| Ein Schreibvorgang | Wie er abläuft |
| --- | --- |
| Ändert die Zelle, die es liest | Ein UPDATE, das die Wertspalte in der Zeile des Tags auf `@value` setzt, in einer Transaktion, die rückgängig gemacht wird, wenn sich nicht genau eine Zeile geändert hat: Ein Schlüsselwert, den keine Zeile enthält oder den zwei Zeilen enthalten, schreibt nichts und sagt es. Ein neuerer Wert, der noch wartet, ersetzt einen älteren, wie bei jedem Sollwert. |
| Führt seinen Schreibbefehl aus | Der Befehl läuft einmal je Schreibvorgang, mit dem Wert als `@value` und Datum und Uhrzeit der Station als `@now`. Jeder Schreibvorgang zählt: Zwei INSERT sind zwei Zeilen, und ein Schreibvorgang, der noch wartet, wird nie für einen neueren verworfen. Eine gespeicherte Prozedur wird mit `EXEC`, `CALL` oder dem `{call ...}` von ODBC aufgerufen. |
| Ein Wert mit Nachkommastellen in einem Ganzzahl-Tag | Gerundet, nicht abgelehnt: Ein Wert mit Nachkommastellen, der in ein Ganzzahl-Tag (Int32 oder Int64) ohne Skalierung und Kalibrierung geschrieben wird, wird auf die nächste ganze Zahl gerundet, bevor er die Datenbank erreicht, Hälften von null weg (2,5 schreibt 3, -2,5 schreibt -3). Nachkommastellen abzulehnen ist die Art, eine Zelle zu lesen, nicht einen Wert zu schreiben. |
| Antwort | Erledigt, oder abgelehnt mit den Worten der Datenbank selbst: ein Recht, das dem Benutzer fehlt, eine Einschränkung, die die Zeile verletzt, ein Wert, den die Spalte nicht annimmt. |

Ein Befehl, den der Treiber nicht ausführen kann, wird auf der Seite des Tags abgelehnt:

| Sie tragen ein | Die Seite des Tags sagt |
| --- | --- |
| Nichts | "Tragen Sie den Schreibbefehl ein: ein UPDATE, ein INSERT oder den Aufruf einer gespeicherten Prozedur mit @value." |
| Einen Befehl, der mit einem anderen Wort beginnt, etwa `DELETE` | "Der Schreibbefehl beginnt mit UPDATE, INSERT, EXEC oder CALL." |
| Zwei Anweisungen | "Der Schreibbefehl enthält mehr als eine Anweisung. Behalten Sie eine oder rufen Sie eine gespeicherte Prozedur auf." |
| Ein nicht geschlossenes Anführungszeichen, eine Klammer oder ein Kommentar | "Ein Anführungszeichen, eine Klammer oder ein Kommentar des Schreibbefehls ist nicht geschlossen." |
| Kein `@value` | "Setzen Sie @value dorthin, wo der geschriebene Wert hingehört: Er ist immer ein Parameter und wird nie in den Text eingefügt." |
| Einen anderen Wert als `@value` und `@now`, etwa `@line` | "Ein Schreibbefehl nimmt nur @value, den geschriebenen Wert, und @now, Datum und Uhrzeit der Station." |
| Ein UPDATE ohne WHERE | "Ein UPDATE ohne WHERE ändert jede Zeile der Tabelle. Fügen Sie das WHERE hinzu, das die Zeile auswählt." |

Namen mit `@@`, die Funktionen von SQL Server, und die Argumentnamen eines `EXEC` (`@speed = @value`)
sind keine Werte und bleiben, wie sie geschrieben sind. Das Speichern hält dieselben Regeln ein, und der
Feed sagt es, wenn es ein Tag ablehnt:
"Das Tag 'Order quantity' gibt nicht an, wo sein Wert in der Datenbank steht, oder hat eine Abfrage oder einen Schreibbefehl, den sein Treiber ablehnt. Öffnen Sie es und korrigieren Sie das Feld, das das Tag-Panel markiert."

Die Busadresse fügt das Schreiben hinzu: `row?table=recipes&column=setpoint&key=code&equals=R-100&write=cell`
für eine Zelle, `none?write=command&command=` gefolgt vom Befehl für ein Tag mit **Nur schreiben**, das
seinen Befehl ausführt.

## Gerätesuche {#discovery}

### Die Karte Suchbereich {#the-scan-scope-card}

Mit Connector konfigurieren trägt die Seite des Treibers ihre Karte **Suchbereich**, unter der Zeile
"Meldet sich an wie das Gerät und bietet ein Tag für jede Spalte seiner Tabellen an, in der neuesten Zeile oder in der Zeile mit dem kleinsten Schlüssel."

| Feld | Bedeutung | Standard |
| --- | --- | --- |
| **Zu durchsuchendes Gerät** | Ein Gerät des Treibers, nach seinem Namen. Eine Datenbank wird nur über ein Gerät durchsucht, das ihre Anmeldung hält; eine Änderung des Geräts, die noch auf das Speichern wartet, wird zuerst gespeichert. | das erste Gerät nach Namen |
| **Tabellen** | "Leer für alle Tabellen, oder ein Name oder ein Muster, in dem * für beliebige Zeichen steht, wie Rezept*." | leer |

Ein Treiber ohne Gerät enthält nur
"Eine Datenbank wird über ein Gerät durchsucht, das ihre Anmeldung hält. Legen Sie ein Gerät dieses Treibers an und geben Sie an, wo seine Datenbank ist."
Was dem Bereich noch fehlt, wird rot unter der Karte genannt, und Geräte suchen bleibt ausgegraut, bis
er vollständig ist:

| Der Bereich | Die Karte sagt |
| --- | --- |
| Ein Gerät, das nicht angibt, wo seine Datenbank ist, oder eine Einstellung hat, die sein Treiber nicht verwenden kann | "Das Gerät 'Recipes' gibt noch nicht an, wo seine Datenbank ist, oder hat eine Einstellung, die sein Treiber nicht verwenden kann. Vervollständigen Sie zuerst seine Karte Datenbank." |
| Ein Muster mit Schema, Anführungszeichen, eckiger Klammer oder Semikolon, etwa `dbo.Recipe*` | "'dbo.Recipe*' ist kein Tabellenname und kein Muster. Schreiben Sie einen Namen oder ein Muster, in dem * für beliebige Zeichen steht, ohne Schema und ohne Anführungszeichen." |
| Ein Gerät, das nicht mehr existiert | "Das zum Durchsuchen gewählte Gerät existiert nicht mehr. Wählen Sie ein anderes Gerät zum Durchsuchen." |

Hat das zu durchsuchende Gerät eine Änderung, die nicht gespeichert werden kann, startet der Suchlauf
nicht:
"Die Gerätesuche wurde nicht gestartet, weil die Änderung am zu durchsuchenden Gerät nicht gespeichert werden konnte."

### Was der Suchlauf anbietet {#what-the-scan-offers}

Der Suchlauf meldet sich an wie das Gerät, liest die Liste, die die Datenbank selbst von ihren Tabellen
und Sichten (ohne die des Systems) und deren Spalten führt, und sagt, welche Tabelle er liest, etwa
"Spalten von recipes werden gelesen…". Er bietet einen Kandidaten an, das Gerät selbst, markiert als
**Bereits konfiguriert**: Seine Karte listet, angehakt, die Spalten auf, die noch kein Tag des Geräts
liest, und **Tags hinzufügen**, vor **Öffnen**, fügt die angehakten dem Gerät hinzu, mit Nur lesen.

| Die Tabelle | Ihre angebotenen Tags |
| --- | --- |
| Hat eine Spalte mit Datum und Uhrzeit | Eines je Spalte, das die neueste Zeile nach der ersten solchen Spalte liest. |
| Hat keine | Eines je Spalte außer ihrem Schlüssel, das die Zeile mit dem kleinsten Wert ihres Primärschlüssels liest (oder ihrer ersten Spalte, in einer Tabelle ohne Primärschlüssel): Ändern Sie den **Schlüsselwert** des Tags, um eine andere Zeile zu lesen. |
| Hat keine und keine Zeile | Wird ausgelassen. |
| Eine Spalte, die kein Tag-Typ liest (Binärdaten, eine Geometrie) | Wird ausgelassen und in der Zeile gezählt, die den Suchlauf abschließt. |

Die Tags heißen nach ihrer Tabelle und Spalte (`recipes.oven_temperature`), haben den Typ der Spalte,
und jedes ist **Nur lesen**. Ein Suchlauf liest höchstens 100 Tabellen und 500 Werte; einer, der dort
abgeschnitten wird, sagt
"Der Suchlauf von Recipes hat früh angehalten. Ein Suchlauf liest höchstens 100 Tabellen und 500 Werte: Geben Sie einen Tabellennamen oder ein Muster an, um die anderen zu sehen."
Das übrige Verhalten des Suchlaufs steht unter [Gerätesuche](connector-discovery).

Die eigene Seite eines Geräts tut dasselbe ohne die Seite seines Treibers: **Tags suchen** im Kopf seiner Karte der Tags meldet sich an wie das Gerät, durchsucht seine Datenbank, so wie es gespeichert ist, und bietet nur die Spalten an, für die es noch kein Tag hat (siehe [Die Tags eines Geräts suchen](connector-discovery#discovering-a-devices-tags)).

## Beispiel: ein Fertigungsauftrag aus SQL Server {#example-a-production-order-from-sql-server}

Das MES hält seine Fertigungsaufträge in SQL Server, in der Tabelle `dbo.ProductionOrders` der
Datenbank `MES`, eine Zeile je Auftragsnummer:

1. Auf der Seite des Treibers **Gerät hinzufügen**; nennen Sie es `MES`. Auf der Karte **Datenbank**
   behalten Sie **SQL Server**, tragen **Datenbankserver** `mes-sql` ein (oder `mes-sql\SQLEXPRESS`
   für eine benannte Instanz), lassen **Port** leer und tragen **Datenbankname** `MES` ein.
2. Auf der Karte **Anmeldung** tragen Sie **Benutzername** `station_reader` und sein **Passwort** ein,
   oder schalten **Mit dem Windows-Konto der Station anmelden** ein. Für einen Server im Werk mit
   eigenem Zertifikat wählen Sie **Verschlüsselt, jedes Zertifikat akzeptiert**.
3. **Tag hinzufügen** `Order quantity`, Int32: **Tabelle** `dbo.ProductionOrders`, **Wertspalte**
   `Quantity`, **Zu lesende Zeile** **Über ihren Schlüssel**, **Schlüsselspalte** `OrderNo`,
   **Schlüsselwert** `4711`. Seine Busadresse ist
   `row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711`.
4. **Tag hinzufügen** `Product`, String, auf derselben Tabelle und Zeile, **Wertspalte**
   `ProductCode`: Beide Tags werden in einer Abfrage gelesen.

Mit den Werkzeugen:

```
device_upsert name=MES driver=Database databaseKind=SqlServer host="mes-sql\SQLEXPRESS" databaseName=MES username=station_reader password=<password> databaseEncryption=TrustServerCertificate
tag_upsert device=MES name="Order quantity" opcUaType=Int32 databaseTable=dbo.ProductionOrders address=Quantity databaseRow=Key databaseKeyColumn=OrderNo databaseKeyValue=4711
tag_upsert device=MES name=Product opcUaType=String databaseTable=dbo.ProductionOrders address=ProductCode databaseRow=Key databaseKeyColumn=OrderNo databaseKeyValue=4711
```

## Beispiel: ein Rezeptsollwert aus PostgreSQL {#example-a-recipe-setpoint-from-postgresql}

Das Rezeptsystem fügt `recipe_setpoints` jedes Mal eine Zeile hinzu, wenn sich ein Sollwert ändert,
datiert in seiner Spalte `updated_at`:

1. **Gerät hinzufügen**; nennen Sie es `Recipes`, **Datenbanktyp** **PostgreSQL**,
   **Datenbankserver** `recipes-db`, **Port** leer (der 5432 von PostgreSQL), **Datenbankname**
   `recipes`, dann **Benutzername** und **Passwort** auf der Karte **Anmeldung**.
2. **Tag hinzufügen** `Oven setpoint`, Double: **Tabelle** `recipe_setpoints`, **Wertspalte**
   `oven_temperature`, **Zu lesende Zeile** **Neueste**, **Datumsspalte** `updated_at`.

Jede neue Zeile wird beim nächsten Lesevorgang des Tags gelesen. PostgreSQL liest einen einfachen Namen
in Kleinbuchstaben; eine mit Großbuchstaben angelegte Spalte, etwa `OvenTemp`, wird deshalb in
Anführungszeichen geschrieben: `"OvenTemp"`. Mit den Werkzeugen:

```
device_upsert name=Recipes driver=Database databaseKind=PostgreSql host=recipes-db databaseName=recipes username=station password=<password>
tag_upsert device=Recipes name="Oven setpoint" opcUaType=Double databaseTable=recipe_setpoints address=oven_temperature databaseRow=Newest databaseDateColumn=updated_at
```

## Beispiel: eine Ergebniszeile als INSERT geschrieben {#example-a-result-line-written-as-an-insert}

Das Qualitätssystem will eine Zeile je gemessenem Teil in seiner Tabelle `test_results`:

1. Auf dem Gerät dieser Datenbank **Tag hinzufügen** `Result`, Double, **Zugriff** **Nur schreiben**.
2. **Ein Schreibvorgang** **Führt seinen Schreibbefehl aus**, und in **Schreibbefehl** tragen Sie
   `INSERT INTO test_results (result, recorded_at) VALUES (@value, @now)` ein, das die Zeile anlegt.

Jeder Wert, den Logic in `Result` schreibt, wird eine eigene Zeile, mit Datum und Uhrzeit der Station
daneben; Schreibvorgänge, die kurz aufeinander folgen, bleiben alle erhalten, in ihrer Reihenfolge,
keiner für einen neueren verworfen. Ein Tag mit **Lesen und schreiben**, das eine Zelle liest, würde
stattdessen bei jedem Schreibvorgang seine eine Zeile ändern. Mit den Werkzeugen:

```
tag_upsert device=Quality name=Result opcUaType=Double accessType=WriteOnly databaseWriteMode=Command databaseCommand="INSERT INTO test_results (result, recorded_at) VALUES (@value, @now)"
```

## Beispiel: Access und Oracle über ODBC {#example-access-and-oracle-through-odbc}

| Datenbank | Auf diesem Computer | Auf dem Gerät |
| --- | --- | --- |
| Microsoft Access | Die Access Database Engine von Microsoft, 64 Bit | **Datenbanktyp** **Andere (ODBC)**, **Datenquelle oder Verbindungstext** `Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\Data\plant.accdb`, ohne Benutzer |
| Oracle | Der 64-Bit-ODBC-Treiber von Oracle und eine dafür in den ODBC-Datenquellen (64 Bit) angelegte Datenquelle `ERP` | **Datenbanktyp** **Andere (ODBC)**, **Datenquelle oder Verbindungstext** `ERP`, **Benutzername** und **Passwort** auf der Karte **Anmeldung** |

Gibt es die Datenquelle oder den Treiber, die ein Gerät nennt, nur für 32-Bit-Programme (ein Access
eines 32-Bit-Office, eine in den ODBC-Datenquellen (32 Bit) angelegte Datenquelle) oder gar nicht auf
diesem Rechner, sagt das Panel des Geräts, welcher der beiden Fälle es ist, mit dem Namen, den das
Gerät angibt, und gibt die ganze Anleitung, solange das Gerät eingeschaltet ist und keine Verbindung
herstellt. Der Fehler des Geräts sagt dasselbe auf Englisch, ebenso `driver_capabilities` unter
`prerequisite` des Datenbanktreibers (`perDevice` true):

| Teil | Was er sagt |
| --- | --- |
| Was fehlt | Ein 64-Bit-ODBC-Treiber für diese Datenquelle oder diesen Treiber: Ganter Lab ist ein 64-Bit-Programm und sieht nur die 64-Bit-ODBC-Treiber und die damit angelegten Datenquellen, und es kann den Treiber eines anderen Herstellers nicht mitliefern |
| Woher | Vom Hersteller der Datenbank; für Access die Microsoft-Seite "Download and install Microsoft 365 Access Runtime", deren Runtime die Access Database Engine enthält |
| Welche Version | Die 64-Bit-Version (für Access die 64-Bit-Download-Schaltfläche Ihrer Sprache), nie die 32-Bit-Version |
| Installation | Als Administrator; danach, bei einem Gerät, das eine Datenquelle nennt, diese in den ODBC-Datenquellen (64 Bit) mit demselben Namen und derselben Datenbank anlegen |
| Bekannter Stolperstein | Für Access auf einem Rechner mit Office: Laut der Microsoft-Seite entspricht die installierte Access Runtime dem vorhandenen Office und ist nicht mit einem per Windows Installer installierten Office kompatibel, und laut der Microsoft-Seite zur Wahl zwischen der 64-Bit- und der 32-Bit-Version von Office wird eine Version deinstalliert, bevor die andere installiert wird |
| Danach | Beim nächsten Verbindungsversuch verbindet sich das Gerät mit seiner Konfiguration, ohne Ganter Lab neu zu starten |

Der Hinweis endet mit den 64-Bit-ODBC-Treibern dieses Rechners, unter dem Namen, den `Driver={...}`
erwartet. Eine Datenquelle oder ein Treiber, die es in 64 Bit gibt und die trotzdem scheitern, zeigen
den eigenen Fehler des Treiber-Managers.

Über ODBC wird ein einfacher Name so gelesen, wie die Datenbank ihn liest (in Großbuchstaben bei
Oracle), und ein Name in Anführungszeichen genau; Datum und Uhrzeit werden auf die ganze Sekunde
gesendet, die feinste Auflösung, die jeder ODBC-Treiber annimmt. Mit den Werkzeugen:

```
device_upsert name="Plant book" driver=Database databaseKind=Odbc databaseOdbc="Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\Data\plant.accdb"
device_upsert name=ERP driver=Database databaseKind=Odbc databaseOdbc=ERP username=STATION password=<password>
```

## Agent-Werkzeuge {#agent-tools}

`driver_capabilities` beschreibt diesen Treiber unter `database`: den einfachen Weg, jede Datenbank mit
ihren Feldern und ihrem eigenen Port, die Wahl der Verschlüsselung, die zwei Arten, wie ein Tag liest,
und die zwei, wie es schreibt, die Regel, dass ein Wert nur als `@value` oder `@now` in eine Anweisung
kommt, die Typen, die Wartezeiten, den Rat zum Datenbankbenutzer, die Gerätesuche und vier vollständige
Beispiele. `device_upsert driver=Database` nimmt `databaseKind` (`SqlServer`, der Standard,
`PostgreSql`, `MySql`, `Sqlite` oder `Odbc`), und für einen Server `host` (ein `server\INSTANCE` oder
`server,port` von SQL Server wird aufgeteilt), `port`, `databaseName`, und bei SQL Server
`databaseInstance`, `databaseWindowsSignIn` und `databaseEncryption` (`Verified`,
`TrustServerCertificate` oder `Off`); `databaseFile` für SQLite, `databaseOdbc` für ODBC; `username`
und `password`, das nie zurückgegeben wird. Ein Gerät, das es zurückgibt, trägt `database`, mit der
Adresse und ob das Gerät noch auf seinen Benutzer wartet. `tag_upsert` nimmt die Wertspalte als
`address`, `databaseSource` (`Fields` oder `Query`), `databaseTable`, `databaseRow` (`Key` oder
`Newest`), `databaseKeyColumn`, `databaseKeyValue`, `databaseDateColumn`, `databaseQuery`, und sobald
der `accessType` schreibt, `databaseWriteMode` (`Cell` oder `Command`) und `databaseCommand`; ein neues
Tag ist Nur lesen und Double, wenn `opcUaType` nichts anderes sagt. `device_discover driver=Database`
nimmt `databaseDevice`, den Namen eines konfigurierten Geräts des Treibers, und optional
`databaseTables`, einen Tabellennamen oder ein Muster mit `*`; die Tags, die dem Gerät fehlen, werden
mit `tag_upsert` hinzugefügt. `connector_browse` gibt den `databaseKind` jedes Geräts zurück. Die
übrigen Werkzeuge stehen unter [Agent-Werkzeuge](agent-tools).

## Grenzen, die man kennen sollte {#limits-worth-knowing}

- Der Treiber liest und schreibt die Werte seiner Tags, einen Wert je Tag. Er zeichnet die Läufe der
  Station nicht in einer Datenbank auf: Eine Zeile je Lauf mit Seriennummer, Urteil und Messwerten ist
  nichts, was dieser Treiber schreibt.
- Mit dem Produkt wird kein ODBC-Treiber ausgeliefert. Oracle, Access, Firebird und jede Datenbank
  außer den vier direkt erreichten brauchen ihren eigenen 64-Bit-ODBC-Treiber, auf dem Computer
  installiert; ein 32-Bit-Treiber wird nicht gesehen.
- Das Gerät speichert kein Windows-Passwort: Die Windows-Anmeldung bei SQL Server und eine SQLite-Datei
  auf einer Netzwerkfreigabe verwenden das Konto, unter dem die Station läuft; die Datenbank oder die
  Freigabe muss dieses Konto also hereinlassen.
- Eine Abfrage liest nur. Was die Datenbank ändert, ist ein Schreibvorgang, und ein Schreibvorgang
  geschieht nur bei einem Tag, dessen Zugriff schreibt.
- Ein Wert kommt nie in den Text einer Anweisung: Der geschriebene Wert ist immer der Parameter
  `@value`, Datum und Uhrzeit der Station `@now`, und auch ein Schlüsselwert ist ein Parameter. Eine
  Anweisung, die einen anderen Wert nennt, wird abgelehnt.
- Ein Schreibvorgang in eine Zelle ändert genau eine Zeile; ein UPDATE-Befehl braucht sein WHERE.
- Die Datenbank wird bei jedem Lesevorgang gefragt: Eine Änderung, die ein anderes Programm macht, wird
  beim nächsten Lesevorgang gesehen, und eine Datenbank mit vielen Tags einer Zeile wird je Durchlauf
  einmal für alle gefragt.
- Eine Binärspalte wird nicht gelesen.
