返答を意識しつつ function ノードと合わせて Node-RED SQLite で複数行を一括 INSERT するメモ

返答を意識しつつ function ノードと合わせて Node-RED SQLite で複数行を一括 INSERT するメモ

返答を意識しつつ function ノードと合わせて Node-RED SQLite で複数行を一括 INSERT するメモです。

背景

Node-RED の SQLite ノード node-red-node-sqlite で、だいたい単一行の書き込みがうまくいって、わーいとなっていると、出てくるのが 20 行くらい一気に複数データを送りたくなります。

SQLite ノードには Batch Without Response = 一括(応答なし)というモードがあるんですが、名前の通りで返答が返ってきません。頑張って使っていたんですけど、わたしの SQL 力と、そこから繰り出されるコーディングエージェント指示力からすると、絶対エラーや不具合が途中で起きるので、さすがにつらくなってきました。自分の能力を超えることはできない好例ですね!

よく使っている方法

複数 VALUES の一括指定を配列で作って function ノードによる組み立て方式をよく使ってます。とっても泥臭いやつです。

たしか、最近の SQLite (3.7.11 以降?だったかな?)では 1 つの INSERT 文に複数の VALUES 句をカンマ区切りで並べられます。

これならセミコロンは文末の 1 つだけで済むため、Node-RED の SQLite ノードでも問題なく実行できるんです。

CREATE TABLE IF NOT EXISTS my_table (
  col_a TEXT,
  col_b INTEGER
);

のようなテーブルがあるとします。

INSERT INTO my_table (col_a, col_b) VALUES
  ('val1', 100),
  ('val2', 200),
  ('val3', 300);

SQL の基本形はこんなイメージです。これを function ノードでの組み立てる例です。

こんなフローです。

const payload = [
	{"col_a":'val1', "col_b":100},
	{"col_a":'val2', "col_b":200},
	{"col_a":'val3', "col_b":300}
]
msg.payload = payload;
return msg;

「複数データ配列」と書かれている手前の function ノードでこのように挿入データが配列でできてる前提です。

const rows = msg.payload; // 配列を想定
const values = rows.map(r => `('${r.col_a}', ${r.col_b})`).join(',\n');
msg.topic = `INSERT INTO my_table (col_a, col_b) VALUES ${values}`;
return msg;

SQLite ノード手前の「複数行クエリ対応」と書かれた function ノードで VALUES 部分を組み立てて msg.topic に渡します。

こんな風に書き込まれます!

エラーもちゃんと出る

たとえば、このように my_table2 とテーブル名を間違えたとしてちゃんとエラーが出てくれます。たすかるー。

フロー JSON データ

フローの JSON データです。

[{"id":"67cf4e5b9f0f67fe","type":"sqlite","z":"37d930dc7ad11e37","mydb":"70b3c9b194e4aa90","sqlquery":"msg.topic","sql":"","name":"","x":350,"y":280,"wires":[["aef1ab113c68b996"]]},{"id":"0144c6d44772c94f","type":"function","z":"37d930dc7ad11e37","name":"複数行クエリ対応","func":"const rows = msg.payload; // 配列を想定\nconst values = rows.map(r => `('${r.col_a}', ${r.col_b})`).join(',\\n');\nmsg.topic = `INSERT INTO my_table (col_a, col_b) VALUES ${values}`;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":180,"wires":[["cc58a93642726679","67cf4e5b9f0f67fe"]]},{"id":"d4412f951d846944","type":"inject","z":"37d930dc7ad11e37","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":180,"wires":[["b789d41cf75d5ebb"]]},{"id":"b789d41cf75d5ebb","type":"function","z":"37d930dc7ad11e37","name":"複数データ配列","func":"const payload = [\n    { \"col_a\": 'val1', \"col_b\": 100 },\n    { \"col_a\": 'val2', \"col_b\": 200 },\n    { \"col_a\": 'val3', \"col_b\": 300 }\n]\nmsg.payload = payload;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":380,"y":180,"wires":[["0144c6d44772c94f"]]},{"id":"cc58a93642726679","type":"debug","z":"37d930dc7ad11e37","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"topic","targetType":"msg","statusVal":"","statusType":"auto","x":860,"y":180,"wires":[]},{"id":"aef1ab113c68b996","type":"debug","z":"37d930dc7ad11e37","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":600,"y":280,"wires":[]},{"id":"70b3c9b194e4aa90","type":"sqlitedb","db":"test.db","mode":"RWC"},{"id":"b150fef5c3128a0b","type":"global-config","env":[],"modules":{"node-red-node-sqlite":"1.1.1"}}]

ということで

20 行程度の一括挿入であればこの方法で十分対応可能。自分の場合 50 とか 100 行でもやってしまうときがありますね。

しいていえば、何かしらユーザー入力を直接埋め込む場合は SQL インジェクションに注意ですね!