AutomationFlowsAI & RAG › AI Agent to Chat with Snowflake Database

AI Agent to Chat with Snowflake Database

ByMark Shcherbakov @lowcodingdev on n8n.io

I prepared a detailed guide showcasing the process of building an AI agent that interacts with a Snowflake database using n8n. This setup enables conversational querying, secure execution of SQL queries, and dynamic report generation with rich visualization capabilities.

Chat trigger trigger★★★★☆ complexityAI-powered28 nodesChat TriggerAgentOpenAI ChatMemory Buffer WindowSnowflake ToolSnowflakeExecute Workflow TriggerTool Workflow
AI & RAG Trigger: Chat trigger Nodes: 28 Complexity: ★★★★☆ AI nodes: yes Added:
AI Agent to Chat with Snowflake Database — n8n workflow card showing Chat Trigger, Agent, OpenAI Chat integration

This workflow corresponds to n8n.io template #5435 — we link there as the canonical source.

This workflow follows the Agent → Chat Trigger 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
{
  "updatedAt": "2026-01-26T22:12:23.328Z",
  "createdAt": "2025-12-15T19:34:19.460Z",
  "id": "VrreoHxSO7d0UAO2",
  "name": "AI Agent to chat with Snowflake database",
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "options": {}
      },
      "name": "When chat message received",
      "position": [
        32,
        928
      ],
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1.1,
      "id": "a5b8617e-563f-446a-8e1d-bce121f245f4"
    },
    {
      "parameters": {
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are Snowflake SQL assistant.\n\nUse tools to retrieve data from Snowflake and answer user.\n\nIMPORTANT Always check database schema and table definition for preparing SQL query."
        }
      },
      "name": "AI Agent1",
      "position": [
        448,
        816
      ],
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2,
      "id": "1b69e537-ee28-45f3-8323-6dff48cc70aa"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('When chat message received').item.json.sessionId }}"
      },
      "name": "Simple Memory",
      "position": [
        384,
        1040
      ],
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "id": "4cc7e324-7f98-49af-896e-8230ff08b6bc"
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Get table definition to find all columns and types.",
        "operation": "executeQuery",
        "query": "SELECT \n    column_name,\n    data_type\n\nFROM \n    WEB_APPLICATIONS.information_schema.columns\nWHERE \n    table_name = '{{ $fromAI(\"table_name\") }}'\n    AND table_schema = 'WEBAPP_AIWORKFLOW'\nORDER BY \n    ordinal_position;\n"
      },
      "name": "Get table definition",
      "position": [
        768,
        1040
      ],
      "type": "n8n-nodes-base.snowflakeTool",
      "typeVersion": 1,
      "id": "4777ceb7-0d5d-4f61-9767-bdb76dd30586",
      "credentials": {
        "snowflake": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "path": "getReport",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Webhook",
      "position": [
        32,
        1840
      ],
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "id": "d6b98488-a877-4afe-b4cc-295abf0c4d83"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1257d6ca-3c5c-476b-8d26-f8fb84a0c38e",
              "name": "html",
              "type": "string",
              "value": "=<!DOCTYPE html>\n<html>\n<head>\n    <title>Dashboard</title>\n    <style>\n        body { \n            font-family: Arial, sans-serif; \n            padding: 20px;\n            margin: 0;\n        }\n        .container { \n            max-width: 1200px; \n            margin: 0 auto; \n        }\n        table {\n            border-collapse: collapse;\n            width: 100%;\n            margin: 20px 0;\n        }\n        th, td {\n            border: 1px solid #ddd;\n            padding: 8px;\n            text-align: left;\n        }\n        th {\n            background-color: #f2f2f2;\n            cursor: pointer;\n        }\n        th:hover {\n            background-color: #ddd;\n        }\n        .controls {\n            margin: 20px 0;\n            padding: 10px;\n            background: #f8f8f8;\n            border-radius: 4px;\n        }\n        .btn {\n            padding: 8px 16px;\n            margin: 0 5px;\n            cursor: pointer;\n            background: #4CAF50;\n            color: white;\n            border: none;\n            border-radius: 4px;\n        }\n        .btn:hover {\n            background: #45a049;\n        }\n        .tab-container {\n            margin: 20px 0;\n        }\n        .tab-button {\n            padding: 10px 20px;\n            border: none;\n            background: #f2f2f2;\n            cursor: pointer;\n        }\n        .tab-button.active {\n            background: #4CAF50;\n            color: white;\n        }\n        .graph-controls {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n            gap: 15px;\n            margin: 20px 0;\n            padding: 15px;\n            background: #f8f8f8;\n            border-radius: 4px;\n        }\n        .control-group {\n            display: flex;\n            flex-direction: column;\n            gap: 5px;\n        }\n        select, input {\n            padding: 8px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n        }\n        #graphContainer {\n            background: white;\n            padding: 20px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n            margin: 20px 0;\n        }\n/* Sorting icons styles */\nth {\n    position: relative;\n    padding-right: 25px; /* Space for sort icon */\n}\nth:after {\n    content: '\u2195';\n    position: absolute;\n    right: 8px;\n    color: #999;\n}\nth.sort-asc:after {\n    content: '\u2191';\n    color: #000;\n}\nth.sort-desc:after {\n    content: '\u2193';\n    color: #000;\n}\n\n/* Pagination styles */\n.pagination {\n    display: flex;\n    gap: 10px;\n    align-items: center;\n    margin: 20px 0;\n    justify-content: center;\n}\n.pagination button {\n    padding: 5px 10px;\n    border: 1px solid #ddd;\n    background: white;\n    cursor: pointer;\n    border-radius: 4px;\n}\n.pagination button:disabled {\n    background: #f5f5f5;\n    cursor: not-allowed;\n    opacity: 0.5;\n}\n.pagination span {\n    padding: 0 10px;\n}\n.page-size {\n    margin-left: 20px;\n}\n.templates-section {\n    grid-column: 1 / -1;\n    padding: 10px;\n    border-bottom: 1px solid #ddd;\n    margin-bottom: 15px;\n}\n\n.template-buttons {\n    display: flex;\n    gap: 10px;\n    flex-wrap: wrap;\n    margin-top: 10px;\n}\n\n.template-button {\n    padding: 8px 16px;\n    background: #f0f0f0;\n    border: 1px solid #ddd;\n    border-radius: 4px;\n    cursor: pointer;\n    transition: all 0.3s;\n}\n\n.template-button:hover {\n    background: #e0e0e0;\n}\n\n.template-button.active {\n    background: #4CAF50;\n    color: white;\n    border-color: #4CAF50;\n}\n    </style>\n   <!-- React and ReactDOM dependencies -->\n    <script crossorigin src=\"https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js\"></script>\n    <script crossorigin src=\"https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js\"></script>\n    \n\n\n<!-- Add this after other script tags -->\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n</head>\n<body>\n    <div class=\"container\">\n        <h1>Dashboard</h1>\n        \n        <div class=\"tab-container\">\n            <button class=\"tab-button active\" onclick=\"switchTab('table')\">Table View</button>\n            <button class=\"tab-button\" onclick=\"switchTab('graph')\">Graph View</button>\n        </div>\n\n        <!-- Table View -->\n        <div id=\"tableView\">\n            <div class=\"controls\">\n                <input type=\"text\" id=\"filterInput\" placeholder=\"Search...\" />\n                <select id=\"filterColumn\">\n                    <option value=\"all\">All Columns</option>\n                </select>\n                <button class=\"btn\" onclick=\"exportToCSV()\">Export to CSV</button>\n            </div>\n            <div id=\"tableContainer\"></div>\n<div class=\"pagination\">\n    <button onclick=\"previousPage()\" id=\"prevBtn\">Previous</button>\n    <span id=\"pageInfo\">Page 1 of 1</span>\n    <button onclick=\"nextPage()\" id=\"nextBtn\">Next</button>\n    <select id=\"pageSize\" onchange=\"changePageSize()\" class=\"page-size\">\n        <option value=\"5\">5 per page</option>\n        <option value=\"10\" selected>10 per page</option>\n        <option value=\"20\">20 per page</option>\n        <option value=\"50\">50 per page</option>\n    </select>\n</div>\n        </div>\n\n        <!-- Graph View -->\n       <div id=\"graphView\" style=\"display: none;\">\n    <div class=\"graph-controls\">\n        <!-- Add templates section -->\n        <div class=\"templates-section\">\n            <h3>Chart Templates</h3>\n            <div id=\"templateButtons\" class=\"template-buttons\">\n                <!-- Template buttons will be added here dynamically -->\n            </div>\n        </div>\n        \n        <!-- Existing controls -->\n        <div class=\"control-group\">\n            <label>X Axis:</label>\n            <select id=\"xAxis\" onchange=\"updateGraph()\"></select>\n        </div>\n        <div class=\"control-group\">\n            <label>Y Axis:</label>\n            <select id=\"yAxis\" onchange=\"updateGraph()\"></select>\n        </div>\n        <div class=\"control-group\">\n            <label>Group By:</label>\n            <select id=\"groupBy\" onchange=\"updateGraph()\"></select>\n        </div>\n        <div class=\"control-group\">\n            <label>Chart Type:</label>\n            <select id=\"chartType\" onchange=\"updateGraph()\">\n    <option value=\"bar\">Bar Chart</option>\n    <option value=\"line\">Line Chart</option>\n    <option value=\"pie\">Pie Chart</option>\n    <option value=\"doughnut\">Doughnut Chart</option>\n</select>\n        </div>\n<!-- Add this in graph-controls div after other controls -->\n<div class=\"control-group\">\n    <label>Aggregation:</label>\n    <select id=\"aggregation\" onchange=\"updateGraph()\">\n        <option value=\"sum\">Sum</option>\n        <option value=\"average\">Average</option>\n        <option value=\"count\">Count</option>\n        <option value=\"min\">Minimum</option>\n        <option value=\"max\">Maximum</option>\n    </select>\n</div>\n    </div>\n    <div id=\"graphContainer\">\n        <canvas id=\"myChart\"></canvas>\n    </div>\n</div>\n\n    <script>\n        // Sample data - sales transactions\n        const data = {{ JSON.stringify($json.data) }};\n        // Column type definitions for formatting\n        function detectColumnTypes(data) {\n    if (!data || data.length === 0) return {};\n    \n    // Get keys from first data item\n    const firstItem = data[0];\n    const columnTypes = {};\n    \n    // Determine type for each column\n    Object.keys(firstItem).forEach(key => {\n        const value = firstItem[key];\n        let type = 'string'; // default type\n        \n        if (value instanceof Date) {\n            type = 'date';\n        } else if (typeof value === 'number') {\n            type = 'number';\n        } else if (typeof value === 'boolean') {\n            type = 'boolean';\n        } else if (typeof value === 'string') {\n            // Check if string is actually a date\n            const dateCheck = new Date(value);\n            if (!isNaN(dateCheck) && value.includes('-')) {\n                type = 'date';\n            } else {\n                type = 'string';\n            }\n        }\n        \n        columnTypes[key] = type;\n    });\n    \n    return columnTypes;\n}\n\n// Remove the hardcoded columnTypes and replace with:\nlet columnTypes = {};\n\n        let filteredData = [...data];\n        let currentPage = 1;\n        let pageSize = 10;\nlet currentSortColumn = null;\nlet sortAscending = true;\n\n\n\nfunction initializeGraphControls() {\n    const templateContainer = document.getElementById('templateButtons');\n    templateContainer.innerHTML = chartTemplates.map((template, index) => `\n        <button class=\"template-button\" onclick=\"applyTemplate(${index})\">\n            ${template.title}\n        </button>\n    `).join('');\n}\n\n    function initialize() {\n    // Detect column types from data\n    columnTypes = detectColumnTypes(data);\n    console.log('Detected columns:', columnTypes);\n    \n    const columns = Object.keys(columnTypes);\n    \n    // Initialize filter dropdown\n    const filterColumn = document.getElementById('filterColumn');\n    filterColumn.innerHTML = '<option value=\"all\">All Columns</option>';\n    columns.forEach(column => {\n        filterColumn.innerHTML += `<option value=\"${column}\">${column}</option>`;\n    });\n\n    // Initialize graph axis selectors\n    const xAxis = document.getElementById('xAxis');\n    const yAxis = document.getElementById('yAxis');\n    // In the initialize function, add empty option for groupBy\nconst groupBy = document.getElementById('groupBy');\ngroupBy.innerHTML = '<option value=\"\">No Grouping</option>';\ncolumns.forEach(column => {\n    groupBy.innerHTML += `<option value=\"${column}\">${column}</option>`;\n});\n    \n    xAxis.innerHTML = '';\n    yAxis.innerHTML = '';\n    \n    columns.forEach(column => {\n        xAxis.innerHTML += `<option value=\"${column}\">${column}</option>`;\n        yAxis.innerHTML += `<option value=\"${column}\">${column}</option>`;\n        groupBy.innerHTML += `<option value=\"${column}\">${column}</option>`;\n    });\n\n    // Set default Y axis to a numeric column\n    const numericColumns = columns.filter(col => columnTypes[col] === 'number');\n    if (numericColumns.length > 0) {\n        yAxis.value = numericColumns[0];\n    }\n\n    setupEventListeners();\n    refreshTable();\n    initializeGraphControls();\n}\n\n        // Format cell values based on type\n        function formatValue(value, type) {\n            if (value === null || value === undefined) return '';\n            \n            switch (type) {\n                case 'date':\n                    return new Date(value).toLocaleDateString();\n                case 'number':\n                    return value.toLocaleString();\n                case 'boolean':\n                    return value ? 'Yes' : 'No';\n                default:\n                    return value.toString();\n            }\n        }\n\n        // Create and update table\n        function createTable() {\n    const table = document.createElement('table');\n    const thead = document.createElement('thead');\n    const tbody = document.createElement('tbody');\n    \n    // Create header row with sorting indicators\n    const headerRow = document.createElement('tr');\n    Object.keys(columnTypes).forEach(column => {\n        const th = document.createElement('th');\n        th.textContent = column.charAt(0).toUpperCase() + column.slice(1);\n        th.onclick = () => sortTable(column);\n        \n        // Add sorting indicators\n        if (column === currentSortColumn) {\n            th.classList.add(sortAscending ? 'sort-asc' : 'sort-desc');\n        }\n        \n        headerRow.appendChild(th);\n    });\n    thead.appendChild(headerRow);\n    \n    // Apply pagination\n    const startIndex = (currentPage - 1) * pageSize;\n    const endIndex = Math.min(startIndex + pageSize, filteredData.length);\n    const paginatedData = filteredData.slice(startIndex, endIndex);\n    \n    // Create data rows\n    paginatedData.forEach(row => {\n        const tr = document.createElement('tr');\n        Object.entries(columnTypes).forEach(([column, type]) => {\n            const td = document.createElement('td');\n            td.textContent = formatValue(row[column], type);\n            if (type === 'boolean') {\n                td.style.color = row[column] ? 'green' : 'red';\n            }\n            tr.appendChild(td);\n        });\n        tbody.appendChild(tr);\n    });\n    \n    table.appendChild(thead);\n    table.appendChild(tbody);\n    updatePagination();\n    return table;\n}\n\n        // Sort table by column\n        // Replace your existing sortTable function\nfunction sortTable(column) {\n    if (currentSortColumn === column) {\n        sortAscending = !sortAscending;\n    } else {\n        currentSortColumn = column;\n        sortAscending = true;\n    }\n    \n    const type = columnTypes[column];\n    filteredData.sort((a, b) => {\n        const valueA = a[column];\n        const valueB = b[column];\n        \n        let comparison = 0;\n        switch (type) {\n            case 'number':\n                comparison = valueA - valueB;\n                break;\n            case 'date':\n                comparison = new Date(valueA) - new Date(valueB);\n                break;\n            case 'boolean':\n                comparison = valueA === valueB ? 0 : valueA ? -1 : 1;\n                break;\n            default:\n                comparison = valueA.toString().localeCompare(valueB.toString());\n        }\n        return sortAscending ? comparison : -comparison;\n    });\n    \n    refreshTable();\n}\n\n        // Filter data\n        function filterData() {\n            const filterValue = document.getElementById('filterInput').value.toLowerCase();\n            const filterColumn = document.getElementById('filterColumn').value;\n            \n            filteredData = data.filter(row => {\n                if (filterColumn === 'all') {\n                    return Object.values(row).some(value => \n                        value.toString().toLowerCase().includes(filterValue)\n                    );\n                } else {\n                    return row[filterColumn].toString().toLowerCase().includes(filterValue);\n                }\n            });\n            \n            refreshTable();\n            if (document.getElementById('graphView').style.display !== 'none') {\n                updateGraph();\n            }\n        }\n\n        // Update graph\n       function updateGraph() {\n    const xAxis = document.getElementById('xAxis').value;\n    const yAxis = document.getElementById('yAxis').value;\n    const groupBy = document.getElementById('groupBy').value;\n    const chartType = document.getElementById('chartType').value;\n    const aggregation = document.getElementById('aggregation').value;\n    \n    // Prepare data\n    const groupedData = {};\n    filteredData.forEach(row => {\n        const groupKey = groupBy ? row[groupBy] : 'All';\n        if (!groupedData[groupKey]) {\n            groupedData[groupKey] = {};\n        }\n        const xValue = formatValue(row[xAxis], columnTypes[xAxis]);\n        if (!groupedData[groupKey][xValue]) {\n            groupedData[groupKey][xValue] = [];\n        }\n        groupedData[groupKey][xValue].push(parseFloat(row[yAxis]) || 0);\n    });\n\n    // Process data with aggregation\n    const labels = [...new Set(filteredData.map(item => formatValue(item[xAxis], columnTypes[xAxis])))];\n    const datasets = Object.entries(groupedData).map(([key, values]) => ({\n        label: key,\n        data: labels.map(label => {\n            const vals = values[label] || [];\n            switch(aggregation) {\n                case 'sum':\n                    return vals.reduce((a, b) => a + b, 0);\n                case 'average':\n                    return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;\n                case 'count':\n                    return vals.length;\n                case 'min':\n                    return vals.length ? Math.min(...vals) : 0;\n                case 'max':\n                    return vals.length ? Math.max(...vals) : 0;\n                default:\n                    return vals.reduce((a, b) => a + b, 0);\n            }\n        })\n    }));\n    // Create chart\n    const ctx = document.getElementById('myChart');\n    if (window.currentChart) {\n        window.currentChart.destroy();\n    }\n    \n   // Update the chart configuration in updateGraph function\nwindow.currentChart = new Chart(ctx, {\n    type: chartType,\n    data: {\n        labels: labels,\n        datasets: datasets.map(dataset => ({\n            ...dataset,\n            // Line chart settings\n            borderWidth: 2,\n            tension: 0,  // Remove curve for straight lines\n            pointRadius: 4,  // Larger points\n            pointHoverRadius: 6,\n            \n            // Bar chart settings\n            barPercentage: 0.8,  // Make bars wider (0-1)\n            categoryPercentage: 0.9,  // Space between bar groups\n            \n            // Colors\n            backgroundColor: dataset.backgroundColor || 'rgba(75, 192, 192, 0.6)',\n            borderColor: dataset.borderColor || 'rgba(75, 192, 192, 1)',\n        }))\n    },\n    options: {\n        responsive: true,\n        plugins: {\n            legend: {\n                position: 'top',\n            }\n        },\n        scales: chartType !== 'pie' && chartType !== 'doughnut' ? {\n            y: {\n                beginAtZero: true,\n                ticks: {\n                    font: {\n                        size: 12\n                    }\n                }\n            },\n            x: {\n                ticks: {\n                    font: {\n                        size: 12\n                    }\n                }\n            }\n        } : undefined,\n        // Pie chart size control\n        layout: {\n            padding: 20\n        },\n        aspectRatio: chartType === 'pie' || chartType === 'doughnut' ? 2 : 1.5,\n    }\n});}\n\n        // Export to CSV\n        function exportToCSV() {\n            const headers = Object.keys(columnTypes);\n            const csv = [\n                headers.join(','),\n                ...filteredData.map(row =>\n                    headers.map(header => {\n                        const value = formatValue(row[header], columnTypes[header]);\n                        return `\"${value.toString().replace(/\"/g, '\"\"')}\"`\n                    }).join(',')\n                )\n            ].join('\\n');\n            \n            const blob = new Blob([csv], { type: 'text/csv' });\n            const url = window.URL.createObjectURL(blob);\n            const a = document.createElement('a');\n            a.href = url;\n            a.download = 'export.csv';\n            a.click();\n        }\n\n        // Switch between table and graph views\n        function switchTab(tab) {\n            document.querySelectorAll('.tab-button').forEach(button => {\n                button.classList.remove('active');\n            });\n            document.querySelector(`[onclick=\"switchTab('${tab}')\"]`).classList.add('active');\n            \n            document.getElementById('tableView').style.display = tab === 'table' ? 'block' : 'none';\n            document.getElementById('graphView').style.display = tab === 'graph' ? 'block' : 'none';\n            \n            if (tab === 'graph') {\n                updateGraph();\n            }\n        }\n\n        // Set up event listeners\n\n        // Set up event listeners\nfunction setupEventListeners() {\n    // Filter input listener\n    document.getElementById('filterInput').addEventListener('input', filterData);\n    document.getElementById('filterColumn').addEventListener('change', filterData);\n    \n    // Graph control listeners\n    document.getElementById('xAxis').addEventListener('change', updateGraph);\n    document.getElementById('yAxis').addEventListener('change', updateGraph);\n    document.getElementById('groupBy').addEventListener('change', updateGraph);\n    document.getElementById('chartType').addEventListener('change', updateGraph);\n    document.getElementById('aggregation').addEventListener('change', updateGraph); // Add this line\n}\n\n        // Refresh table display\n        function refreshTable() {\n            const container = document.getElementById('tableContainer');\n            container.innerHTML = '';\n            container.appendChild(createTable());\n        }\n// Add these pagination functions before initialize()\nfunction updatePagination() {\n    const totalPages = Math.ceil(filteredData.length / pageSize);\n    document.getElementById('pageInfo').textContent = `Page ${currentPage} of ${totalPages}`;\n    document.getElementById('prevBtn').disabled = currentPage === 1;\n    document.getElementById('nextBtn').disabled = currentPage === totalPages || totalPages === 0;\n}\n\nfunction previousPage() {\n    if (currentPage > 1) {\n        currentPage--;\n        refreshTable();\n    }\n}\n\nfunction nextPage() {\n    const totalPages = Math.ceil(filteredData.length / pageSize);\n    if (currentPage < totalPages) {\n        currentPage++;\n        refreshTable();\n    }\n}\n\nfunction changePageSize() {\n    pageSize = parseInt(document.getElementById('pageSize').value);\n    currentPage = 1;\n    refreshTable();\n}\n        // Start the application\n        initialize();\n    </script>\n</body>\n</html>"
            }
          ]
        },
        "options": {}
      },
      "name": "Set HTML",
      "position": [
        704,
        1760
      ],
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "id": "c2e8aad8-c0bf-4811-b40d-d695a8745673",
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.html }}",
        "options": {}
      },
      "name": "Respond to Webhook",
      "position": [
        1152,
        1840
      ],
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "id": "838225b5-06ef-42ef-8e82-84baaf9af631"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "{{ $json.query.sql }}"
      },
      "name": "Snowflake1",
      "position": [
        256,
        1840
      ],
      "retryOnFail": false,
      "type": "n8n-nodes-base.snowflake",
      "typeVersion": 1,
      "id": "c174c286-663c-4a24-ab70-b969d3e00af0",
      "credentials": {
        "snowflake": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "name": "Aggregate1",
      "position": [
        480,
        1760
      ],
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "id": "89f7ccf6-c130-4d7f-a41c-babc5581a120"
    },
    {
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "query"
            }
          ]
        }
      },
      "name": "When Executed by Another Workflow",
      "position": [
        32,
        1440
      ],
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "id": "d9ea31f1-8a38-4b18-9c44-a7539b69150b"
    },
    {
      "parameters": {
        "description": "Generate custom SQL queries using knowledge about DB schema and table definitions to provide needed response for user request.\nUse ->> operator to extract JSON data.\n\nSupported functions for big data analysis:\n\u2022 GROUP BY \u2013 for grouping data\n\u2022 SUM() \u2013 for summing values\n\u2022 AVG() \u2013 for calculating averages\n\u2022 COUNT() \u2013 for counting records\n\u2022 MIN() \u2013 for finding the minimum value\n\u2022 MAX() \u2013 for finding the maximum value\n\u2022 MEDIAN() \u2013 for median calculation\n\u2022 STDDEV() \u2013 for standard deviation\n\u2022 VARIANCE() \u2013 for variance calculation\n\u2022 PERCENTILE_CONT() \u2013 for percentile calculations\n\u2022 MODE() \u2013 for most frequent value\n\u2022 TREND() \u2013 for trend analysis over time\n\u2022 WINDOW FUNCTIONS \u2013 for advanced analytics (e.g., ROW_NUMBER(), RANK(), PARTITION BY)\n\nQuery example:\nSELECT * FROM FILES",
        "workflowId": {
          "__rl": true,
          "mode": "id",
          "value": "VrreoHxSO7d0UAO2"
        },
        "workflowInputs": {
          "attemptToConvertTypes": false,
          "convertFieldsToString": false,
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "query"
          ],
          "schema": [
            {
              "canBeUsedToMatch": true,
              "defaultMatch": false,
              "display": true,
              "displayName": "query",
              "id": "query",
              "removed": false,
              "required": false,
              "type": "string"
            }
          ],
          "value": {
            "query": "={{ $fromAI('query', ``, 'string') }}"
          }
        }
      },
      "name": "Retrieve Data",
      "position": [
        640,
        1040
      ],
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "id": "2dc3a12d-30e6-4b6a-a973-12bbcf1936e0"
    },
    {
      "parameters": {
        "content": "### Tool\n",
        "height": 560,
        "width": 1140
      },
      "name": "Sticky Note",
      "position": [
        -48,
        1136
      ],
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "id": "7841c8f9-df7f-4504-a13a-b496343b42c2"
    },
    {
      "parameters": {
        "content": "### Report workflow",
        "height": 480,
        "width": 1360
      },
      "name": "Sticky Note1",
      "position": [
        -48,
        1584
      ],
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "id": "a861b02a-2da6-4791-9a5d-9acebc67d4e5"
    },
    {
      "parameters": {
        "content": "### Agent",
        "height": 440,
        "width": 900
      },
      "name": "Sticky Note2",
      "position": [
        -16,
        752
      ],
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "id": "03815a31-4a66-421e-ad45-f156b0491301"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "{{ $json.query }}"
      },
      "name": "Execute SQL",
      "position": [
        256,
        1440
      ],
      "retryOnFail": false,
      "type": "n8n-nodes-base.snowflake",
      "typeVersion": 1,
      "id": "76b7dff9-3e94-42fc-9c05-946159d539fd",
      "credentials": {
        "snowflake": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "name": "Aggregate Data",
      "position": [
        480,
        1344
      ],
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "id": "7baac261-b0fb-4591-ae7e-8ff7add504c1"
    },
    {
      "parameters": {
        "conditions": {
          "combinator": "and",
          "conditions": [
            {
              "id": "5a485a5a-28fb-4914-8fb6-131a159db08d",
              "leftValue": "={{ $json.data }}",
              "operator": {
                "operation": "notEmpty",
                "singleValue": true,
                "type": "array"
              },
              "rightValue": 5
            }
          ],
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          }
        },
        "options": {}
      },
      "name": "If Count>100",
      "position": [
        704,
        1344
      ],
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "id": "7f186d10-7d71-4e41-9964-e720b694ec23"
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={\"output\":\"[Link to report](http://localhost:5678/webhook/getReport?sql={{ $('When Executed by Another Workflow').item.json.query.urlEncode() }})\"}",
        "options": {}
      },
      "name": "Link to Report",
      "position": [
        928,
        1248
      ],
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "id": "643d62de-3427-406c-a2f7-ea9a15f51f52"
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={{ $json }}",
        "options": {}
      },
      "name": "Return Data",
      "position": [
        928,
        1440
      ],
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "id": "e257695c-9512-4f87-bb2b-62def98c91d2"
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={{ $json }}",
        "options": {}
      },
      "name": "Return Error",
      "position": [
        480,
        1536
      ],
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "id": "0e801546-70f4-48cb-9f93-95c5f935063e"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1257d6ca-3c5c-476b-8d26-f8fb84a0c38e",
              "name": "html",
              "type": "string",
              "value": "=<!DOCTYPE html>\n<html>\n<head>\n    <title>Error Status</title>\n    <style>\n        body { \n            font-family: Arial, sans-serif; \n            padding: 20px;\n            margin: 0;\n            display: flex;\n            min-height: 100vh;\n            align-items: center;\n            justify-content: center;\n            background-color: #f8f9fa;\n        }\n        .container { \n            max-width: 600px;\n            margin: 0 auto;\n            text-align: center;\n            padding: 40px;\n            background: white;\n            border-radius: 8px;\n            box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n        }\n        .error-icon {\n            font-size: 48px;\n            margin-bottom: 20px;\n            color: #dc3545;\n        }\n        .title {\n            color: #dc3545;\n            margin-bottom: 16px;\n            font-size: 24px;\n        }\n        .message {\n            color: #666;\n            margin-bottom: 24px;\n            font-size: 16px;\n            line-height: 1.5;\n        }\n        .btn {\n            padding: 10px 20px;\n            background: #dc3545;\n            color: white;\n            border: none;\n            border-radius: 4px;\n            cursor: pointer;\n            font-size: 16px;\n            text-decoration: none;\n            display: inline-block;\n            margin: 0 8px;\n        }\n        .btn:hover {\n            background: #c82333;\n        }\n        .btn-secondary {\n            background: #6c757d;\n        }\n        .btn-secondary:hover {\n            background: #5a6268;\n        }\n        .details {\n            margin-top: 20px;\n            padding-top: 20px;\n            border-top: 1px solid #eee;\n            color: #888;\n            font-size: 14px;\n        }\n        .error-code {\n            font-family: monospace;\n            background: #f8f9fa;\n            padding: 8px 16px;\n            border-radius: 4px;\n            display: inline-block;\n            margin: 8px 0;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"error-icon\">\u26a0\ufe0f</div>\n        <h1 class=\"title\">Error Occurred</h1>\n        <p class=\"message\">An error occurred while preparing the analysis data.</p>\n        <p class=\"message\">Please try again later or contact support if the problem persists.</p>\n        <div class=\"error-code\">Error Code: HTML_PREP_ERROR</div>\n        <div>\n            <a href=\"javascript:window.close();\" class=\"btn\">Close Window</a>\n            <a href=\"javascript:location.reload();\" class=\"btn btn-secondary\">Try Again</a>\n        </div>\n        <div class=\"details\">\n            <p>Analysis ID: <span id=\"analysisId\"></span></p>\n            <p>Time: <span id=\"errorTime\"></span></p>\n        </div>\n    </div>\n\n    <script>\n        // Get URL parameters\n        const urlParams = new URLSearchParams(window.location.search);\n        const analysisId = urlParams.get('analysis_id');\n        \n        // Update details\n        document.getElementById('analysisId').textContent = analysisId || 'N/A';\n        document.getElementById('errorTime').textContent = new Date().toLocaleString();\n    </script>\n</body>\n</html>"
            }
          ]
        },
        "options": {}
      },
      "name": "Error page",
      "position": [
        928,
        1904
      ],
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "id": "7e062a30-f1cc-4898-ae54-5e2e955439f1"
    },
    {
      "parameters": {
        "content": "### Map this workflow",
        "height": 80,
        "width": 160,
        "color": 5
      },
      "name": "Sticky Note5",
      "position": [
        304,
        1264
      ],
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "id": "1087bcff-22fe-4811-b72f-8ff99b4b44b8"
    },
    {
      "parameters": {
        "content": "### Replace name of schema and database",
        "height": 80,
        "color": 5
      },
      "name": "Sticky Note6",
      "position": [
        560,
        1264
      ],
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "id": "08818af9-ef1a-4c5e-84ac-1641480cdcee"
    },
    {
      "parameters": {
        "model": "devstral-small-2:latest",
        "options": {}
      },
      "name": "Ollama Chat Model",
      "position": [
        256,
        1040
      ],
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "id": "395a131f-e6f9-4f66-aa9b-fe597c3f7454",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Get list of all tables in database",
        "operation": "executeQuery",
        "query": "SELECT table_schema, table_name\nFROM information_schema.tables\nWHERE table_schema = 'WEBAPP_AIWORKFLOW';"
      },
      "name": "DB Schema",
      "position": [
        512,
        1040
      ],
      "type": "n8n-nodes-base.snowflakeTool",
      "typeVersion": 1,
      "id": "75021462-acb0-4ef2-a4da-6ec4c9209d8f",
      "credentials": {
        "snowflake": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "AI Agent1": {
      "main": [
        []
      ]
    },
    "Aggregate Data": {
      "main": [
        [
          {
            "index": 0,
            "node": "If Count>100",
            "type": "main"
          }
        ]
      ]
    },
    "Aggregate1": {
      "main": [
        [
          {
            "index": 0,
            "node": "Set HTML",
            "type": "main"
          }
        ]
      ]
    },
    "DB Schema": {
      "ai_tool": [
        [
          {
            "index": 0,
            "node": "AI Agent1",
            "type": "ai_tool"
          }
        ]
      ]
    },
    "Error page": {
      "main": [
        [
          {
            "index": 0,
            "node": "Respond to Webhook",
            "type": "main"
          }
        ]
      ]
    },
    "Execute SQL": {
      "main": [
        [
          {
            "index": 0,
            "node": "Aggregate Data",
            "type": "main"
          }
        ],
        [
          {
            "index": 0,
            "node": "Return Error",
            "type": "main"
          }
        ]
      ]
    },
    "Get table definition": {
      "ai_tool": [
        [
          {
            "index": 0,
            "node": "AI Agent1",
            "type": "ai_tool"
          }
        ]
      ]
    },
    "If Count>100": {
      "main": [
        [
          {
            "index": 0,
            "node": "Link to Report",
            "type": "main"
          }
        ],
        [
          {
            "index": 0,
            "node": "Return Data",
            "type": "main"
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "index": 0,
            "node": "AI Agent1",
            "type": "ai_languageModel"
          }
        ]
      ]
    },
    "Retrieve Data": {
      "ai_tool": [
        [
          {
            "index": 0,
            "node": "AI Agent1",
            "type": "ai_tool"
          }
        ]
      ]
    },
    "Set HTML": {
      "main": [
        [
          {
            "index": 0,
            "node": "Respond to Webhook",
            "type": "main"
          }
        ],
        [
          {
            "index": 0,
            "node": "Error page",
            "type": "main"
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "index": 0,
            "node": "AI Agent1",
            "type": "ai_memory"
          }
        ]
      ]
    },
    "Snowflake1": {
      "main": [
        [
          {
            "index": 0,
            "node": "Aggregate1",
            "type": "main"
          }
        ],
        [
          {
            "index": 0,
            "node": "Error page",
            "type": "main"
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "index": 0,
            "node": "Snowflake1",
            "type": "main"
          }
        ]
      ]
    },
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "index": 0,
            "node": "Execute SQL",
            "type": "main"
          }
        ]
      ]
    },
    "When chat message received": {
      "main": [
        [
          {
            "index": 0,
            "node": "AI Agent1",
            "type": "main"
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false
  },
  "staticData": null,
  "meta": {
    "templateId": "5435",
    "templateCredsSetupCompleted": true
  },
  "versionId": "f6369845-41ce-4f49-b38c-8f35b3f9034d",
  "activeVersionId": "f6369845-41ce-4f49-b38c-8f35b3f9034d",
  "triggerCount": 2,
  "shared": [
    {
      "updatedAt": "2025-12-15T19:34:19.460Z",
      "createdAt": "2025-12-15T19:34:19.460Z",
      "role": "workflow:owner",
      "workflowId": "VrreoHxSO7d0UAO2",
      "projectId": "aRJv9cLftn98cx8V"
    }
  ],
  "activeVersion": {
    "updatedAt": "2026-01-26T22:22:28.278Z",
    "createdAt": "2026-01-26T22:12:23.333Z",
    "versionId": "f6369845-41ce-4f49-b38c-8f35b3f9034d",
    "workflowId": "VrreoHxSO7d0UAO2",
    "nodes": [
      {
        "parameters": {
          "options": {}
        },
        "name": "When chat message received",
        "position": [
          32,
          928
        ],
        "type": "@n8n/n8n-nodes-langchain.chatTrigger",
        "typeVersion": 1.1,
        "webhookId": "8cc040be-d37a-4090-b93e-44603371d13a",
        "id": "a5b8617e-563f-446a-8e1d-bce121f245f4"
      },
      {
        "parameters": {
          "hasOutputParser": true,
          "options": {
            "systemMessage": "You are Snowflake SQL assistant.\n\nUse tools to retrieve data from Snowflake and answer user.\n\nIMPORTANT Always check database schema and table definition for preparing SQL query."
          }
        },
        "name": "AI Agent1",
        "position": [
          448,
          816
        ],
        "type": "@n8n/n8n-nodes-langchain.agent",
        "typeVersion": 2,
        "id": "1b69e537-ee28-45f3-8323-6dff48cc70aa"
      },
      {
        "parameters": {
          "sessionIdType": "customKey",
          "sessionKey": "={{ $('When chat message received').item.json.sessionId }}"
        },
        "name": "Simple Memory",
        "position": [
          384,
          1040
        ],
        "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
        "typeVersion": 1.3,
        "id": "4cc7e324-7f98-49af-896e-8230ff08b6bc"
      },
      {
        "parameters": {
          "descriptionType": "manual",
          "toolDescription": "Get table definition to find all columns and types.",
          "operation": "executeQuery",
          "query": "SELECT \n    column_name,\n    data_type\n\nFROM \n    WEB_APPLICATIONS.information_schema.columns\nWHERE \n    table_name = '{{ $fromAI(\"table_name\") }}'\n    AND table_schema = 'WEBAPP_AIWORKFLOW'\nORDER BY \n    ordinal_position;\n"
        },
        "name": "Get table definition",
        "position": [
          768,
          1040
        ],
        "type": "n8n-nodes-base.snowflakeTool",
        "typeVersion": 1,
        "id": "4777ceb7-0d5d-4f61-9767-bdb76dd30586",
        "credentials": {
          "snowflake": {
            "id": "oi8Cr7VRLwSbn6CV",
            "name": "Snowflake account"
          }
        }
      },
      {
        "parameters": {
          "path": "getReport",
          "responseMode": "responseNode",
          "options": {}
        },
        "name": "Webhook",
        "position": [
          32,
          1840
        ],
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "webhookId": "87893585-d157-468d-a9af-7238784e814c",
        "id": "d6b98488-a877-4afe-b4cc-295abf0c4d83"
      },
      {
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1257d6ca-3c5c-476b-8d26-f8fb84a0c38e",
                "name": "html",
                "type": "string",
                "value": "=<!DOCTYPE html>\n<html>\n<head>\n    <title>Dashboard</title>\n    <style>\n        body { \n            font-family: Arial, sans-serif; \n            padding: 20px;\n            margin: 0;\n        }\n        .container { \n            max-width: 1200px; \n            margin: 0 auto; \n        }\n        table {\n            border-collapse: collapse;\n            width: 100%;\n            margin: 20px 0;\n        }\n        th, td {\n            border: 1px solid #ddd;\n            padding: 8px;\n            text-align: left;\n        }\n        th {\n            background-color: #f2f2f2;\n            cursor: pointer;\n        }\n        th:hover {\n            background-color: #ddd;\n        }\n        .controls {\n            margin: 20px 0;\n            padding: 10px;\n            background: #f8f8f8;\n            border-radius: 4px;\n        }\n        .btn {\n            padding: 8px 16px;\n            margin: 0 5px;\n            cursor: pointer;\n            background: #4CAF50;\n            color: white;\n            border: none;\n            border-radius: 4px;\n        }\n        .btn:hover {\n            background: #45a049;\n        }\n        .tab-container {\n            margin: 20px 0;\n        }\n        .tab-button {\n            padding: 10px 20px;\n            border: none;\n            background: #f2f2f2;\n            cursor: pointer;\n        }\n        .tab-button.active {\n            background: #4CAF50;\n            color: white;\n        }\n        .graph-controls {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n            gap: 15px;\n            margin: 20px 0;\n            padding: 15px;\n            background: #f8f8f8;\n            border-radius: 4px;\n        }\n        .control-group {\n            display: flex;\n            flex-direction: column;\n            gap: 5px;\n        }\n        select, input {\n            padding: 8px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n        }\n        #graphContainer {\n            background: white;\n            padding: 20px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n            margin: 20px 0;\n        }\n/* Sorting icons styles */\nth {\n    position: relative;\n    padding-right: 25px; /* Space for sort icon */\n}\nth:after {\n    content: '\u2195';\n    position: absolute;\n    right: 8px;\n    color: #999;\n}\nth.sort-asc:after {\n    content: '\u2191';\n    color: #000;\n}\nth.sort-desc:after {\n    content: '\u2193';\n    color: #000;\n}\n\n/* Pagination styles */\n.pagination {\n    display: flex;\n    gap: 10px;\n    align-items: center;\n    margin: 20px 0;\n    justify-content: center;\n}\n.pagination button {\n    padding: 5px 10px;\n    border: 1px solid #ddd;\n    background: white;\n    cursor: pointer;\n    border-radius: 4px;\n}\n.pagination button:disabled {\n    background: #f5f5f5;\n    cursor: not-allowed;\n    opacity: 0.5;\n}\n.pagination span {\n    padding: 0 10px;\n}\n.page-size {\n    margin-left: 20px;\n}\n.templates-section {\n    grid-column: 1 / -1;\n    padding: 10px;\n    border-bottom: 1px solid #ddd;\n    margin-bottom: 15px;\n}\n\n.template-buttons {\n    display: flex;\n    gap: 10px;\n    flex-wrap: wrap;\n    margin-top: 10px;\n}\n\n.template-button {\n    padding: 8px 16px;\n    background: #f0f0f0;\n    border: 1px solid #ddd;\n    border-radius: 4px;\n    cursor: pointer;\n    transition: all 0.3s;\n}\n\n.template-button:hover {\n    background: #e0e0e0;\n}\n\n.template-button.active {\n    background: #4CAF50;\n    color: white;\n    border-color: #4CAF50;\n}\n    </style>\n   <!-- React and ReactDOM dependencies -->\n    <script crossorigin src=\"https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js\"></script>\n    <script crossorigin src=\"https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js\"></script>\n    \n\n\n<!-- Add this after other script tags -->\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n</head>\n<body>\n    <div class=\"container\">\n        <h1>Dashboard</h1>\n        \n        <div class=\"tab-container\">\n            <button class=\"tab-button active\" onclick=\"switchTab('table')\">Table View</button>\n            <button class=\"tab-button\" onclick=\"switchTab('graph')\">Graph View</button>\n        </div>\n\n        <!-- Table View -->\n        <div id=\"tableView\">\n            <div class=\"controls\">\n                <input type=\"text\" id=\"filterInput\" placeholder=\"Search...\" />\n                <select id=\"filterColumn\">\n                    <option value=\"all\">All Columns</option>\n                </select>\n                <button class=\"btn\" onclick=\"exportToCSV()\">Export to CSV</button>\n            </div>\n            <div id=\"tableContainer\"></div>\n<div class=\"pagination\">\n    <button onclick=\"previousPage()\" id=\"prevBtn\">Previous</button>\n    <span id=\"pageInfo\">Page 1 of 1</span>\n    <button onclick=\"nextPage()\" id=\"nextBtn\">Next</button>\n    <select id=\"pageSize\" onchange=\"changePageSize()\" class=\"page-size\">\n        <option value=\"5\">5 per page</option>\n        <option value=\"10\" selected>10 per page</option>\n        <option value=\"20\">20 per page</option>\n        <option value=\"50\">50 per page</option>\n    </select>\n</div>\n        </div>\n\n        <!-- Graph View -->\n       <div id=\"graphView\" style=\"display: none;\">\n    <div class=\"graph-controls\">\n        <!-- Add templates section -->\n        <div class=\"templates-section\">\n            <h3>Chart Templates</h3>\n            <div id=\"templateButtons\" class=\"template-buttons\">\n                <!-- Template buttons will be added here dynamically -->\n            </div>\n        </div>\n        \n        <!-- Existing controls -->\n        <div class=\"control-group\">\n            <label>X Axis:</label>\n            <select id=\"xAxis\" onchange=\"updateGraph()\"></select>\n        </div>\n        <div class=\"control-group\">\n            <label>Y Axis:</label>\n            <select id=\"yAxis\" onchange=\"updateGraph()\"></select>\n        </div>\n        <div class=\"control-group\">\n            <label>Group By:</label>\n            <select id=\"groupBy\" onchange=\"updateGraph()\"></select>\n        </div>\n        <div class=\"control-group\">\n            <label>Chart Type:</label>\n            <select id=\"chartType\" onchange=\"updateGraph()\">\n    <option value=\"bar\">Bar Chart</option>\n    <option value=\"line\">Line Chart</option>\n    <option value=\"pie\">Pie Chart</option>\n    <option value=\"doughnut\">Doughnut Chart</option>\n</select>\n        </div>\n<!-- Add this in graph-controls div after other controls -->\n<div class=\"control-group\">\n    <label>Aggregation:</label>\n    <select id=\"aggregation\" onchange=\"updateGraph()\">\n        <option value=\"sum\">Sum</option>\n        <option value=\"average\">Average</option>\n        <option value=\"count\">Count</option>\n        <option value=\"min\">Minimum</option>\n        <option value=\"max\">Maximum</option>\n    </select>\n</div>\n    </div>\n    <div id=\"graphContainer\">\n        <canvas id=\"myChart\"></canvas>\n    </div>\n</div>\n\n    <script>\n        // Sample data - sales transactions\n        const data = {{ JSON.stringify($json.data) }};\n        // Column type definitions for formatting\n        function detectColumnTypes(data) {\n    if (!data || data.length === 0) return {};\n    \n    // Get keys from first data item\n    const firstItem = data[0];\n    const columnTypes = {};\n    \n    // Determine type for each column\n    Object.keys(firstItem).forEach(key => {\n        const value = firstItem[key];\n        let type = 'string'; // default type\n        \n        if (value instanceof Date) {\n            type = 'date';\n        } else if (typeof value === 'number') {\n            type = 'number';\n        } else if (typeof value === 'boolean') {\n            type = 'boolean';\n        } else if (typeof value === 'string') {\n            // Check if string is actually a date\n            const dateCheck = new Date(value);\n            if (!isNaN(dateCheck) && value.includes('-')) {\n                type = 'date';\n            } else {\n                type = 'string';\n            }\n        }\n        \n        columnTypes[key] = type;\n    });\n    \n    return columnTypes;\n}\n\n// Remove the hardcoded columnTypes and replace with:\nlet columnTypes = {};\n\n        let filteredData = [...data];\n        let currentPage = 1;\n        let pageSize = 10;\nlet currentSortColumn = null;\nlet sortAscending = true;\n\n\n\nfunction initializeGraphControls() {\n    const templateContainer = document.getElementById('templateButtons');\n    templateContainer.innerHTML = chartTemplates.map((template, index) => `\n        <button class=\"template-button\" onclick=\"applyTemplate(${index})\">\n            ${template.title}\n        </button>\n    `).join('');\n}\n\n    function initialize() {\n    // Detect column types from data\n    columnTypes = detectColumnTypes(data);\n    console.log('Detected columns:', columnTypes);\n    \n    const columns = Object.keys(columnTypes);\n    \n    // Initialize filter dropdown\n    const filterColumn = document.getElementById('filterColumn');\n    filterColumn.innerHTML = '<option value=\"all\">All Columns</option>';\n    columns.forEach(column => {\n        filterColumn.innerHTML += `<option value=\"${column}\">${column}</option>`;\n    });\n\n    // Initialize graph axis selectors\n    const xAxis = document.getElementById('xAxis');\n    const yAxis = document.getElementById('yAxis');\n    // In the initialize function, add empty option for groupBy\nconst groupBy = document.getElementById('groupBy');\ngroupBy.innerHTML = '<option value=\"\">No Grouping</option>';\ncolumns.forEach(column => {\n    groupBy.innerHTML += `<option value=\"${column}\">${column}</option>`;\n});\n    \n    xAxis.innerHTML = '';\n    yAxis.innerHTML = '';\n    \n    columns.forEach(column => {\n        xAxis.innerHTML += `<option value=\"${column}\">${column}</option>`;\n        yAxis.innerHTML += `<option value=\"${column}\">${column}</option>`;\n        groupBy.innerHTML += `<option value=\"${column}\">${column}</option>`;\n    });\n\n    // Set default Y axis to a numeric column\n    const numericColumns = columns.filter(col => columnTypes[col] === 'number');\n    if (numericColumns.length > 0) {\n        yAxis.value = numericColumns[0];\n    }\n\n    setupEventListeners();\n    refreshTable();\n    initializeGraphControls();\n}\n\n        // Format cell values based on type\n        function formatValue(value, type) {\n            if (value === null || value === undefined) return '';\n            \n            switch (type) {\n                case 'date':\n                    return new Date(value).toLocaleDateString();\n                case 'number':\n                    return value.toLocaleString();\n                case 'boolean':\n                    return value ? 'Yes' : 'No';\n                default:\n                    return value.toString();\n            }\n        }\n\n        // Create and update table\n        function createTable() {\n    const table = document.createElement('table');\n    const thead = document.createElement('thead');\n    const tbody = document.createElement('tbody');\n    \n    // Create header row with sorting indicators\n    const headerRow = document.createElement('tr');\n    Object.keys(columnTypes).forEach(column => {\n        const th = document.createElement('th');\n        th.textContent = column.charAt(0).toUpperCase() + column.slice(1);\n        th.onclick = () => sortTable(column);\n        \n        // Add sorting indicators\n        if (column === currentSortColumn) {\n            th.classList.add(sortAscending ? 'sort-asc' : 'sort-desc');\n        }\n        \n        headerRow.appendChild(th);\n    });\n    thead.appendChild(headerRow);\n    \n    // Apply pagination\n    const startIndex = (currentPage - 1) * pageSize;\n    const endIndex = Math.min(startIndex + pageSize, filteredData.length);\n    const paginatedData = filteredData.slice(startIndex, endIndex);\n    \n    // Create data rows\n    paginatedData.forEach(row => {\n        const tr = document.createElement('tr');\n        Object.entries(columnTypes).forEach(([column, type]) => {\n            const td = document.createElement('td');\n            td.textContent = formatValue(row[column], type);\n            if (type === 'boolean') {\n                td.style.color = row[column] ? 'green' : 'red';\n            }\n            tr.appendChild(td);\n        });\n        tbody.appendChild(tr);\n    });\n    \n    table.appendChild(thead);\n    table.appendChild(tbody);\n    updatePagination();\n    return table;\n}\n\n        // Sort table by column\n        // Replace your existing sortTable function\nfunction sortTable(column) {\n    if (currentSortColumn === column) {\n        sortAscending = !sortAscending;\n    } else {\n        currentSortColumn = column;\n        sortAscending = true;\n    }\n    \n    const type = columnTypes[column];\n    filteredData.sort((a, b) => {\n        const valueA = a[column];\n        const valueB = b[column];\n        \n        let comparison = 0;\n        switch (type) {\n            case 'number':\n                comparison = valueA - valueB;\n                break;\n            case 'date':\n                comparison = new Date(valueA) - new Date(valueB);\n                break;\n            case 'boolean':\n                comparison = valueA === valueB ? 0 : valueA ? -1 : 1;\n                break;\n            default:\n                comparison = valueA.toString().localeCompare(valueB.toString());\n        }\n        return sortAscending ? comparison : -comparison;\n    });\n    \n    refreshTable();\n}\n\n        // Filter data\n        function filterData() {\n            const filterValue = document.getElementById('filterInput').value.toLowerCase();\n            const filterColumn = document.getElementById('filterColumn').value;\n            \n            filteredData = data.filter(row => {\n                if (filterColumn === 'all') {\n                    return Object.values(row).some(value => \n                        value.toString().toLowerCase().includes(filterValue)\n                    );\n                } else {\n                    return row[filterColumn].toString().toLowerCase().includes(filterValue);\n                }\n            });\n            \n            refreshTable();\n            if (document.getElementById('graphView').style.display !== 'none') {\n                updateGraph();\n            }\n        }\n\n        // Update graph\n       function updateGraph() {\n    const xAxis = document.getElementById('xAxis').value;\n    const yAxis = 

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

I prepared a detailed guide showcasing the process of building an AI agent that interacts with a Snowflake database using n8n. This setup enables conversational querying, secure execution of SQL queries, and dynamic report generation with rich visualization capabilities.

Source: https://n8n.io/workflows/5435/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

by Varritech Technologies

Chat Trigger, Agent, OpenAI Chat +8
AI & RAG

Airtable AI Agent. Uses lmChatOpenAi, agent, toolWorkflow, toolCode. Chat trigger; 42 nodes.

OpenAI Chat, Agent, Tool Workflow +6
AI & RAG

Ai Agent To Chat With Airtable And Analyze Data. Uses lmChatOpenAi, agent, stickyNote, memoryBufferWindow. Chat trigger; 41 nodes.

OpenAI Chat, Agent, Memory Buffer Window +6
AI & RAG

I prepared a detailed guide that shows the entire process of building an AI agent that integrates with Airtable data in n8n. This template covers everything from data preparation to advanced configura

OpenAI Chat, Agent, Memory Buffer Window +6
AI & RAG

Categories: AI Agents, Design Automation, Business Tools

Tool Workflow, HTTP Request Tool, Memory Buffer Window +7