# Database

> 工場のシステムがデータベースのテーブルに持つ値（SQL Server、PostgreSQL、MySQL と MariaDB、SQLite、または ODBC 経由の任意のデータベース）をタグとして読み、タグが指示した場所に書き戻します。「データベース」と「サインイン」のカード、テーブル・列・行でセルを読む、または独自のクエリを実行するタグ、タグの 2 つの書き込み方、列ごとにタグを提案するスキャン、4 つの完全な例、そしてドライバーがしないことです。

Database ドライバーは、工場のシステムがデータベースのテーブルに持つ値を読みます。MES が持つ製造指示、ERP が持つレシピや設定値、別の試験システムが記録した結果などです。各タグは 1 つのテーブルの値を 1 つ読み、値はほかのどのタグの値とも同じところに届きます。Logic、ダッシュボード、そして [Process](process) の試験台の実行記録です。タグが指示すれば、ドライバーは書き戻しもします。タグが読むセルへ、またはタグ独自のコマンドで書きます。

SQL Server、PostgreSQL、MySQL と MariaDB、SQLite には、コンピューターに何もインストールせずに接続します。それ以外のデータベース（Oracle、Access、Firebird など）には、そのデータベース用の ODBC ドライバーを通して接続し、そのドライバーの 64 ビット版がコンピューターにインストールされている必要があります。

主な用途は読み取りで、手順は短く済みます。機器にデータベースの場所とステーションのサインイン方法を入力し、スキャンがテーブルの列ごとにタグを提案し、どのタグも読み取りのみで作られます。書き込みに関するものは、タグのアクセスがステーションに書き込みを許したときにだけ、そのタグに現れます。

このドライバーの機器は 1 つのデータベースです。同じサーバーの 2 つのデータベースは 2 つの機器になります。機器はドライバーの下に置かれ、フォルダーに整理でき、シリアル回線もブローカーも持たないどのドライバーとも同じく **ドライバー → フォルダー → 機器 → タグ** となります。

## ドライバーのページ {#the-drivers-page}

