AutomationFlowsData & Sheets › Convert Natural Language to SQL Query

Convert Natural Language to SQL Query

Original n8n title: Natural Language to Sql Query

Natural Language to SQL Query. Uses postgres, openAi. Webhook trigger; 8 nodes.

Webhook trigger★★★★☆ complexityAI-powered8 nodesPostgresOpenAI
Data & Sheets Trigger: Webhook Nodes: 8 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the OpenAI → Postgres recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Natural Language to SQL Query",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "text-to-sql",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000001",
      "name": "Webhook - Natural Language Query",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT table_name, column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'public' ORDER BY table_name, ordinal_position",
        "options": {}
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000002",
      "name": "PostgreSQL - Get Schema",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        480,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const schemaRows = $input.all().map(item => item.json);\nconst tables = {};\nfor (const row of schemaRows) {\n  if (!tables[row.table_name]) tables[row.table_name] = [];\n  tables[row.table_name].push(`${row.column_name} (${row.data_type}${row.is_nullable === 'YES' ? ', nullable' : ''})`);\n}\nconst schemaText = Object.entries(tables).map(([table, cols]) => `Table: ${table}\\n  Columns: ${cols.join(', ')}`).join('\\n\\n');\nconst userQuery = $('Webhook - Natural Language Query').first().json.body.query;\nreturn [{ json: { schemaText, userQuery } }];"
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000003",
      "name": "Code - Format Schema",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        300
      ]
    },
    {
      "parameters": {
        "resource": "chat",
        "model": "gpt-4o",
        "messages": {
          "values": [
            {
              "content": "=You are a PostgreSQL query expert. Convert the following natural language question into a safe, read-only SQL query.\n\nRules:\n1. ONLY generate SELECT statements. Never generate INSERT, UPDATE, DELETE, DROP, ALTER, or any DDL/DML.\n2. Use proper JOIN syntax where needed.\n3. Add appropriate WHERE clauses, GROUP BY, ORDER BY as needed.\n4. Limit results to 100 rows maximum.\n5. Use aliases for readability.\n6. Handle NULL values appropriately.\n\nDatabase Schema:\n{{ $json.schemaText }}\n\nUser Question: {{ $json.userQuery }}\n\nReturn ONLY the SQL query, no explanations or markdown formatting."
            }
          ]
        },
        "options": {
          "temperature": 0.1,
          "maxTokens": 1024
        }
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000004",
      "name": "OpenAI - Generate SQL",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.4,
      "position": [
        960,
        300
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "let sql = $input.first().json.message.content.trim();\n// Remove markdown code blocks if present\nsql = sql.replace(/^```(sql)?\\n?/i, '').replace(/\\n?```$/i, '').trim();\n// Safety check: only allow SELECT statements\nconst upperSql = sql.toUpperCase().trim();\nif (!upperSql.startsWith('SELECT') && !upperSql.startsWith('WITH')) {\n  throw new Error('Generated query is not a SELECT statement. Aborting for safety.');\n}\nconst forbidden = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'TRUNCATE', 'CREATE', 'GRANT', 'REVOKE'];\nfor (const word of forbidden) {\n  if (upperSql.includes(word + ' ')) {\n    throw new Error(`Forbidden keyword \"${word}\" found in query. Aborting.`);\n  }\n}\nreturn [{ json: { sql, originalQuery: $('Code - Format Schema').first().json.userQuery } }];"
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000005",
      "name": "Code - Validate SQL Safety",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ $json.sql }}",
        "options": {}
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000006",
      "name": "PostgreSQL - Execute Query",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1440,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const results = $input.all().map(item => item.json);\nconst sql = $('Code - Validate SQL Safety').first().json.sql;\nconst originalQuery = $('Code - Validate SQL Safety').first().json.originalQuery;\nreturn [{ json: { success: true, originalQuery, generatedSql: sql, resultCount: results.length, results } }];"
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000007",
      "name": "Code - Format Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}"
      },
      "id": "d4e5f6a7-4444-4000-8000-000000000008",
      "name": "Respond with Results",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1920,
        300
      ]
    }
  ],
  "connections": {
    "Webhook - Natural Language Query": {
      "main": [
        [
          {
            "node": "PostgreSQL - Get Schema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL - Get Schema": {
      "main": [
        [
          {
            "node": "Code - Format Schema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Format Schema": {
      "main": [
        [
          {
            "node": "OpenAI - Generate SQL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Generate SQL": {
      "main": [
        [
          {
            "node": "Code - Validate SQL Safety",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Validate SQL Safety": {
      "main": [
        [
          {
            "node": "PostgreSQL - Execute Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL - Execute Query": {
      "main": [
        [
          {
            "node": "Code - Format Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Format Results": {
      "main": [
        [
          {
            "node": "Respond with Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    {
      "name": "ai-data"
    }
  ]
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Natural Language to SQL Query. Uses postgres, openAi. Webhook trigger; 8 nodes.

Source: https://github.com/mlnjsh/n8n-workflows-mega/blob/main/workflows/ai-data/04-text-to-sql-query.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Postgres. Uses openAi, postgres, postgresTool, httpRequest. Webhook trigger; 19 nodes.

OpenAI, Postgres, Postgres Tool +2
Data & Sheets

Crop Planning. Uses postgres, httpRequest, respondToWebhook, googleGemini. Webhook trigger; 11 nodes.

Postgres, HTTP Request, Google Gemini +1
Data & Sheets

OTTO - Épico 1: Resolução Determinística de Identidade. Uses openAi, postgres, httpRequest. Webhook trigger; 11 nodes.

OpenAI, Postgres, HTTP Request
Data & Sheets

OTTO - Épico 2: Funil de Hipótese (IA Sugere, Sistema Julga). Uses openAi, postgres, httpRequest. Webhook trigger; 10 nodes.

OpenAI, Postgres, HTTP Request
Data & Sheets

This workflow automatically processes bank statements from various formats and extracts structured transaction data with intelligent categorization using AI. File Upload - Accepts bank statements via

Spreadsheet File, OpenAI, Postgres