上传到 n8n
高级
这是一个Miscellaneous, AI RAG, Multimodal AI领域的自动化工作流,包含 29 个节点。主要使用 Code, Merge, Switch, Postgres, Supabase 等节点。 使用 OpenAI 和 Supabase 将 Excel 数据转换为 AI 就绪向量
前置要求
- •PostgreSQL 数据库连接信息
- •Supabase URL 和 API Key
- •可能需要目标 API 的认证凭证
使用的节点 (29)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "N4vmvG8N8J7d1713",
"meta": {
"instanceId": "89249a8a187ba6e01e16112a0d334a3aa01d510ad8f88d223e12cc0a2a8beb6b"
},
"name": "上传到n8n",
"tags": [],
"nodes": [
{
"id": "1ab6c2f3-8ec9-44f4-aa99-1113bf211dac",
"name": "当点击\"执行工作流\"时",
"type": "n8n-nodes-base.manualTrigger",
"position": [
1440,
224
],
"parameters": {},
"typeVersion": 1
},
{
"id": "41676fa7-273a-4b53-b2ab-0ef1c84a685c",
"name": "移除HTML",
"type": "n8n-nodes-base.code",
"position": [
2800,
224
],
"parameters": {
"jsCode": "// Pass all Excel data through, normalize dates, and clean up text\nconst results = [];\n\n// Excel date formatting (simple)\nfunction fixExcelDate(excelDate) {\n if (!excelDate || isNaN(excelDate)) return null;\n const date = new Date((excelDate - 25569) * 86400 * 1000);\n return date.toISOString().split('T')[0];\n}\n\nfor (const item of $input.all()) {\n const cleanedItem = { ...item.json };\n \n // Fix Excel dates - replace originals to avoid duplicates\n if (cleanedItem['Creation Date']) {\n cleanedItem['Creation Date'] = fixExcelDate(cleanedItem['Creation Date']);\n }\n if (cleanedItem['Closed on']) {\n cleanedItem['Closed on'] = fixExcelDate(cleanedItem['Closed on']);\n }\n \n // Clean Question field\n if (cleanedItem['Question']) {\n cleanedItem['Question_clean'] = cleanedItem['Question']\n .toString()\n .replace(/<[^>]*>/g, '') // remove HTML tags\n .replace(/ /g, ' ') // replace with space\n .replace(/&[^;]+;/g, ' ') // replace other HTML entities\n .replace(/\"\\s\"/g, '') // remove \" \"\n .replace(/\\s+/g, ' ') // collapse multiple spaces\n .trim(); // trim edges\n }\n \n // Clean Answer field - FIXED: was processing Question instead of Answer\n if (cleanedItem['Answer']) {\n cleanedItem['Answer_clean'] = cleanedItem['Answer']\n .toString()\n .replace(/<[^>]*>/g, '') // remove HTML tags\n .replace(/ /g, ' ') // replace with space\n .replace(/&[^;]+;/g, ' ') // replace other HTML entities\n .replace(/\"\\s\"/g, '') // remove \" \"\n .replace(/\\s+/g, ' ') // collapse multiple spaces\n .trim(); // trim edges\n }\n \n // Add flags - FIXED: boolean logic and field names\n cleanedItem['has_Question'] = (cleanedItem['Question_clean'] && cleanedItem['Question_clean'].length > 10);\n cleanedItem['has_Answer'] = (cleanedItem['Answer_clean'] && cleanedItem['Answer_clean'].length > 10);\n \n results.push({\n json: cleanedItem\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "062375f2-1e05-4f9d-94fb-8a20924f36da",
"name": "遍历项目",
"type": "n8n-nodes-base.splitInBatches",
"position": [
3360,
288
],
"parameters": {
"options": {
"reset": false
}
},
"typeVersion": 3
},
{
"id": "59fd4fa7-2911-4170-acd7-fce1b7277b08",
"name": "切换",
"type": "n8n-nodes-base.switch",
"position": [
3840,
96
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Question",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "679333a5-0792-402a-b3d1-406593bfd7d7",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.has_Question }}",
"rightValue": "=true"
}
]
},
"renameOutput": true
},
{
"outputKey": "Answer",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "57db0cab-2abc-4f43-a4bf-b14f2d4034c1",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.has_Answer }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {
"allMatchingOutputs": true
}
},
"typeVersion": 3.2
},
{
"id": "59433026-4b9d-41af-8555-11801a87cbeb",
"name": "合并",
"type": "n8n-nodes-base.merge",
"position": [
5696,
256
],
"parameters": {
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "3ea830bf-4347-4214-9c99-5ac2c996f30c",
"name": "合并导入数据",
"type": "n8n-nodes-base.merge",
"position": [
2320,
224
],
"parameters": {
"mode": "combine",
"options": {},
"joinMode": "keepNonMatches",
"outputDataFrom": "input1",
"fieldsToMatchString": "UUID"
},
"typeVersion": 3.2
},
{
"id": "65279f10-28bc-430f-a259-ceea614f0976",
"name": "代码\"问题\"",
"type": "n8n-nodes-base.code",
"position": [
4496,
0
],
"parameters": {
"jsCode": "// Process only Customer Question field\nconst item = $input.first().json;\n\n// Check whether we need to process Customer Question\nif (!item.has_Question || !item.Question_clean) {\n return [];\n}\n\n// Just send the text to OpenAI – no extra metadata\nreturn [{\n json: {\n record_id: item.id || item.UUID,\n text_to_embed: item.Question_clean,\n original_item: item\n }\n}];"
},
"typeVersion": 2
},
{
"id": "6eee5eea-8559-476b-ab41-b3dba790224d",
"name": "代码\"答案\"",
"type": "n8n-nodes-base.code",
"position": [
4496,
176
],
"parameters": {
"jsCode": "// Process only Customer Answer field\nconst item = $input.first().json;\n\n// Check whether we need to process Customer Answer\nif (!item.has_Answer || !item.Answer_clean) {\n return [];\n}\n\n// Just send the text to OpenAI – no extra metadata\nreturn [{\n json: {\n text_to_embed: item.Answer_clean\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0c6a7336-ae00-4070-bf4c-be25a0607fcd",
"name": "OpenAI答案嵌入",
"type": "n8n-nodes-base.httpRequest",
"position": [
5120,
192
],
"parameters": {
"url": "https://api.openai.com/v1/embeddings",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"text-embedding-3-small\",\n \"input\": \"{{ $json.text_to_embed }}\",\n \"encoding_format\": \"float\"\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "5411a7ea-3792-47a9-8ee3-ae35a4a3eb37",
"name": "OpenAI问题嵌入",
"type": "n8n-nodes-base.httpRequest",
"position": [
5136,
-48
],
"parameters": {
"url": "https://api.openai.com/v1/embeddings",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"text-embedding-3-small\",\n \"input\": \"{{ $json.text_to_embed }}\",\n \"encoding_format\": \"float\"\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "30eb9104-cde9-4d8f-8dac-4ce62c405093",
"name": "合并字段用于数据库插入",
"type": "n8n-nodes-base.code",
"position": [
6112,
272
],
"parameters": {
"jsCode": "const inputs = $input.all();\n\nlet originalData = null;\nlet QuestionEmbedding = null;\nlet AnswerEmbedding = null;\n\nfor (const input of inputs) {\n // Original data from Loop Over Items\n if (input.json.UUID && !input.json.data) {\n originalData = input.json;\n }\n // Direct OpenAI HTTP response\n else if (input.json.data && input.json.data[0] && input.json.data[0].embedding) {\n // Here you must distinguish between Question and Answer embeddings\n if (!AnswerEmbedding) {\n QuestionEmbedding = input.json.data[0].embedding;\n } else {\n AnswerEmbedding = input.json.data[0].embedding;\n }\n }\n}\n\nreturn [{\n json: {\n id: originalData.UUID,\n ...originalData,\n Question_embedding: QuestionEmbedding,\n Answer_embedding: AnswerEmbedding\n }\n}];"
},
"typeVersion": 2
},
{
"id": "9f18eea1-2f78-4b07-beca-8cc95577b622",
"name": "写入行到数据库",
"type": "n8n-nodes-base.supabase",
"position": [
6608,
272
],
"parameters": {
"tableId": "excel_records",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "UUID",
"fieldValue": "={{ $json.UUID }}"
},
{
"fieldId": "Projectcode",
"fieldValue": "={{ $json.Projectcode }}"
},
{
"fieldId": "Bool_1",
"fieldValue": "={{ $json['Bool 1'] }}"
},
{
"fieldId": "Bool_2",
"fieldValue": "={{ $json['Bool 2'] }}"
},
{
"fieldId": "Bool_3",
"fieldValue": "={{ $json['Bool 3'] }}"
},
{
"fieldId": "Handler",
"fieldValue": "={{ $json.Handler }}"
},
{
"fieldId": "Status",
"fieldValue": "={{ $json.Status }}"
},
{
"fieldId": "Creation_date",
"fieldValue": "={{ $json['Creation Date'] }}"
},
{
"fieldId": "Closed_on",
"fieldValue": "={{ $json['Closed on'] }}"
},
{
"fieldId": "E-mailadres",
"fieldValue": "={{ $json['E-mailadres'] }}"
},
{
"fieldId": "City",
"fieldValue": "={{ $json.City }}"
},
{
"fieldId": "Question",
"fieldValue": "={{ $json.Question }}"
},
{
"fieldId": "Question_clean",
"fieldValue": "={{ $json.Question_clean }}"
},
{
"fieldId": "Answer",
"fieldValue": "={{ $json.Answer }}"
},
{
"fieldId": "Answer_clean",
"fieldValue": "={{ $json.Answer_clean }}"
},
{
"fieldId": "Question_embedding",
"fieldValue": "={{ $json.Question_embedding }}"
},
{
"fieldId": "Answer_embedding",
"fieldValue": "={{ $json.Answer_embedding }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "018da39f-630b-4866-b25a-5b58a204dfd8",
"name": "检索现有行",
"type": "n8n-nodes-base.supabase",
"position": [
1776,
352
],
"parameters": {
"tableId": "excel_records",
"operation": "getAll",
"returnAll": true,
"filterType": "none"
},
"typeVersion": 1
},
{
"id": "8b219fe6-e73c-4c91-bcb2-941147c35f39",
"name": "在Supabase中构建表",
"type": "n8n-nodes-base.postgres",
"disabled": true,
"position": [
928,
976
],
"parameters": {
"query": "-- ============================================\n-- Prerequisites (needed for UUID + embeddings)\n-- ============================================\n-- gen_random_uuid() comes from pgcrypto\ncreate extension if not exists pgcrypto;\n-- vector type for embeddings (pgvector)\ncreate extension if not exists vector;\n\n-- ============================================\n-- Recreate table (drop+create)\n-- ============================================\ndrop table if exists public.excel_records cascade;\n\ncreate table public.excel_records (\n -- Primary key + timestamps\n id uuid primary key default gen_random_uuid(),\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n\n -- Your original columns (names preserved)\n \"UUID\" text unique,\n \"Projectcode\" text,\n \"Bool_1\" boolean,\n \"Bool_2\" boolean,\n \"Bool_3\" boolean,\n \"Handler\" text,\n \"Status\" text,\n \"Creation_date\" date,\n \"Closed_on\" date,\n \"City\" text,\n\n -- Email with basic validation; keep the quoted name with dash\n \"E-mailadres\" text\n check (\"E-mailadres\" is null\n or \"E-mailadres\" ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'),\n\n -- Q/A text (raw + cleaned variants)\n \"Question\" text,\n \"Question_clean\" text,\n \"Answer\" text,\n \"Answer_clean\" text,\n\n -- Embeddings (adjust the dimension if your model differs)\n \"Question_embedding\" vector(1536),\n \"Answer_embedding\" vector(1536),\n\n -- Full-text search (stored columns; we’ll maintain them via triggers)\n \"Question_fts\" tsvector,\n \"Answer_fts\" tsvector\n);\n\n-- ============================================\n-- Auto-maintain updated_at on UPDATE\n-- ============================================\ncreate or replace function public.excel_records_set_updated_at()\nreturns trigger language plpgsql as $$\nbegin\n new.updated_at := now();\n return new;\nend$$;\n\ndrop trigger if exists excel_records_trg_set_updated_at on public.excel_records;\ncreate trigger excel_records_trg_set_updated_at\nbefore update on public.excel_records\nfor each row execute function public.excel_records_set_updated_at();\n\n-- ============================================\n-- Maintain FTS columns on INSERT/UPDATE\n-- (uses 'simple' config; switch to 'dutch' if you prefer)\n-- ============================================\ncreate or replace function public.excel_records_fts_refresh()\nreturns trigger language plpgsql as $$\nbegin\n new.\"Question_fts\" := to_tsvector('simple', coalesce(new.\"Question_clean\", new.\"Question\"));\n new.\"Answer_fts\" := to_tsvector('simple', coalesce(new.\"Answer_clean\", new.\"Answer\"));\n return new;\nend$$;\n\ndrop trigger if exists excel_records_trg_fts_ins on public.excel_records;\ncreate trigger excel_records_trg_fts_ins\nbefore insert on public.excel_records\nfor each row execute function public.excel_records_fts_refresh();\n\ndrop trigger if exists excel_records_trg_fts_upd on public.excel_records;\ncreate trigger excel_records_trg_fts_upd\nbefore update of \"Question\",\"Question_clean\",\"Answer\",\"Answer_clean\" on public.excel_records\nfor each row execute function public.excel_records_fts_refresh();\n\n-- ============================================\n-- Indexes (FTS + embeddings + common filters)\n-- ============================================\n-- Full-text GIN indexes\ncreate index if not exists excel_records_question_fts_gin\n on public.excel_records using gin (\"Question_fts\");\ncreate index if not exists excel_records_answer_fts_gin\n on public.excel_records using gin (\"Answer_fts\");\n\n-- Vector similarity (IVFFLAT with cosine distance)\n-- Tip: performance improves after data is inserted and ANALYZE has run.\ncreate index if not exists excel_records_question_vec_ivfflat\n on public.excel_records using ivfflat (\"Question_embedding\" vector_cosine_ops)\n with (lists = 100);\ncreate index if not exists excel_records_answer_vec_ivfflat\n on public.excel_records using ivfflat (\"Answer_embedding\" vector_cosine_ops)\n with (lists = 100);\n\n-- Helpful general indexes\ncreate index if not exists excel_records_created_at_idx on public.excel_records (created_at);\n\n-- ============================================\n-- Row Level Security (RLS)\n-- Enabled by default; deny-all until policies allow access.\n-- ============================================\nalter table public.excel_records enable row level security;\n\n-- ---------- Default policy: server-side only ----------\n-- This allows only the service role (server key) to read/write.\n-- Use this if the table is NOT accessed directly from the client.\ndrop policy if exists excel_records_service_role_rw on public.excel_records;\ncreate policy excel_records_service_role_rw\non public.excel_records\nusing (auth.role() = 'service_role')\nwith check (auth.role() = 'service_role');\n\n-- ---------- OPTIONAL: per-user ownership ----------\n-- If you want client access where each user sees only their own rows:\n-- 1) Add an owner column:\n-- alter table public.excel_records add column if not exists user_id uuid;\n-- 2) Set it on insert from your app (or default to auth.uid()):\n-- alter table public.excel_records alter column user_id set default auth.uid();\n-- 3) Replace the policy set with:\n-- drop policy if exists excel_records_service_role_rw on public.excel_records;\n-- create policy excel_records_read_own on public.excel_records for select using (user_id = auth.uid());\n-- create policy excel_records_ins_own on public.excel_records for insert with check (user_id = auth.uid());\n-- create policy excel_records_upd_own on public.excel_records for update using (user_id = auth.uid()) with check (user_id = auth.uid());\n-- create policy excel_records_del_own on public.excel_records for delete using (user_id = auth.uid());\n\n-- ============================================\n-- Notes / knobs you can tweak\n-- ============================================\n-- • Embedding size: change vector(1536) if your model uses a different dimension.\n-- • FTS language: change 'simple' to 'dutch' in excel_records_fts_refresh() if preferred.\n-- • After bulk inserts, run ANALYZE for best IVFFLAT performance:\n-- analyze public.excel_records;\n",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.6
},
{
"id": "f85dd3f6-851e-46d0-80b8-05db9de5429a",
"name": "从表格获取行",
"type": "n8n-nodes-base.microsoftExcel",
"position": [
1792,
112
],
"parameters": {
"options": {},
"resource": "worksheet",
"workbook": {
"__rl": true,
"mode": "list",
"value": "01VJX45VT23GD6IE3HAVD3GBYREFIPIRAO",
"cachedResultUrl": "",
"cachedResultName": ""
},
"operation": "readRows",
"worksheet": {
"__rl": true,
"mode": "list",
"value": "{94B58D19-7964-4A20-BFF2-BBE769717A5B}",
"cachedResultUrl": "",
"cachedResultName": ""
}
},
"typeVersion": 2.1
},
{
"id": "fd45828b-8e0f-43e3-b782-999de869a2b4",
"name": "便签 - 概览",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
-400
],
"parameters": {
"color": 3,
"width": 600,
"height": 1020,
"content": "## 从Excel到Supabase的简单上传与RAG"
},
"typeVersion": 1
},
{
"id": "62b88a14-6095-4d4a-bdf3-6abddf7307e6",
"name": "便签 - 写入行到数据库",
"type": "n8n-nodes-base.stickyNote",
"position": [
6496,
512
],
"parameters": {
"color": 7,
"width": 380,
"height": 1292,
"content": "## 📁 写入行到数据库"
},
"typeVersion": 1
},
{
"id": "bc3409c2-7359-4b3b-80e1-816486efe036",
"name": "便签 - 合并字段用于数据库插入",
"type": "n8n-nodes-base.stickyNote",
"position": [
6032,
544
],
"parameters": {
"color": 7,
"width": 380,
"height": 1020,
"content": "## 📁 合并字段用于数据库插入"
},
"typeVersion": 1
},
{
"id": "a03a9c71-c3c2-441a-9703-702472bc5dd1",
"name": "便签 - 合并",
"type": "n8n-nodes-base.stickyNote",
"position": [
5568,
544
],
"parameters": {
"color": 7,
"width": 380,
"height": 188,
"content": "## 合并"
},
"typeVersion": 1
},
{
"id": "98e58b46-3447-4b6f-9ebf-164e404d5b4d",
"name": "便签 - OpenAI问题嵌入",
"type": "n8n-nodes-base.stickyNote",
"position": [
5072,
544
],
"parameters": {
"color": 7,
"width": 380,
"height": 1068,
"content": "## 🤖 OpenAI问题与答案嵌入"
},
"typeVersion": 1
},
{
"id": "696c2c7c-c313-4326-b43e-09b6a2725b2c",
"name": "便签 - 代码\"问题\"",
"type": "n8n-nodes-base.stickyNote",
"position": [
4608,
544
],
"parameters": {
"color": 7,
"width": 380,
"height": 844,
"content": "## 💡 代码\"问题\""
},
"typeVersion": 1
},
{
"id": "6b48106f-8c97-4ee9-95f6-72dc9424ca95",
"name": "便签 - 代码\"答案\"",
"type": "n8n-nodes-base.stickyNote",
"position": [
4192,
544
],
"parameters": {
"color": 7,
"width": 380,
"height": 748,
"content": "## 📁 代码\"答案\""
},
"typeVersion": 1
},
{
"id": "dd5d2ec1-fbcb-4c2a-95f4-e4196fd662c6",
"name": "便签 - Switch",
"type": "n8n-nodes-base.stickyNote",
"position": [
3744,
544
],
"parameters": {
"color": 7,
"width": 380,
"height": 1020,
"content": "## 🪄 Switch"
},
"typeVersion": 1
},
{
"id": "22b81066-f654-4ad7-a488-4e802a6fd548",
"name": "便签 - 循环遍历项目",
"type": "n8n-nodes-base.stickyNote",
"position": [
3200,
528
],
"parameters": {
"color": 7,
"width": 380,
"height": 188,
"content": "## 🔄 循环遍历项目"
},
"typeVersion": 1
},
{
"id": "bc824b94-057a-4222-8ef9-eb3b0a608f77",
"name": "便签 - 移除HTML",
"type": "n8n-nodes-base.stickyNote",
"position": [
2688,
592
],
"parameters": {
"color": 7,
"width": 380,
"height": 1020,
"content": "## 🗑️ 移除HTML"
},
"typeVersion": 1
},
{
"id": "d7a24067-1bf9-41c4-bbe4-fbb16c626193",
"name": "便签 - 合并导入数据",
"type": "n8n-nodes-base.stickyNote",
"position": [
2256,
592
],
"parameters": {
"color": 7,
"width": 380,
"height": 1020,
"content": "## 🔗 合并导入数据"
},
"typeVersion": 1
},
{
"id": "771905a2-eea7-42b1-be9b-201a362c0c83",
"name": "便签 - 检索现有行",
"type": "n8n-nodes-base.stickyNote",
"position": [
1808,
592
],
"parameters": {
"color": 7,
"width": 380,
"height": 1020,
"content": "## 📁 检索现有行"
},
"typeVersion": 1
},
{
"id": "fe912454-19f6-450a-ae48-26c691a8b592",
"name": "Sticky Note - Get rows from sheet",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
592
],
"parameters": {
"color": 7,
"width": 380,
"height": 1020,
"content": "## 📄 Get Rows from Sheet \n\n### 🎯 Purpose\nRetrieve rows from a specified worksheet in an Excel workbook to automate data extraction for documentation or processing.\n\n### 🔄 Inputs / Outputs\n- **Inputs:** \n - Workbook ID and worksheet ID to identify which data to read.\n- **Outputs:** \n - Rows of data extracted from the specified Excel sheet.\n\n### ⚙️ Key Fields to Configure\n- **Resource:** \n - Set to \"worksheet\" to indicate the node operates on a worksheet.\n- **Operation:** \n - Use \"readRows\" to specify that you want to retrieve data from the worksheet.\n- **Workbook:** \n - Provide the ID of the workbook (`01VJX45VT23GD6IE3HAVD3GBYREFIPIRAO`) to read from.\n- **Worksheet:** \n - Specify the ID of the desired worksheet (`{94B58D19-7964-4A20-BFF2-BBE769717A5B}`) to extract data from.\n\n### 📝 Tip / Validation\n- Ensure that the provided workbook and worksheet IDs are correct and accessible via the configured Microsoft Excel account to avoid permission or data retrieval errors.\n- Check the extracted data format to ensure compatibility with downstream nodes in your workflow."
},
"typeVersion": 1
},
{
"id": "985f7de8-b1c0-4f44-8193-3bc270d4e9fc",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
704
],
"parameters": {
"color": 4,
"width": 624,
"height": 576,
"content": "## Adjust the column schema in both Supabase and Excel to match your requirements.\n## This example uses the following fields:\n\nExcel:\n- UUID\n- Projectcode\n- Bool 1\n- Bool 2\n- Bool 3\n- Handler\n- Status\n- Creation Date\n- Closed on\n- E-mailadres\n- City\n- Question\n- Answer\n\n## Use the Postgres node or copy/past the sql \n## The SQL: \n\n-- ============================================\n-- Prerequisites (needed for UUID + embeddings)\n-- ============================================\n-- gen_random_uuid() comes from pgcrypto\ncreate extension if not exists pgcrypto;\n-- vector type for embeddings (pgvector)\ncreate extension if not exists vector;\n\n-- ============================================\n-- Recreate table (drop+create)\n-- ============================================\ndrop table if exists public.excel_records cascade;\n\ncreate table public.excel_records (\n -- Primary key + timestamps\n id uuid primary key default gen_random_uuid(),\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n\n -- Your original columns (names preserved)\n \"UUID\" text unique,\n \"Projectcode\" text,\n \"Bool_1\" boolean,\n \"Bool_2\" boolean,\n \"Bool_3\" boolean,\n \"Handler\" text,\n \"Status\" text,\n \"Creation_date\" date,\n \"Closed_on\" date,\n \"City\" text,\n\n -- Email with basic validation; keep the quoted name with dash\n \"E-mailadres\" text\n check (\"E-mailadres\" is null\n or \"E-mailadres\" ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'),\n\n -- Q/A text (raw + cleaned variants)\n \"Question\" text,\n \"Question_clean\" text,\n \"Answer\" text,\n \"Answer_clean\" text,\n\n -- Embeddings (adjust the dimension if your model differs)\n \"Question_embedding\" vector(1536),\n \"Answer_embedding\" vector(1536),\n\n -- Full-text search (stored columns; we’ll maintain them via triggers)\n \"Question_fts\" tsvector,\n \"Answer_fts\" tsvector\n);\n\n-- ============================================\n-- Auto-maintain updated_at on UPDATE\n-- ============================================\ncreate or replace function public.excel_records_set_updated_at()\nreturns trigger language plpgsql as $$\nbegin\n new.updated_at := now();\n return new;\nend$$;\n\ndrop trigger if exists excel_records_trg_set_updated_at on public.excel_records;\ncreate trigger excel_records_trg_set_updated_at\nbefore update on public.excel_records\nfor each row execute function public.excel_records_set_updated_at();\n\n-- ============================================\n-- Maintain FTS columns on INSERT/UPDATE\n-- (uses 'simple' config; switch to 'dutch' if you prefer)\n-- ============================================\ncreate or replace function public.excel_records_fts_refresh()\nreturns trigger language plpgsql as $$\nbegin\n new.\"Question_fts\" := to_tsvector('simple', coalesce(new.\"Question_clean\", new.\"Question\"));\n new.\"Answer_fts\" := to_tsvector('simple', coalesce(new.\"Answer_clean\", new.\"Answer\"));\n return new;\nend$$;\n\ndrop trigger if exists excel_records_trg_fts_ins on public.excel_records;\ncreate trigger excel_records_trg_fts_ins\nbefore insert on public.excel_records\nfor each row execute function public.excel_records_fts_refresh();\n\ndrop trigger if exists excel_records_trg_fts_upd on public.excel_records;\ncreate trigger excel_records_trg_fts_upd\nbefore update of \"Question\",\"Question_clean\",\"Answer\",\"Answer_clean\" on public.excel_records\nfor each row execute function public.excel_records_fts_refresh();\n\n-- ============================================\n-- Indexes (FTS + embeddings + common filters)\n-- ============================================\n-- Full-text GIN indexes\ncreate index if not exists excel_records_question_fts_gin\n on public.excel_records using gin (\"Question_fts\");\ncreate index if not exists excel_records_answer_fts_gin\n on public.excel_records using gin (\"Answer_fts\");\n\n-- Vector similarity (IVFFLAT with cosine distance)\n-- Tip: performance improves after data is inserted and ANALYZE has run.\ncreate index if not exists excel_records_question_vec_ivfflat\n on public.excel_records using ivfflat (\"Question_embedding\" vector_cosine_ops)\n with (lists = 100);\ncreate index if not exists excel_records_answer_vec_ivfflat\n on public.excel_records using ivfflat (\"Answer_embedding\" vector_cosine_ops)\n with (lists = 100);\n\n-- Helpful general indexes\ncreate index if not exists excel_records_created_at_idx on public.excel_records (created_at);\n\n-- ============================================\n-- Row Level Security (RLS)\n-- Enabled by default; deny-all until policies allow access.\n-- ============================================\nalter table public.excel_records enable row level security;\n\n-- ---------- Default policy: server-side only ----------\n-- This allows only the service role (server key) to read/write.\n-- Use this if the table is NOT accessed directly from the client.\ndrop policy if exists excel_records_service_role_rw on public.excel_records;\ncreate policy excel_records_service_role_rw\non public.excel_records\nusing (auth.role() = 'service_role')\nwith check (auth.role() = 'service_role');\n\n-- ---------- OPTIONAL: per-user ownership ----------\n-- If you want client access where each user sees only their own rows:\n-- 1) Add an owner column:\n-- alter table public.excel_records add column if not exists user_id uuid;\n-- 2) Set it on insert from your app (or default to auth.uid()):\n-- alter table public.excel_records alter column user_id set default auth.uid();\n-- 3) Replace the policy set with:\n-- drop policy if exists excel_records_service_role_rw on public.excel_records;\n-- create policy excel_records_read_own on public.excel_records for select using (user_id = auth.uid());\n-- create policy excel_records_ins_own on public.excel_records for insert with check (user_id = auth.uid());\n-- create policy excel_records_upd_own on public.excel_records for update using (user_id = auth.uid()) with check (user_id = auth.uid());\n-- create policy excel_records_del_own on public.excel_records for delete using (user_id = auth.uid());\n\n-- ============================================\n-- Notes / knobs you can tweak\n-- ============================================\n-- • Embedding size: change vector(1536) if your model uses a different dimension.\n-- • FTS language: change 'simple' to 'dutch' in excel_records_fts_refresh() if preferred.\n-- • After bulk inserts, run ANALYZE for best IVFFLAT performance:\n-- analyze public.excel_records;\n\n\n"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "f479af33-fceb-4089-af22-b498700a16ff",
"connections": {
"Merge": {
"main": [
[
{
"node": "Merge fields for database insert",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Code \"Question\"",
"type": "main",
"index": 0
}
],
[
{
"node": "Code \"Answer\"",
"type": "main",
"index": 0
}
]
]
},
"Remove HTML ": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Code \"Answer\"": {
"main": [
[
{
"node": "Embeddings OpenAI Answer",
"type": "main",
"index": 0
}
]
]
},
"Code \"Question\"": {
"main": [
[
{
"node": "Embeddings OpenAI Question",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Switch",
"type": "main",
"index": 0
},
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"Merge import Data": {
"main": [
[
{
"node": "Remove HTML ",
"type": "main",
"index": 0
}
]
]
},
"Get rows from sheet": {
"main": [
[
{
"node": "Merge import Data",
"type": "main",
"index": 0
}
]
]
},
"Write row to database": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Retrieve existing rows": {
"main": [
[
{
"node": "Merge import Data",
"type": "main",
"index": 1
}
]
]
},
"Embeddings OpenAI Answer": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Embeddings OpenAI Question": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Merge fields for database insert": {
"main": [
[
{
"node": "Write row to database",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Execute workflow’": {
"main": [
[
{
"node": "Get rows from sheet",
"type": "main",
"index": 0
},
{
"node": "Retrieve existing rows",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 杂项, AI RAG 检索增强, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
创建自更新的RAG聊天机器人(Google Drive、Gemini和Supabase)
使用Google Drive、Gemini和Supabase创建自更新的RAG聊天机器人
Set
Code
Merge
+19
45 节点Anirudh Aeran
内容创作
PDF 转订单
使用AI将PDF采购订单自动化转换为Adobe Commerce销售订单
If
Set
Code
+19
96 节点JKingma
文档提取
每日 WhatsApp 群组智能分析:GPT-4.1 分析与语音消息转录
每日 WhatsApp 群组智能分析:GPT-4.1 分析与语音消息转录
If
Set
Code
+20
52 节点Daniel Lianes
杂项
使用Veo 3、Gemini和Creatomate生成并发布专业视频广告
使用Veo 3、Gemini和Creatomate生成并发布专业视频广告
Jwt
Set
Code
+11
66 节点LukaszB
内容创作
上下文混合RAG AI文案
Google Drive到Supabase上下文向量数据库同步用于RAG应用
If
Set
Code
+25
76 节点Michael Taleb
AI RAG 检索增强
使用Gemini AI视觉分析与Telegram警报监控X平台品牌提及
使用Gemini AI视觉分析与Telegram警报监控X平台品牌提及
If
Set
Code
+13
24 节点Atta
杂项
工作流信息
难度等级
高级
节点数量29
分类3
节点类型10
作者
Wessel Bulte
@uuesselCybersecurity and automation consultant specializing in n8n workflows for GDPR compliance, process optimization, and business integration. Helping teams streamline operations with secure, scalable automation solutions.
外部链接
在 n8n.io 查看 →
分享此工作流