見出しの下の行には「各機器は 1 つのデータベースを読み取ります。機器を追加し、データベースの場所とステーションのサインイン方法を入力してからスキャンすると、列をタグとして追加できます。」と書かれています。Connector の構成の権限があるとき、ページにはこのドライバー独自のスキャンの範囲のカードがあり、スキャンがデータベースを読む機器を持ちます（[機器検出](#discovery)を参照）。その下には、どのドライバーとも同じ**機器**の表（[機器の表](connector-drivers#the-device-table)を参照）があり、タイトルの右にある**機器を追加**で増やします。空のときは「機器はまだありません。」と表示します。ドライバーのバーには**機器検出**があり、スキャンの範囲のカードがそろうまで灰色です。このドライバーは**ファイルとデータベース**の下にあり、[File](driver-file) ドライバーの隣にあります。

## 機器 {#device}

このドライバーの機器には「接続」のカードがありません。その位置に**データベース**のカードがあり、サインインのない SQLite 以外のデータベースでは、続いて**サインイン**のカードがあります。「機器」のカードの**読み出し周期（ms）**は既定で 1000 で、タグがデータベースを読む頻度を表し、どのドライバーとも同じように各タグで上書きできます。

新しい機器はサーバーのない SQL Server です。「サーバーがまだ入力されていません」と表示し、ページには「データベースの場所が示されるまで、ステーションはこの機器に接続しません。そのため、まだ何も読み取っておらず、表示するエラーもありません。下の「データベース」カードに入力すると (サーバーとデータベース名、ファイル、またはデータ ソース)、ステーションが接続します。」と表示され、場所が示されるまで何も読みません。見出しに表示される基準 URI は実行時のアドレスで、ユーザーもパスワードも決して含みません。SQL Server のインスタンスなら `database://sqlserver?server=mes-sql&instance=SQLEXPRESS&database=MES`、SQLite ファイルなら `database://sqlite?file=C%3A%5CData%5Crecipes.db`、ODBC のデータ ソースなら `database://odbc?source=ERP` です。

### 「データベース」のカード {#the-database-card}

| 欄 | 表示される条件 | 意味 | 既定値 |
| --- | --- | --- | --- |
| **データベースの種類** | 常に | **SQL Server**、**PostgreSQL**、**MySQL / MariaDB**、**SQLite**、**その他 (ODBC)** のいずれか。最後のものは「このコンピューターにそのデータベース用にインストールされた 64 ビットの ODBC ドライバーを使います (Oracle、Access、Firebird など)。」と表示します。 | **SQL Server** |
| **サーバー** | SQL Server、PostgreSQL、MySQL / MariaDB | SQL Server では「ホスト名または IP アドレスです。SERVER\INSTANCE や SERVER,PORT は、それぞれの欄に分けて読み取ります。」、ほかの 2 つでは「ホスト名または IP アドレスです。コロンの後に書いたポートは「ポート」欄に移ります。」 | 空 |
| **ポート** | 同上 | 空ならデータベース自身のポートで、空の入力欄にそれが表示されます（1433、5432、3306）。SQL Server のインスタンスでポートがないときは何も表示されず、サーバーの SQL Server Browser がポートを伝えます。 | 空 |
| **インスタンス名** | SQL Server | 「既定のインスタンスなら空欄です。ポートのない名前付きインスタンスは SQL Server Browser で見つけます。」英字、数字、`_`、`$` で 16 文字まで、英字か `_` で始めます。 | 空 |
| **データベース名** | SQL Server、PostgreSQL、MySQL / MariaDB | テーブルを持つサーバー上のデータベースで、128 文字までです。 | 空 |
| **データベース ファイル** | SQLite | 「SQLite ファイルの完全なパスです。ステーションを実行しているアカウントの Windows の権限でアクセスします。書き込むタグがなければ読み取り専用で開きます。」入力欄にはヒントとして `C:\Data\recipes.db` が表示されます。ファイルを作成することはありません。 | 空 |
| **データ ソースまたは接続文字列** | その他 (ODBC) | 「Windows の ODBC データ ソース (64 ビット) にあるデータ ソース名、または Driver={...};Dbq=C:\Data\plant.accdb のような接続文字列です。パスワードは接続文字列ではなく、パスワード欄に入力します。」 | 空 |

選んだ種類が読まない欄は表示されず、読むときのために値を保ちます。別の種類を選ぶと、前のデータベース自身のポートのままだったポートは空になり、新しいデータベースはそれ自身のポートで接続されます。「サーバー」欄は、データベースのツールが一緒に書くものを分けて読み取ります。

| 「サーバー」に入力 | 結果 |
| --- | --- |
| `mes-sql\SQLEXPRESS`（SQL Server） | サーバー `mes-sql`、インスタンス名 `SQLEXPRESS` |
| `mes-sql,1433`（SQL Server） | サーバー `mes-sql`、ポート 1433 |
| `10.0.0.5:5433` | サーバー `10.0.0.5`、ポート 5433 |

カードがデータベースの場所を示していない間、機器はそのまま保存されて待機し、灰色の点で、何も読まず、エラーも表示しません。

| データベースの種類 | 待つもの | 機器の表示 |
| --- | --- | --- |
| SQL Server、PostgreSQL、MySQL / MariaDB | サーバー、次にデータベース名 | 「サーバーがまだ入力されていません」、次に「データベース名がまだ入力されていません」 |
| SQLite | ファイル | 「データベース ファイルがまだ入力されていません」 |
| その他 (ODBC) | データ ソースまたは接続文字列 | 「データ ソースがまだ入力されていません」 |

カードが受け付けない設定はカードの下に表示され、直すまで機器は保存されません。

| 入力したもの | カードの表示 |
| --- | --- |
| スキーム、パス、空白を含むサーバー（`http://mes-sql` など） | 「サーバーはホスト名か IP アドレスのみです。スキーム、パス、空白は含めません。一緒に書いた SQL Server のインスタンスとポートは、それぞれの欄に移ります。」 |
| 空白や `_` と `$` 以外の記号を含むインスタンス名 | 「インスタンス名は、英字、数字、アンダースコア、ドル記号で 16 文字までです。」 |
| 128 文字を超えるデータベース名 | 「データベース名は制御文字を含まない 128 文字以内です。」 |
| 完全なパスでないファイル（`recipes.db` など） | 「データベース ファイルは完全なパスです。ドライブ、フォルダー、ファイル (C:\Data\recipes.db)、またはネットワーク共有 (\\server\data\recipes.db) です。」 |
| フォルダー、または `*`、`?`、`<`、`>`、引用符を含むパス | 「フォルダーではなく、データベース ファイルそのもののパスを、* ? \| < > や引用符なしで入力してください。」 |
| 角かっこ、波かっこ、丸かっこ、コンマ、セミコロン、または `? * ! @ \` のいずれかを含むデータ ソース名 | カードは、ODBC データ ソースでのそのソースの名前か、接続文字列を求めます。 |
| `key=value` の組でできていない接続文字列（開いたままの波かっこなど） | 「接続文字列はセミコロンで区切った key=value の組です。セミコロンを含む値は波かっこで囲みます。」 |
| `PWD=` や `Password=` を含む接続文字列 | 「接続文字列からパスワードを外し、パスワード欄に入力してください。接続文字列は表示され診断ログにも書かれますが、パスワード欄は保護して保存します。」 |

保存にも同じ規則が適用され、拒否したときはフィードがそれを伝えます。「機器 'MES' に、ドライバーが使えないデータベースの設定があります。開いて、パネルが示す欄を直してください。」

### 「サインイン」のカード {#the-sign-in-card}

SQLite 以外のすべてのデータベースは**サインイン**のカードを持ち、ステーションがサインインに使うユーザーを持ちます。

| 欄 | 表示される条件 | 意味 | 既定値 |
| --- | --- | --- | --- |
| **ステーションの Windows アカウントでサインイン** | SQL Server | オン：「ステーションを実行している Windows アカウントで SQL Server にサインインします。ユーザーもパスワードも保存しません。」オフ：「データベースのユーザーとそのパスワードで SQL Server にサインインします。」 | オフ |
| **ユーザー名** | ユーザーでサインインするサーバー、および省略可能な ODBC | データベースのユーザーです。 | 空 |
| **パスワード** | 同上 | ユーザーのパスワードで、ドライバーのほかの資格情報と同じく保護して保存します。二度と表示されず、アドレスにも診断ログにも書かれず、エージェントに返されることもありません。 | 空 |
| **暗号化** | SQL Server | **暗号化、証明書を確認**：「接続は暗号化され、サーバーの証明書を、このコンピューターが信頼する証明書と照合します。」**暗号化、どの証明書も受け入れる**：「接続は暗号化され、どの証明書も受け入れます。独自の証明書を持つ工場のサーバーで必要になる設定です。サーバーの身元は確認しません。」**暗号化しない**：「値とコマンドは暗号化されずに送られます。暗号化を提供しない古いサーバーのためだけの設定です。」 | **暗号化、証明書を確認** |

PostgreSQL と MySQL には「暗号化」の欄はありません。サーバーが提供すれば、接続は暗号化されます。工場のサーバーによくある独自の証明書を持つ SQL Server は、信頼されていない機関が発行した証明書チェーンについてのデータベース自身の言葉で接続を拒否します。そのサーバーが求める選択は**暗号化、どの証明書も受け入れる**です。

欄の下で、カードは依頼すべきユーザーを伝えます。「データベースの管理者に、ステーションに必要な権限だけを持つユーザーを依頼してください。読み取りと、タグが変更するテーブルだけへの書き込みです。」ステーションはそのユーザーができることのすべてで別のシステムのデータを読み書きするので、このユーザーがステーションが変更できるものの本当の限界です。どの書き込みも、どのドライバーとも同じく診断ログに記録されます。

ユーザーでサインインするサーバーで、まだユーザーが入力されていなければ、代わりに「ステーションがサインインに使うユーザーを入力してください。それまで、ステーションはこの機器に接続しません。」と表示します。これは拒否ではありません。機器は保存され、ユーザーが入力されるまで、そのエラーはデータベースのユーザーがまだ入力されていないことを伝えます。

### オンライン、待機、オフライン {#online-waiting-and-offline}

| 状況 | 機器とそのタグの動作 |
| --- | --- |
| 「データベース」のカードがデータベースの場所を示していない | 機器は待機し、灰色の点で、何も読まず、エラーもありません（上記を参照）。 |
| ユーザーでサインインするサーバーで、ユーザーが入力されていない | ステーションは機器に接続しません。エラーはデータベースのユーザーがまだ入力されていないことを伝えます。 |
| サーバーが応答しない、サインインを拒否する、または SQLite ファイルが存在しない | 機器はデータベース自身が示す原因とともにオフラインになり、ステーションはできるようになり次第接続します。接続は最大 10 s 待ちます。 |
| 接続が失われた（サーバーが再起動した、ネットワークが切れた） | 機器はオフラインになり、再び接続します。 |
| データベースが拒否したステートメント、または 10 s たっても終わらないステートメント | そのタグの読み取りの失敗で、データベース自身の言葉が付きます。機器はほかのタグを読み続けます。 |
| タグが、まだ行のないテーブルの最新の行を読む | そのタグはエラーなしで待機し、最初の行が書き込まれるまで、機器の稼働状態の数に入りません。機器がオンラインかどうかは、ほかのタグが示します。タグが一度読んだあとで空になったテーブルは、この待機ではありません。タグは最後の読み取り値を保ち、その値は、どのドライバーとも同じく、読み出し周期に 10 s を足した時間より古くなると失敗として扱われます。 |

各機器は自分だけの接続を持ちます。接続時に開かれ、共有されることもプールに置かれることもないので、サーバーが切った接続が再び使われることはありません。

### サーバーの準備 {#prepare-the-server}

ネットワーク越しの 3 つのデータベースは、よくあるインストールではほかのコンピューターに閉じた状態で出荷されます。次の設定はどれも、サーバーを管理する人がサーバー側で行います。ステーションはどれも変えません。

| データベース | ステーションを締め出しているもの | 変える場所 |
| --- | --- | --- |
| SQL Server | TCP/IP が無効 (SQL Server Express と Developer はこの状態で出荷されます) | SQL Server Configuration Manager > SQL Server Network Configuration > Protocols for インスタンス: TCP/IP を有効にし、SQL Server Services で SQL Server (インスタンス) を再起動します |
| SQL Server | 名前付きインスタンスが自分で選んだポートで待ち受けている | TCP/IP > IP Addresses > IPAll: TCP Dynamic Ports を空にして TCP Port にポートを入力し、再起動します。または機器の「ポート」欄を空にしてインスタンス名を指定します |
| SQL Server | ポートのない名前付きインスタンスは SQL Server Browser で見つけますが、それが停止しているかブロックされている (エラー 26) | SQL Server Services: SQL Server Browser を開始し、Start Mode を Automatic にします。サーバーのファイアウォールで UDP 1434 を許可します |
| PostgreSQL | listen_addresses が既定値の localhost | postgresql.conf: listen_addresses = '*' にして PostgreSQL を再起動します |
| PostgreSQL | pg_hba.conf にこのステーションのアドレスを通す行がない (SQLSTATE 28000) | pg_hba.conf: データベース、ユーザー、このステーションのアドレスに対する host 行を scram-sha-256 で加え (MD5 で保存されたパスワードなら md5)、SELECT pg_reload_conf(); を実行します |
| MySQL、MariaDB | 多くのパッケージが設定するように bind-address が 127.0.0.1、または skip-networking が有効 | 構成ファイルの [mysqld] セクション: bind-address = 0.0.0.0 にして skip-networking を消し、再起動します |
| MySQL、MariaDB | このステーションのアドレス用のアカウントがない (エラー 1130) | このステーションのアドレスのユーザーを CREATE USER で作り、タグに必要な権限を GRANT します |

どの場合も、サーバーのコンピューターのファイアウォールでデータベースのポートを許可する必要があります。サーバーがこのいずれかの形で応答すると、機器のページはデータベースの英語のエラーの代わりに、どの設定かを、この手順とこの機器の値 (ポート、インスタンス、サーバーから見えたこのステーションのアドレス、pg_hba.conf の行、MySQL のアカウント。それぞれコピーボタン付き) とともに示します。この通知は、機器が接続するか、サーバーが別の形で応答すれば、ひとりでに消えます。ジャーナルとエージェントも同じ手順を読みます。

## タグのアドレス {#tag-addressing}

### タグの読み方 {#how-a-tag-reads}

このドライバーのタグには独自のアドレス欄がありません。**信号源**のカードが読み方を尋ねます。

| **読み取る内容** | タグに表示されるもの | 欄 |
| --- | --- | --- |
| **テーブルと列** | セル、つまりテーブルのある行の値の列です。 | **テーブル**、**値の列**、**読み取る行**、そして行の欄 |
| **独自のクエリ** | 独自の SELECT が返す最初の行の最初の列です。 | **クエリの本文** |

新しいタグは**テーブルと列**をキーで読み、Double になります。提供される型は Boolean、Int32、Int64、Float、Double、String、DateTime です。セルを読むとき、整数のタグは小数部分のある値を丸めずに拒否し（書き込みはその逆で、[書き込み](#writing)を参照）、テキストは数値であるときだけ数値として読みます。どのタグも**読み取りのみ**で作られます。書き込みで加わるものは[書き込み](#writing)にあります。

### テーブルと列 {#table-and-column}

| 欄 | 表示される条件 | 意味 | 既定値 |
| --- | --- | --- | --- |
| **テーブル** | テーブルと列 | 「名前、またはスキーマと名前 (dbo.Orders) です。空白や大文字を含む名前は、[Order Lines] のように引用符か角かっこで囲みます。」 | 空 |
| **値の列** | テーブルと列 | セルがタグの値になる列です。 | 空 |
| **読み取る行** | テーブルと列 | **キーで選ぶ**：「キー列にキーの値を持つ行です。同じテーブルと行のタグは、1 つのクエリでまとめて読み取ります。」**最新の行**：「日付列の値が最大の行です。日時、または増えるだけの数値です。同じテーブルと行のタグは、1 つのクエリでまとめて読み取ります。」 | **キーで選ぶ** |
| **キー列** | キーで選ぶ | 行を選ぶ列です（`OrderNo` など）。 | 空 |
| **キーの値** | キーで選ぶ | 読み取る行でキー列が持つもので（`4711` など）、テキストとして入力します。データベースはそれをその列の値として読みます。 | 空 |
| **日付列** | 最新の行 | 最大値が最新の行になる列です（`updated_at` など）。 | 空 |

名前はデータベースが読むとおりに読まれます。

| 書き方 | 読まれ方 |
| --- | --- |
| `Orders` | 単純な名前で、データベースが単純な名前を読むとおりに読みます。PostgreSQL は小文字、Oracle は大文字です。 |
| `"Order Lines"`、`[Order Lines]` | データベース自身が受け付ける引用符が何であれ、名前そのものです。 |
| `dbo.Orders` | スキーマとテーブルです。引用符の外の点が両者を分けます。 |

ドライバーは名前の各部分を、そのデータベースが受け付ける引用符で囲むので、名前がテキストとしてステートメントに貼り付けられることはありません。欄が機能しないタグは保存されず、そのページが足りないものを伝えます。

| 空のままにした、または入力したもの | タグのページの表示 |
| --- | --- |
| テーブルなし | 「値があるテーブルを入力してください。」 |
| 3 つの部分からなるテーブル、または閉じていない引用符 | 「テーブルは名前、またはスキーマと名前 (dbo.Orders) です。空白を含む名前は引用符か角かっこで囲みます。各部分は 128 文字までです。」 |
| 値の列なし | 「値の列を入力してください。」 |
| 2 つの部分からなる値の列 | 「値の列は 1 つの名前です。空白を含む名前は引用符か角かっこで囲みます。」 |
| キーで選ぶ、キー列なし | 「行を選ぶキー列を入力してください。」 |
| キーで選ぶ、キーの値なし | 「キーの値を入力してください。読み取る行でキー列が持つ値です。」 |
| 最新の行、日付列なし | 「最大値が最新の行になる日付列を入力してください。」 |

読み取りのみの**取得元のアドレス（バス上）**の行は、ドライバーが受け取るものを示します。キーで選ぶ行なら `row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711`、最新の行なら `newest?table=recipe_setpoints&column=oven_temperature&by=updated_at` です。

### 独自のクエリ {#own-query}

| 欄 | 意味 | 既定値 |
| --- | --- | --- |
| **クエリの本文** | 「読み取るだけの SELECT を 1 つ書きます。値は最初の行の最初の列です。@now はステーションの日時です。」WITH で始まり読み取るだけのクエリも受け付けます。 | 空 |

クエリはデータベースが読むとおりに読まれます。引用符で囲んだテキスト、引用符で囲んだ名前、コメントの中にあるものはステートメントの語ではありません。読み取り以上のことをするクエリは、入力した場所で拒否されます。

| 入力したもの | タグのページの表示 |
| --- | --- |
| 何もなし | 「クエリを入力してください。最初の行の最初の列が値になる SELECT を 1 つ書きます。」 |
| SELECT でないステートメント（`UPDATE` や `DELETE` など） | 「クエリは SELECT か WITH で始めます。書き込むには、タグのアクセスを書き込み可能にして書き込みコマンドを使ってください。」 |
| データベースを変更する SELECT（`SELECT ... INTO`、`FOR UPDATE`、`INSERT`、`UPDATE`、`EXEC` を含むもの） | 「このクエリはデータベースを変更します (INSERT、UPDATE、SELECT INTO、FOR UPDATE、EXEC など)。クエリは読み取りだけです。」 |
| 2 つのステートメント | 「クエリに複数のステートメントがあります。SELECT を 1 つだけにしてください。」 |
| 閉じていない引用符、かっこ、コメント | 「クエリの引用符、かっこ、またはコメントが閉じていません。」 |
| `@now` 以外の値（`@order` など） | 「クエリで使えるのは、ステーションの日時を表す @now だけです。それ以外の値は本文に直接書いてください。」 |

クエリのタグのバス上のアドレスは、`query?sql=` の後にパーセント エンコードしたクエリが続きます。

## 読み取り {#reading}

| 読み取るタグ | 読み方 |
| --- | --- |
| キーで選ぶ | キー列がキーの値に等しい行の、そのタグの列です。キーの値はパラメーターとしてデータベースに渡します。 |
| 最新の行 | 日付列がその列の最大値を持つ行の、そのタグの列です（`WHERE ... = (SELECT MAX(...) FROM ...)`）。どのデータベースも ODBC も同じように読む形です。2 つの行が同じ値なら最初の行を読みます。 |
| 同じテーブルと行の複数のタグ | 1 回の巡回で、それらの列すべてを 1 つのクエリで読み、工場のデータベースにタグごとに問い合わせないようにします。データベースがそのクエリを拒否したとき（テーブルにない列など）は、そのタグ自身の列だけを読み、以後その行は列ごとに読みます。間違った列はそのタグだけの損失で済みます。 |
| 独自のクエリ | 書いたとおりのクエリで、`@now` はパラメーターとして渡します。 |

| データベースの応答 | タグ |
| --- | --- |
| タグが読む型の値 | その値を読みます。 |
| 空のセル（NULL） | 読み取りに失敗します。 |
| キーの値を持つ行がない、または 2 行以上ある | 読み取りに失敗します。タグは 1 つの行を読みます。 |
| まだ行のないテーブルの最新の行 | エラーなしで待機します。 |
| 行を返さないクエリ | 読み取りに失敗します。 |
| タグの型が読まない値（Int32 のタグのテキスト、整数のタグの小数部分） | 理由とともに読み取りに失敗します。 |

読み取りに失敗しても、タグは最後の読み取り値を保ちます。その値は、どのドライバーとも同じく、読み出し周期に 10 s を足した時間より古くなると失敗として扱われます。データベースには読み取りのたびに問い合わせます。ほかのプログラムが加えた変更は、加えた瞬間ではなく、次の読み取りで見えます。

## 書き込み {#writing}

タグが書き込むのは、その**アクセス**が**読み書き**か**書き込みのみ**になってからです。そのとき**信号源**のカードに、書き込みで何をするかが加わります。

| 欄 | 表示される条件 | 意味 | 既定値 |
| --- | --- | --- | --- |
| **書き込み時の動作** | タグが書き込む | **読み取るセルを変更**：「このタグが読み取るセルを、1 つの行だけ変更します。待機中の新しい値は古い値に置き換わります。」**書き込みコマンドを実行**：「下の書き込みコマンドを実行します。書き込みはすべて有効で、待機中のものが新しい書き込みのために捨てられることはありません。」独自のクエリを読むタグには変更するセルがなく、**書き込みコマンドを実行**だけが提示されます。 | **読み取るセルを変更** |
| **書き込みコマンド** | 書き込みコマンドを実行 | 「WHERE 付きの UPDATE、INSERT、またはストアド プロシージャの呼び出しです。@value は書き込む値、@now はステーションの日時です。値は常にパラメーターとして渡され、本文に貼り付けられることはありません。」空の入力欄にはヒントとして `INSERT INTO results (result, recorded_at) VALUES (@value, @now)` が表示されます。 | 空 |

書き込みコマンドを実行する**書き込みのみ**のタグは何も読まないので、その**信号源**のカードには書き込みだけがあります。

| 書き込み | 進み方 |
| --- | --- |
| 読み取るセルを変更 | タグの行で値の列を `@value` にする 1 つの UPDATE を、ちょうど 1 行が変わらなければ取り消されるトランザクションの中で実行します。どの行も持たないキーの値や、2 つの行が持つキーの値では何も書かず、そう伝えます。設定値と同じく、待機中の新しい値は古い値に置き換わります。 |
| 書き込みコマンドを実行 | 書き込みごとに 1 回、値を `@value`、ステーションの日時を `@now` としてコマンドを実行します。書き込みはすべて有効です。2 つの INSERT は 2 つの行になり、待機中の書き込みが新しい書き込みのために捨てられることはありません。ストアド プロシージャは `EXEC`、`CALL`、または ODBC の `{call ...}` で呼び出します。 |
| 整数のタグに書き込む小数部分のある値 | 丸められ、拒否されません。スケールも校正もない整数のタグ（Int32 または Int64）に小数部分のある値を書き込むと、データベースに届く前にいちばん近い整数に丸められ、半分はゼロから遠い側へ向かいます（2.5 は 3、-2.5 は -3 になります）。小数部分を拒否するのはセルを読むときで、値を書き込むときではありません。 |
| 応答 | 完了、またはデータベース自身の言葉での拒否です。ユーザーに足りない権限、行が破る制約、列が受け付けない値などです。 |

ドライバーが実行できないコマンドは、タグのページで拒否されます。

| 入力したもの | タグのページの表示 |
| --- | --- |
| 何もなし | 「書き込みコマンドを入力してください。@value を含む UPDATE、INSERT、またはストアド プロシージャの呼び出しです。」 |
| 別の語で始まるコマンド（`DELETE` など） | 「書き込みコマンドは UPDATE、INSERT、EXEC、CALL のいずれかで始めます。」 |
| 2 つのステートメント | 「書き込みコマンドに複数のステートメントがあります。1 つだけにするか、ストアド プロシージャを呼び出してください。」 |
| 閉じていない引用符、かっこ、コメント | 「書き込みコマンドの引用符、かっこ、またはコメントが閉じていません。」 |
| `@value` なし | 「書き込む値が入る位置に @value を置いてください。値は常にパラメーターとして渡され、本文に貼り付けられることはありません。」 |
| `@value` と `@now` 以外の値（`@line` など） | 「書き込みコマンドで使えるのは、書き込む値の @value と、ステーションの日時の @now だけです。」 |
| WHERE のない UPDATE | 「WHERE のない UPDATE はテーブルのすべての行を変更します。行を選ぶ WHERE を加えてください。」 |

`@@` で始まる名前（SQL Server の関数）と、`EXEC` の引数名（`@speed = @value`）は値ではなく、書いたとおりに残ります。保存にも同じ規則が適用され、タグを拒否したときはフィードがそれを伝えます。「タグ 'Order quantity' は、データベース内の値の位置を示していないか、ドライバーが拒否するクエリまたは書き込みコマンドを持っています。開いて、タグのパネルが示す欄を直してください。」

バス上のアドレスには書き込みが加わります。セルなら `row?table=recipes&column=setpoint&key=code&equals=R-100&write=cell`、コマンドを実行する**書き込みのみ**のタグなら `none?write=command&command=` の後にコマンドが続きます。

## 機器検出 {#discovery}

### 「スキャンの範囲」のカード {#the-scan-scope-card}

Connector の構成の権限があるとき、ドライバーのページには**スキャンの範囲**のカードがあり、その下に「機器と同じ方法でサインインし、テーブルの列ごとに、最新の行またはキーが最小の行のタグを提案します。」という行があります。

| 欄 | 意味 | 既定値 |
| --- | --- | --- |
| **スキャンする機器** | このドライバーの機器を名前で選びます。データベースは、そのサインイン情報を持つ機器を通してだけスキャンします。保存待ちの機器の編集は先に保存されます。 | 名前順で最初の機器 |
| **テーブル** | 「空欄ならすべてのテーブルです。名前か、Recipe* のように * が任意の文字を表すパターンも書けます。」 | 空 |

機器のないドライバーでは「データベースは、そのサインイン情報を持つ機器を通してスキャンします。このドライバーの機器を追加し、データベースの場所を入力してください。」だけが表示されます。範囲にまだ足りないものはカードの下に赤で表示され、範囲がそろうまで**機器検出**は灰色のままです。

| 範囲 | カードの表示 |
| --- | --- |
| データベースの場所を示していない、またはドライバーが使えない設定を持つ機器 | 「機器 'Recipes' は、データベースの場所をまだ示していないか、ドライバーが使えない設定を持っています。先に「データベース」カードを完成させてください。」 |
| スキーム、引用符、角かっこ、セミコロンを含むパターン（`dbo.Recipe*` など） | 「'dbo.Recipe*' はテーブルの名前でもパターンでもありません。名前か、* が任意の文字を表すパターンを、スキーマや引用符なしで書いてください。」 |
| もう存在しない機器 | 「スキャンに選んだ機器はもう存在しません。スキャンする別の機器を選んでください。」 |

スキャンする機器に保存できない編集があるときは、スキャンは始まりません。「スキャンする機器の編集を保存できなかったため、機器検出は始まりませんでした。」

### スキャンが提示するもの {#what-the-scan-offers}

スキャンは機器と同じ方法でサインインし、データベース自身が持つテーブルとビュー（システムのものを除く）とその列の一覧を読み、読んでいるテーブルを「recipes の列を読み取っています…」のように伝えます。候補は 1 つで、機器そのものが**設定済み**として示されます。そのカードには、機器のどのタグもまだ読んでいない列がチェック付きで並び、**開く**の前にある**タグにして追加**で、チェックしたものを読み取りのみで機器に追加します。

| テーブル | 候補のタグ |
| --- | --- |
| 日時の列を持つ | 列ごとに 1 つで、そのような最初の列で最新の行を読みます。 |
| 持たない | キー以外の列ごとに 1 つで、主キー（主キーのないテーブルでは最初の列）の値が最小の行を読みます。別の行を読むには、タグの**キーの値**を変えます。 |
| 持たず、行もない | 除外されます。 |
| どのタグの型も読まない列（バイナリ データ、ジオメトリ） | 除外され、スキャンを締めくくる行で数えられます。 |

タグはテーブルと列から名前が付けられ（`recipes.oven_temperature`）、列の型に合わせた型を持ち、どれも**読み取りのみ**です。スキャンが読むのはテーブル 100 個、値 500 個までで、そこで打ち切られたスキャンは「Recipes のスキャンは途中で止まりました。スキャンが読み取るのはテーブル 100 個、値 500 個までです。ほかを見るには、テーブル名かパターンを指定してください。」と伝えます。スキャンのほかの動作は[機器検出](connector-discovery)にあります。

機器自身のページでも、ドライバーのページを使わずに同じことができます。タグのカードの見出しにある**タグ検出**は機器と同じようにサインインし、保存されているとおりにデータベースをスキャンして、まだタグがない列だけを出します（[機器のタグを検出する](connector-discovery#discovering-a-devices-tags)を参照）。

## 例：SQL Server の製造指示 {#example-a-production-order-from-sql-server}

MES は製造指示を SQL Server に、データベース `MES` のテーブル `dbo.ProductionOrders` に、指示番号ごとに 1 行で持っています。

1. ドライバーのページで**機器を追加**し、`MES` と名付けます。**データベース**のカードで **SQL Server** のまま、**サーバー**に `mes-sql`（名前付きインスタンスなら `mes-sql\SQLEXPRESS`）を入力し、**ポート**は空のまま、**データベース名**に `MES` を入力します。
2. **サインイン**のカードで**ユーザー名**に `station_reader` とその**パスワード**を入力するか、**ステーションの Windows アカウントでサインイン**をオンにします。独自の証明書を持つ工場のサーバーなら**暗号化、どの証明書も受け入れる**を選びます。
3. **タグを追加**で `Order quantity`、Int32：**テーブル** `dbo.ProductionOrders`、**値の列** `Quantity`、**読み取る行** **キーで選ぶ**、**キー列** `OrderNo`、**キーの値** `4711`。バス上のアドレスは `row?table=dbo.ProductionOrders&column=Quantity&key=OrderNo&equals=4711` です。
4. **タグを追加**で `Product`、String、同じテーブルと行で**値の列**は `ProductCode`。2 つのタグは 1 つのクエリで読まれます。

ツールでは次のとおりです。

```
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
```

## 例：PostgreSQL のレシピの設定値 {#example-a-recipe-setpoint-from-postgresql}

レシピのシステムは、設定値が変わるたびに `recipe_setpoints` に行を追加し、`updated_at` 列に日時を入れます。

1. **機器を追加**し、`Recipes` と名付けます。**データベースの種類** **PostgreSQL**、**サーバー** `recipes-db`、**ポート**は空（PostgreSQL の 5432）、**データベース名** `recipes`、そして**サインイン**のカードで**ユーザー名**と**パスワード**を入力します。
2. **タグを追加**で `Oven setpoint`、Double：**テーブル** `recipe_setpoints`、**値の列** `oven_temperature`、**読み取る行** **最新の行**、**日付列** `updated_at`。

新しい行はタグの次の読み取りで読まれます。PostgreSQL は単純な名前を小文字で読むので、大文字を使って作った列（`OvenTemp` など）は引用符で囲んで `"OvenTemp"` と書きます。ツールでは次のとおりです。

```
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
```

## 例：INSERT で書く結果の行 {#example-a-result-line-written-as-an-insert}

品質システムは、測定した部品ごとに 1 行をテーブル `test_results` に求めています。

1. そのデータベースの機器で、**タグを追加**で `Result`、Double、**アクセス**は**書き込みのみ**にします。
2. **書き込み時の動作**は**書き込みコマンドを実行**にし、**書き込みコマンド**には行を追加する `INSERT INTO test_results (result, recorded_at) VALUES (@value, @now)` を書きます。

Logic が `Result` に書く値はそれぞれ独立した行になり、隣にステーションの日時が入ります。続けて行われた書き込みもすべて順番に残り、新しいもののために捨てられるものはありません。セルを読む**読み書き**のタグなら、書き込みのたびにその 1 つの行を変更します。ツールでは次のとおりです。

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

## 例：ODBC 経由の Access と Oracle {#example-access-and-oracle-through-odbc}

| データベース | このコンピューターに必要なもの | 機器の設定 |
| --- | --- | --- |
| Microsoft Access | Microsoft の Access Database Engine、64 ビット版 | **データベースの種類** **その他 (ODBC)**、**データ ソースまたは接続文字列** `Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\Data\plant.accdb`、ユーザーなし |
| Oracle | Oracle の 64 ビットの ODBC ドライバーと、それを使って ODBC データ ソース (64 ビット) に作ったデータ ソース `ERP` | **データベースの種類** **その他 (ODBC)**、**データ ソースまたは接続文字列** `ERP`、**サインイン**のカードで**ユーザー名**と**パスワード** |

機器が指定したデータ ソースまたはドライバーが 32 ビット プログラム用にしかない場合 (32 ビット版 Office の Access、ODBC データ ソース (32 ビット) で作成したデータ ソースなど)、またはこのコンピューターにまったくない場合、機器のパネルは、機器が指定した名前とともにどちらのケースかを示し、機器がオンで接続できていない間、手順をすべて示します。機器のエラーも英語で同じことを示し、`driver_capabilities` も Database ドライバーの `prerequisite` (`perDevice` が true) で同じことを示します。

| 項目 | 内容 |
| --- | --- |
| 足りないもの | そのデータ ソースまたはドライバー用の 64 ビットの ODBC ドライバー。Ganter Lab は 64 ビット プログラムなので、64 ビットの ODBC ドライバーと、それで作成したデータ ソースしか見えません。また、他社のドライバーを含めることはできません |
| 入手先 | データベースのメーカー。Access の場合は Microsoft のページ「Download and install Microsoft 365 Access Runtime」で、そのランタイムに Access Database Engine が含まれています |
| 選ぶもの | 64 ビット版 (Access の場合は、使う言語の 64 ビットのダウンロード ボタン)。32 ビット版は選びません |
| インストール方法 | 管理者として実行します。その後、データ ソース名を指定している機器の場合は、ODBC データ ソース (64 ビット) で同じ名前、同じデータベースのデータ ソースを作成します |
| よくあるつまずき | Office があるコンピューターでの Access：Microsoft のページによると、インストールされる Access Runtime は既存の Office に合わせたものになり、Windows インストーラーでインストールされた Office とは互換性がありません。また、Office の 64 ビット版と 32 ビット版の選び方についての Microsoft のページによると、一方のバージョンをアンインストールしてから、もう一方をインストールします |
| その後 | 次の接続の試行で、機器は今の設定のまま接続します。Ganter Lab を再起動する必要はありません |

通知の最後には、このコンピューターにある 64 ビットの ODBC ドライバーが、`Driver={...}` に書く名前で並びます。64 ビット側にあるのに失敗するデータ ソースやドライバーは、ドライバー マネージャー自身のエラーを表示します。

ODBC 経由では、単純な名前はデータベースが読むとおりに（Oracle では大文字で）読まれ、引用符で囲んだ名前はそのとおりに読まれます。日時は、どの ODBC ドライバーも受け付ける最も細かい単位である秒単位で送ります。ツールでは次のとおりです。

```
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-tools}

