不用写SQL了!用Dify工作流+DeepSeek玩转Doris数据可视化(附ECharts自动生成秘籍)

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

不用写SQL了!用Dify工作流+DeepSeek玩转Doris数据可视化(附ECharts自动生成秘籍)

还记得那些被SQL支配的日子吗?业务部门提一个简单的数据需求,你得先理解他们的“人话”,然后在大脑里翻译成数据库能懂的“机器语言”,再花时间调试语法、验证结果,最后才能把数据交给他们。整个过程就像在两种语言之间来回切换,效率低不说,还容易出错。

但现在,情况完全不一样了。我最近在团队里部署了一套基于Dify工作流、DeepSeek大模型和Apache Doris的ChatBI系统,效果简直让人惊喜。业务同事现在可以直接用自然语言提问:“帮我看看上个月各渠道的获客成本和转化率对比”,系统就能自动生成SQL、查询数据,并返回一个完整的ECharts可视化图表。整个过程不到30秒,而且完全不需要我写一行代码。

这种“提问即得图表”的体验,彻底改变了我们团队的数据协作方式。今天我就来分享这套方案的核心配置技巧,特别是如何让DeepSeek自动选择最佳图表类型、如何处理各种数据异常场景,以及如何优化整个“提问→图表”的端到端流程。

1. 重新理解ChatBI:从“翻译官”到“数据对话伙伴”

在深入技术细节之前,我们先要搞清楚ChatBI到底是什么。很多人把它简单理解为“用自然语言生成SQL”,但这只是冰山一角。真正的ChatBI应该是一个完整的数据对话系统,它包含四个关键层次:

第一层:语义理解与意图识别 当用户说“帮我看看最近卖得好的产品”时,系统需要理解:

  • “最近”具体指什么时间范围?(近7天?近30天?)
  • “卖得好”用什么指标衡量?(销售额?销量?增长率?)
  • “产品”在数据库里对应哪个表、哪些字段?

第二层:SQL生成与优化 这不仅仅是把自然语言翻译成SQL那么简单。好的ChatBI系统需要考虑:

  • 查询性能:避免全表扫描,合理使用索引
  • 语法兼容性:适配特定数据库的方言和函数
  • 安全性:防止SQL注入,控制数据访问权限

第三层:数据结果处理 查询返回的数据可能需要进一步处理:

  • 空值处理:如何展示没有数据的情况
  • 异常值识别:自动发现数据中的异常点
  • 数据格式化:数字、日期、货币等格式统一

第四层:可视化智能推荐 这是最体现“智能”的地方——系统需要根据数据特征自动选择最合适的图表类型:

  • 时间序列数据 → 折线图
  • 分类对比数据 → 柱状图
  • 占比关系数据 → 饼图或环形图
  • 相关性分析 → 散点图

基于这个四层架构,我们来看看Dify工作流如何实现每个环节的自动化。

2. Dify工作流架构设计:六个节点的精妙配合

Dify的工作流编排能力是这套方案的核心。通过拖拽节点和配置参数,我们可以构建一个完整的ChatBI处理流水线。下面是我在实际项目中验证过的最佳节点配置方案:

2.1 Input节点:不只是接收问题

很多人以为Input节点就是简单接收用户输入,其实这里大有文章。我在配置时通常会做三件事:

# 在Input节点的后处理脚本中添加
def preprocess_input(user_query: str) -> dict:
    """
    对用户输入进行预处理
    1. 识别关键业务术语并标准化
    2. 提取时间范围关键词
    3. 标记查询意图类型
    """
    # 标准化业务术语
    term_mapping = {
        "销售额": "sales_amount",
        "销量": "sales_quantity", 
        "客户数": "customer_count",
        "转化率": "conversion_rate"
    }
    
    # 识别时间范围
    time_patterns = {
        "最近一周": "last_7_days",
        "上个月": "last_month",
        "本季度": "current_quarter"
    }
    
    # 识别查询类型
    query_type = "unknown"
    if any(word in user_query for word in ["趋势", "变化", "增长"]):
        query_type = "trend_analysis"
    elif any(word in user_query for word in ["对比", "排名", "最高"]):
        query_type = "comparison"
    elif any(word in user_query for word in ["分布", "占比", "比例"]):
        query_type = "distribution"
    
    return {
        "original_query": user_query,
        "normalized_terms": term_mapping,
        "time_range": time_patterns,
        "query_type": query_type
    }

