Node-Red +SQLite範例
SQLite 是一款非常獨特且極受歡迎的關聯式資料庫管理系統(RDBMS)。與 MySQL、PostgreSQL 或 Oracle 等傳統資料庫不同,SQLite 的核心設計理念是輕量化、嵌入式且無需設定。
以下為您詳細拆解 SQLite 的核心功能與特性:
💡 核心特性 (Key Features)
1. 嵌入式與伺服器無關 (Serverless)
傳統資料庫需要啟動一個獨立的伺服器行程(Process),並透過網路連接埠(如 MySQL 的 3306)進行通訊。
SQLite 沒有獨立的伺服器行程。
它是一個軟體庫(Library),直接被編譯並嵌入到你的應用程式中。應用程式透過直接呼叫函數來讀寫資料,完全省去了網路傳輸的開銷。
2. 單一檔案儲存 (Single-File Database)
SQLite 將整個資料庫(包含結構定義、資料表、索引、資料本身)完整地儲存在電腦硬碟中的單一個普通檔案中。
這使得資料庫的備份、複製、共享變得極其簡單,就像移動一個 Word 文件一樣方便。
3. 零配置 (Zero-Configuration)
使用 SQLite 不需要安裝、不需要設定設定檔、不需要管理使用者權限,更不需要啟動或停止服務。
只要你的應用程式載入了 SQLite 庫,指定一個檔案路徑,資料庫就馬建好並可以使用了。
4. 跨平台與高相容性
SQLite 的資料庫檔案格式是跨平台的。你在 Windows 上生成的資料庫檔案,可以直接複製到 Mac、Linux、iOS 或 Android 上直接讀取,完全不需要轉換格式。
5. 動態型別系統 (Manifest Typing)
多數 SQL 資料庫(如 MySQL)在建立欄位時如果指定了
INTEGER,就絕對不能存入字串。SQLite 採用動態型別,資料的型別是跟著「資料本身」走,而不是跟著「欄位」走。你可以在一個
INTEGER欄位裡存入字串(除了INTEGER PRIMARY KEY例外)。
🛠️ 主要功能 (Major Capabilities)
完整的 SQL 支援: 支援絕大多數的 SQL-92 標準,包含複雜的查詢(
JOIN、SUBQUERY)、檢視表(VIEW)、觸發器(TRIGGER)以及視窗函數(Window Functions)。ACID 事務支援: 具備完整的原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)與持久性(Durability)。即使程式崩潰或突然斷電,也能確保資料不會損壞。
記憶體資料庫 (In-Memory Database): 支援直接在記憶體中建立資料庫(使用
:memory:作為檔名),讀寫速度極快,非常適合用於暫存資料或單元測試(Unit Test)。高效能的讀取: 對於中小型規模的資料,SQLite 的讀取速度往往比傳統客戶端/伺服器架構的資料庫還要快,因為它沒有網路延遲。
⚖️ 優缺點與適用場景
| 優點 (Pros) | 缺點與限制 (Cons) |
| 極度輕量:核心庫大小通常小於 1MB。 | 不適合高併發寫入:寫入時會鎖定整個資料庫檔案,若有大量同時寫入的需求會遭遇瓶頸。 |
| 維護成本極低:免去維護資料庫伺服器的煩惱。 | 缺乏細粒度權限控制:無法針對單一使用者設定特定資料表的讀寫權限。 |
| 隨處可見:Android、iOS、Windows、Mac 內部都內建了 SQLite。 | 不適合海量資料:雖然理論支援到 281 TB,但超大型數據更適合分布式資料庫。 |
🎯 最佳適用場景:
行動裝置與桌面軟體:App(iOS/Android)儲存用戶資料、瀏覽器(如 Chrome)儲存歷史紀錄。
物聯網與嵌入式設備 (IoT):無人機、智慧家電、車載系統。
軟體開發與測試:在開發階段快速驗證功能,或作為單元測試的隔離資料庫。
低流量網站/工具:個人部落格、企業內部小型管理系統。
[{"id":"19c22864913f7b0e","type":"tab","label":"學生成績管理系統","disabled":false,"info":""},{"id":"c94f3be1821949c2","type":"debug","z":"19c22864913f7b0e","name":"debug 389","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":200,"wires":[]},{"id":"3ed0de022680f2db","type":"debug","z":"19c22864913f7b0e","name":"debug 390","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":550,"y":400,"wires":[]},{"id":"in_sw_id_v2","type":"ui_text_input","z":"19c22864913f7b0e","name":"學號輸入","label":"1. 學號 (主鍵必填)","tooltip":"","group":"ui_grp_left_form_switch_v2","order":1,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"student_id","sendOnBlur":true,"x":80,"y":100,"wires":[["change_save_switch_v2"]]},{"id":"in_sw_name_v2","type":"ui_text_input","z":"19c22864913f7b0e","name":"姓名輸入","label":"2. 姓名 (新增時必填)","tooltip":"","group":"ui_grp_left_form_switch_v2","order":2,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"name","sendOnBlur":true,"x":80,"y":140,"wires":[["change_save_switch_v2"]]},{"id":"in_sw_gender_v2","type":"ui_switch","z":"19c22864913f7b0e","name":"性別開關","label":"3. 性別 (關閉:女 / 開啟:男)","tooltip":"","group":"ui_grp_left_form_switch_v2","order":3,"width":0,"height":0,"passthru":false,"decouple":"false","topic":"gender","style":"","onvalue":"男","onvalueType":"str","onicon":"","oncolor":"","offvalue":"女","offvalueType":"str","officon":"","offcolor":"","x":80,"y":180,"wires":[["change_save_switch_v2"]]},{"id":"in_sw_dob_v2","type":"ui_text_input","z":"19c22864913f7b0e","name":"生日輸入","label":"4. 出生年次","tooltip":"","group":"ui_grp_left_form_switch_v2","order":4,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"birth_date","sendOnBlur":true,"className":"","topicType":"str","x":80,"y":220,"wires":[["change_save_switch_v2"]]},{"id":"in_sw_school_v2","type":"ui_text_input","z":"19c22864913f7b0e","name":"畢業科系輸入","label":"5. 畢業科系","tooltip":"","group":"ui_grp_left_form_switch_v2","order":5,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"grad_school","sendOnBlur":true,"x":100,"y":260,"wires":[["change_save_switch_v2"]]},{"id":"in_sw_note_v2","type":"ui_text_input","z":"19c22864913f7b0e","name":"備註輸入","label":"6. 備註","tooltip":"","group":"ui_grp_left_form_switch_v2","order":6,"width":0,"height":0,"passthru":false,"mode":"text","delay":0,"topic":"note","sendOnBlur":true,"x":80,"y":300,"wires":[["change_save_switch_v2"]]},{"id":"change_save_switch_v2","type":"change","z":"19c22864913f7b0e","name":"即時暫存欄位資料","rules":[{"t":"set","p":"student_form[msg.topic]","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":200,"wires":[["c94f3be1821949c2"]]},{"id":"btn_sw_1_v2","type":"ui_button","z":"19c22864913f7b0e","name":"鍵1_建立資料表","group":"ui_grp_right_btn_switch_v2","order":1,"width":0,"height":0,"passthru":false,"label":"建立資料表","tooltip":"","color":"#ffffff","bgcolor":"#17a2b8","payload":"","payloadType":"str","topic":"CREATE_TABLE","x":100,"y":360,"wires":[["func_sw_core_v2"]]},{"id":"btn_sw_2_v2","type":"ui_button","z":"19c22864913f7b0e","name":"鍵2_新增","group":"ui_grp_right_btn_switch_v2","order":2,"width":0,"height":0,"passthru":false,"label":"新增資料","tooltip":"","color":"#ffffff","bgcolor":"#28a745","payload":"","payloadType":"str","topic":"ADD","x":80,"y":400,"wires":[["func_sw_core_v2"]]},{"id":"btn_sw_3_v2","type":"ui_button","z":"19c22864913f7b0e","name":"鍵3_更正","group":"ui_grp_right_btn_switch_v2","order":3,"width":0,"height":0,"passthru":false,"label":"更正資料","tooltip":"","color":"#ffffff","bgcolor":"#ffc107","payload":"","payloadType":"str","topic":"UPDATE","x":80,"y":440,"wires":[["func_sw_core_v2"]]},{"id":"btn_sw_4_v2","type":"ui_button","z":"19c22864913f7b0e","name":"鍵4_刪除","group":"ui_grp_right_btn_switch_v2","order":4,"width":0,"height":0,"passthru":false,"label":"刪除資料","tooltip":"","color":"#ffffff","bgcolor":"#dc3545","payload":"","payloadType":"str","topic":"DELETE","x":80,"y":480,"wires":[["func_sw_core_v2"]]},{"id":"btn_sw_5_v2","type":"ui_button","z":"19c22864913f7b0e","name":"鍵5_查詢","group":"ui_grp_right_btn_switch_v2","order":5,"width":0,"height":0,"passthru":false,"label":"查詢資料(依學號)","tooltip":"","color":"#ffffff","bgcolor":"#6c757d","payload":"","payloadType":"str","topic":"QUERY","x":80,"y":520,"wires":[["func_sw_core_v2"]]},{"id":"btn_sw_6_v2","type":"ui_button","z":"19c22864913f7b0e","name":"鍵6_顯示所有","group":"ui_grp_right_btn_switch_v2","order":6,"width":0,"height":0,"passthru":false,"label":"顯示所有資料","tooltip":"","color":"#ffffff","bgcolor":"#007bff","payload":"","payloadType":"str","topic":"DISPLAY_ALL","x":100,"y":560,"wires":[["func_sw_core_v2"]]},{"id":"func_sw_core_v2","type":"function","z":"19c22864913f7b0e","name":"6鍵核心冒號映射邏輯","func":"var action = msg.topic;\nvar form = flow.get(\"student_form\") || {};\n\nvar student_id = form.student_id ? form.student_id.trim() : \"\";\nvar name = form.name ? form.name.trim() : \"\";\nvar gender = form.gender ? form.gender.trim() : \"女\";\nvar birth = form.birth_date || \"\";\nvar school = form.grad_school ? form.grad_school.trim() : \"\";\nvar note = form.note ? form.note.trim() : \"\";\n\nswitch (action) {\n case \"CREATE_TABLE\":\n msg.topic = `CREATE TABLE IF NOT EXISTS students (\n student_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n gender TEXT,\n birth_date TEXT,\n grad_school TEXT,\n note TEXT\n );`;\n msg.payload = {};\n break;\n\n case \"ADD\":\n if (student_id === \"\" || name === \"\") {\n node.error(\"【系統警告】新增資料失敗:學號與姓名欄位為必填項目!\");\n return null;\n }\n msg.topic = `INSERT INTO students (student_id, name, gender, birth_date, grad_school, note) VALUES ('${student_id}','${name}','${gender}','${birth}','${school}','${note}')`;\n break;\n //msg.topic = \"INSERT INTO LEDSTATUS ( STATUS , Date , Time ) VALUES ($myLED, $var_date , $var_time ) \";\n //msg.payload = [myLED, var_date, var_time]\n //return msg;\n\n case \"UPDATE\":\n if (student_id === \"\") {\n node.error(\"【系統警告】更正資料失敗:必須提供欲修改之『學號』!\");\n return null;\n }\n let temp = \"update students set\";\n temp = temp + \" name= '\" + name + \"'\";\n temp = temp + \", gender= '\" + gender + \"'\";\n temp = temp + \", birth_date= '\" + birth + \"'\";\n temp = temp + \", grad_school= '\" + school + \"'\";\n temp = temp + \", note= '\" + note + \"'\";\n temp = temp + \" where student_id= '\" + student_id + \"'\";\n msg.topic = temp;\n return msg;\n\n case \"DELETE\":\n if (student_id === \"\") {\n node.error(\"【系統警告】刪除資料失敗:請指定要刪除的『學號』!\");\n return null;\n }\n let temp1 = \"DELETE FROM students \";\n temp1 = temp1 + \" where student_id= '\" + student_id + \"'\";\n msg.topic = temp1; \n break;\n\n case \"QUERY\":\n if (student_id === \"\") {\n node.error(\"【系統警告】查詢失敗:請在左側輸入欲查詢的『學號』!\");\n return null;\n }\n let temp3= \"SELECT * FROM students \";\n temp3 = temp3 + \" where student_id= '\" + student_id + \"'\";\n msg.topic = temp3; \n break;\n\n case \"DISPLAY_ALL\":\n msg.topic = `SELECT * FROM students ORDER BY student_id ASC;`;\n break;\n\n default:\n return null;\n}\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":460,"wires":[["3ed0de022680f2db","sqlite_node_switch_v2"]]},{"id":"sqlite_node_switch_v2","type":"sqlite","z":"19c22864913f7b0e","mydb":"sqlite_config_switch_v2","sqlquery":"msg.topic","sql":"","name":"SQLite 資料庫","x":540,"y":500,"wires":[["ui_table_switch_v2"]]},{"id":"ui_table_switch_v2","type":"ui_table","z":"19c22864913f7b0e","group":"ui_grp_bottom_table_switch_v2","name":"學生資料顯示面版","order":1,"width":"12","height":"6","columns":[{"field":"student_id","title":"學號","width":"15%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"name","title":"姓名","width":"15%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"gender","title":"性別","width":"10%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"birth_date","title":"出生年次","width":"20%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"grad_school","title":"畢業科系","width":"20%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"note","title":"備註","width":"20%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":730,"y":500,"wires":[]},{"id":"f6a5c8e22f729820","type":"comment","z":"19c22864913f7b0e","name":"資料表 students (stduent.db)","info":"CREATE TABLE \"students\" (\n\t\"student_id\"\tTEXT,\n\t\"name\"\tTEXT NOT NULL,\n\t\"gender\"\tTEXT,\n\t\"birth_date\"\tTEXT,\n\t\"grad_school\"\tTEXT,\n\t\"note\"\tTEXT,\n\tPRIMARY KEY(\"student_id\")\n);","x":340,"y":160,"wires":[]},{"id":"808d139d0ecb740b","type":"comment","z":"19c22864913f7b0e","name":"update","info":"let temp=\"update Score set\";\ntemp=temp+\" chinese= \" + chin ;\ntemp=temp+\", mathematics= \"+ math ;\ntemp=temp+\", english= \" + engl ;\ntemp=temp+\", total= \" + tot ;\ntemp=temp+\", average= \" + avg ;\ntemp=temp+\" where name= '\"+ name +\"'\";\nmsg.topic=temp;\nreturn msg;","x":290,"y":340,"wires":[]},{"id":"cd9c3ea5f68887f0","type":"function","z":"19c22864913f7b0e","name":"更正 function","func":"var action = msg.topic;\nvar form = flow.get(\"student_form\") || {};\n\nvar student_id = form.student_id ? form.student_id.trim() : \"\";\nvar name = form.name ? form.name.trim() : \"\";\nvar gender = form.gender ? form.gender.trim() : \"女\";\nvar birth = form.birth_date || \"\";\nvar school = form.grad_school ? form.grad_school.trim() : \"\";\nvar note = form.note ? form.note.trim() : \"\";\nif (student_id === \"\") {\n node.error(\"【系統警告】更正資料失敗:必須提供欲修改之『學號』!\");\n return null;\n }\n\nlet temp = \"update students set\";\ntemp = temp + \" name= '\" + name +\"'\";\ntemp = temp + \", gender= '\" + gender + \"'\";\ntemp = temp + \", birth_date= '\" + birth + \"'\";\ntemp = temp + \", grad_school= '\" + school + \"'\";\ntemp = temp + \", note= '\" + note + \"'\";\ntemp = temp + \" where student_id= '\" + student_id + \"'\";\nmsg.topic = temp;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":540,"wires":[["sqlite_node_switch_v2"]]},{"id":"ui_grp_left_form_switch_v2","type":"ui_group","name":"1. 學生資料輸入欄位","tab":"stud_tab_main_switch_v2","order":1,"disp":true,"width":"8","collapse":false},{"id":"ui_grp_right_btn_switch_v2","type":"ui_group","name":"2. 功能操作快捷鍵","tab":"stud_tab_main_switch_v2","order":2,"disp":true,"width":"4","collapse":false},{"id":"sqlite_config_switch_v2","type":"sqlitedb","db":"student.db","mode":"RWC"},{"id":"ui_grp_bottom_table_switch_v2","type":"ui_group","name":"3. 資料顯示面板","tab":"stud_tab_main_switch_v2","order":3,"disp":true,"width":"12","collapse":false},{"id":"stud_tab_main_switch_v2","type":"ui_tab","name":"學生資料管理系統","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
這份 JSON 程式碼構建了一個非常完整且設計精良的學生資料管理系統(第一部分)。它採用了 Node-RED Dashboard 作為前端視覺化網頁,並以 SQLite 作為後端實體資料庫。
以下為您詳細拆解與說明這個程式的核心架構、資料流向與節點分工。
🏗️ 一、 系統視覺與佈局架構 (UI Dashboard)
整個系統在網頁畫面上被劃分為三個主要區塊,利用寬度配置(Grid 系統)實現了「左側表單、右側按鍵、下方看板」的經典佈局:
+-------------------------------------------------------------------------+
| 學生資料管理系統 (Tab) |
+----------------------------------------------------+--------------------+
| | |
| 1. 學生資料輸入欄位 (Group A, 寬度: 8) | 2. 功能操作快捷鍵 |
| - 學號、姓名、性別開關(Switch)、 | (Group B, 寬度: 4)|
| 出生年次、畢業科系、備註 | - 6 個實體按鈕 |
| | |
+----------------------------------------------------+--------------------+
| 3. 資料顯示面板 (Group C, 寬度: 12) |
| - 學生資料表格 (ui_table) |
+-------------------------------------------------------------------------+
左側:
ui_grp_left_form_switch_v2(寬度 8) 容納 6 個輸入元件。特別的是「性別」使用了ui_switch,開啟傳送"男",關閉傳送"女"。右側:
ui_grp_right_btn_switch_v2(寬度 4) 垂直排列 6 個功能按鈕(建立資料表、新增、更正、刪除、查詢、顯示所有)。下方:
ui_grp_bottom_table_switch_v2(寬度 12) 橫跨整個畫面,用來放置ui_table表格,呈現從 SQLite 查詢出來的最新資料。
🔄 二、 核心資料流向 (Data Flow)
這個程式的精妙之處在於它解決了網頁表單常見的「即時觸發」問題。它將程序分為「輸入期」與「執行期」:
1. 輸入期:資料即時暫存
當使用者在左側網頁的 6 個輸入框(如學號、姓名)中輸入文字或切換性別時:
每個輸入節點都會發送一個帶有自己
msg.topic(例如student_id)的事件。這些事件會流向中間的 Change 節點 「即時暫存欄位資料」。
關鍵邏輯: 它會執行
flow.student_form[msg.topic] = msg.payload,把資料寫入 Node-RED 的 Flow 記憶體環境變數 中。此時完全不觸發資料庫。
2. 執行期:多鍵合一的 SQL 映射
當使用者點擊右側 6 個功能鍵中的任意一個時:
按鈕會發送它專屬的指令標籤(存放於
msg.topic),例如新增按鈕發送"ADD",更正按鈕發送"UPDATE"。訊號進入核心 Function 節點 「6鍵核心冒號映射邏輯」。
🧠 三、 核心 Function 節點邏輯解析 (func_sw_core_v2)
這個 Function 節點是全系統的大腦,它負責從 Flow 記憶體中撈出剛剛暫存的 6 個欄位資料,並根據點擊的按鈕(action),利用 JavaScript 的 樣板字串(Template Literals) 動態組裝出純文字的 SQL 命令:
var action = msg.topic;
var form = flow.get("student_form") || {};
// 1. 從記憶體提取 6 個欄位資料,並進行嚴謹的修剪(trim)與防錯預設
var student_id = form.student_id ? form.student_id.trim() : "";
var name = form.name ? form.name.trim() : "";
var gender = form.gender ? form.gender.trim() : "女"; // 若沒動過開關,預設為女
var birth = form.birth_date || "";
var school = form.grad_school ? form.grad_school.trim() : "";
var note = form.note ? form.note.trim() : "";
// 2. 依據按鈕動作,組裝對應的 SQL 語句,並加上單引號防止字串報錯
switch (action) {
case "CREATE_TABLE":
msg.topic = `CREATE TABLE IF NOT EXISTS students (...);`;
break;
case "ADD":
// 欄位防空驗證
if (student_id === "" || name === "") { node.error("..."); return null; }
// 【已修正】將變數直接安全地嵌入單引號之中
msg.topic = `INSERT INTO students (...) VALUES ('${student_id}','${name}','${gender}','${birth}','${school}','${note}')`;
break;
case "UPDATE":
// 組合更正字串
msg.topic = `update students set name='${name}', gender='${gender}', birth_date='${birth}', grad_school='${school}', note='${note}' where student_id='${student_id}'`;
break;
case "DELETE":
msg.topic = `DELETE FROM students where student_id= '${student_id}'`;
break;
case "QUERY":
msg.topic = `SELECT * FROM students where student_id= '${student_id}'`;
break;
case "DISPLAY_ALL":
msg.topic = `SELECT * FROM students ORDER BY student_id ASC;`;
break;
}
return msg;
🗄️ 四、 資料庫與後端顯示 (SQLite & Table)
SQLite 設定節點 (
sqlite_config_switch_v2)配置連接到本地的實體資料庫檔案
student.db,模式為RWC(可讀、可寫、不存在則自動建立)。
SQLite 實體節點 (
sqlite_node_switch_v2)它的 SQL Type 被配置為
via msg.topic(即配合sqlquery: "msg.topic")。這意味著它會直接接收前面 Function 節點組裝出來的字串命令並在資料庫內執行。
前端表格顯示 (
ui_table_switch_v2)當執行的是
QUERY或DISPLAY_ALL時,SQLite 資料庫會回傳一個包含查詢結果的 JSON 陣列物件。此資料流入
ui_table節點後,表格會依據預設好的 6 個欄頭(學號、姓名、性別、出生年次、畢業科系、備註)自動將資料對齊、渲染並顯示在網頁最下方。
💡 系統優點總結
資料與介面分離:輸入欄位時不會頻繁衝擊資料庫,只有點擊右側功能鍵時才進行單次、精確的資料處理。
字串安全封裝:程式在 SQL 拼接過程中,為每個變數(如
name、school)都外包了單引號'${name}',完美解決了 SQLite 之前將中文誤判為no such column的錯誤。高相容性結構:利用
flow context暫存,即使未來增加第 7、第 8 個科目或成績欄位,也只需要在 Change 節點與核心 Function 節點中擴充變數,整體架構極具彈性。


沒有留言:
張貼留言