不用写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;
输出要求
- 只输出一个完整的SQL语句
- 不要包含
sql代码块标记 - 不要添加任何解释或注释
- 确保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"
}
这个格式化脚本做了几件重要的事:
- 安全性检查:阻止DROP、DELETE等危险操作
- 语法验证:确保基本的SQL结构正确
- 错误处理:即使格式化失败,也能继续流程
2.4 Doris Execute节点:数据库连接的最佳实践
配置Doris连接时,很多人会忽略连接池和超时设置。以下是我的推荐配置:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 连接字符串 |

&spm=1001.2101.3001.5002&articleId=154856635&d=1&t=3&u=6b41dbbbd4d7481b8f29d113c759f4c1)
1474

被折叠的 条评论
为什么被折叠?



