8
n8n 中文网amn8n.com

上传到 n8n

高级

这是一个Miscellaneous, AI RAG, Multimodal AI领域的自动化工作流,包含 29 个节点。主要使用 Code, Merge, Switch, Postgres, Supabase 等节点。 使用 OpenAI 和 Supabase 将 Excel 数据转换为 AI 就绪向量

前置要求
  • PostgreSQL 数据库连接信息
  • Supabase URL 和 API Key
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 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(/&nbsp;/g, ' ')     // replace &nbsp; 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(/&nbsp;/g, ' ')     // replace &nbsp; 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)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量29
分类3
节点类型10
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Wessel Bulte

Wessel Bulte

@uuessel

Cybersecurity 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 查看

分享此工作流