这个预处理步骤虽然简单,但能显著提升后续节点的处理准确性。比如,当系统知道这是一个“趋势分析”类查询时,就可以提前准备时间序列相关的图表配置。

2.2 Text2SQL节点:DeepSeek的精准调教

这是整个流程中最关键的节点。DeepSeek的能力很强,但如果不加约束,它生成的SQL可能会五花八门。我的经验是,必须给DeepSeek制定明确的“工作规范”。

核心提示词结构设计:

# 角色定义
你是一位精通Apache Doris的数据分析专家,负责将用户的自然语言问题转换为高效、准确的SQL查询语句。

## 可用数据库信息
### 表结构(TPC-H数据集)
1. **customer表** - 客户信息
   - c_custkey: 客户ID(主键)
   - c_name: 客户名称
   - c_address: 客户地址
   - c_nationkey: 国家ID(外键关联nation表)
   - c_phone: 联系电话
   - c_acctbal: 账户余额
   - c_mktsegment: 市场细分
   - c_comment: 备注

2. **orders表** - 订单信息
   - o_orderkey: 订单ID(主键)
   - o_custkey: 客户ID(外键关联customer表)
   - o_orderstatus: 订单状态
   - o_totalprice: 订单总价
   - o_orderdate: 订单日期
   - o_orderpriority: 订单优先级
   - o_clerk: 业务员
   - o_shippriority: 发货优先级
   - o_comment: 备注

## 查询规则
### 必须遵守
1. **仅使用提供的表和字段**,不要假设不存在的字段
2. **SQL必须兼容Doris语法**,特别注意:
   - 使用`DATE_FORMAT()`处理日期格式化
   - 聚合函数使用`SUM()`、`COUNT()`、`AVG()`等
   - 字符串比较区分大小写
3. **输出完整可执行的SQL**,不要包含解释性文字
4. **合理使用JOIN**,确保关联条件正确
5. **包含必要的GROUP BY和ORDER BY**

### 查询优化建议
1. **避免SELECT ***,只选择需要的字段
2. **合理使用LIMIT**,特别是测试查询时
3. **日期范围查询优化**:
   ```sql
   -- 推荐:使用BETWEEN
   WHERE o_orderdate BETWEEN '2024-01-01' AND '2024-03-31'
   
   -- 不推荐:使用函数处理
   WHERE YEAR(o_orderdate) = 2024 AND MONTH(o_orderdate) = 3

示例查询

示例1:基础查询

用户问题:"查询客户数量" 生成SQL:

SELECT COUNT(DISTINCT c_custkey) AS customer_count FROM customer;

示例2:关联查询

用户问题:"查询每个客户的订单总金额" 生成SQL:

SELECT 
    c.c_name AS customer_name,
    SUM(o.o_totalprice) AS total_order_amount
FROM customer c
JOIN orders o ON c.c_custkey = o.o_custkey
GROUP BY c.c_name
ORDER BY total_order_amount DESC
LIMIT 10;

示例3:时间范围查询

用户问题:"查询2024年第一季度的订单趋势" 生成SQL:

SELECT 
    DATE_FORMAT(o_orderdate, '%Y-%m') AS order_month,
    COUNT(o_orderkey) AS order_count,
    SUM(o_totalprice) AS total_sales
FROM orders
WHERE o_orderdate BETWEEN '2024-01-01' AND '2024-03-31'
GROUP BY DATE_FORMAT(o_orderdate, '%Y-%m')
ORDER BY order_month;

输出要求

  1. 只输出一个完整的SQL语句
  2. 不要包含sql代码块标记
  3. 不要添加任何解释或注释
  4. 确保SQL以分号结尾

这个提示词模板有几个关键设计点:
- **明确的约束**:告诉模型什么能做、什么不能做
- **具体的示例**:提供多种查询场景的参考
- **性能优化提示**:引导模型生成高效的SQL
- **输出格式控制**:确保输出干净、可执行

> **注意**:提示词的长度和质量直接影响SQL生成的准确性。我建议至少包含5-7个不同类型的查询示例,覆盖单表查询、多表关联、聚合计算、时间范围过滤等常见场景。

### 2.3 SQL Formatting节点:处理LLM的“坏习惯”

即使有再好的提示词,DeepSeek有时还是会输出一些格式问题。常见的“坏习惯”包括:
- 在SQL前后添加```sql```代码块标记
- 在LIMIT子句后添加解释性文字
- 使用不一致的换行和缩进

我的解决方案是使用一个专门的格式化节点:

```python
import re