`driver_capabilities` はこのドライバーを `database` の下で説明します。簡単な手順、各データベースとその欄と自身のポート、暗号化の選択、タグの 2 つの読み方と 2 つの書き込み方、値は `@value` か `@now` としてだけステートメントに入るという規則、型、待ち時間、データベースのユーザーについての助言、機器検出、そして 4 つの完全な例です。`device_upsert driver=Database` は `databaseKind`（既定の `SqlServer`、`PostgreSql`、`MySql`、`Sqlite`、`Odbc`）を受け取り、サーバーでは `host`（SQL Server の `server\INSTANCE` や `server,port` は分けて読みます）、`port`、`databaseName`、SQL Server ではさらに `databaseInstance`、`databaseWindowsSignIn`、`databaseEncryption`（`Verified`、`TrustServerCertificate`、`Off`）、SQLite では `databaseFile`、ODBC では `databaseOdbc`、そして `username` と、決して返されない `password` を受け取ります。返す機器は `database` を持ち、そこにはアドレスと、機器がまだユーザーを待っているかどうかがあります。`tag_upsert` は値の列を `address` として、`databaseSource`（`Fields` か `Query`）、`databaseTable`、`databaseRow`（`Key` か `Newest`）、`databaseKeyColumn`、`databaseKeyValue`、`databaseDateColumn`、`databaseQuery`、そして `accessType` が書き込むときは `databaseWriteMode`（`Cell` か `Command`）と `databaseCommand` を受け取ります。新しいタグは、`opcUaType` で別に指定しない限り、読み取りのみの Double です。`device_discover driver=Database` は、このドライバーの設定済みの機器の名前である `databaseDevice` と、省略可能な `databaseTables`（テーブル名か `*` を使うパターン）を受け取ります。機器に足りないタグは `tag_upsert` で追加します。`connector_browse` は各機器の `databaseKind` を返します。ほかのツールは[エージェントのツール](agent-tools)にあります。

