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 →
{
"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.
openAiApipostgres
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Postgres. Uses openAi, postgres, postgresTool, httpRequest. Webhook trigger; 19 nodes.
Crop Planning. Uses postgres, httpRequest, respondToWebhook, googleGemini. Webhook trigger; 11 nodes.
OTTO - Épico 1: Resolução Determinística de Identidade. Uses openAi, postgres, httpRequest. Webhook trigger; 11 nodes.
OTTO - Épico 2: Funil de Hipótese (IA Sugere, Sistema Julga). Uses openAi, postgres, httpRequest. Webhook trigger; 10 nodes.
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