def format_sql(raw_sql: str) -> dict:
    """
    清理和格式化LLM生成的SQL
    处理常见问题:
    1. 移除代码块标记
    2. 清理多余的解释文字
    3. 标准化换行和缩进
    4. 验证SQL基本语法
    """
    
    # 第一步:移除代码块标记
    cleaned = raw_sql.strip()
    if cleaned.startswith('```sql'):
        cleaned = cleaned[6:]
    if cleaned.startswith('```'):
        cleaned = cleaned[3:]
    if cleaned.endswith('```'):
        cleaned = cleaned[:-3]
    
    # 第二步:移除所有换行符(Doris执行时不需要)
    cleaned = cleaned.replace('\n', ' ').replace('\r', ' ')
    
    # 第三步:清理LIMIT子句后的多余内容
    # 匹配模式:LIMIT 数字; 后面的任何文字
    cleaned = re.sub(r'(LIMIT\s+\d+\s*;).*', r'\1', cleaned, flags=re.IGNORECASE)
    
    # 第四步:移除SQL中的注释
    cleaned = re.sub(r'--.*?(?=\n|$)', '', cleaned)  # 单行注释
    cleaned = re.sub(r'/\*.*?\*/', '', cleaned, flags=re.DOTALL)  # 多行注释
    
    # 第五步:标准化空格(多个空格变一个)
    cleaned = re.sub(r'\s+', ' ', cleaned).strip()
    
    # 第六步:确保以分号结尾
    if not cleaned.endswith(';'):
        cleaned += ';'
    
    # 第七步:基本语法验证
    sql_upper = cleaned.upper()
    if 'SELECT' not in sql_upper:
        raise ValueError("生成的SQL不包含SELECT语句")
    
    # 检查是否有明显的语法错误
    forbidden_patterns = [
        r'DROP\s+TABLE',
        r'TRUNCATE\s+TABLE',
        r'DELETE\s+FROM',
        r'UPDATE\s+\w+\s+SET'
    ]
    
    for pattern in forbidden_patterns:
        if re.search(pattern, sql_upper, re.IGNORECASE):
            raise ValueError(f"SQL包含危险操作: {pattern}")
    
    return {
        "formatted_sql": cleaned,
        "original_length": len(raw_sql),
        "formatted_length": len(cleaned)
    }

def main(text2sql_output: str) -> dict:
    try:
        result = format_sql(text2sql_output)
        return {"text2sql": result["formatted_sql"]}
    except Exception as e:
        # 如果格式化失败,返回原始SQL并记录错误
        return {
            "text2sql": text2sql_output,
            "formatting_error": str(e),
            "warning": "SQL格式化失败,使用原始SQL"
        }

这个格式化脚本做了几件重要的事:

  1. 安全性检查:阻止DROP、DELETE等危险操作
  2. 语法验证:确保基本的SQL结构正确
  3. 错误处理:即使格式化失败,也能继续流程

2.4 Doris Execute节点:数据库连接的最佳实践

配置Doris连接时,很多人会忽略连接池和超时设置。以下是我的推荐配置:

参数 推荐值 说明
连接字符串

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值