## 知っておきたい制限 {#limits-worth-knowing}

- ドライバーはタグの値を、タグごとに 1 つ読み書きします。ステーションの実行記録をデータベースに記録することはしません。シリアル番号、判定、測定値を持つ実行ごとの 1 行は、このドライバーが書くものではありません。
- 製品には ODBC ドライバーは付属しません。Oracle、Access、Firebird など、直接接続する 4 つ以外のデータベースには、そのデータベース自身の 64 ビットの ODBC ドライバーをコンピューターにインストールする必要があります。32 ビットのものは見えません。
- 機器は Windows のパスワードを保存しません。SQL Server への Windows のサインインと、ネットワーク共有上の SQLite ファイルは、ステーションを実行しているアカウントを使うので、データベースや共有はそのアカウントを受け入れる必要があります。
- クエリは読み取るだけです。データベースを変更するのは書き込みで、書き込みはアクセスが書き込みを許すタグでだけ起こります。
- 値がステートメントの本文に入ることはありません。書き込む値は常にパラメーター `@value`、ステーションの日時は `@now` で、キーの値もパラメーターです。それ以外の値を名前で示すステートメントは拒否されます。
- セルへの書き込みはちょうど 1 行を変更します。UPDATE のコマンドには WHERE が必要です。
- データベースには読み取りのたびに問い合わせます。ほかのプログラムが加えた変更は次の読み取りで見え、同じ行のタグが多いデータベースには、1 回の巡回でそれらすべてについて 1 回だけ問い合わせます。
- バイナリの列は読み取りません。
