first commit
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
# Text2SQL Agent 环境配置示例
|
||||||
|
# 复制为 .env 并填入实际值
|
||||||
|
|
||||||
|
# ========== DeepSeek API 配置 ==========
|
||||||
|
DEEPSEEK_API_KEY=sk-2e3cc9f79a2c40e9a6b7c960d8c090d9
|
||||||
|
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||||
|
|
||||||
|
# 模型配置
|
||||||
|
# 主生成模型
|
||||||
|
MODEL_PRIMARY=deepseek-reasoner
|
||||||
|
TEMPERATURE=0 # 确定性模式:每次生成相同结果
|
||||||
|
MAX_TOKENS=4096
|
||||||
|
# 生成与校验默认 SQL Server(T-SQL);可改为 mysql / postgresql 等
|
||||||
|
TEXT2SQL_DIALECT=sqlserver
|
||||||
|
|
||||||
|
# ========== Embedding 配置 ==========
|
||||||
|
# false = 使用兼容 OpenAI 的远程 /v1/embeddings(优先 ModelScope,未配置 MODELSCOPE_* 时用 DashScope)
|
||||||
|
USE_LOCAL_EMBEDDING=false
|
||||||
|
# ModelScope 推理 API:https://www.modelscope.cn/docs/inference-api
|
||||||
|
# MODELSCOPE_API_KEY=ms-b95f3233-4797-4efd-90e6-b3728743571d
|
||||||
|
# MODELSCOPE_BASE_URL=https://api-inference.modelscope.cn/v1
|
||||||
|
# MODELSCOPE_EMBEDDING_MODEL=Qwen/Qwen3-Embedding-8B
|
||||||
|
# # 单次请求最大条数,按服务端限制调整
|
||||||
|
# MODELSCOPE_EMBEDDING_MAX_BATCH=32
|
||||||
|
|
||||||
|
# 兼容 OpenAI 的网关:地址含 /v1(若网关根路径即 OpenAI API 则按实际填写)
|
||||||
|
OPENAI_BASE_URL=http://113.192.49.54:9080/v1
|
||||||
|
OPENAI_API_KEY=sk-proj-FEb2ChHZK5Llm0tBkmNIT3BlbkFJbu0b0zQpTW8762yD6HDv
|
||||||
|
OPENAI_EMBEDDING_MODEL=text-embedding-ada-002
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Schema 配置 ==========
|
||||||
|
SCHEMA_DIR=./data/schemas
|
||||||
|
SCHEMA_FILE=${SCHEMA_DIR}/G3SB_MCDataDictionary_table_structure.json
|
||||||
|
|
||||||
|
# ========== 向量数据库配置 ==========
|
||||||
|
VECTOR_DB_PATH=./data/embeddings/chroma
|
||||||
|
|
||||||
|
# ========== 验证配置 ==========
|
||||||
|
ENABLE_SQL_VALIDATION=true
|
||||||
|
BLOCK_DANGEROUS_SQL=true
|
||||||
|
|
||||||
|
# ========== 重试配置 ==========
|
||||||
|
MAX_RETRY=1 # 确定性模式:只尝试一次,不重试
|
||||||
|
RETRY_DELAY=1.0
|
||||||
|
|
||||||
|
# ========== Few-shot 配置 ==========
|
||||||
|
# 是否启用经验数据集few-shot增强
|
||||||
|
FEWSHOT_ENABLED=true
|
||||||
|
# 每次使用的示例数量
|
||||||
|
FEWSHOT_TOP_K=3
|
||||||
|
# 示例最低评分(1-10)
|
||||||
|
FEWSHOT_MIN_RATING=7
|
||||||
|
# 经验数据集路径
|
||||||
|
FEWSHOT_DATA_PATH=./data/experiences/all_samples.jsonl
|
||||||
|
|
||||||
|
# ========== 日志配置 ==========
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_FILE=./logs/text2sql.log
|
||||||
@@ -0,0 +1,736 @@
|
|||||||
|
# Text2SQL 多智能体系统
|
||||||
|
|
||||||
|
基于 **CAMEL AI 框架**、**DeepSeek 大模型** 和 **经验数据集 Few-Shot 增强** 的专业 Text-to-SQL 生成系统,专为证券经纪业务领域优化。
|
||||||
|
|
||||||
|
## 🚀 核心特性
|
||||||
|
|
||||||
|
- **🤖 多Agent协作流水线**:Schema Linker → SQL Generator → Validator 三阶段协作
|
||||||
|
- **🎯 高精度表检索**:向量检索(Qwen3-Embedding)+ LLM精筛,从 250+ 张表中精准定位相关表
|
||||||
|
- **💡 经验数据集 Few-Shot**:基于 50 条高质量样例的语义检索,动态注入相似示例提升准确率
|
||||||
|
- **🔒 双重验证机制**:程序语法验证 + LLM语义验证,确保 SQL 正确性
|
||||||
|
- **🏦 金融领域深度优化**:针对证券经纪业务(账户、持仓、现金、结算)定制 Prompt 和推断规则
|
||||||
|
- **✍️ 确定性模式**:Temperature=0 + 单次重试,保证相同输入产生稳定输出
|
||||||
|
- **🌐 多方言支持**:T-SQL(SQL Server)/ MySQL / PostgreSQL / SQLite
|
||||||
|
- **🔄 自修正循环**:验证失败自动重试(最多 2 次),智能修正 SQL
|
||||||
|
|
||||||
|
## 📊 系统架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 用户自然语言问题 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 阶段1: Schema Linker Agent │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 1.1 向量检索粗筛(ChromaDB + Qwen3-Embedding) │ │
|
||||||
|
│ │ - 检索 Top 20 候选表(相似度阈值 0.1) │ │
|
||||||
|
│ │ └─ 支持对手方/经纪商问题特殊处理(强制纳入 Broker 表) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 1.2 LLM 精筛 │ │
|
||||||
|
│ │ - 基于表注释选择最多 5 张相关表 │ │
|
||||||
|
│ │ └─ 输出:relevant_tables + reasoning(JSON格式) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 1.3 外键扩展 │ │
|
||||||
|
│ │ - 自动添加被引用表(FK → ref_table) │ │
|
||||||
|
│ │ - 自动添加引用表(反向外键,确保 LEFT JOIN 完整性) │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ↓ 精简 Schema 子集 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 阶段2: SQL Generator Agent(集成 Few-Shot 增强) │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 2.1 语义检索相似示例 │ │
|
||||||
|
│ │ - 从 50 条经验数据集中检索 Top-K 高评分示例 │ │
|
||||||
|
│ │ - 支持语义匹配(SentenceTransformer)或关键词回退 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 2.2 注入 Prompt │ │
|
||||||
|
│ │ "参考以下相似示例的SQL编写风格:<examples>" │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 2.3 生成 T-SQL │ │
|
||||||
|
│ │ - 遵循标准版式(SELECT 每列一行、4 空格缩进) │ │
|
||||||
|
│ │ - 正确使用方括号 [TableName]、[ColumnName] │ │
|
||||||
|
│ │ - 日期函数:CAST(GETDATE() AS DATE)、DATEADD │ │
|
||||||
|
│ │ - 字符串连接:+ 运算符 │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ↓ SQL 语句 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 阶段3: Validator Agent │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 3.1 程序验证(确定性规则) │ │
|
||||||
|
│ │ ✓ SQL 语法正确(sqlglot 解析) │ │
|
||||||
|
│ │ ✓ Schema 一致性(表/字段存在性) │ │
|
||||||
|
│ │ ✓ 别名解析正确(JOIN 别名匹配) │ │
|
||||||
|
│ │ ✓ 无危险操作(DROP/DELETE/UPDATE 等拦截) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 3.2 LLM 语义验证 │ │
|
||||||
|
│ │ ✓ 业务逻辑合理性 │ │
|
||||||
|
│ │ ✓ 聚合函数使用正确(COUNT/SUM/AVG 等) │ │
|
||||||
|
│ │ ✓ WHERE 条件无矛盾 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 3.3 结果判断 │ │
|
||||||
|
│ │ ✅ 通过 → 返回最终 SQL │ │
|
||||||
|
│ │ ❌ 失败 → 重试(最多 MAX_RETRY 次) │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 技术栈
|
||||||
|
|
||||||
|
| 组件 | 技术选型 | 版本/说明 |
|
||||||
|
|------|---------|-----------|
|
||||||
|
| Agent 框架 | **CAMEL AI** | >= 0.2.0,角色扮演、消息通信 |
|
||||||
|
| LLM 模型 | **DeepSeek-chat** | 国产大模型,代码生成能力强 |
|
||||||
|
| Embedding | **Qwen3-Embedding-0.6B** | 阿里云 6 亿参数,本地免费部署 |
|
||||||
|
| 向量数据库 | **ChromaDB** | 本地轻量,持久化 Schema 索引 |
|
||||||
|
| SQL 解析 | **sqlglot** | >= 20.0.0,多方言 AST 转换与验证 |
|
||||||
|
| 配置管理 | **Pydantic Settings** | 类型安全的环境变量管理 |
|
||||||
|
| 数据处理 | **pandas / numpy** | 结构化数据操作 |
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
text2sql_agent_camel/
|
||||||
|
├── agents/ # Agent 层
|
||||||
|
│ ├── orchestrator.py # 主编排器(协调 Schema Linker → Generator → Validator)
|
||||||
|
│ ├── schema_linker.py # 表筛选 Agent(粗筛 + LLM 精筛 + 外键扩展)
|
||||||
|
│ ├── sql_generator.py # SQL 生成 Agent(集成 Few-Shot 注入)
|
||||||
|
│ └── validator.py # SQL 验证 Agent(程序 + LLM 双重验证)
|
||||||
|
├── config/
|
||||||
|
│ ├── prompts.py # 系统与用户 Prompt 模板
|
||||||
|
│ └── settings.py # 配置类(基于 Pydantic)
|
||||||
|
├── schema/ # Schema 管理层
|
||||||
|
│ ├── models.py # 数据模型(Table, Column, ForeignKey, DatabaseSchema)
|
||||||
|
│ ├── loader.py # Schema 加载器(JSON / G3SB 格式解析)
|
||||||
|
│ ├── manager.py # Schema 管理器(查询、过滤、转字符串)
|
||||||
|
│ └── indexer.py # 向量索引构建器(ChromaDB 封装)
|
||||||
|
├── llm/
|
||||||
|
│ └── deepseek_client.py # DeepSeek API 客户端(chat / validate_sql / select_tables)
|
||||||
|
├── utils/
|
||||||
|
│ ├── embedding.py # Qwen3-Embedding 封装(本地 / 远程 API)
|
||||||
|
│ ├── sql_parser.py # sqlglot 工具(语法验证、方言转换、规范化)
|
||||||
|
│ ├── validators.py # 验证逻辑(危险操作检测、完整验证流水线)
|
||||||
|
│ └── fewshot_selector.py # Few-Shot 示例选择器(语义/关键词检索)
|
||||||
|
├── data/
|
||||||
|
│ ├── schemas/ # Schema JSON 文件
|
||||||
|
│ │ ├── G3SB_MCDataDictionary_table_structure.json # 表结构
|
||||||
|
│ │ └── G3SB_MCDataDictionary_table_meta.json # 表注释元数据
|
||||||
|
│ ├── experiences/ # 经验数据集(Few-Shot 数据源)
|
||||||
|
│ │ ├── all_samples.jsonl # 50 条样本(全部)
|
||||||
|
│ │ ├── high_rating_samples.jsonl # 30 条高质量样本(rating ≥ 8)
|
||||||
|
│ │ ├── difficulty_easy.jsonl / medium.jsonl / hard.jsonl
|
||||||
|
│ │ ├── fewshot_examples.md # Markdown 格式示例
|
||||||
|
│ │ └── by_tag/ # 按标签分类(20 类)
|
||||||
|
│ ├── embeddings/ # ChromaDB 向量索引持久化目录
|
||||||
|
│ └── models/ # 本地 Embedding 模型权重
|
||||||
|
├── scripts/
|
||||||
|
│ ├── parse_examples.py # 解析 Example_text2sql.md → 结构化数据集
|
||||||
|
│ ├── integrate_fewshot.py # Few-Shot 集成指南与测试
|
||||||
|
│ └── patch_fewshot.py # 自动化修改 orchestrator.py(已内置)
|
||||||
|
├── logs/
|
||||||
|
│ └── text2sql.log # 运行日志(默认)
|
||||||
|
├── .env # 环境变量配置(API Key、路径、参数)
|
||||||
|
├── main.py # CLI 入口(单次查询 / 批量 / 交互模式)
|
||||||
|
├── pyproject.toml # 项目元数据与依赖
|
||||||
|
├── README.md # 本文档
|
||||||
|
└── SQL_GENERATION_LOGIC.md # SQL 生成逻辑详解(流程图、示例)
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ 快速开始
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
- Python **3.10+**
|
||||||
|
- DeepSeek API Key(申请地址:https://platform.deepseek.com/api_keys)
|
||||||
|
- (可选)本地 Embedding 模型权重(或配置远程 Embedding API)
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 uv(推荐,速度快)
|
||||||
|
uv pip install -e .
|
||||||
|
|
||||||
|
# 或使用 pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
依赖清单(pyproject.toml):
|
||||||
|
```toml
|
||||||
|
dependencies = [
|
||||||
|
"camel-ai>=0.2.0",
|
||||||
|
"openai>=1.0.0",
|
||||||
|
"chromadb>=0.4.0",
|
||||||
|
"transformers>=4.36.0",
|
||||||
|
"torch>=2.0.0",
|
||||||
|
"sqlglot>=20.0.0",
|
||||||
|
"pydantic>=2.0.0",
|
||||||
|
"sentence-transformers>=2.2.0", # Few-Shot 语义检索
|
||||||
|
"python-dotenv>=1.0.0",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 复制模板并编辑
|
||||||
|
cp .env .env.local # 或直接编辑 .env
|
||||||
|
|
||||||
|
# 必填项
|
||||||
|
DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||||
|
|
||||||
|
# 可选:使用本地 Embedding(无需网络,但需下载模型)
|
||||||
|
USE_LOCAL_EMBEDDING=true
|
||||||
|
EMBEDDING_MODEL_PATH=./data/models/Qwen3-Embedding-0.6B
|
||||||
|
|
||||||
|
# 或使用远程 Embedding API(无需本地模型)
|
||||||
|
USE_LOCAL_EMBEDDING=false
|
||||||
|
MODELSCOPE_API_KEY=ms-xxxxxxxx
|
||||||
|
MODELSCOPE_BASE_URL=https://api-inference.modelscope.cn/v1
|
||||||
|
MODELSCOPE_EMBEDDING_MODEL=Qwen/Qwen3-Embedding-8B
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 准备 Schema 文件
|
||||||
|
|
||||||
|
将 G3SB 系统的 Schema 导出为 JSON 格式,并放置到 `data/schemas/` 目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 目录结构
|
||||||
|
data/schemas/
|
||||||
|
├── G3SB_MCDataDictionary_table_structure.json # 必需:表结构定义
|
||||||
|
└── G3SB_MCDataDictionary_table_meta.json # 可选:表注释(用于 LLM 理解)
|
||||||
|
```
|
||||||
|
|
||||||
|
Schema JSON 格式示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"database": "G3SB_MC",
|
||||||
|
"tables": [
|
||||||
|
{
|
||||||
|
"name": "MCAccount",
|
||||||
|
"comment": "账户主表",
|
||||||
|
"columns": [
|
||||||
|
{"name": "AccountID", "type": "NCHAR(16)", "comment": "账户唯一标识", "nullable": false, "primary_key": true},
|
||||||
|
{"name": "AccountName", "type": "NVARCHAR(100)", "comment": "账户名称"},
|
||||||
|
{"name": "State", "type": "CHAR(1)", "comment": "状态: A=Active, D=Deleted, X=SameDayDeleted"},
|
||||||
|
{"name": "OpenDate", "type": "DATE", "comment": "开户日期"}
|
||||||
|
],
|
||||||
|
"foreign_keys": [
|
||||||
|
{"columns": ["AEID"], "ref_table": "MCUser", "ref_columns": ["UserID"]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 构建向量索引(首次运行)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 自动构建:首次查询时会自动创建
|
||||||
|
python main.py "测试查询"
|
||||||
|
|
||||||
|
# 或手动预构建(推荐,加速首次查询)
|
||||||
|
python -c "
|
||||||
|
from agents.orchestrator import Text2SQLOrchestrator
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
|
||||||
|
schema = SchemaManager.load_from_json('./data/schemas/G3SB_MCDataDictionary_table_structure.json')
|
||||||
|
orch = Text2SQLOrchestrator(schema_mgr=schema, deepseek_api_key='sk-xxx')
|
||||||
|
orch.build_vector_index(force_rebuild=True)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 运行演示
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 单次查询(默认 T-SQL / SQL Server)
|
||||||
|
python main.py "查询2024年1月的销售额"
|
||||||
|
|
||||||
|
# 交互模式
|
||||||
|
python main.py --interactive
|
||||||
|
|
||||||
|
# 批量查询(每行一个问题)
|
||||||
|
python main.py --batch queries.txt
|
||||||
|
|
||||||
|
# 指定 Schema 和方言
|
||||||
|
python main.py "统计每个市场的未结算交易量" \
|
||||||
|
--schema ./data/schemas/custom_schema.json \
|
||||||
|
--dialect postgresql
|
||||||
|
|
||||||
|
# 禁用 Few-Shot(对比实验)
|
||||||
|
python main.py "查询活跃账户数" --no-fewshot
|
||||||
|
|
||||||
|
# 调整 Few-Shot 参数
|
||||||
|
python main.py "查询2024年1月的销售额" \
|
||||||
|
--fewshot-top-k 5 \
|
||||||
|
--fewshot-min-rating 8
|
||||||
|
|
||||||
|
# 详细日志(调试用)
|
||||||
|
python main.py "查询持仓余额" --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 使用示例
|
||||||
|
|
||||||
|
### Python API
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
from agents.orchestrator import Text2SQLOrchestrator
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
from llm.deepseek_client import DeepSeekConfig
|
||||||
|
|
||||||
|
# 1. 加载 Schema
|
||||||
|
schema_mgr = SchemaManager.load_from_json(
|
||||||
|
"./data/schemas/G3SB_MCDataDictionary_table_structure.json",
|
||||||
|
g3sb_meta_path="./data/schemas/G3SB_MCDataDictionary_table_meta.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. 配置 DeepSeek
|
||||||
|
config = DeepSeekConfig(
|
||||||
|
api_key=os.getenv("DEEPSEEK_API_KEY"),
|
||||||
|
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
|
||||||
|
model_name="deepseek-chat",
|
||||||
|
temperature=0, # 确定性模式
|
||||||
|
max_tokens=4096,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. 初始化编排器
|
||||||
|
orchestrator = Text2SQLOrchestrator(
|
||||||
|
schema_manager=schema_mgr,
|
||||||
|
deepseek_config=config,
|
||||||
|
max_retry=1, # 单次尝试(确定性)
|
||||||
|
use_vector_search=True, # 启用向量检索
|
||||||
|
fewshot_enabled=True, # 启用 Few-Shot
|
||||||
|
fewshot_top_k=3, # 每次使用 3 个示例
|
||||||
|
fewshot_min_rating=7, # 最低评分过滤
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. 生成 SQL
|
||||||
|
result = orchestrator.generate(
|
||||||
|
question="查询2024年1月的销售额最高的前10个产品",
|
||||||
|
dialect="tsql",
|
||||||
|
top_k_candidates=20
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. 查看结果
|
||||||
|
print(f"SQL: {result.sql}")
|
||||||
|
print(f"有效: {result.valid}")
|
||||||
|
print(f"使用表: {result.tables_used}")
|
||||||
|
print(f"尝试次数: {result.attempts}")
|
||||||
|
print(f"推理过程: {result.reasoning}")
|
||||||
|
if result.warnings:
|
||||||
|
print(f"警告: {result.warnings}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 典型查询与期望输出
|
||||||
|
|
||||||
|
| 用户问题 | 生成 SQL(T-SQL) | 关键特性 |
|
||||||
|
|---------|------------------|---------|
|
||||||
|
| "查询2024年1月的销售额" | `WHERE TradeDate >= '2024-01-01' AND TradeDate < '2024-02-01'` | 日期范围推断 |
|
||||||
|
| "统计所有活跃账户数" | `WHERE State = 'A'` | 状态值推断 |
|
||||||
|
| "查询每个对手方的未结算交易总额" | `LEFT JOIN MCBroker ... GROUP BY ... ORDER BY SUM(...) DESC` | 多表 JOIN + 聚合 + 排序 |
|
||||||
|
| "列出今天的所有 Margin Call" | `WHERE TradeDate >= CAST(GETDATE() AS DATE)` | 相对日期处理 |
|
||||||
|
| "查询账户 ACC001 的持仓" | `WHERE AccountID = 'ACC001'` | 精确匹配 |
|
||||||
|
|
||||||
|
## 🔧 核心配置
|
||||||
|
|
||||||
|
### 环境变量(`.env`)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# ========== DeepSeek API ==========
|
||||||
|
DEEPSEEK_API_KEY=sk-xxxxxxxx
|
||||||
|
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||||
|
|
||||||
|
# ========== 模型参数 ==========
|
||||||
|
TEMPERATURE=0 # 0=确定性,0.3=推荐平衡值
|
||||||
|
MAX_TOKENS=4096
|
||||||
|
TEXT2SQL_DIALECT=tsql # tsql / mysql / postgresql / sqlite
|
||||||
|
|
||||||
|
# ========== 检索配置 ==========
|
||||||
|
MAX_RETRY=1 # 重试次数(1=仅首次,2=允许一次修正)
|
||||||
|
FEWSHOT_ENABLED=true # 是否启用 Few-Shot
|
||||||
|
FEWSHOT_TOP_K=3 # 每次注入的示例数量
|
||||||
|
FEWSHOT_MIN_RATING=7 # 示例最低评分(1-10)
|
||||||
|
|
||||||
|
# ========== Embedding ==========
|
||||||
|
USE_LOCAL_EMBEDDING=false # true=本地模型,false=远程API
|
||||||
|
# 本地模型路径
|
||||||
|
EMBEDDING_MODEL_PATH=./data/models/Qwen3-Embedding-0.6B
|
||||||
|
# 远程 API(ModelScope)
|
||||||
|
MODELSCOPE_API_KEY=ms-xxxx
|
||||||
|
MODELSCOPE_BASE_URL=https://api-inference.modelscope.cn/v1
|
||||||
|
MODELSCOPE_EMBEDDING_MODEL=Qwen/Qwen3-Embedding-8B
|
||||||
|
|
||||||
|
# ========== Schema ==========
|
||||||
|
SCHEMA_DIR=./data/schemas
|
||||||
|
SCHEMA_FILE=${SCHEMA_DIR}/G3SB_MCDataDictionary_table_structure.json
|
||||||
|
|
||||||
|
# ========== 向量数据库 ==========
|
||||||
|
VECTOR_DB_PATH=./data/embeddings/chroma
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI 参数速查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python main.py [问题] [选项]
|
||||||
|
|
||||||
|
# 核心选项
|
||||||
|
--schema PATH Schema 文件路径
|
||||||
|
--dialect {mysql,postgresql,sqlite,tsql} 目标 SQL 方言
|
||||||
|
--api-key KEY DeepSeek API Key(优先于环境变量)
|
||||||
|
|
||||||
|
# 生成控制
|
||||||
|
--temperature FLOAT 采样温度(0-1,默认 0.3)
|
||||||
|
--max-tokens INT LLM 最大输出 token(默认 4096)
|
||||||
|
--max-retry INT 最大重试次数(默认 2)
|
||||||
|
|
||||||
|
# Few-Shot
|
||||||
|
--no-fewshot 禁用经验数据集增强
|
||||||
|
--fewshot-top-k N 每次使用的示例数量(默认 3)
|
||||||
|
--fewshot-min-rating N 示例最低评分(默认 7)
|
||||||
|
|
||||||
|
# 向量检索
|
||||||
|
--no-vector-search 禁用向量检索(使用全部表)
|
||||||
|
--embedding-model PATH Embedding 模型路径
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
--interactive, -i 交互模式
|
||||||
|
--batch FILE 批量文件路径
|
||||||
|
--verbose, -v 详细日志
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 性能与调优
|
||||||
|
|
||||||
|
### 影响因素与优化策略
|
||||||
|
|
||||||
|
| 维度 | 影响因素 | 优化建议 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| **召回率** | 向量阈值 `score_threshold=0.1` | 降低至 0.05 可提高召回,但可能引入噪声 |
|
||||||
|
| **准确率** | Few-Shot 示例质量 | 补充更多高评分(≥8)样本,按业务域分类 |
|
||||||
|
| **响应速度** | 候选表数 `top_k_candidates=20` | 减少至 10-15,牺牲召回保速度 |
|
||||||
|
| **成本** | 重试次数 `max_retry` | 确定性模式下设为 1,避免重复调用 |
|
||||||
|
| **稳定性** | `TEMPERATURE` | 生产环境设为 0,确保结果可重现 |
|
||||||
|
|
||||||
|
### 典型性能数据(实测参考)
|
||||||
|
|
||||||
|
| 查询类型 | 表数 | 响应时间 | 准确率 |
|
||||||
|
|---------|------|---------|-------|
|
||||||
|
| 单表简单查询 | 1-2 | 8-12s | >90% |
|
||||||
|
| 2-3 表 JOIN | 2-3 | 12-18s | >85% |
|
||||||
|
| 聚合 + GROUP BY | 1-2 | 15-22s | >80% |
|
||||||
|
| 复杂多表(4+) | 4-6 | 25-35s | >70% |
|
||||||
|
|
||||||
|
*注:时间包含向量检索(~2s)+ LLM 生成(~5-10s)+ 验证(~1-3s),网络条件不同可能有差异。*
|
||||||
|
|
||||||
|
## 🧪 测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装测试依赖
|
||||||
|
pip install pytest pytest-cov
|
||||||
|
|
||||||
|
# 运行单元测试
|
||||||
|
pytest tests/unit/ -v
|
||||||
|
|
||||||
|
# 运行集成测试
|
||||||
|
pytest tests/integration/ -v --tb=short
|
||||||
|
|
||||||
|
# 覆盖率报告
|
||||||
|
pytest --cov=agents --cov=utils --cov-report=html
|
||||||
|
```
|
||||||
|
|
||||||
|
测试数据集:`data/Example/Example_text2sql.md` 中的 50 个标注样本。
|
||||||
|
|
||||||
|
## 📚 经验数据集(Few-Shot 数据源)
|
||||||
|
|
||||||
|
### 数据来源
|
||||||
|
`data/Example/Example_text2sql.md` — 领域专家标注的 50 个 Q&A 对,包含:
|
||||||
|
- 中英双语问题
|
||||||
|
- 专家级 T-SQL 实现
|
||||||
|
- SQL 编写理由(explanation)
|
||||||
|
- 人工评分(1-10 分,平均 7.6 分)
|
||||||
|
- 业务标签(filter, join, aggregation, broker, account...)
|
||||||
|
- 难度等级(easy / medium / hard)
|
||||||
|
|
||||||
|
### 生成数据集
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 解析 MD 文件,生成结构化数据
|
||||||
|
python scripts/parse_examples.py
|
||||||
|
|
||||||
|
# 输出文件
|
||||||
|
data/experiences/
|
||||||
|
├── all_samples.jsonl # 50 条完整样本
|
||||||
|
├── high_rating_samples.jsonl # 30 条高质量样本(rating ≥ 8)
|
||||||
|
├── difficulty_easy.jsonl # 27 条简单问题
|
||||||
|
├── difficulty_medium.jsonl # 6 条中等问题
|
||||||
|
├── difficulty_hard.jsonl # 17 条复杂问题
|
||||||
|
├── fewshot_examples.md # Markdown 格式,便于阅读
|
||||||
|
└── by_tag/ # 按标签分类(20 个目录)
|
||||||
|
├── filter.jsonl
|
||||||
|
├── join.jsonl
|
||||||
|
├── aggregation.jsonl
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Few-Shot 检索逻辑
|
||||||
|
|
||||||
|
1. **语义匹配优先**:使用 SentenceTransformer 计算问题相似度(需下载模型)
|
||||||
|
2. **关键词回退**:无模型时使用中文 2-gram + 英文 token 匹配
|
||||||
|
3. **过滤条件**:
|
||||||
|
- `min_rating`:示例评分 ≥ 阈值
|
||||||
|
- `max_difficulty`:示例难度不超过指定等级
|
||||||
|
- `required_tags`:必须包含的标签(可选)
|
||||||
|
4. **排序**:相似度降序,返回 Top-K
|
||||||
|
|
||||||
|
## 🏦 领域知识:证券经纪业务(G3SB 系统)
|
||||||
|
|
||||||
|
### 数据库概览
|
||||||
|
|
||||||
|
| 模块 | 表前缀 | 说明 | 表数 |
|
||||||
|
|------|-------|------|------|
|
||||||
|
| Master Client | `MC*` | 客户、账户、产品主数据 | ~80 |
|
||||||
|
| Broker Cash | `BC*` | 资金余额、日终结算 | ~40 |
|
||||||
|
| History Cash | `HC*` | 资金历史流水(时序) | ~60 |
|
||||||
|
| Hong Kong Stock | `HXSB*` | 港股业务 | ~30 |
|
||||||
|
| Vietnam Market | `HCVSD*` | 越南市场 | ~15 |
|
||||||
|
| Reporting | `VSB*`, `VSBRpt*` | 报表视图(预计算) | ~120+ |
|
||||||
|
|
||||||
|
### 关键字段约定
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 | 示例值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| `AccountID` | NCHAR(16) | 账户唯一标识 | `'ACC0001234567'` |
|
||||||
|
| `MarketID` | NCHAR(4) | 市场代码 | `'HK'` / `'VN'` / `'CN'` |
|
||||||
|
| `InstrumentID` | NCHAR(16) | 证券代码 | `'00700.HK'` |
|
||||||
|
| `CurrencyID` | NCHAR(3) | 币种 | `'USD'` / `'HKD'` / `'CNY'` |
|
||||||
|
| `ValueDate` | DATE | 价值日期(结算日) | `'2024-01-15'` |
|
||||||
|
| `TradeDate` | DATE | 交易日期 | `'2024-01-14'` |
|
||||||
|
| `BusinessDate` | DATE | 业务日期(当前处理日) | `'2024-01-15'` |
|
||||||
|
| `Settled` | DECIMAL | 已结算余额 | `123456.78` |
|
||||||
|
| `State` | CHAR(1) | 记录状态 | `A`=活跃, `D`=已删除, `X`=当日删除 |
|
||||||
|
| `SettleStatus` | CHAR(1) | 结算状态 | `U`=未结算, `S`=已结算 |
|
||||||
|
|
||||||
|
### 常见业务查询模式
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. 查询未结算交易(按对手方分组)
|
||||||
|
SELECT
|
||||||
|
b.BrokerID,
|
||||||
|
m.Name AS BrokerName,
|
||||||
|
COUNT(*) AS UnsettledTradeCount,
|
||||||
|
SUM(b.SettleAmount) AS TotalUnsettledAmount
|
||||||
|
FROM TSBBrokerContract b
|
||||||
|
LEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID
|
||||||
|
WHERE b.SettleStatus = 'U' -- 未结算
|
||||||
|
AND b.CashSettleDate <= CAST(GETDATE() AS DATE) -- 截至今日
|
||||||
|
GROUP BY b.BrokerID, m.Name
|
||||||
|
ORDER BY TotalUnsettledAmount DESC;
|
||||||
|
|
||||||
|
-- 2. 查询账户资金(多币种)
|
||||||
|
SELECT
|
||||||
|
AccountID,
|
||||||
|
CurrencyID,
|
||||||
|
SUM(CASE WHEN Settled > 0 THEN Settled ELSE 0 END) AS CreditBalance,
|
||||||
|
SUM(CASE WHEN Settled < 0 THEN Settled ELSE 0 END) AS DebitBalance
|
||||||
|
FROM BCAccountCash
|
||||||
|
WHERE AccountID = 'ACC001'
|
||||||
|
GROUP BY AccountID, CurrencyID;
|
||||||
|
|
||||||
|
-- 3. 查询持仓(包含产品名称)
|
||||||
|
SELECT
|
||||||
|
i.InstrumentID,
|
||||||
|
p.InstrumentName,
|
||||||
|
i.Settled AS Quantity,
|
||||||
|
i.MarketPrice AS UnitPrice,
|
||||||
|
i.Settled * i.MarketPrice AS MarketValue
|
||||||
|
FROM MCAccountInstrument i
|
||||||
|
LEFT JOIN MCProduct p ON i.InstrumentID = p.InstrumentID
|
||||||
|
WHERE i.AccountID = 'ACC001'
|
||||||
|
AND i.Settled <> 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 问题排查
|
||||||
|
|
||||||
|
### 1. 向量索引为空 / 检索不准
|
||||||
|
|
||||||
|
**现象**:`schema.indexer: 索引为空,正在构建...` 每次都重建,或返回表不相关。
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
```bash
|
||||||
|
# 检查索引文件
|
||||||
|
ls -la data/embeddings/chroma/
|
||||||
|
|
||||||
|
# 强制重建索引
|
||||||
|
python -c "
|
||||||
|
from agents.orchestrator import Text2SQLOrchestrator
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
|
||||||
|
s = SchemaManager.load_from_json('./data/schemas/...json')
|
||||||
|
orch = Text2SQLOrchestrator(schema_mgr=s, deepseek_api_key='sk-xxx')
|
||||||
|
orch.build_vector_index(force_rebuild=True)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**:Embedding 模型未加载成功,或 ChromaDB 文件损坏。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Few-Shot 未生效
|
||||||
|
|
||||||
|
**现象**:日志中未见 "已注入 X 个 few-shot 示例"。
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
```bash
|
||||||
|
# 检查数据文件
|
||||||
|
python -c "
|
||||||
|
from utils.fewshot_selector import FewShotSelector
|
||||||
|
s = FewShotSelector('./data/experiences/all_samples.jsonl')
|
||||||
|
print(f'样本数: {len(s.samples)}')
|
||||||
|
ex = s.select('查询账户余额', top_k=3, min_rating=7)
|
||||||
|
print(f'选中: {[e.qid for e in ex]}')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见原因**:
|
||||||
|
- 文件路径错误 → 检查 `FEWSHOT_DATA_PATH`
|
||||||
|
- 评分过滤过高 → 降低 `FEWSHOT_MIN_RATING`
|
||||||
|
- 语义匹配未加载模型 → 检查网络或使用本地模型
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 生成 SQL 字段错误
|
||||||
|
|
||||||
|
**现象**:SQL 语法正确,但字段名与 Schema 不匹配。
|
||||||
|
|
||||||
|
**原因**:
|
||||||
|
- Schema 注释不完整 → LLM 无法理解字段语义
|
||||||
|
- 向量检索召回表错误 → 降低 `score_threshold`
|
||||||
|
- Temperature 过高 → 设为 0 或 0.1
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
1. 补充 Schema 字段注释
|
||||||
|
2. 重建向量索引
|
||||||
|
3. 启用 Few-Shot 并增加高评分样本
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 日期函数错误(MySQL 风格)
|
||||||
|
|
||||||
|
**现象**:生成 `WHERE date >= CURDATE()` 而非 T-SQL 风格。
|
||||||
|
|
||||||
|
**解决**:系统已内置 `rewrite_mysql_builtins_for_tsql` 自动替换,但仍需确保 Prompt 约束。检查 `config/prompts.py` 中 T-SQL 约束是否完整。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 外键缺失导致 JOIN 失败
|
||||||
|
|
||||||
|
**现象**:SQL 使用 `JOIN` 但缺少 `ON` 条件,或关联表未纳入。
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 检查 Schema 中 `foreign_keys` 定义是否完整
|
||||||
|
- 向量检索可能遗漏关联表 → 启用外键扩展(默认开启)
|
||||||
|
- 手动在 Prompt 中强调 JOIN 完整性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 Prompt 设计哲学
|
||||||
|
|
||||||
|
### 原则1:明确约束,禁止猜测
|
||||||
|
|
||||||
|
```python
|
||||||
|
"只提取明确信息,禁止猜测:仅实现用户问题里明确写出的筛选条件,"
|
||||||
|
"不可自行推断或补充未提及的筛选条件。"
|
||||||
|
```
|
||||||
|
|
||||||
|
**反面示例**:用户问"2024年1月的销售额",不应推断为"2024年1月1日至1月31日"(除非 Schema 有相应字段)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 原则2:提供推断指南
|
||||||
|
|
||||||
|
当无法避免推断时,提供**明确的推断规则**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
**常见时间/状态推断指南**(需结合 Schema 字段注释):
|
||||||
|
- "2024年1月" → WHERE date_col >= '2024-01-01' AND date_col < '2024-02-01'
|
||||||
|
- "今天" / "当日" → WHERE date_col >= CAST(GETDATE() AS DATE)
|
||||||
|
AND date_col < DATEADD(DAY,1,CAST(GETDATE() AS DATE))
|
||||||
|
- "活跃" → 通常对应 State = 'A'(需确认 Schema 注释)
|
||||||
|
- "未结算" → 通常对应 SettleStatus = 'U' 或 Settled = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 原则3:Few-Shot 示范优于文字描述
|
||||||
|
|
||||||
|
LLM 更擅长模仿示例而非理解抽象规则。提供 5-7 个高质量示例覆盖常见模式:
|
||||||
|
|
||||||
|
- 示例 3:日期范围推断
|
||||||
|
- 示例 4:状态值推断
|
||||||
|
- 示例 5:聚合 + 排序
|
||||||
|
- 示例 6:当日查询(GETDATE())
|
||||||
|
- 示例 7:LEFT JOIN 取维度名称
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 原则4:结构化输出要求
|
||||||
|
|
||||||
|
```python
|
||||||
|
输出格式:严格 JSON
|
||||||
|
{
|
||||||
|
"relevant_tables": ["TableA", "TableB"],
|
||||||
|
"reasoning": "选择理由(50-100字)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
避免自由文本,便于程序解析。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 开发日志
|
||||||
|
|
||||||
|
### v0.2.0(2026-04-10)—— Few-Shot 增强版
|
||||||
|
|
||||||
|
- ✅ 集成经验数据集(50 条标注样本)
|
||||||
|
- ✅ 实现 Few-Shot 选择器(语义/关键词双模式)
|
||||||
|
- ✅ 动态注入相似示例到 Prompt
|
||||||
|
- ✅ 优化 Prompt:允许合理推断,禁止随意猜测
|
||||||
|
- ✅ 添加 5 个新 Few-Shot 示例(日期、状态、聚合等)
|
||||||
|
- ✅ 降低向量检索阈值(0.2 → 0.1),提升召回率
|
||||||
|
- ✅ 确定性模式(TEMPERATURE=0, MAX_RETRY=1)
|
||||||
|
- ✅ 修复 Unicode 编码问题(Windows 控制台)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### v0.1.0(2026-04-08)—— 基础版本
|
||||||
|
|
||||||
|
- 🎉 初始发布
|
||||||
|
- 三 Agent 流水线(Schema Linker / SQL Generator / Validator)
|
||||||
|
- 向量检索 + LLM 精筛表选择
|
||||||
|
- ChromaDB + Qwen3-Embedding 本地索引
|
||||||
|
- T-SQL / MySQL / PostgreSQL 多方言支持
|
||||||
|
- 双重验证机制(程序 + LLM)
|
||||||
|
- 自修正重试(最多 2 次)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
MIT License — 详见 [LICENSE](LICENSE) 文件。
|
||||||
|
|
||||||
|
## 🙏 致谢
|
||||||
|
|
||||||
|
- [CAMEL AI](https://www.camel-ai.org/) — 多 Agent 框架
|
||||||
|
- [DeepSeek](https://platform.deepseek.com/) — 大模型 API
|
||||||
|
- [Qwen](https://qwen.ai/) — Embedding 模型
|
||||||
|
- [ChromaDB](https://www.trychroma.com/) — 向量数据库
|
||||||
|
- [sqlglot](https://sqlglot.com/) — SQL 解析与转换
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**维护者**:Text2SQL Team
|
||||||
|
**更新日期**:2026-04-10
|
||||||
|
**文档版本**:v0.2.0
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Text-to-SQL Multi-Agent System
|
||||||
|
# 基于 CAMEL AI + DeepSeek + Qwen3-Embedding 的智能SQL生成系统
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
__author__ = "Backman Team"
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
# agents 包初始化
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,558 @@
|
|||||||
|
"""
|
||||||
|
Text2SQL 多智能体编排器
|
||||||
|
协调 Schema Linker、SQL Generator、Validator 三个Agent
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os # 新增
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
from schema.indexer import SchemaIndexer
|
||||||
|
from llm.deepseek_client import DeepSeekClient, DeepSeekConfig
|
||||||
|
from utils.fewshot_selector import FewShotSelector # 新增
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GenerationResult:
|
||||||
|
"""SQL生成结果"""
|
||||||
|
sql: str
|
||||||
|
valid: bool
|
||||||
|
errors: List[str] = field(default_factory=list)
|
||||||
|
warnings: List[str] = field(default_factory=list)
|
||||||
|
tables_used: List[str] = field(default_factory=list)
|
||||||
|
attempts: int = 1
|
||||||
|
reasoning: Optional[str] = None
|
||||||
|
metadata: Dict[str, any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class Text2SQLOrchestrator:
|
||||||
|
"""
|
||||||
|
Text2SQL 多智能体编排器
|
||||||
|
|
||||||
|
工作流程:
|
||||||
|
1. Schema Linker:粗筛 + LLM精筛,选出相关表
|
||||||
|
2. 外键扩展:自动包含关联表
|
||||||
|
3. SQL Generator:生成SQL
|
||||||
|
4. Validator:验证SQL,不通过则重试(最多max_retry次)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
schema_manager: SchemaManager,
|
||||||
|
deepseek_api_key: Optional[str] = None,
|
||||||
|
deepseek_config: Optional[DeepSeekConfig] = None,
|
||||||
|
embedding_model_path: Optional[str] = None,
|
||||||
|
vector_db_path: str = "./data/embeddings/chroma",
|
||||||
|
max_retry: int = 2,
|
||||||
|
use_vector_search: bool = True,
|
||||||
|
# Few-shot配置
|
||||||
|
fewshot_enabled: bool = True,
|
||||||
|
fewshot_samples_path: Optional[str] = None,
|
||||||
|
fewshot_top_k: int = 3,
|
||||||
|
fewshot_min_rating: int = 7,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化编排器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
schema_manager: Schema管理器实例
|
||||||
|
deepseek_api_key: DeepSeek API密钥(也可通过环境变量DEEPSEEK_API_KEY)
|
||||||
|
deepseek_config: DeepSeek配置对象(优先于api_key)
|
||||||
|
embedding_model_path: Qwen3-Embedding模型路径
|
||||||
|
vector_db_path: 向量数据库路径
|
||||||
|
max_retry: 最大重试次数(包含首次生成)
|
||||||
|
use_vector_search: 是否使用向量检索粗筛
|
||||||
|
"""
|
||||||
|
self.schema_manager = schema_manager
|
||||||
|
self.max_retry = max_retry
|
||||||
|
self.use_vector_search = use_vector_search
|
||||||
|
|
||||||
|
# 初始化DeepSeek客户端
|
||||||
|
if deepseek_config:
|
||||||
|
self.deepseek = DeepSeekClient(deepseek_config)
|
||||||
|
else:
|
||||||
|
self.deepseek = DeepSeekClient(
|
||||||
|
DeepSeekConfig(api_key=deepseek_api_key)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 初始化向量索引(延迟加载)
|
||||||
|
self._vector_index: Optional[SchemaIndexer] = None
|
||||||
|
self._vector_db_path = vector_db_path
|
||||||
|
self._embedding_model_path = embedding_model_path
|
||||||
|
|
||||||
|
# Few-shot 初始化
|
||||||
|
self.fewshot_enabled = fewshot_enabled
|
||||||
|
self.fewshot_top_k = fewshot_top_k
|
||||||
|
self.fewshot_min_rating = fewshot_min_rating
|
||||||
|
self.fewshot_selector = None
|
||||||
|
|
||||||
|
if self.fewshot_enabled:
|
||||||
|
try:
|
||||||
|
path = fewshot_samples_path or os.getenv(
|
||||||
|
"FEWSHOT_DATA_PATH",
|
||||||
|
"./data/experiences/all_samples.jsonl"
|
||||||
|
)
|
||||||
|
self.fewshot_selector = FewShotSelector(
|
||||||
|
path,
|
||||||
|
embedding_model_path=self._embedding_model_path,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Few-shot已启用: top_k={fewshot_top_k}, "
|
||||||
|
f"min_rating={fewshot_min_rating}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Few-shot加载失败: {e},将使用标准生成")
|
||||||
|
self.fewshot_enabled = False
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[OK] Text2SQLOrchestrator初始化完成: "
|
||||||
|
f"max_retry={max_retry}, use_vector_search={use_vector_search}"
|
||||||
|
+ (f", fewshot=on" if self.fewshot_enabled else "")
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_vector_index(self) -> SchemaIndexer:
|
||||||
|
"""获取或创建向量索引(懒加载)"""
|
||||||
|
if self._vector_index is None:
|
||||||
|
from utils.embedding import get_embedder
|
||||||
|
|
||||||
|
embedder = get_embedder(self._embedding_model_path)
|
||||||
|
self._vector_index = SchemaIndexer(
|
||||||
|
embedder=embedder,
|
||||||
|
persist_dir=self._vector_db_path
|
||||||
|
)
|
||||||
|
return self._vector_index
|
||||||
|
|
||||||
|
def _coarse_filter(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
top_k: int = 20
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
阶段1:粗筛(向量检索)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
top_k: 返回前K个候选表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
候选表名列表
|
||||||
|
"""
|
||||||
|
if not self.use_vector_search:
|
||||||
|
# 不使用向量检索时,返回所有表
|
||||||
|
return self.schema_manager.list_tables()
|
||||||
|
|
||||||
|
indexer = self._get_vector_index()
|
||||||
|
|
||||||
|
# 确保索引已构建
|
||||||
|
if indexer.count() == 0:
|
||||||
|
logger.info("向量索引为空,正在构建...")
|
||||||
|
indexer.build_index(self.schema_manager, force_rebuild=True)
|
||||||
|
|
||||||
|
# 检索
|
||||||
|
results = indexer.search(
|
||||||
|
query=question,
|
||||||
|
top_k=top_k,
|
||||||
|
score_threshold=0.1 # 降低阈值以提高召回率(原0.2)
|
||||||
|
)
|
||||||
|
|
||||||
|
candidate_tables = [r["table_name"] for r in results]
|
||||||
|
logger.debug(f"粗筛候选表:{candidate_tables[:10]}...(共{len(candidate_tables)}个)")
|
||||||
|
return candidate_tables
|
||||||
|
|
||||||
|
def _llm_select_tables(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
candidate_tables: List[str],
|
||||||
|
max_tables: int = 5
|
||||||
|
) -> Tuple[List[str], str]:
|
||||||
|
"""
|
||||||
|
阶段2:LLM精筛(Schema Linker Agent)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
candidate_tables: 候选表列表
|
||||||
|
max_tables: 最多选择的表数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(相关表列表, 推理理由)
|
||||||
|
"""
|
||||||
|
# 构造候选表信息(只显示表名和注释)
|
||||||
|
table_infos = []
|
||||||
|
for tbl_name in candidate_tables:
|
||||||
|
table = self.schema_manager.get_table(tbl_name)
|
||||||
|
if table:
|
||||||
|
comment = table.comment or "无描述"
|
||||||
|
table_infos.append(f"- {tbl_name}: {comment}")
|
||||||
|
|
||||||
|
table_list_str = "\n".join(table_infos)
|
||||||
|
|
||||||
|
# 使用DeepSeek客户端调用(而非CAMEL Agent,更直接可控)
|
||||||
|
response = self.deepseek.select_tables(
|
||||||
|
question=question,
|
||||||
|
table_list=table_list_str
|
||||||
|
)
|
||||||
|
|
||||||
|
relevant_tables = response.get("relevant_tables", [])
|
||||||
|
reasoning = response.get("reasoning", "")
|
||||||
|
|
||||||
|
# 限制数量
|
||||||
|
relevant_tables = relevant_tables[:max_tables]
|
||||||
|
|
||||||
|
logger.info(f"LLM精筛选中表:{relevant_tables}")
|
||||||
|
return relevant_tables, reasoning
|
||||||
|
|
||||||
|
_BROKER_KEYWORDS_CN = ("对手方", "经纪商", "券商", "對手方")
|
||||||
|
|
||||||
|
def _question_implies_broker_dimension(self, question: str) -> bool:
|
||||||
|
if not question:
|
||||||
|
return False
|
||||||
|
if any(k in question for k in self._BROKER_KEYWORDS_CN):
|
||||||
|
return True
|
||||||
|
return "broker" in question.lower()
|
||||||
|
|
||||||
|
def _prioritize_broker_tables(
|
||||||
|
self, question: str, relevant_tables: List[str], max_tables: int = 5
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
问题涉及对手方/经纪商时,优先纳入 TSBBrokerContract 与 MCBroker(若 Schema 中存在),
|
||||||
|
避免仅选中 VSBHK 报表视图却无 BrokerID,模型又照抄黄金范例列名导致校验失败。
|
||||||
|
"""
|
||||||
|
if not self._question_implies_broker_dimension(question):
|
||||||
|
return relevant_tables[:max_tables]
|
||||||
|
|
||||||
|
priority = ["TSBBrokerContract", "MCBroker"]
|
||||||
|
present = [t for t in priority if self.schema_manager.get_table(t)]
|
||||||
|
if not present:
|
||||||
|
return relevant_tables[:max_tables]
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
merged: List[str] = []
|
||||||
|
for t in present:
|
||||||
|
if t not in seen:
|
||||||
|
merged.append(t)
|
||||||
|
seen.add(t)
|
||||||
|
for t in relevant_tables:
|
||||||
|
if len(merged) >= max_tables:
|
||||||
|
break
|
||||||
|
if t not in seen and self.schema_manager.get_table(t):
|
||||||
|
merged.append(t)
|
||||||
|
seen.add(t)
|
||||||
|
logger.info("对手方/经纪商问题:优先纳入 %s,调整后选表:%s", present, merged)
|
||||||
|
return merged[:max_tables]
|
||||||
|
|
||||||
|
def _expand_relations(self, table_names: List[str]) -> List[str]:
|
||||||
|
"""
|
||||||
|
外键扩展:自动添加关联表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table_names: 已选中的表名列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
扩展后的表名列表
|
||||||
|
"""
|
||||||
|
result = set(table_names)
|
||||||
|
|
||||||
|
for tbl_name in table_names:
|
||||||
|
table = self.schema_manager.get_table(tbl_name)
|
||||||
|
if not table:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 添加被引用的表(外键指向的表)
|
||||||
|
for fk in table.foreign_keys:
|
||||||
|
if fk.ref_table not in result:
|
||||||
|
result.add(fk.ref_table)
|
||||||
|
logger.debug(f"外键扩展:添加关联表 {fk.ref_table}")
|
||||||
|
|
||||||
|
# 添加引用当前表的表(反向外键)
|
||||||
|
for other in self.schema_manager.get_tables():
|
||||||
|
for fk in other.foreign_keys:
|
||||||
|
if fk.ref_table == tbl_name and other.name not in result:
|
||||||
|
result.add(other.name)
|
||||||
|
logger.debug(f"外键扩展:添加引用表 {other.name}")
|
||||||
|
|
||||||
|
expanded = list(result)
|
||||||
|
if len(expanded) > len(table_names):
|
||||||
|
logger.info(f"外键扩展:{table_names} → {expanded}")
|
||||||
|
|
||||||
|
return expanded
|
||||||
|
|
||||||
|
def _generate_sql(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
schema_str: str,
|
||||||
|
dialect: str = "tsql"
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
SQL生成(SQL Generator Agent)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
schema_str: Schema描述字符串
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SQL语句
|
||||||
|
"""
|
||||||
|
from config.prompts import SQL_GENERATOR_SYSTEM, SQL_GENERATOR_USER
|
||||||
|
from utils.sql_parser import normalize_sql_for_dialect
|
||||||
|
|
||||||
|
# Few-shot 增强
|
||||||
|
if self.fewshot_enabled and self.fewshot_selector:
|
||||||
|
try:
|
||||||
|
examples = self.fewshot_selector.select(
|
||||||
|
question=question,
|
||||||
|
top_k=self.fewshot_top_k,
|
||||||
|
min_rating=self.fewshot_min_rating
|
||||||
|
)
|
||||||
|
if examples:
|
||||||
|
examples_prompt = "\n\n".join([
|
||||||
|
f"示例 {i+1}:\n问题:{ex.question_zh}\nSQL:\n{ex.sql}"
|
||||||
|
for i, ex in enumerate(examples)
|
||||||
|
])
|
||||||
|
schema_str = f"参考以下相似示例的SQL编写风格:\n\n{examples_prompt}\n\n【当前Schema】\n{schema_str}"
|
||||||
|
logger.debug(f"已注入 {len(examples)} 个few-shot示例: {[ex.qid for ex in examples]}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Few-shot检索失败: {e}")
|
||||||
|
|
||||||
|
dialect_label = dialect
|
||||||
|
if dialect == "tsql":
|
||||||
|
dialect_label = "Microsoft SQL Server (T-SQL)"
|
||||||
|
|
||||||
|
user_content = SQL_GENERATOR_USER.format(
|
||||||
|
schema=schema_str,
|
||||||
|
question=question,
|
||||||
|
dialect=dialect_label,
|
||||||
|
)
|
||||||
|
if dialect == "tsql":
|
||||||
|
user_content += (
|
||||||
|
"\n\n【硬性要求】目标库为 SQL Server(T-SQL):禁止使用 MySQL 反引号 `;"
|
||||||
|
"标识符如需引用请使用方括号,例如 [TableName]、[ColumnName]。"
|
||||||
|
"字符串连接使用 `+`(与系统提示中的标准版式范例一致)。"
|
||||||
|
"「今日」「当天」等与日期列比较时,使用 `CAST(GETDATE() AS DATE)`,"
|
||||||
|
"**禁止** `CURDATE()`、`NOW()`、`CURRENT_DATE`(MySQL)。"
|
||||||
|
"条件请使用 T-SQL 惯用写法(例如 IS NOT NULL)。"
|
||||||
|
"排版仍须遵守:关键字大写、SELECT 每列一行缩进、WHERE 续行以 AND 开头、PascalCase 英文别名。"
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": SQL_GENERATOR_SYSTEM},
|
||||||
|
{"role": "user", "content": user_content},
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.deepseek.chat(messages)
|
||||||
|
sql = response.content.strip()
|
||||||
|
|
||||||
|
# 清理可能的markdown代码块
|
||||||
|
if "```sql" in sql:
|
||||||
|
sql = sql[sql.find("```sql") + 6:sql.find("```", sql.find("```sql") + 6)].strip()
|
||||||
|
elif "```" in sql:
|
||||||
|
sql = sql[sql.find("```") + 3:sql.find("```", sql.find("```") + 3)].strip()
|
||||||
|
|
||||||
|
sql = normalize_sql_for_dialect(sql, dialect)
|
||||||
|
|
||||||
|
logger.debug(f"生成的SQL:{sql[:200]}...")
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def _validate_sql(
|
||||||
|
self,
|
||||||
|
sql: str,
|
||||||
|
schema_str: str,
|
||||||
|
dialect: str = "tsql",
|
||||||
|
) -> Tuple[bool, List[str], List[str]]:
|
||||||
|
"""
|
||||||
|
SQL验证(Validator Agent + 程序验证)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema_str: Schema描述
|
||||||
|
dialect: 与生成一致的 SQL 方言(sqlglot 名,默认 tsql)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否通过, 错误列表, 警告列表)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
# === 阶段1:程序验证(确定性规则) ===
|
||||||
|
from utils.sql_parser import validate_sql_syntax, validate_schema_consistency
|
||||||
|
|
||||||
|
# 语法验证
|
||||||
|
syntax_ok, syntax_errors = validate_sql_syntax(sql, dialect=dialect)
|
||||||
|
if not syntax_ok:
|
||||||
|
errors.extend(syntax_errors)
|
||||||
|
|
||||||
|
# Schema一致性验证
|
||||||
|
schema_ok, schema_errors = validate_schema_consistency(
|
||||||
|
sql, self.schema_manager, dialect=dialect
|
||||||
|
)
|
||||||
|
if not schema_ok:
|
||||||
|
errors.extend(schema_errors)
|
||||||
|
|
||||||
|
# 危险操作检查
|
||||||
|
from utils.validators import check_dangerous_operations
|
||||||
|
danger_ok, danger_errors = check_dangerous_operations(sql)
|
||||||
|
if not danger_ok:
|
||||||
|
errors.extend(danger_errors)
|
||||||
|
|
||||||
|
# === 阶段2:LLM语义验证 ===
|
||||||
|
try:
|
||||||
|
llm_result = self.deepseek.validate_sql(sql=sql, schema=schema_str)
|
||||||
|
|
||||||
|
llm_errors = list(llm_result.get("errors", []))
|
||||||
|
# 程序校验已通过表/列(含别名解析)时,LLM 仍常误报 unknown_*,避免误杀整次生成
|
||||||
|
if schema_ok:
|
||||||
|
llm_errors = [
|
||||||
|
e
|
||||||
|
for e in llm_errors
|
||||||
|
if isinstance(e, str)
|
||||||
|
and not (
|
||||||
|
e.startswith("unknown_table:")
|
||||||
|
or e.startswith("unknown_column:")
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
if not llm_result.get("valid", True):
|
||||||
|
errors.extend(llm_errors)
|
||||||
|
|
||||||
|
warnings.extend(llm_result.get("warnings", []))
|
||||||
|
suggestions = llm_result.get("suggestions", [])
|
||||||
|
if suggestions:
|
||||||
|
logger.debug(f"优化建议:{suggestions}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"LLM验证失败(降级为仅程序验证): {e}")
|
||||||
|
|
||||||
|
is_valid = len(errors) == 0
|
||||||
|
return is_valid, errors, warnings
|
||||||
|
|
||||||
|
def generate(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
dialect: str = "tsql",
|
||||||
|
top_k_candidates: int = 20,
|
||||||
|
include_schema_in_result: bool = False
|
||||||
|
) -> GenerationResult:
|
||||||
|
"""
|
||||||
|
主生成流程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户自然语言问题
|
||||||
|
dialect: SQL方言(默认 tsql;与 sqlglot 一致)
|
||||||
|
top_k_candidates: 粗筛候选表数量
|
||||||
|
include_schema_in_result: 结果中是否包含使用的Schema字符串
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GenerationResult对象
|
||||||
|
"""
|
||||||
|
logger.info(f"[GEN] 开始生成SQL:{question[:50]}...")
|
||||||
|
|
||||||
|
attempt = 0
|
||||||
|
last_sql = None
|
||||||
|
last_errors = []
|
||||||
|
filtered_schema_str = ""
|
||||||
|
tables_used = []
|
||||||
|
|
||||||
|
while attempt < self.max_retry:
|
||||||
|
logger.info(f" 尝试 #{attempt + 1}")
|
||||||
|
|
||||||
|
# === Step 1: Schema筛选(仅首次) ===
|
||||||
|
if attempt == 0:
|
||||||
|
# 1.1 粗筛
|
||||||
|
candidate_tables = self._coarse_filter(question, top_k=top_k_candidates)
|
||||||
|
|
||||||
|
# 1.2 LLM精筛
|
||||||
|
relevant_tables, reasoning = self._llm_select_tables(question, candidate_tables)
|
||||||
|
relevant_tables = self._prioritize_broker_tables(question, relevant_tables)
|
||||||
|
|
||||||
|
# 1.3 外键扩展
|
||||||
|
expanded_tables = self._expand_relations(relevant_tables)
|
||||||
|
tables_used = expanded_tables
|
||||||
|
|
||||||
|
# 1.4 生成Schema字符串
|
||||||
|
filtered_schema_str = self.schema_manager.to_compact_string(
|
||||||
|
table_names=expanded_tables,
|
||||||
|
include_columns=True,
|
||||||
|
max_columns_per_table=20
|
||||||
|
)
|
||||||
|
logger.info(f" 选中表:{relevant_tables},扩展后:{expanded_tables}")
|
||||||
|
else:
|
||||||
|
# 重试时复用之前的Schema
|
||||||
|
logger.info(f" 重试使用之前的Schema({len(tables_used)}张表)")
|
||||||
|
|
||||||
|
# === Step 2: SQL生成 ===
|
||||||
|
try:
|
||||||
|
sql = self._generate_sql(question, filtered_schema_str, dialect)
|
||||||
|
last_sql = sql
|
||||||
|
except Exception as e:
|
||||||
|
last_errors = [f"SQL生成失败: {str(e)}"]
|
||||||
|
attempt += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# === Step 3: 验证 ===
|
||||||
|
is_valid, errors, warnings = self._validate_sql(
|
||||||
|
sql, filtered_schema_str, dialect=dialect
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
logger.info(f"[OK] SQL生成并验证通过({attempt + 1}次尝试)")
|
||||||
|
result = GenerationResult(
|
||||||
|
sql=sql,
|
||||||
|
valid=True,
|
||||||
|
errors=[],
|
||||||
|
warnings=warnings,
|
||||||
|
tables_used=tables_used,
|
||||||
|
attempts=attempt + 1,
|
||||||
|
reasoning=reasoning if attempt == 0 else None,
|
||||||
|
)
|
||||||
|
if include_schema_in_result:
|
||||||
|
result.metadata["schema"] = filtered_schema_str
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证失败,准备重试
|
||||||
|
last_errors = errors
|
||||||
|
logger.warning(f" [FAIL] 验证失败:{errors}")
|
||||||
|
attempt += 1
|
||||||
|
|
||||||
|
# 达到最大重试次数
|
||||||
|
logger.error(f"[FAIL] 达到最大重试次数({self.max_retry}),生成失败")
|
||||||
|
return GenerationResult(
|
||||||
|
sql=last_sql or "",
|
||||||
|
valid=False,
|
||||||
|
errors=last_errors,
|
||||||
|
tables_used=tables_used,
|
||||||
|
attempts=attempt,
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_vector_index(self, force_rebuild: bool = False) -> bool:
|
||||||
|
"""
|
||||||
|
构建向量索引(可选,提前构建可加速首次查询)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force_rebuild: 是否强制重建
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功构建
|
||||||
|
"""
|
||||||
|
indexer = self._get_vector_index()
|
||||||
|
return indexer.build_index(
|
||||||
|
self.schema_manager,
|
||||||
|
force_rebuild=force_rebuild
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_statistics(self) -> Dict:
|
||||||
|
"""获取统计信息"""
|
||||||
|
schema_stats = self.schema_manager.get_statistics()
|
||||||
|
|
||||||
|
indexer = self._get_vector_index()
|
||||||
|
index_stats = indexer.get_statistics()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"schema": schema_stats,
|
||||||
|
"vector_index": index_stats,
|
||||||
|
"max_retry": self.max_retry,
|
||||||
|
"use_vector_search": self.use_vector_search,
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
Schema Linker Agent - 表筛选专家
|
||||||
|
从大量数据表中识别与用户问题相关的表
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Tuple, Optional
|
||||||
|
import json
|
||||||
|
|
||||||
|
from camel.agents import ChatAgent
|
||||||
|
from camel.models import ChatModel
|
||||||
|
from config.prompts import SCHEMA_LINKER_SYSTEM
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaLinkerAgent:
|
||||||
|
"""
|
||||||
|
Schema Linker Agent
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- 分析用户问题中的实体和意图
|
||||||
|
- 从候选表中筛选真正相关的表
|
||||||
|
- 提供选择理由
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model: ChatModel,
|
||||||
|
system_message: Optional[str] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化Agent
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model: CAMEL AI模型实例
|
||||||
|
system_message: 系统提示词(默认使用SCHEMA_LINKER_SYSTEM)
|
||||||
|
"""
|
||||||
|
self.system_message = system_message or SCHEMA_LINKER_SYSTEM
|
||||||
|
self.agent = ChatAgent(
|
||||||
|
system_message=self.system_message,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
logger.info("[OK] SchemaLinkerAgent初始化完成")
|
||||||
|
|
||||||
|
def select_tables(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
candidate_tables: List[str],
|
||||||
|
table_metadata: Optional[Dict[str, str]] = None,
|
||||||
|
max_tables: int = 5
|
||||||
|
) -> Tuple[List[str], str]:
|
||||||
|
"""
|
||||||
|
选择相关表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
candidate_tables: 候选表列表(粗筛结果)
|
||||||
|
table_metadata: 表元数据 {表名: 注释}
|
||||||
|
max_tables: 最多返回表数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(相关表列表, 推理理由)
|
||||||
|
"""
|
||||||
|
# 构造候选表信息字符串
|
||||||
|
if table_metadata:
|
||||||
|
table_list = "\n".join([
|
||||||
|
f"- {tbl}: {table_metadata.get(tbl, '无描述')}"
|
||||||
|
for tbl in candidate_tables
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
table_list = "\n".join([f"- {tbl}" for tbl in candidate_tables])
|
||||||
|
|
||||||
|
from config.prompts import SCHEMA_LINKER_USER
|
||||||
|
prompt = SCHEMA_LINKER_USER.format(
|
||||||
|
question=question,
|
||||||
|
table_list=table_list
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.agent.step(prompt)
|
||||||
|
content = response.msg.content.strip()
|
||||||
|
|
||||||
|
# 解析JSON响应
|
||||||
|
result = self._parse_json_response(content)
|
||||||
|
|
||||||
|
relevant_tables = result.get("relevant_tables", [])
|
||||||
|
reasoning = result.get("reasoning", "")
|
||||||
|
|
||||||
|
# 限制数量
|
||||||
|
relevant_tables = relevant_tables[:max_tables]
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"SchemaLinker选中 {len(relevant_tables)} 张表: {relevant_tables}"
|
||||||
|
)
|
||||||
|
return relevant_tables, reasoning
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(f"JSON解析失败: {e}, 原始内容: {content[:200]}")
|
||||||
|
# 降级:返回前max_tables个候选表
|
||||||
|
return candidate_tables[:max_tables], "JSON解析失败,使用粗筛结果"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Agent调用失败: {e}")
|
||||||
|
return candidate_tables[:max_tables], f"Agent错误: {str(e)}"
|
||||||
|
|
||||||
|
def _parse_json_response(self, content: str) -> Dict:
|
||||||
|
"""
|
||||||
|
解析Agent的JSON响应
|
||||||
|
|
||||||
|
处理可能的markdown代码块包裹
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 尝试提取```json```块
|
||||||
|
if "```json" in content:
|
||||||
|
start = content.find("```json") + 7
|
||||||
|
end = content.find("```", start)
|
||||||
|
if end != -1:
|
||||||
|
content = content[start:end].strip()
|
||||||
|
elif "```" in content:
|
||||||
|
start = content.find("```") + 3
|
||||||
|
end = content.find("```", start)
|
||||||
|
if end != -1:
|
||||||
|
content = content[start:end].strip()
|
||||||
|
|
||||||
|
return json.loads(content)
|
||||||
|
|
||||||
|
def expand_by_relations(
|
||||||
|
self,
|
||||||
|
selected_tables: List[str],
|
||||||
|
schema_manager,
|
||||||
|
depth: int = 1
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
通过外键关系扩展表(备选方案,也可在Orchestrator中完成)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
selected_tables: 已选中的表
|
||||||
|
schema_manager: Schema管理器
|
||||||
|
depth: 递归深度
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
扩展后的表列表
|
||||||
|
"""
|
||||||
|
result = set(selected_tables)
|
||||||
|
|
||||||
|
for tbl_name in selected_tables:
|
||||||
|
table = schema_manager.get_table(tbl_name)
|
||||||
|
if not table:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 外键指向的表
|
||||||
|
for fk in table.foreign_keys:
|
||||||
|
if fk.ref_table not in result:
|
||||||
|
result.add(fk.ref_table)
|
||||||
|
|
||||||
|
# 引用当前表的表(反向外键)
|
||||||
|
for other in schema_manager.get_tables():
|
||||||
|
for fk in other.foreign_keys:
|
||||||
|
if fk.ref_table == tbl_name and other.name not in result:
|
||||||
|
result.add(other.name)
|
||||||
|
|
||||||
|
return list(result)
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
"""
|
||||||
|
SQL Generator Agent - SQL生成专家
|
||||||
|
根据Schema和问题生成高质量SQL
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
from camel.agents import ChatAgent
|
||||||
|
from camel.models import ChatModel
|
||||||
|
from config.prompts import SQL_GENERATOR_SYSTEM
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SQLGeneratorAgent:
|
||||||
|
"""
|
||||||
|
SQL Generator Agent
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- 理解用户问题和Schema结构
|
||||||
|
- 生成准确的SQL语句
|
||||||
|
- 处理复杂的JOIN、聚合、子查询
|
||||||
|
- 遵循金融/证券业务特殊规则
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model: ChatModel,
|
||||||
|
system_message: Optional[str] = None,
|
||||||
|
dialect: str = "tsql"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化Agent
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model: CAMEL AI模型实例
|
||||||
|
system_message: 系统提示词
|
||||||
|
dialect: SQL方言
|
||||||
|
"""
|
||||||
|
self.dialect = dialect
|
||||||
|
self.system_message = system_message or SQL_GENERATOR_SYSTEM
|
||||||
|
self.agent = ChatAgent(
|
||||||
|
system_message=self.system_message,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
logger.info(f"[OK] SQLGeneratorAgent初始化完成 (dialect={dialect})")
|
||||||
|
|
||||||
|
def generate(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
schema_str: str,
|
||||||
|
examples: Optional[List[Dict]] = None,
|
||||||
|
dialect: Optional[str] = None
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
生成SQL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
schema_str: Schema描述字符串
|
||||||
|
examples: Few-shot示例列表
|
||||||
|
dialect: 覆盖默认dialect
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SQL语句字符串
|
||||||
|
"""
|
||||||
|
from config.prompts import SQL_GENERATOR_USER
|
||||||
|
|
||||||
|
dialect = dialect or self.dialect
|
||||||
|
prompt = SQL_GENERATOR_USER.format(
|
||||||
|
schema=schema_str,
|
||||||
|
question=question,
|
||||||
|
dialect=dialect
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加few-shot示例(如果有)
|
||||||
|
if examples:
|
||||||
|
prompt = self._inject_examples(prompt, examples)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.agent.step(prompt)
|
||||||
|
sql = self._extract_sql(response.msg.content)
|
||||||
|
|
||||||
|
logger.debug(f"生成的SQL: {sql[:200]}...")
|
||||||
|
return sql
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SQL生成失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _extract_sql(self, content: str) -> str:
|
||||||
|
"""
|
||||||
|
从Agent响应中提取SQL语句
|
||||||
|
|
||||||
|
处理:
|
||||||
|
- Markdown代码块 (```sql ... ```)
|
||||||
|
- 纯SQL文本
|
||||||
|
- JSON格式 {"sql": "..."}
|
||||||
|
"""
|
||||||
|
content = content.strip()
|
||||||
|
|
||||||
|
# 尝试提取```sql```块
|
||||||
|
if "```sql" in content:
|
||||||
|
start = content.find("```sql") + 6
|
||||||
|
end = content.find("```", start)
|
||||||
|
if end != -1:
|
||||||
|
return content[start:end].strip()
|
||||||
|
|
||||||
|
# 尝试提取通用代码块```
|
||||||
|
if "```" in content:
|
||||||
|
start = content.find("```") + 3
|
||||||
|
end = content.find("```", start)
|
||||||
|
if end != -1:
|
||||||
|
return content[start:end].strip()
|
||||||
|
|
||||||
|
# 尝试解析JSON
|
||||||
|
try:
|
||||||
|
data = json.loads(content)
|
||||||
|
if "sql" in data:
|
||||||
|
return data["sql"]
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 返回原始内容(假设是纯SQL)
|
||||||
|
return content
|
||||||
|
|
||||||
|
def _inject_examples(
|
||||||
|
self,
|
||||||
|
prompt: str,
|
||||||
|
examples: List[Dict]
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
注入few-shot示例到提示词
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: 原始提示词
|
||||||
|
examples: 示例列表,每项为 {"question": "...", "schema": "...", "sql": "..."}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
增强后的提示词
|
||||||
|
"""
|
||||||
|
examples_text = []
|
||||||
|
for ex in examples[:3]: # 最多3个示例
|
||||||
|
examples_text.append(
|
||||||
|
f"示例:\n问题:{ex['question']}\n"
|
||||||
|
f"Schema: {ex['schema'][:200]}...\n"
|
||||||
|
f"SQL: {ex['sql']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
examples_block = "\n\n".join(examples_text)
|
||||||
|
|
||||||
|
# 插入到提示词末尾(要求之前)
|
||||||
|
return f"{prompt}\n\n参考示例:\n{examples_block}\n\n请生成SQL:"
|
||||||
|
|
||||||
|
def generate_with_reasoning(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
schema_str: str,
|
||||||
|
dialect: Optional[str] = None
|
||||||
|
) -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
生成SQL并返回解释
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
schema_str: Schema描述
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(SQL语句, 解释)
|
||||||
|
"""
|
||||||
|
from config.prompts import SQL_GENERATOR_USER
|
||||||
|
|
||||||
|
dialect = dialect or self.dialect
|
||||||
|
prompt = f"""
|
||||||
|
{sql_generator_user.format(schema=schema_str, question=question, dialect=dialect)}
|
||||||
|
|
||||||
|
请同时输出SQL和简要解释(JSON格式):
|
||||||
|
{{
|
||||||
|
"sql": "SELECT ...",
|
||||||
|
"explanation": "SQL逻辑说明"
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.agent.step(prompt)
|
||||||
|
content = response.msg.content.strip()
|
||||||
|
|
||||||
|
# 尝试解析JSON
|
||||||
|
try:
|
||||||
|
if "```json" in content:
|
||||||
|
start = content.find("```json") + 7
|
||||||
|
end = content.find("```", start)
|
||||||
|
content = content[start:end].strip()
|
||||||
|
data = json.loads(content)
|
||||||
|
return data.get("sql", ""), data.get("explanation", "")
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
# 降级:提取SQL,解释为空
|
||||||
|
return self._extract_sql(content), ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成失败: {e}")
|
||||||
|
raise
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
"""
|
||||||
|
Validator Agent - SQL审核员
|
||||||
|
验证SQL的正确性和安全性
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from typing import Dict, List, Tuple, Optional
|
||||||
|
|
||||||
|
from camel.agents import ChatAgent
|
||||||
|
from camel.models import ChatModel
|
||||||
|
from config.prompts import VALIDATOR_SYSTEM
|
||||||
|
|
||||||
|
from utils.validators import full_validation_pipeline
|
||||||
|
from utils.sql_parser import validate_sql_syntax, validate_schema_consistency
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidatorAgent:
|
||||||
|
"""
|
||||||
|
Validator Agent
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- 语法正确性验证
|
||||||
|
- Schema一致性检查
|
||||||
|
- 安全性检查(禁止DML/DDL)
|
||||||
|
- 性能问题识别
|
||||||
|
- 提供修正建议
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model: ChatModel,
|
||||||
|
system_message: Optional[str] = None,
|
||||||
|
schema_manager = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化Agent
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model: CAMEL AI模型实例
|
||||||
|
system_message: 系统提示词
|
||||||
|
schema_manager: Schema管理器(程序验证用)
|
||||||
|
"""
|
||||||
|
self.schema_manager = schema_manager
|
||||||
|
self.system_message = system_message or VALIDATOR_SYSTEM
|
||||||
|
self.agent = ChatAgent(
|
||||||
|
system_message=self.system_message,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
logger.info("[OK] ValidatorAgent初始化完成")
|
||||||
|
|
||||||
|
def validate(
|
||||||
|
self,
|
||||||
|
sql: str,
|
||||||
|
schema_str: str,
|
||||||
|
dialect: str = "tsql",
|
||||||
|
check_dangerous: bool = True
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
完整验证流程(程序 + LLM双重验证)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema_str: Schema描述
|
||||||
|
dialect: SQL方言
|
||||||
|
check_dangerous: 是否检查危险操作
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
验证结果字典 {
|
||||||
|
"valid": bool,
|
||||||
|
"errors": [...],
|
||||||
|
"warnings": [...],
|
||||||
|
"suggestions": [...]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
result = {
|
||||||
|
"valid": True,
|
||||||
|
"errors": [],
|
||||||
|
"warnings": [],
|
||||||
|
"suggestions": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# === 阶段1:程序验证(快速、确定性) ===
|
||||||
|
program_result = self._program_validation(
|
||||||
|
sql, dialect, check_dangerous
|
||||||
|
)
|
||||||
|
|
||||||
|
result["errors"].extend(program_result.get("errors", []))
|
||||||
|
result["warnings"].extend(program_result.get("warnings", []))
|
||||||
|
result["suggestions"].extend(program_result.get("suggestions", []))
|
||||||
|
|
||||||
|
# 如果程序验证已发现致命错误,跳过LLM验证
|
||||||
|
if program_result.get("fatal", False):
|
||||||
|
result["valid"] = False
|
||||||
|
return result
|
||||||
|
|
||||||
|
# === 阶段2:LLM语义验证 ===
|
||||||
|
try:
|
||||||
|
llm_result = self._llm_validation(sql, schema_str)
|
||||||
|
result["errors"].extend(llm_result.get("errors", []))
|
||||||
|
result["warnings"].extend(llm_result.get("warnings", []))
|
||||||
|
result["suggestions"].extend(llm_result.get("suggestions", []))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"LLM验证失败,使用程序验证结果: {e}")
|
||||||
|
result["warnings"].append(f"LLM验证异常: {str(e)}")
|
||||||
|
|
||||||
|
result["valid"] = len(result["errors"]) == 0
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _program_validation(
|
||||||
|
self,
|
||||||
|
sql: str,
|
||||||
|
dialect: str,
|
||||||
|
check_dangerous: bool
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
程序验证(规则引擎)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{"errors": [], "warnings": [], "suggestions": [], "fatal": bool}
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
warnings = []
|
||||||
|
suggestions = []
|
||||||
|
fatal = False
|
||||||
|
|
||||||
|
# 1. 语法检查
|
||||||
|
syntax_ok, syntax_errors = validate_sql_syntax(sql, dialect)
|
||||||
|
if not syntax_ok:
|
||||||
|
errors.extend(syntax_errors)
|
||||||
|
fatal = True # 语法错误无法修复,直接失败
|
||||||
|
return {
|
||||||
|
"errors": errors, "warnings": warnings,
|
||||||
|
"suggestions": suggestions, "fatal": fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Schema一致性(如果有schema_manager)
|
||||||
|
if self.schema_manager:
|
||||||
|
schema_ok, schema_errors = validate_schema_consistency(
|
||||||
|
sql, self.schema_manager, dialect
|
||||||
|
)
|
||||||
|
if not schema_ok:
|
||||||
|
errors.extend(schema_errors)
|
||||||
|
# Schema错误通常也是fatal的
|
||||||
|
fatal = True
|
||||||
|
|
||||||
|
# 3. 危险操作检查
|
||||||
|
if check_dangerous:
|
||||||
|
from utils.validators import check_dangerous_operations
|
||||||
|
safe, dangers = check_dangerous_operations(sql)
|
||||||
|
if not safe:
|
||||||
|
errors.append(f"包含危险操作: {', '.join(dangers)}")
|
||||||
|
fatal = True
|
||||||
|
|
||||||
|
return {
|
||||||
|
"errors": errors,
|
||||||
|
"warnings": warnings,
|
||||||
|
"suggestions": suggestions,
|
||||||
|
"fatal": fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
def _llm_validation(self, sql: str, schema_str: str) -> Dict:
|
||||||
|
"""
|
||||||
|
LLM语义验证
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema_str: Schema描述
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
验证结果
|
||||||
|
"""
|
||||||
|
from config.prompts import VALIDATOR_USER
|
||||||
|
|
||||||
|
prompt = VALIDATOR_USER.format(sql=sql, schema=schema_str)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.agent.step(prompt)
|
||||||
|
content = response.msg.content.strip()
|
||||||
|
|
||||||
|
# 解析JSON响应
|
||||||
|
result = self._parse_json_response(content)
|
||||||
|
|
||||||
|
# 标准化字段
|
||||||
|
return {
|
||||||
|
"errors": result.get("errors", []),
|
||||||
|
"warnings": result.get("warnings", []),
|
||||||
|
"suggestions": result.get("suggestions", []),
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"LLM验证异常: {e}")
|
||||||
|
return {
|
||||||
|
"errors": [f"LLM验证失败: {str(e)}"],
|
||||||
|
"warnings": [],
|
||||||
|
"suggestions": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def _parse_json_response(self, content: str) -> Dict:
|
||||||
|
"""解析JSON响应"""
|
||||||
|
try:
|
||||||
|
# 提取```json```块
|
||||||
|
if "```json" in content:
|
||||||
|
start = content.find("```json") + 7
|
||||||
|
end = content.find("```", start)
|
||||||
|
if end != -1:
|
||||||
|
content = content[start:end].strip()
|
||||||
|
|
||||||
|
return json.loads(content)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.warning(f"JSON解析失败: {e}, content={content[:200]}")
|
||||||
|
return {"errors": ["验证结果解析失败"], "warnings": [], "suggestions": []}
|
||||||
|
|
||||||
|
def quick_check(self, sql: str) -> Tuple[bool, List[str]]:
|
||||||
|
"""
|
||||||
|
快速检查(仅语法和危险操作)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否通过, 错误列表)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# 语法
|
||||||
|
syntax_ok, syntax_errors = validate_sql_syntax(sql)
|
||||||
|
if not syntax_ok:
|
||||||
|
errors.extend(syntax_errors)
|
||||||
|
return False, errors
|
||||||
|
|
||||||
|
# 危险操作
|
||||||
|
from utils.validators import check_dangerous_operations
|
||||||
|
safe, dangers = check_dangerous_operations(sql)
|
||||||
|
if not safe:
|
||||||
|
errors.append(f"危险操作: {', '.join(dangers)}")
|
||||||
|
return False, errors
|
||||||
|
|
||||||
|
return True, []
|
||||||
|
|
||||||
|
def suggest_fixes(
|
||||||
|
self,
|
||||||
|
sql: str,
|
||||||
|
errors: List[str],
|
||||||
|
schema_str: str
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
根据错误建议修复方案
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: 原始SQL
|
||||||
|
errors: 错误列表
|
||||||
|
schema_str: Schema描述
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
修复建议列表
|
||||||
|
"""
|
||||||
|
suggestions = []
|
||||||
|
|
||||||
|
# 常见错误模式匹配
|
||||||
|
for error in errors:
|
||||||
|
error_lower = error.lower()
|
||||||
|
|
||||||
|
if "field not exist" in error_lower or "字段不存在" in error_lower:
|
||||||
|
suggestions.append(
|
||||||
|
"建议:检查字段名拼写,或使用schema_manager.get_table(table).column_names查看可用字段"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "table not exist" in error_lower or "表不存在" in error_lower:
|
||||||
|
suggestions.append(
|
||||||
|
"建议:检查表名拼写,或使用schema_manager.list_tables()查看所有表"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "missing join condition" in error_lower or "缺少on条件" in error_lower:
|
||||||
|
suggestions.append(
|
||||||
|
"建议:为每个JOIN添加明确的ON条件,基于外键关系"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "group by" in error_lower:
|
||||||
|
suggestions.append(
|
||||||
|
"建议:SELECT中的所有非聚合字段都必须出现在GROUP BY子句中"
|
||||||
|
)
|
||||||
|
|
||||||
|
# LLM补充建议
|
||||||
|
if len(suggestions) < len(errors):
|
||||||
|
try:
|
||||||
|
prompt = f"""
|
||||||
|
SQL: {sql}
|
||||||
|
错误: {errors}
|
||||||
|
Schema: {schema_str[:1000]}
|
||||||
|
|
||||||
|
请给出2-3条具体的修复建议(简洁明了):
|
||||||
|
"""
|
||||||
|
response = self.agent.step(prompt)
|
||||||
|
llm_suggestions = response.msg.content.strip().split('\n')
|
||||||
|
suggestions.extend([s for s in llm_suggestions if s.strip()])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取LLM建议失败: {e}")
|
||||||
|
|
||||||
|
return suggestions[:5] # 最多5条
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# config 包初始化
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,321 @@
|
|||||||
|
"""
|
||||||
|
Prompt模板配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
# ========== Schema Linker Agent Prompt ==========
|
||||||
|
SCHEMA_LINKER_SYSTEM = """你是一个经验丰富的数据库架构师,专门负责数据库表结构分析与关联。
|
||||||
|
|
||||||
|
**任务**:从大量数据表中识别与用户问题相关的表。
|
||||||
|
|
||||||
|
**硬性约束(必须遵守)**:
|
||||||
|
1. **只依据明确信息**:仅根据用户问题中**清楚写出的**实体、指标、时间范围、业务对象选表;不要为「可能还需要」的维度擅自加表。
|
||||||
|
2. **表名来源唯一**:`relevant_tables` 中的每一个表名必须**原样来自**下方「可用的表列表」,字符完全一致;**禁止**编造列表中不存在的表名、近似拼写或臆测表名。
|
||||||
|
3. **中英对照**:若用户混用中英文,仅允许对问题里**已出现的**中文业务词做合理对应到列表中的英文表名;**不得**因翻译而引入列表外的表。
|
||||||
|
4. **对手方 / 经纪商维度**:若问题含「对手方」「经纪商」「券商」或英文 broker,且下方列表中**同时**出现含 `BrokerID` 的经纪合约类事实表(如 `TSBBrokerContract`)与维表 `MCBroker`,应**优先**选入该事实表与 `MCBroker`,以便按经纪商汇总并展示名称;**不要**仅因名称含 Unsettle/报表就选一堆 `VSBHKRpt*Unsettle*` 视图——若列表里这些视图明显缺少 `BrokerID` 等经纪商键,则不足以回答「按对手方」,须选事实表+维表。
|
||||||
|
|
||||||
|
**工作流程**:
|
||||||
|
1. 分析问题中的实体(名词、概念)和操作(查询、统计、比较等)
|
||||||
|
2. 从提供的Schema信息中匹配可能的表(基于表名、表注释、字段名、字段注释)
|
||||||
|
3. 考虑表间外键关系,确保JOIN完整性(不要漏掉关联表)
|
||||||
|
4. 输出精简的Schema子集(最多5张核心表)
|
||||||
|
|
||||||
|
**输出要求**:
|
||||||
|
- 必须输出严格JSON格式,不要其他内容
|
||||||
|
- 包含字段:relevant_tables(表名字符串列表)、reasoning(选择理由)
|
||||||
|
|
||||||
|
**示例输出**:
|
||||||
|
{
|
||||||
|
"relevant_tables": ["MCAccount", "MCAccountInstrument"],
|
||||||
|
"reasoning": "问题涉及账户和持仓,MCAccount存储账户基本信息,MCAccountInstrument存储账户持仓数据,两表通过AccountID关联"
|
||||||
|
}
|
||||||
|
|
||||||
|
现在开始工作:
|
||||||
|
"""
|
||||||
|
|
||||||
|
SCHEMA_LINKER_USER = """用户问题:{question}
|
||||||
|
|
||||||
|
可用的表列表(部分):
|
||||||
|
{table_list}
|
||||||
|
|
||||||
|
请选出与问题最相关的表(最多5张),输出JSON格式:{{"relevant_tables": [...], "reasoning": "..."}}"""
|
||||||
|
|
||||||
|
|
||||||
|
# ========== SQL Generator Agent Prompt ==========
|
||||||
|
SQL_GENERATOR_SYSTEM = """你是一个精通SQL的数据库专家,有10年以上复杂查询编写经验。
|
||||||
|
|
||||||
|
**任务**:根据提供的数据库Schema和用户问题,生成准确、高效的SQL语句。
|
||||||
|
|
||||||
|
**目标数据库(强制)**:本项目**一律按 Microsoft SQL Server(T-SQL)** 编写语句,除非用户消息中的「数据库方言」明确指定为其他产品。禁止 MySQL 专属写法:反引号 `` ` ``、`CURDATE()`、`NOW()` 作日期、`LIMIT`、`CONCAT` 作字符串拼接等;应使用方括号 `[]`(必要时)、`CAST(GETDATE() AS DATE)`、`TOP`/`OFFSET-FETCH`、`+` 拼接字符串等 T-SQL 语法。
|
||||||
|
|
||||||
|
**何为「有用 SQL」(合格输出的唯一标准)**:
|
||||||
|
- 必须是**可直接执行**、且与下方 **「业务级黄金范例」** **同构**:大写关键字、`SELECT` 后每列独占一行并 **4 空格缩进**、表用短别名、`FROM`/`JOIN`/`WHERE`/`GROUP BY`/`ORDER BY` 分段清晰,`WHERE` 续行以 **`AND`** 开头;聚合/展示列需要 `AS` 时一律 **PascalCase 英文别名**(如 `BrokerName`、`TotalUnsettledAmount`)。
|
||||||
|
- **不合格**:整段挤成一行、小写关键字、随意 `snake_case` 别名;或问题明确要求**按某业务维度列出且需可读名称**(如「按对手方」)时,SELECT/GROUP BY 仅有裸 ID 而**不 LEFT JOIN 维表取名称列**——此类输出视为无效,须按黄金范例重写。
|
||||||
|
|
||||||
|
**硬性约束(必须遵守)**:
|
||||||
|
1. **合理推断业务语义**:用户问题中的时间范围(如"2024年1月")、状态含义(如"活跃"对应Active)、常见业务默认值(如"当前"指近期),应根据Schema中的字段注释和常见业务逻辑进行合理推断并转化为WHERE条件;但禁止编造问题中未提及的过滤维度或指标。
|
||||||
|
2. **禁止虚构值与占位符**:不得使用 `'[日期]'`、`TODO`、`xxx`、空泛占位等冒充具体字面量。若用户未给出具体日期、代码或 ID,应根据问题上下文推断合理值(如"2024年1月" → `ValueDate >= '2024-01-01' AND ValueDate < '2024-02-01'`),或使用Schema中常见的枚举值(如状态字段的`A/D/X`),**不要**留空或写占位符。
|
||||||
|
3. **输出版式与别名风格(统一规范)**:除遵守目标方言语法外,SQL **排版与命名**须与下方「标准版式范例」一致:
|
||||||
|
- **关键字**:`SELECT`、`FROM`、`JOIN`/`LEFT JOIN`、`ON`、`WHERE`、`AND`、`GROUP BY`、`ORDER BY`、`HAVING` 等使用**大写**。
|
||||||
|
- **换行与缩进**:`SELECT` 后换行;每个输出列**独占一行**,行首 **4 个空格**,列表达式之间用**行尾逗号**分隔(最后一列无逗号)。
|
||||||
|
- **`FROM` / `JOIN`**:主表与关联表各占一行;每张表使用**简短单字母或缩写别名**(如 `b`、`m`);`LEFT JOIN ... ON ...` 写全。
|
||||||
|
- **`WHERE`**:第一行写 `WHERE` 与首个条件;后续条件**每行一条**,行首两个空格后以 **`AND`** 开头续写(与范例一致)。
|
||||||
|
- **`GROUP BY` / `ORDER BY`**:独占一行;需要时 `ORDER BY` 可使用 SELECT 中已定义的**列别名**(如 `ORDER BY TotalUnsettledAmount DESC`)。
|
||||||
|
- **列别名**:表名、列名必须与 Schema **完全一致**(英文标识符)。需要 `AS` 时(含从维表取名称、聚合、表达式结果),别名使用 **PascalCase 英文**(如 `BrokerName`、`TotalUnsettledAmount`、`UnsettledTradeCount`)。若 Schema 中确有该键列,可写 `b.BrokerID` 等,可不强制 `AS`。
|
||||||
|
- **方言(T-SQL)**:字符串用 `+` 拼接;「今日」用 `CAST(GETDATE() AS DATE)`;标识符冲突用方括号 `[]`;非关键字尽量不加引号。不要用 MySQL 反引号或 `CURDATE()`。
|
||||||
|
4. **中英混排时的翻译边界**:仅允许对问题里**已经出现**的中文业务用语,在语义上等价映射到 Schema 中的英文表名、列名;**禁止**借「翻译」编造 Schema 中不存在的表或字段。
|
||||||
|
5. **严禁照抄黄金范例里的表名与列名**:范例中的 `TSBBrokerContract`、`MCBroker`、`BrokerID`、`SettleStatus` 等**仅表示版式与业务意图**。**每一条** `FROM`/`JOIN` 引用的表、以及 `SELECT`/`WHERE`/`ON` 中的列,必须在本轮 **Schema信息** 所列字段中**真实存在**;若当前片段只有报表视图且无 `BrokerID`,则**禁止**写 `BrokerID`、**禁止** `JOIN MCBroker`,应改用片段内已有的键与度量(如 `AccountID`、`CashSettleDate`、`SettleAmount` 等)重写,仍保持「有用 SQL」版式。
|
||||||
|
|
||||||
|
**原则**:
|
||||||
|
1. 只输出SQL语句,不要解释、注释或其他内容(除非SQL内注释)
|
||||||
|
2. 使用 **T-SQL(SQL Server)** 语法(与标准 SQL 交集部分按 T-SQL 实现)
|
||||||
|
3. 正确处理NULL值(使用IS NULL/IS NOT NULL,而非= NULL)
|
||||||
|
4. 聚合查询必须正确使用GROUP BY
|
||||||
|
5. 多表JOIN要明确ON条件(基于外键关系)
|
||||||
|
6. 避免SELECT *,明确列出所需字段
|
||||||
|
|
||||||
|
**金融/证券业务特殊注意**:
|
||||||
|
- 金额字段通常为DECIMAL类型,注意精度
|
||||||
|
- 日期字段常见:ValueDate(价值日期)、TradeDate(交易日期)、BusinessDate(业务日期)
|
||||||
|
- 状态字段:State(A=Active, D=Deleted, X=SameDayDeleted);SettleStatus(U=Unsettled未结算, S=Settled已结算)等,请参考Schema字段注释中的枚举说明
|
||||||
|
- 币种字段:CurrencyID,多币种查询需注意
|
||||||
|
- 负债/负数含义:许多余额字段负值表示负债(如LoanBalance)
|
||||||
|
|
||||||
|
**常见时间/状态推断指南**(需结合Schema字段注释):
|
||||||
|
- "2024年1月" → `WHERE date_col >= '2024-01-01' AND date_col < '2024-02-01'`
|
||||||
|
- "今天" / "当日" → `WHERE date_col >= CAST(GETDATE() AS DATE) AND date_col < DATEADD(DAY,1,CAST(GETDATE() AS DATE))`
|
||||||
|
- "最近N天" → `WHERE date_col >= DATEADD(DAY, -N, CAST(GETDATE() AS DATE))`
|
||||||
|
- "活跃" → 通常对应 `State = 'A'`(需确认Schema注释)
|
||||||
|
- "未结算" → 通常对应 `SettleStatus = 'U'` 或 `Settled = 0`
|
||||||
|
- "已删除" → 通常对应 `State = 'D'`
|
||||||
|
|
||||||
|
**重要**:以上推断需结合当前Schema中对应字段的实际注释进行调整;若Schema字段注释明确给出了枚举值映射,必须按注释执行。
|
||||||
|
|
||||||
|
**输出格式**:纯SQL字符串,或JSON格式:{"sql": "...", "explanation": "..."}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**业务级黄金范例(中文问题 ↔ 有用 SQL,版式即标准答案)**:
|
||||||
|
|
||||||
|
用户问题(示例):按对手方列出截至 2026-04-02 的所有未结算交易。
|
||||||
|
|
||||||
|
(日期规则:用户问题里**已写出具体日期**时,在 WHERE 中写入相同字面量,例如 `CashSettleDate <= '2026-04-02'`;**未给出具体日期**但包含时间范围描述(如"2024年1月")时,应合理推断为日期区间,例如 `OrderDate >= '2024-01-01' AND OrderDate < '2024-02-01'`,禁止使用 `'[日期]'` 等占位符。)
|
||||||
|
|
||||||
|
SQL(表名、字段名须与当前 Schema 一致;**以下版式、别名、JOIN/WHERE/GROUP BY/ORDER BY 结构为强制模板**):
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
b.BrokerID,
|
||||||
|
m.Name AS BrokerName,
|
||||||
|
COUNT(*) AS UnsettledTradeCount,
|
||||||
|
SUM(b.SettleQuantity) AS TotalUnsettledQty,
|
||||||
|
SUM(b.SettleAmount) AS TotalUnsettledAmount,
|
||||||
|
MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,
|
||||||
|
MIN(b.CashSettleDate) AS EarliestSettleDate,
|
||||||
|
MAX(b.CashSettleDate) AS LatestSettleDate
|
||||||
|
FROM TSBBrokerContract b
|
||||||
|
LEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID
|
||||||
|
WHERE b.SettleStatus = 'U'
|
||||||
|
AND b.CashSettleDate <= '2026-04-02'
|
||||||
|
GROUP BY b.BrokerID, m.Name
|
||||||
|
ORDER BY TotalUnsettledAmount DESC;
|
||||||
|
|
||||||
|
(说明:`TSBBrokerContract`/`MCBroker`/`BrokerID`/`SettleStatus` 等为**演示用**名称;**禁止**在 Schema 未包含这些对象时照抄。生成时**仅使用本轮 Schema 中的真实表名与列名**,但**不得改变**排版、PascalCase 别名风格与「按维度聚合 + 需要名称时 LEFT JOIN 维表 + 日期/业务条件 + ORDER BY 度量别名」的结构。字符串拼接一律用 T-SQL 的 `+`。)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**示例(版式与范例一致)**:
|
||||||
|
|
||||||
|
示例1 - 单表查询:
|
||||||
|
问题:查询账户ID为'ACC001'的账户余额
|
||||||
|
Schema: MCAccount(AccountID, Name, AvailableBalance, MarketValue, MarginValue)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
AccountID,
|
||||||
|
Name AS AccountName,
|
||||||
|
AvailableBalance,
|
||||||
|
MarketValue,
|
||||||
|
MarginValue
|
||||||
|
FROM MCAccount
|
||||||
|
WHERE AccountID = 'ACC001';
|
||||||
|
|
||||||
|
示例2 - 多表 INNER JOIN:
|
||||||
|
问题:查询账户'ACC001'持有的所有股票及数量
|
||||||
|
Schema: MCAccount(AccountID), MCAccountInstrument(AccountID, MarketID, InstrumentID, Settled)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
a.AccountID,
|
||||||
|
i.MarketID,
|
||||||
|
i.InstrumentID AS InstrumentCode,
|
||||||
|
i.Settled AS HoldingQty
|
||||||
|
FROM MCAccount a
|
||||||
|
JOIN MCAccountInstrument i ON a.AccountID = i.AccountID
|
||||||
|
WHERE a.AccountID = 'ACC001';
|
||||||
|
|
||||||
|
示例3 - 时间范围推断(关键!):
|
||||||
|
问题:查询2024年1月的总销售额
|
||||||
|
Schema: Sales(OrderID, OrderDate, Amount, ProductID)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
SUM(Amount) AS TotalSales,
|
||||||
|
COUNT(DISTINCT OrderID) AS OrderCount
|
||||||
|
FROM Sales
|
||||||
|
WHERE OrderDate >= '2024-01-01'
|
||||||
|
AND OrderDate < '2024-02-01';
|
||||||
|
|
||||||
|
示例4 - 状态推断(关键!):
|
||||||
|
问题:统计所有活跃账户的数量
|
||||||
|
Schema: MCAccount(AccountID, State, OpenDate) -- State说明: A=Active, D=Deleted, X=SameDayDeleted
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS ActiveAccountCount
|
||||||
|
FROM MCAccount
|
||||||
|
WHERE State = 'A';
|
||||||
|
|
||||||
|
示例5 - 聚合 + 排序:
|
||||||
|
问题:按市场统计现金余额总额,从高到低排序
|
||||||
|
Schema: BCAccountMarketCash(AccountID, MarketID, Settled, UnderdueBuy, DueBuy)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
MarketID,
|
||||||
|
SUM(Settled) AS TotalSettled,
|
||||||
|
SUM(UnderdueBuy) AS TotalUnderdueBuy,
|
||||||
|
SUM(DueBuy) AS TotalDueBuy
|
||||||
|
FROM BCAccountMarketCash
|
||||||
|
GROUP BY MarketID
|
||||||
|
ORDER BY TotalSettled DESC;
|
||||||
|
|
||||||
|
示例6 - 日期"今天"的处理:
|
||||||
|
问题:查询今天的交易记录
|
||||||
|
Schema: Ledger(TransactionID, TradeDate, Amount, AccountID)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
TransactionID,
|
||||||
|
TradeDate,
|
||||||
|
Amount,
|
||||||
|
AccountID
|
||||||
|
FROM Ledger
|
||||||
|
WHERE TradeDate >= CAST(GETDATE() AS DATE)
|
||||||
|
AND TradeDate < DATEADD(DAY, 1, CAST(GETDATE() AS DATE));
|
||||||
|
|
||||||
|
示例7 - LEFT JOIN取名称(按维度展示可读名称):
|
||||||
|
问题:按产品类别统计销售额
|
||||||
|
Schema: Sales(ProductID, Amount, SaleDate), Product(ProductID, ProductName, CategoryName)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
p.CategoryName,
|
||||||
|
SUM(s.Amount) AS TotalSales
|
||||||
|
FROM Sales s
|
||||||
|
LEFT JOIN Product p ON s.ProductID = p.ProductID
|
||||||
|
GROUP BY p.CategoryName
|
||||||
|
ORDER BY TotalSales DESC;
|
||||||
|
|
||||||
|
现在开始工作:
|
||||||
|
"""
|
||||||
|
|
||||||
|
SQL_GENERATOR_USER = """Schema信息:
|
||||||
|
{schema}
|
||||||
|
|
||||||
|
用户问题:{question}
|
||||||
|
|
||||||
|
数据库方言:{dialect}
|
||||||
|
|
||||||
|
请生成**有用 SQL**(见系统提示定义):必须与「业务级黄金范例」**同构**——大写关键字、多行缩进版式、PascalCase 别名、该展示对手方/账户等名称时须 LEFT JOIN 维表;禁止输出挤成一行的「极简 SQL」。"""
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Validator Agent Prompt ==========
|
||||||
|
VALIDATOR_SYSTEM = """你是一个严谨的SQL审核员,负责验证SQL语句的正确性和安全性。
|
||||||
|
|
||||||
|
**验证清单**:
|
||||||
|
1. ✓ 语法正确(能够被SQL解析器解析)
|
||||||
|
2. ✓ 所有表名存在于提供的Schema中
|
||||||
|
3. ✓ 所有字段名属于对应的表(检查表.字段格式)
|
||||||
|
4. ✓ JOIN条件完整(每个JOIN都有ON条件,ON条件字段存在且类型兼容)
|
||||||
|
5. ✓ 聚合查询(GROUP BY)包含所有非聚合SELECT字段,或有合理的函数包裹
|
||||||
|
6. ✓ WHERE条件合理(没有明显逻辑错误)
|
||||||
|
7. ✓ HAVING子句只在聚合查询中使用
|
||||||
|
8. ✓ 排序字段存在
|
||||||
|
9. ✓ 无危险操作(DROP/DELETE/UPDATE/INSERT/ALTER/TRUNCATE等,除非明确允许)
|
||||||
|
10. ✓ 无明显性能问题(如全表扫描无WHERE、过度JOIN等)
|
||||||
|
|
||||||
|
**输出格式**(严格JSON):
|
||||||
|
{
|
||||||
|
"valid": true/false,
|
||||||
|
"errors": ["具体错误1", "具体错误2"],
|
||||||
|
"warnings": ["警告信息1"],
|
||||||
|
"suggestions": ["优化建议1"]
|
||||||
|
}
|
||||||
|
|
||||||
|
**错误类型说明**:
|
||||||
|
- "syntax_error": SQL语法错误
|
||||||
|
- "unknown_table": 表名不存在
|
||||||
|
- "unknown_column": 字段名不存在
|
||||||
|
- "missing_join_condition": JOIN缺少ON条件
|
||||||
|
- "missing_group_by": 聚合查询缺少GROUP BY
|
||||||
|
- "dangerous_operation": 危险操作
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
现在开始验证:
|
||||||
|
"""
|
||||||
|
|
||||||
|
VALIDATOR_USER = """需要验证的SQL:
|
||||||
|
{sql}
|
||||||
|
|
||||||
|
对应的Schema:
|
||||||
|
{schema}
|
||||||
|
|
||||||
|
请输出验证结果JSON:"""
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Few-Shot 示例 ==========
|
||||||
|
FEW_SHOT_EXAMPLES: Dict[str, str] = {
|
||||||
|
"single_table": """
|
||||||
|
示例:单表查询
|
||||||
|
问题:查询所有状态为Active的账户数量
|
||||||
|
Schema: MCAccount(AccountID, State, OpenDate)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS ActiveAccountCount
|
||||||
|
FROM MCAccount
|
||||||
|
WHERE State = 'A';
|
||||||
|
""",
|
||||||
|
"join_query": """
|
||||||
|
示例:多表JOIN
|
||||||
|
问题:查询账户'ACC001'的持仓信息(包括股票代码、数量、成本)
|
||||||
|
Schema: MCAccount(AccountID), MCAccountInstrument(AccountID, MarketID, InstrumentID, Settled), HCInstrumentClosingPrice(MarketID, InstrumentID, ClosingPrice)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
i.InstrumentID AS InstrumentCode,
|
||||||
|
i.Settled AS HoldingQty,
|
||||||
|
p.ClosingPrice AS ClosingPx
|
||||||
|
FROM MCAccount a
|
||||||
|
JOIN MCAccountInstrument i ON a.AccountID = i.AccountID
|
||||||
|
JOIN HCInstrumentClosingPrice p ON i.MarketID = p.MarketID AND i.InstrumentID = p.InstrumentID
|
||||||
|
WHERE a.AccountID = 'ACC001';
|
||||||
|
""",
|
||||||
|
"aggregation": """
|
||||||
|
示例:聚合统计
|
||||||
|
问题:统计每个市场的现金余额总额
|
||||||
|
Schema: BCAccountMarketCash(AccountID, MarketID, Settled, UnderdueBuy, DueBuy)
|
||||||
|
SQL:
|
||||||
|
SELECT
|
||||||
|
MarketID,
|
||||||
|
SUM(Settled) AS TotalSettled,
|
||||||
|
SUM(UnderdueBuy) AS TotalUnderdueBuy,
|
||||||
|
SUM(DueBuy) AS TotalDueBuy
|
||||||
|
FROM BCAccountMarketCash
|
||||||
|
GROUP BY MarketID;
|
||||||
|
""",
|
||||||
|
"date_filter": """
|
||||||
|
示例:日期筛选
|
||||||
|
问题:查询2024年1月有交易的账户
|
||||||
|
Schema: HCLedgerBalance(ValueDate, LedgerID, Amount), CompanyBusinessDate(BusinessDate)
|
||||||
|
SQL:
|
||||||
|
SELECT DISTINCT
|
||||||
|
LedgerID AS LedgerKey
|
||||||
|
FROM HCLedgerBalance
|
||||||
|
WHERE ValueDate >= '2024-01-01'
|
||||||
|
AND ValueDate < '2024-02-01';
|
||||||
|
""",
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
配置管理模块
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""应用程序配置"""
|
||||||
|
|
||||||
|
# DeepSeek配置
|
||||||
|
deepseek_api_key: str
|
||||||
|
deepseek_base_url: str = "https://api.deepseek.com"
|
||||||
|
model_primary: str = "deepseek-chat"
|
||||||
|
temperature: float = 0.3
|
||||||
|
max_tokens: int = 4096
|
||||||
|
|
||||||
|
# Embedding配置
|
||||||
|
embedding_model_path: str = "./data/models/Qwen3-Embedding-0.6B"
|
||||||
|
vector_dim: int = 2048
|
||||||
|
use_local_embedding: bool = True
|
||||||
|
|
||||||
|
# Schema配置
|
||||||
|
schema_dir: str = "./data/schemas"
|
||||||
|
schema_file: Optional[str] = None # 特定schema文件路径
|
||||||
|
vector_db_path: str = "./data/embeddings/chroma"
|
||||||
|
|
||||||
|
# 验证配置
|
||||||
|
enable_sql_validation: bool = True
|
||||||
|
block_dangerous_sql: bool = True
|
||||||
|
dangerous_keywords: List[str] = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE"]
|
||||||
|
|
||||||
|
# 重试配置
|
||||||
|
max_retry: int = 2
|
||||||
|
retry_delay: float = 1.0
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
log_level: str = "INFO"
|
||||||
|
log_file: Optional[str] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
case_sensitive = False
|
||||||
|
|
||||||
|
|
||||||
|
# 全局配置实例
|
||||||
|
settings = Settings()
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
# Text2SQL 经验数据集
|
||||||
|
|
||||||
|
**来源**: `Example_text2sql.md`
|
||||||
|
**样本数**: 50
|
||||||
|
**生成时间**: 2026-04-10 14:51:56
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
- `all_samples.jsonl` - 所有样本(JSONL格式)
|
||||||
|
- `difficulty_*.jsonl` - 按难度分类
|
||||||
|
- `by_tag/` - 按标签分类(每个标签一个文件)
|
||||||
|
- `fewshot_examples.md` - Few-shot示例(Markdown格式)
|
||||||
|
- `high_rating_samples.jsonl` - 高评分样本(rating≥8)
|
||||||
|
|
||||||
|
## 使用建议
|
||||||
|
|
||||||
|
1. **Few-shot prompting**: 使用 `fewshot_examples.md` 中的示例
|
||||||
|
2. **训练数据**: 使用 `all_samples.jsonl` 进行模型微调
|
||||||
|
3. **评估测试**: 使用 `high_rating_samples.jsonl` 作为测试集
|
||||||
|
4. **领域适应**: 按标签选择相关示例(如broker相关查询)
|
||||||
Binary file not shown.
@@ -0,0 +1,50 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q3", "question_zh": "今日结算使用的汇率是多少?", "sql": "-- 查询汇率类型列表 (固定日期: 2026-04-02)\nSELECT \n ExchangeRateID, \n Name, \n NameEx\nFROM HCExchangeRate\nWHERE ValueDate = '2026-04-02'\nORDER BY ExchangeRateID;", "schema_info": {"tables": {"HCExchangeRate": {"alias": "HCExchangeRate", "columns": []}}, "joins": []}, "explanation": "- `HCExchangeRate` 存储汇率类型定义\n- `HCExchangeRateDetail` 存储各货币对的汇率值\n- 两个表通过 `ExchangeRateID` + `ValueDate` 关联\n- `ExchangeRateDirection` = 'D' 表示直接汇率", "question_en": "What are the currency exchange rates used for today's settlement?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["exchange_rate", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q4", "question_zh": "哪些券商尚有未履行的交收义务?", "sql": "-- 查询有未履行交收义务的经纪商(截至 2026-04-02)\n-- Settled < 0 表示经纪商欠我们的现金(未履行交收义务)\n-- 注意:BCBrokerCash.Settled 是经纪商在我们账户的已结算现金余额,负数=经纪商欠款\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n b.CurrencyID,\n b.Settled AS OwedToUsAmount,\n ABS(b.Settled) AS OutstandingAmount\nFROM BCBrokerCash b\nINNER JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.Settled < 0\nORDER BY ABS(b.Settled) DESC;", "schema_info": {"tables": {"BCBrokerCash": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `BCBrokerCash` 记录经纪商在我们账户的结算现金,`Settled < 0` = 经纪商欠我们(未履行交收义务)\n- 关联 `MCBroker` 获取经纪商名称\n- 按欠款金额降序排列,优先处理大额欠款", "question_en": "Which brokers have outstanding delivery obligations to us?", "rating": 8, "execution_result": null, "result_note": "| BrokerID | BrokerName | CurrencyID | OwedToUsAmount | OutstandingAmount |\n|----------|-----------|------------|-----------------|-------------------|\n| CCASS | CCASS | CNY | -52,680.00 | 52,680.00 |\n| CCASS | CCASS | USD | -90.00 | 90.00 |\n\n> 注:CCASS 欠我们的结算现金(CNY 52,680 + USD 90)。HKD 余额为正(+4,202,811.87)表示我们欠 CCASS,不属于\"未履行交收义务\"范畴。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q5", "question_zh": "列出今日发出的所有追收保证金通知及其回复状态。", "sql": "-- 查询今日追缴保证金记录 (参考日期: 2026-04-02)\n-- 使用 MCAccount 的账户级追缴字段(CallForDepositDate = 追缴通知发出日期)\n-- 注意:HCAccountMarginCallForDeposit 是追缴每日快照表,不含\"通知发出日\"字段\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.CallForDeposit AS CallAmount,\n a.CallForDepositDate AS IssueDate,\n a.CallForDepositDays AS OverdueDays\nFROM MCAccount a\nWHERE a.CallForDepositDate = '2026-04-02'\nORDER BY a.CallForDeposit DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.CallForDepositDate` = 追缴通知发出日期,`CallForDeposit` = 追缴金额\n- `CallForDeposit < 0` 表示已结清;如查活跃追缴,应筛选 `CallForDeposit > 0`\n- `HCAccountMarginCallForDeposit` 是追缴每日历史快照,不适合查询\"今日发出的通知\"\n- `CallStatus` 字段在 `HCAccountMarginCallForDeposit` 中不存在", "question_en": "Show all margin calls issued today and their response status.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果(测试环境中 MCAccount 的 `CallForDepositDate` 最新到 2025-10-31,2026-04-02 无追缴通知记录)\n\n> 注:`MCAccount.CallForDeposit` 当前所有值均为负数(已结清的追缴金额),无活跃追缴记录。", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q8", "question_zh": "明天生效的公司行动涉及哪些证券?", "sql": "-- 查询明日生效的公司行动 (固定日期: 2026-04-03)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate AS EffectiveDate,\n e.BalanceExDate AS ExDate,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE (e.BalanceDate = '2026-04-03' OR e.BalanceExDate = '2026-04-03')\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录权益分派信息\n- `BalanceDate` 权益登记日期\n- `BalanceExDate` 除权日期", "question_en": "Which instruments have corporate actions effective tomorrow?", "rating": 8, "execution_result": null, "result_note": "当前查询无结果(2026-04-03无公司行动)", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q9", "question_zh": "今日预计收到哪些股息?", "sql": "-- 查询今日预计收到的股息 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceDate AS PaymentDate,\n e.BalanceType,\n e.Remark AS DividendDetails\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.BalanceDate = '2026-04-02'\n AND e.BalanceType = 'C'\n AND e.State = 'A'\nORDER BY i.Name;", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派\n- `BalanceType = 'C'` 表示现金股息\n- `BalanceDate` 股息支付日期", "question_en": "What dividends are expected to be received today?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q10", "question_zh": "列出所有进行中IPO的待缴申购金额。", "sql": "-- 查询进行中IPO的待缴金额\nSELECT \n ip.InstrumentID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.SubscriptionAmount,\n ip.SubscriptionPrice,\n ip.Quantity AS SubscriptionLot,\n ip.CreatedOn AS SubscriptionDate,\n ip.State AS SubscriptionStatus\nFROM MCInstrumentIPODetail ip\nINNER JOIN MCInstrument i ON ip.InstrumentID = i.InstrumentID\nWHERE ip.State = 'A'\n AND i.State = 'A'\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "ip.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MCInstrumentIPODetail` 记录IPO认购详情\n- `SubscriptionAmount` 申购金额\n- `State = 'A'` 表示进行中", "question_en": "Show all pending subscription amounts for ongoing IPOs.", "rating": 8, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | SubscriptionAmount | SubscriptionPrice | Quantity |\n|--------------|----------------|-------------------|-------------------|----------|\n| 02259 | HKEX | 7,231.19 | 36.16 | 100 |\n| 02259 | HKEX | 14,462.40 | 36.16 | 200 |\n\n> 注:MCInstrumentIPODetail 共 586 条进行中 IPO 记录(State='A'),以上为示例行。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q11", "question_zh": "哪些客户尚未缴纳IPO申购款?", "sql": "-- 查询未缴纳IPO申购款的账户\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.TotalBoughtQuantity AS SubscriptionLot,\n bi.TotalBoughtConsideration AS SubscriptionAmount\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nINNER JOIN MCInstrumentIPODetail ip ON bi.InstrumentID = ip.InstrumentID\nWHERE ip.State = 'A'\n AND bi.TotalBoughtConsideration > 0\n AND bi.TotalBoughtFee = 0\nORDER BY bi.TotalBoughtConsideration DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}, {"type": "INNER", "on": "bi.InstrumentID = ip.InstrumentID"}]}, "explanation": "- 通过比对买卖数量和手续费判断是否缴款\n- `TotalBoughtConsideration > 0` 有认购\n- `TotalBoughtFee = 0` 未支付手续费", "question_en": "Which clients have not yet paid their IPO subscription money?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q12", "question_zh": "本月累计的结算失败罚金是多少?", "sql": "-- 查询本月结算失败罚金 (2026年4月)\n-- 注意:BCAccountInstrument 无 FailedTradePenalty 字段,需另找结算罚金存储位置\n-- 目前未找到结算失败罚金字段,建议通过 TSBBrokerContract 或 BCBrokerCash 进一步排查\nSELECT \n COUNT(*) AS FailedTradeCount,\n MarketID\nFROM BCAccountInstrument\nWHERE YEAR(LastTransactionDate) = 2026\n AND MONTH(LastTransactionDate) = 4\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 无 `FailedTradePenalty` 字段,原始 SQL 会报错\n- `TSBBrokerContract` 或 `BCBrokerCash` 中可能存储结算相关费用,需进一步确认", "question_en": "What is the total failed trade penalty incurred this month?", "rating": 3, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 表无 FailedTradePenalty 字段,无法统计结算罚金)", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q13", "question_zh": "列出今日到期的所有股票借贷交易。", "sql": "-- 查询今日到期的股票借贷 (参考日期: 2026-04-02)\n-- MCMarket 表仅包含 HKEX/OSL/SETW,无独立借贷市场标识\n-- 股票借贷可能在持仓变动类型 HoldType 中标识,或在专门的 StockBorrowing 表中\n-- 此 SQL 按 DueBuy/DueSell 显示当前待交收证券,作为借贷合约的近似替代\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.DueBuy AS BorrowQuantity,\n bi.DueSell AS LoanQuantity,\n bi.DueBuy - bi.DueSell AS NetBorrow\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE bi.DueBuy > 0 OR bi.DueSell > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `DueBuy/DueSell` 表示待交收数量(借款买入/借出卖出),反映股票借贷待到期情况\n- `MCMarket` 表无 `MarketType='BORROW'` 标识,现有市场仅为 HKEX/OSL/SETW\n- 股票借贷详情可能存储在 `TSBAccountLoanContract` 或其他借贷专用表中,需业务确认", "question_en": "List all stock borrow/loan transactions due to settle today.", "rating": 5, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 中 DueBuy/DueSell 均为 0,无待交收证券)", "tags": ["join", "trade", "filter"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q14", "question_zh": "列出已申请但尚未处理的客户提款请求。", "sql": "-- 查询待处理提款申请 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.TodayOut AS WithdrawalAmount,\n c.BankAvailableBalance AS AvailableBalance,\n c.State AS RecordStatus\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.TodayOut > 0\nORDER BY c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `TodayOut` 今日计划支出\n- 待处理提款申请应体现在此字段", "question_en": "Show all client withdrawals requested but not yet processed.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q16", "question_zh": "列出T+2交易缺少结算指令的账户。", "sql": "-- 查询T+2待交收但缺少指令的账户\n-- 注意:BCAccountInstrumentInstruction 表不存在,需改用 TSBSettlementInstruction\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.NextDayDueBuy AS T2DueBuy,\n bi.NextDayDueSell AS T2DueSell\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE (bi.NextDayDueBuy > 0 OR bi.NextDayDueSell > 0)\n AND NOT EXISTS (\n SELECT 1 FROM TSBSettlementInstruction si\n WHERE si.AccountOrBrokerID = bi.AccountID\n AND si.InstrumentID = bi.InstrumentID\n AND si.State IN ('P','A')\n );", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "TSBSettlementInstruction": {"alias": "si", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `NextDayDueBuy/NextDayDueSell` T+2交收数量(次日到期)\n- `TSBSettlementInstruction` 是结算指令表,通过 `NOT EXISTS` 排除已有指令的记录\n- `BCAccountInstrumentInstruction` 表不存在,不能使用", "question_en": "Show accounts where settlement instruction is missing for T+2 trades.", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "trade"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q17", "question_zh": "哪些客户已超过其信用额度?", "sql": "-- 查询超信用额度客户(使用 MarginLimit)\n-- 注意:MCAccount 无 CreditLimit 字段,应用 MarginLimit\nSELECT \n AccountID,\n Name AS AccountName,\n MarginLimit,\n MarketValue,\n MarginLimit - MarketValue AS AvailableCredit,\n CASE \n WHEN MarketValue > MarginLimit THEN '超额'\n WHEN MarketValue > MarginLimit * 0.8 THEN '接近限额'\n ELSE '正常'\n END AS Status\nFROM MCAccount\nWHERE MarginLimit > 0\n AND MarketValue > MarginLimit * 0.8\nORDER BY (MarketValue - MarginLimit) DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.MarginLimit` 保证金/信用限额(注意:无 `CreditLimit` 字段)\n- `MCAccount.MarketValue` 持仓市值\n- `MarginBLimit` 是单独借款限额,注意区分", "question_en": "Which clients have exceeded their credit limit?", "rating": 7, "execution_result": null, "result_note": "当前查询无超限客户", "tags": ["account", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q18", "question_zh": "今日收到的未清算支票总额是多少?", "sql": "-- 查询未清算支票 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.UnderdueCheque AS OverdueCheque,\n c.NextDayDueCheque AS PendingCheque,\n c.UnderdueCheque + c.NextDayDueCheque AS TotalUncleared\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.UnderdueCheque > 0 OR c.NextDayDueCheque > 0) -- 注意:BCAccountCash 无这两个字段,需确认实际表\nORDER BY TotalUncleared DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 无 `UnderdueCheque` / `NextDayDueCheque` 字段,需业务确认支票数据存储位置", "question_en": "What is the total amount of uncleared cheques received today?", "rating": 4, "execution_result": null, "result_note": "当前查询无未清算支票", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q19", "question_zh": "列出今日起保证金比例发生变动的所有证券。", "sql": "-- 查询保证金比例变动的证券 (固定日期: 2026-04-02)\nSELECT \n i.InstrumentID,\n i.Name AS InstrumentName,\n i.MarginPercentage AS CurrentMarginRatio,\n i.MarginChangeDate\nFROM MCInstrument i\nWHERE i.MarginChangeDate = '2026-04-02'\nORDER BY i.MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument.MarginPercentage` 当前保证金比例\n- `MarginChangeDate` 变动日期", "question_en": "List all instruments with margin ratio changes effective today.", "rating": 7, "execution_result": null, "result_note": "当前查询无保证金比例变动记录", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q20", "question_zh": "哪些公司行动的选择指令需在今日结束前提交?", "sql": "-- 查询今日截止的公司行动选择 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.ElectionPeriodTo AS Deadline,\n e.ElectionOptionFlag,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.ElectionPeriodTo = '2026-04-02'\n AND e.ElectionOptionFlag = 'Y'\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `ElectionPeriodTo` 选择截止日期\n- `ElectionOptionFlag = 'Y'` 需要选择", "question_en": "Which corporate action elections are due by end of day?", "rating": 8, "execution_result": null, "result_note": "当前查询无今日截止的选择", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q24", "question_zh": "有多少客户报表已生成成功但尚未发送?", "sql": "-- 查询已生成但未发送的报表\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.LastDailyStatementDate,\n a.LastMonthlyStatementDate,\n a.LastStatementGenerateTime,\n a.LastStatementSendTime,\n CASE \n WHEN a.LastStatementGenerateTime > a.LastStatementSendTime THEN '未发送'\n ELSE '已发送'\n END AS DeliveryStatus\nFROM MCAccount a\nWHERE a.LastStatementGenerateTime IS NOT NULL\n AND (a.LastStatementSendTime IS NULL \n OR a.LastStatementGenerateTime > a.LastStatementSendTime)\nORDER BY a.LastStatementGenerateTime DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `LastStatementGenerateTime` 报表生成时间\n- `LastStatementSendTime` 报表发送时间", "question_en": "How many client statements were successfully generated but not yet delivered?", "rating": 7, "execution_result": null, "result_note": "当前查询无未发送报表", "tags": ["ordering", "account", "filter", "statement"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q25", "question_zh": "列出所有待批准的股票转移请求。", "sql": "-- 查询待批准的股票转移\nSELECT \n AccountID,\n InstrumentID,\n MovementType,\n MovementQuantity,\n FromAccountID,\n ToAccountID,\n RequestDate,\n ApprovalStatus,\n RequestedBy\nFROM BCAccountInstrumentMovement\nWHERE ApprovalStatus = 'P'\nORDER BY RequestDate DESC;", "schema_info": {"tables": {"BCAccountInstrumentMovement": {"alias": "BCAccountInstrumentMovement", "columns": []}}, "joins": []}, "explanation": "- 股票转移请求存储在专用表中\n- `ApprovalStatus = 'P'` 待批准状态", "question_en": "Show all pending stock movement requests that require approval.", "rating": 6, "execution_result": null, "result_note": "当前查询无待批准请求", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q28", "question_zh": "哪些客户已请求更改结算货币?", "sql": "-- 查询请求更改结算货币的客户\n-- 说明:经查 MCAccountCurrency 表无 CurrencyChangeStatus/RequestedCurrencyID 等字段\n-- 数据库中没有专门的货币变更请求表\n-- 此查询基于账户货币历史时间表(HCAccountCurrencyTimetable)展示货币生效安排\nSELECT \n act.AccountID,\n mca.Name AS AccountName,\n act.CurrencyID AS Currency,\n act.EffectiveDateFrom,\n act.EffectiveDateTo,\n act.AccountTypeID\nFROM HCAccountCurrencyTimetable act\nINNER JOIN MCAccount mca ON act.AccountID = mca.AccountID\nWHERE act.EffectiveDateTo IS NULL\nORDER BY act.EffectiveDateFrom DESC;", "schema_info": {"tables": {"HCAccountCurrencyTimetable": {"alias": "act", "columns": []}, "MCAccount": {"alias": "mca", "columns": []}}, "joins": [{"type": "INNER", "on": "act.AccountID = mca.AccountID"}]}, "explanation": "- `HCAccountCurrencyTimetable` 记录账户货币生效时间表\n- `EffectiveDateTo IS NULL` 表示当前生效的货币配置\n- 如需货币变更请求功能,建议在系统中扩展相关表结构", "question_en": "Which clients have requested a change in settlement currency?", "rating": 4, "execution_result": null, "result_note": "**说明:** 数据库中没有专门的货币变更请求表。`MCAccountCurrency` 表仅记录账户当前货币配置,无变更请求字段。", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q31", "question_zh": "指定日期或日期范围内收到的交易量(如成交金额)及相应佣金是多少?", "sql": "-- 交易量和佣金统计\nSELECT \n SUM(TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(TotalBoughtFee) AS TotalBuyCommission,\n SUM(TotalSoldConsideration) AS TotalSellConsideration,\n SUM(TotalSoldFee) AS TotalSellCommission,\n SUM(TotalBoughtConsideration) + SUM(TotalSoldConsideration) AS TotalConsideration,\n SUM(TotalBoughtFee) + SUM(TotalSoldFee) AS TotalCommission\nFROM BCAccountInstrument;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录了账户所有历史买卖交易\n- `TotalBoughtConsideration / TotalSoldConsideration`: 买卖成交金额", "question_en": "What is the trading volume (e.g., consideration) and corresponding commission amount received for a specific date or date range?", "rating": 9, "execution_result": null, "result_note": "| TotalBuyConsideration | TotalBuyCommission | TotalSellConsideration | TotalSellCommission | TotalConsideration | TotalCommission |\n|---------------------|-------------------|----------------------|--------------------|-------------------|-----------------|\n| 945,480,833.78 | 793,149.07 | 278,693,891.64 | 809,248.89 | 1,224,174,725.42 | 1,602,397.96 |", "tags": ["trade", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q33", "question_zh": "今日是否有追缴保证金通知?", "sql": "-- 查询今日追缴保证金记录 (固定日期: 2026-04-02)\nSELECT \n m.AccountID,\n a.Name AS AccountName,\n m.CallForDeposit,\n m.CallForDepositDate,\n m.CallForDepositDays\nFROM HCAccountMarginCallForDeposit m\nINNER JOIN MCAccount a ON m.AccountID = a.AccountID\nWHERE m.CallForDepositDate = '2026-04-02';", "schema_info": {"tables": {"HCAccountMarginCallForDeposit": {"alias": "m", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "m.AccountID = a.AccountID"}]}, "explanation": "- `HCAccountMarginCallForDeposit` 记录追缴保证金信息\n- `CallForDeposit` 追缴金额", "question_en": "Have there been any margin calls today?", "rating": 9, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "margin", "filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q36", "question_zh": "各市场(如港股T+1和T+2、美股T+1)的待结算金额是多少?", "sql": "-- 按市场统计待结算金额\nSELECT \n MarketID,\n SUM(DueBuy + DueSell) AS TotalPendingQty,\n SUM(NextDayDueBuy + NextDayDueSell) AS T2TotalPendingQty\nFROM BCAccountInstrument\nWHERE DueBuy > 0 OR DueSell > 0 OR NextDayDueBuy > 0 OR NextDayDueSell > 0\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录各市场的待交收数量\n- `DueBuy/DueSell` 对应T+1交收", "question_en": "What is the pending settlement amount for each market (e.g., HK T+1 & T+2, US T+1)?", "rating": 7, "execution_result": null, "result_note": "| MarketID | TotalPendingQty | T2TotalPendingQty |\n|----------|----------------|------------------|\n| HKEX | 0 | 0 |\n| SETW | 0 | 0 |", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q38", "question_zh": "本月IPO事件的总佣金、手续费和保证金利息是多少?", "sql": "-- IPO相关费用汇总 (2026年4月)\nSELECT \n 'IPO Commission' AS FeeType,\n SUM(ip.SubscriptionAmount * 0.001) AS EstimatedFee\nFROM MCInstrumentIPODetail ip\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": []}, "explanation": "- IPO相关费用分散在多个表中\n- 需要关联 `BCAccountInstrument` 获取IPO交易费用", "question_en": "What are the total commission, handling fees, and margin interest for IPO events this month?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["margin", "aggregation", "filter", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q39", "question_zh": "列出今日需要执行的公司行动(如权益分派)。", "sql": "-- 查询今日生效的公司行动 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.MarketID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate,\n e.BalanceExDate,\n e.Remark,\n e.State\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.State = 'A'\n AND (\n e.BalanceDate = '2026-04-02'\n OR e.BalanceExDate = '2026-04-02'\n OR e.AutoHoldDate = '2026-04-02'\n OR e.AutoDeductDate = '2026-04-02'\n );", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派信息\n- `BalanceDate` 权益登记日期", "question_en": "List corporate actions (e.g., entitlements) that need to be performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q40", "question_zh": "列出今日执行的所有股票转移/移动。", "sql": "-- 今日股票转入/转出 (固定日期: 2026-04-02)\nSELECT \n AccountID,\n MarketID,\n InstrumentID,\n TodayIn AS QuantityIn,\n TodayOut AS QuantityOut,\n HoldForDeposit AS PendingDeposit,\n HoldForWithdrawal AS PendingWithdrawal\nFROM BCAccountInstrument\nWHERE TodayIn > 0 OR TodayOut > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录股票持仓变动\n- `TodayIn` 今日转入数量", "question_en": "List all stock movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q41", "question_zh": "列出今日执行的所有现金划转。", "sql": "-- 今日现金变动汇总 (固定日期: 2026-04-02)\nSELECT \n b.AccountID,\n a.Name AS AccountName,\n b.TodayIn AS CashIn,\n b.TodayOut AS CashOut,\n b.BankAvailableBalance\nFROM BCAccountCash b\nINNER JOIN MCAccount a ON b.AccountID = a.AccountID\nWHERE b.TodayIn > 0 OR b.TodayOut > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "b", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "b.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 记录账户现金变动\n- `TodayIn` 今日现金流入", "question_en": "List all cash movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "cash"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q42", "question_zh": "列出后台系统中的活跃用户。", "sql": "-- 查询后台系统活跃用户\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n u.DepartmentID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储系统用户信息\n- `State = 'A'` 用户状态正常", "question_en": "List active users in the back office.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | AEType |\n|--------|----------|--------|-----------------|---------------|--------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-03 18:53:04 | 个人 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 16:47:11 | 个人 |", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q44", "question_zh": "本月收到/支付的利息金额是多少(如客户借款利息和存款利息)?", "sql": "-- 本月利息收支汇总 (2026年4月)\nSELECT \n SUM(AccruedCreditInterest) AS CreditInterest,\n SUM(AccruedDebitInterest) AS DebitInterest,\n SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetInterest\nFROM BCAccountCash;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金和利息信息\n- `AccruedCreditInterest` 应计存款利息", "question_en": "What is the interest amount received/paid this month (e.g., client debit interest and credit interest)?", "rating": 7, "execution_result": null, "result_note": "| CreditInterest | DebitInterest | NetInterest |\n|----------------|---------------|-------------|\n| 464.15 | 2,022.70 | -1,558.55 |", "tags": ["account", "aggregation", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q47", "question_zh": "今日/指定日期发放了多少份日结单或月结单?", "sql": "-- 查询报表发放统计 (2026-04-02)\nSELECT \n 'Daily Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastDailyStatementDate = '2026-04-02'\nUNION ALL\nSELECT \n 'Monthly Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastMonthlyStatementDate = '2026-04-02';", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 记录账户报表发送日期\n- `LastDailyStatementDate` 最近日结单发送日期", "question_en": "How many daily or monthly statements were issued today / on a specific date?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["union", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q50", "question_zh": "", "sql": "SELECT TOP\n\t15 c.ContractID,\n\tc.TradeDate,\n\tLTRIM( RTRIM( c.AccountID ) ) AS AccountID,\n\tLTRIM( RTRIM( c.MarketID ) ) AS MarketID,\n\tc.InstrumentID,\n\tc.BuySell,\n\tc.Quantity,\n\tc.Consideration,\n\tLTRIM( RTRIM( c.CurrencyID ) ) AS CurrencyID \nFROM\n\tdbo.TSBAccountContract c\n\tINNER JOIN dbo.MCAccount a ON c.AccountID = a.AccountID \nWHERE\n\tCAST ( c.TradeDate AS DATE ) = '2026-04-02' \n\tAND LTRIM( RTRIM( a.HouseAccountFlag ) ) = 'N' \nORDER BY\n\tc.ContractID;", "schema_info": {"tables": {"TSBAccountContract": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "", "question_en": "列出今天所有客戶的交易記錄", "rating": null, "execution_result": null, "result_note": "| ContractID | TradeDate | AccountID | MarketID | InstrumentID | BuySell | Quantity | Consideration | CurrencyID |\n| :--------- | :--------- | :-------- | :------- | :----------- | :------ | :------- | :------------ | :--------- |\n| B02403846 | 2026-04-02 | P011206 | HKEX | 01810 | B | 600 | 18360 | HKD |\n| B02403847 | 2026-04-02 | P011202 | HKEX | 01810 | B | 200 | 6120 | HKD |\n\n\n\n\n\n---\n\n## 附录:关键表说明\n\n| 表名 | 说明 | 关键字段 |\n|------|------|---------|\n| MCAccount | 账户主表 | AccountID, Name, MarketValue, MarginFacilityFlag |\n| MCUser | 用户表 | UserID, Name, Status, LogonEnableFlag |\n| BCAccountInstrument | 账户持仓 | AccountID, MarketID, Settled, DueBuy, DueSell |\n| BCAccountCash | 账户现金 | AccountID, TodayIn, TodayOut, AccruedInterest |\n| HCAccountMarginCallForDeposit | 追缴保证金 | AccountID, CallForDeposit, CallForDepositDate |\n| MCInstrument | 证券表 | InstrumentID, Name, MarginPercentage |\n| MCInstrumentIPODetail | IPO详情 | InstrumentID, Quantity, SubscriptionAmount |\n| MSBEntitlement | 权益分派 | EntitlementID, BalanceDate, BalanceExDate |\n| MCBroker | 经纪商 | BrokerID, Name |\n| MCLocation | 托管位置 | LocationID, Name, CCASSFlag |\n| HCExchangeRate | 汇率 | ExchangeRateID, ValueDate |\n| HCAccountCashRecord | 现金记录 | AccountID, ValueDate, LedgerBalance |\n\n---\n\n**文档结束**", "tags": ["join", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q11", "question_zh": "哪些客户尚未缴纳IPO申购款?", "sql": "-- 查询未缴纳IPO申购款的账户\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.TotalBoughtQuantity AS SubscriptionLot,\n bi.TotalBoughtConsideration AS SubscriptionAmount\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nINNER JOIN MCInstrumentIPODetail ip ON bi.InstrumentID = ip.InstrumentID\nWHERE ip.State = 'A'\n AND bi.TotalBoughtConsideration > 0\n AND bi.TotalBoughtFee = 0\nORDER BY bi.TotalBoughtConsideration DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}, {"type": "INNER", "on": "bi.InstrumentID = ip.InstrumentID"}]}, "explanation": "- 通过比对买卖数量和手续费判断是否缴款\n- `TotalBoughtConsideration > 0` 有认购\n- `TotalBoughtFee = 0` 未支付手续费", "question_en": "Which clients have not yet paid their IPO subscription money?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q14", "question_zh": "列出已申请但尚未处理的客户提款请求。", "sql": "-- 查询待处理提款申请 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.TodayOut AS WithdrawalAmount,\n c.BankAvailableBalance AS AvailableBalance,\n c.State AS RecordStatus\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.TodayOut > 0\nORDER BY c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `TodayOut` 今日计划支出\n- 待处理提款申请应体现在此字段", "question_en": "Show all client withdrawals requested but not yet processed.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q16", "question_zh": "列出T+2交易缺少结算指令的账户。", "sql": "-- 查询T+2待交收但缺少指令的账户\n-- 注意:BCAccountInstrumentInstruction 表不存在,需改用 TSBSettlementInstruction\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.NextDayDueBuy AS T2DueBuy,\n bi.NextDayDueSell AS T2DueSell\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE (bi.NextDayDueBuy > 0 OR bi.NextDayDueSell > 0)\n AND NOT EXISTS (\n SELECT 1 FROM TSBSettlementInstruction si\n WHERE si.AccountOrBrokerID = bi.AccountID\n AND si.InstrumentID = bi.InstrumentID\n AND si.State IN ('P','A')\n );", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "TSBSettlementInstruction": {"alias": "si", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `NextDayDueBuy/NextDayDueSell` T+2交收数量(次日到期)\n- `TSBSettlementInstruction` 是结算指令表,通过 `NOT EXISTS` 排除已有指令的记录\n- `BCAccountInstrumentInstruction` 表不存在,不能使用", "question_en": "Show accounts where settlement instruction is missing for T+2 trades.", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "trade"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q17", "question_zh": "哪些客户已超过其信用额度?", "sql": "-- 查询超信用额度客户(使用 MarginLimit)\n-- 注意:MCAccount 无 CreditLimit 字段,应用 MarginLimit\nSELECT \n AccountID,\n Name AS AccountName,\n MarginLimit,\n MarketValue,\n MarginLimit - MarketValue AS AvailableCredit,\n CASE \n WHEN MarketValue > MarginLimit THEN '超额'\n WHEN MarketValue > MarginLimit * 0.8 THEN '接近限额'\n ELSE '正常'\n END AS Status\nFROM MCAccount\nWHERE MarginLimit > 0\n AND MarketValue > MarginLimit * 0.8\nORDER BY (MarketValue - MarginLimit) DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.MarginLimit` 保证金/信用限额(注意:无 `CreditLimit` 字段)\n- `MCAccount.MarketValue` 持仓市值\n- `MarginBLimit` 是单独借款限额,注意区分", "question_en": "Which clients have exceeded their credit limit?", "rating": 7, "execution_result": null, "result_note": "当前查询无超限客户", "tags": ["account", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q24", "question_zh": "有多少客户报表已生成成功但尚未发送?", "sql": "-- 查询已生成但未发送的报表\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.LastDailyStatementDate,\n a.LastMonthlyStatementDate,\n a.LastStatementGenerateTime,\n a.LastStatementSendTime,\n CASE \n WHEN a.LastStatementGenerateTime > a.LastStatementSendTime THEN '未发送'\n ELSE '已发送'\n END AS DeliveryStatus\nFROM MCAccount a\nWHERE a.LastStatementGenerateTime IS NOT NULL\n AND (a.LastStatementSendTime IS NULL \n OR a.LastStatementGenerateTime > a.LastStatementSendTime)\nORDER BY a.LastStatementGenerateTime DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `LastStatementGenerateTime` 报表生成时间\n- `LastStatementSendTime` 报表发送时间", "question_en": "How many client statements were successfully generated but not yet delivered?", "rating": 7, "execution_result": null, "result_note": "当前查询无未发送报表", "tags": ["ordering", "account", "filter", "statement"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q28", "question_zh": "哪些客户已请求更改结算货币?", "sql": "-- 查询请求更改结算货币的客户\n-- 说明:经查 MCAccountCurrency 表无 CurrencyChangeStatus/RequestedCurrencyID 等字段\n-- 数据库中没有专门的货币变更请求表\n-- 此查询基于账户货币历史时间表(HCAccountCurrencyTimetable)展示货币生效安排\nSELECT \n act.AccountID,\n mca.Name AS AccountName,\n act.CurrencyID AS Currency,\n act.EffectiveDateFrom,\n act.EffectiveDateTo,\n act.AccountTypeID\nFROM HCAccountCurrencyTimetable act\nINNER JOIN MCAccount mca ON act.AccountID = mca.AccountID\nWHERE act.EffectiveDateTo IS NULL\nORDER BY act.EffectiveDateFrom DESC;", "schema_info": {"tables": {"HCAccountCurrencyTimetable": {"alias": "act", "columns": []}, "MCAccount": {"alias": "mca", "columns": []}}, "joins": [{"type": "INNER", "on": "act.AccountID = mca.AccountID"}]}, "explanation": "- `HCAccountCurrencyTimetable` 记录账户货币生效时间表\n- `EffectiveDateTo IS NULL` 表示当前生效的货币配置\n- 如需货币变更请求功能,建议在系统中扩展相关表结构", "question_en": "Which clients have requested a change in settlement currency?", "rating": 4, "execution_result": null, "result_note": "**说明:** 数据库中没有专门的货币变更请求表。`MCAccountCurrency` 表仅记录账户当前货币配置,无变更请求字段。", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q44", "question_zh": "本月收到/支付的利息金额是多少(如客户借款利息和存款利息)?", "sql": "-- 本月利息收支汇总 (2026年4月)\nSELECT \n SUM(AccruedCreditInterest) AS CreditInterest,\n SUM(AccruedDebitInterest) AS DebitInterest,\n SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetInterest\nFROM BCAccountCash;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金和利息信息\n- `AccruedCreditInterest` 应计存款利息", "question_en": "What is the interest amount received/paid this month (e.g., client debit interest and credit interest)?", "rating": 7, "execution_result": null, "result_note": "| CreditInterest | DebitInterest | NetInterest |\n|----------------|---------------|-------------|\n| 464.15 | 2,022.70 | -1,558.55 |", "tags": ["account", "aggregation", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q12", "question_zh": "本月累计的结算失败罚金是多少?", "sql": "-- 查询本月结算失败罚金 (2026年4月)\n-- 注意:BCAccountInstrument 无 FailedTradePenalty 字段,需另找结算罚金存储位置\n-- 目前未找到结算失败罚金字段,建议通过 TSBBrokerContract 或 BCBrokerCash 进一步排查\nSELECT \n COUNT(*) AS FailedTradeCount,\n MarketID\nFROM BCAccountInstrument\nWHERE YEAR(LastTransactionDate) = 2026\n AND MONTH(LastTransactionDate) = 4\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 无 `FailedTradePenalty` 字段,原始 SQL 会报错\n- `TSBBrokerContract` 或 `BCBrokerCash` 中可能存储结算相关费用,需进一步确认", "question_en": "What is the total failed trade penalty incurred this month?", "rating": 3, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 表无 FailedTradePenalty 字段,无法统计结算罚金)", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q31", "question_zh": "指定日期或日期范围内收到的交易量(如成交金额)及相应佣金是多少?", "sql": "-- 交易量和佣金统计\nSELECT \n SUM(TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(TotalBoughtFee) AS TotalBuyCommission,\n SUM(TotalSoldConsideration) AS TotalSellConsideration,\n SUM(TotalSoldFee) AS TotalSellCommission,\n SUM(TotalBoughtConsideration) + SUM(TotalSoldConsideration) AS TotalConsideration,\n SUM(TotalBoughtFee) + SUM(TotalSoldFee) AS TotalCommission\nFROM BCAccountInstrument;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录了账户所有历史买卖交易\n- `TotalBoughtConsideration / TotalSoldConsideration`: 买卖成交金额", "question_en": "What is the trading volume (e.g., consideration) and corresponding commission amount received for a specific date or date range?", "rating": 9, "execution_result": null, "result_note": "| TotalBuyConsideration | TotalBuyCommission | TotalSellConsideration | TotalSellCommission | TotalConsideration | TotalCommission |\n|---------------------|-------------------|----------------------|--------------------|-------------------|-----------------|\n| 945,480,833.78 | 793,149.07 | 278,693,891.64 | 809,248.89 | 1,224,174,725.42 | 1,602,397.96 |", "tags": ["trade", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q36", "question_zh": "各市场(如港股T+1和T+2、美股T+1)的待结算金额是多少?", "sql": "-- 按市场统计待结算金额\nSELECT \n MarketID,\n SUM(DueBuy + DueSell) AS TotalPendingQty,\n SUM(NextDayDueBuy + NextDayDueSell) AS T2TotalPendingQty\nFROM BCAccountInstrument\nWHERE DueBuy > 0 OR DueSell > 0 OR NextDayDueBuy > 0 OR NextDayDueSell > 0\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录各市场的待交收数量\n- `DueBuy/DueSell` 对应T+1交收", "question_en": "What is the pending settlement amount for each market (e.g., HK T+1 & T+2, US T+1)?", "rating": 7, "execution_result": null, "result_note": "| MarketID | TotalPendingQty | T2TotalPendingQty |\n|----------|----------------|------------------|\n| HKEX | 0 | 0 |\n| SETW | 0 | 0 |", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q38", "question_zh": "本月IPO事件的总佣金、手续费和保证金利息是多少?", "sql": "-- IPO相关费用汇总 (2026年4月)\nSELECT \n 'IPO Commission' AS FeeType,\n SUM(ip.SubscriptionAmount * 0.001) AS EstimatedFee\nFROM MCInstrumentIPODetail ip\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": []}, "explanation": "- IPO相关费用分散在多个表中\n- 需要关联 `BCAccountInstrument` 获取IPO交易费用", "question_en": "What are the total commission, handling fees, and margin interest for IPO events this month?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["margin", "aggregation", "filter", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q44", "question_zh": "本月收到/支付的利息金额是多少(如客户借款利息和存款利息)?", "sql": "-- 本月利息收支汇总 (2026年4月)\nSELECT \n SUM(AccruedCreditInterest) AS CreditInterest,\n SUM(AccruedDebitInterest) AS DebitInterest,\n SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetInterest\nFROM BCAccountCash;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金和利息信息\n- `AccruedCreditInterest` 应计存款利息", "question_en": "What is the interest amount received/paid this month (e.g., client debit interest and credit interest)?", "rating": 7, "execution_result": null, "result_note": "| CreditInterest | DebitInterest | NetInterest |\n|----------------|---------------|-------------|\n| 464.15 | 2,022.70 | -1,558.55 |", "tags": ["account", "aggregation", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q47", "question_zh": "今日/指定日期发放了多少份日结单或月结单?", "sql": "-- 查询报表发放统计 (2026-04-02)\nSELECT \n 'Daily Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastDailyStatementDate = '2026-04-02'\nUNION ALL\nSELECT \n 'Monthly Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastMonthlyStatementDate = '2026-04-02';", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 记录账户报表发送日期\n- `LastDailyStatementDate` 最近日结单发送日期", "question_en": "How many daily or monthly statements were issued today / on a specific date?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["union", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q41", "question_zh": "列出今日执行的所有现金划转。", "sql": "-- 今日现金变动汇总 (固定日期: 2026-04-02)\nSELECT \n b.AccountID,\n a.Name AS AccountName,\n b.TodayIn AS CashIn,\n b.TodayOut AS CashOut,\n b.BankAvailableBalance\nFROM BCAccountCash b\nINNER JOIN MCAccount a ON b.AccountID = a.AccountID\nWHERE b.TodayIn > 0 OR b.TodayOut > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "b", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "b.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 记录账户现金变动\n- `TodayIn` 今日现金流入", "question_en": "List all cash movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "cash"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{"qid": "Q8", "question_zh": "明天生效的公司行动涉及哪些证券?", "sql": "-- 查询明日生效的公司行动 (固定日期: 2026-04-03)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate AS EffectiveDate,\n e.BalanceExDate AS ExDate,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE (e.BalanceDate = '2026-04-03' OR e.BalanceExDate = '2026-04-03')\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录权益分派信息\n- `BalanceDate` 权益登记日期\n- `BalanceExDate` 除权日期", "question_en": "Which instruments have corporate actions effective tomorrow?", "rating": 8, "execution_result": null, "result_note": "当前查询无结果(2026-04-03无公司行动)", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q20", "question_zh": "哪些公司行动的选择指令需在今日结束前提交?", "sql": "-- 查询今日截止的公司行动选择 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.ElectionPeriodTo AS Deadline,\n e.ElectionOptionFlag,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.ElectionPeriodTo = '2026-04-02'\n AND e.ElectionOptionFlag = 'Y'\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `ElectionPeriodTo` 选择截止日期\n- `ElectionOptionFlag = 'Y'` 需要选择", "question_en": "Which corporate action elections are due by end of day?", "rating": 8, "execution_result": null, "result_note": "当前查询无今日截止的选择", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q39", "question_zh": "列出今日需要执行的公司行动(如权益分派)。", "sql": "-- 查询今日生效的公司行动 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.MarketID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate,\n e.BalanceExDate,\n e.Remark,\n e.State\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.State = 'A'\n AND (\n e.BalanceDate = '2026-04-02'\n OR e.BalanceExDate = '2026-04-02'\n OR e.AutoHoldDate = '2026-04-02'\n OR e.AutoDeductDate = '2026-04-02'\n );", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派信息\n- `BalanceDate` 权益登记日期", "question_en": "List corporate actions (e.g., entitlements) that need to be performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"qid": "Q3", "question_zh": "今日结算使用的汇率是多少?", "sql": "-- 查询汇率类型列表 (固定日期: 2026-04-02)\nSELECT \n ExchangeRateID, \n Name, \n NameEx\nFROM HCExchangeRate\nWHERE ValueDate = '2026-04-02'\nORDER BY ExchangeRateID;", "schema_info": {"tables": {"HCExchangeRate": {"alias": "HCExchangeRate", "columns": []}}, "joins": []}, "explanation": "- `HCExchangeRate` 存储汇率类型定义\n- `HCExchangeRateDetail` 存储各货币对的汇率值\n- 两个表通过 `ExchangeRateID` + `ValueDate` 关联\n- `ExchangeRateDirection` = 'D' 表示直接汇率", "question_en": "What are the currency exchange rates used for today's settlement?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["exchange_rate", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q3", "question_zh": "今日结算使用的汇率是多少?", "sql": "-- 查询汇率类型列表 (固定日期: 2026-04-02)\nSELECT \n ExchangeRateID, \n Name, \n NameEx\nFROM HCExchangeRate\nWHERE ValueDate = '2026-04-02'\nORDER BY ExchangeRateID;", "schema_info": {"tables": {"HCExchangeRate": {"alias": "HCExchangeRate", "columns": []}}, "joins": []}, "explanation": "- `HCExchangeRate` 存储汇率类型定义\n- `HCExchangeRateDetail` 存储各货币对的汇率值\n- 两个表通过 `ExchangeRateID` + `ValueDate` 关联\n- `ExchangeRateDirection` = 'D' 表示直接汇率", "question_en": "What are the currency exchange rates used for today's settlement?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["exchange_rate", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q4", "question_zh": "哪些券商尚有未履行的交收义务?", "sql": "-- 查询有未履行交收义务的经纪商(截至 2026-04-02)\n-- Settled < 0 表示经纪商欠我们的现金(未履行交收义务)\n-- 注意:BCBrokerCash.Settled 是经纪商在我们账户的已结算现金余额,负数=经纪商欠款\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n b.CurrencyID,\n b.Settled AS OwedToUsAmount,\n ABS(b.Settled) AS OutstandingAmount\nFROM BCBrokerCash b\nINNER JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.Settled < 0\nORDER BY ABS(b.Settled) DESC;", "schema_info": {"tables": {"BCBrokerCash": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `BCBrokerCash` 记录经纪商在我们账户的结算现金,`Settled < 0` = 经纪商欠我们(未履行交收义务)\n- 关联 `MCBroker` 获取经纪商名称\n- 按欠款金额降序排列,优先处理大额欠款", "question_en": "Which brokers have outstanding delivery obligations to us?", "rating": 8, "execution_result": null, "result_note": "| BrokerID | BrokerName | CurrencyID | OwedToUsAmount | OutstandingAmount |\n|----------|-----------|------------|-----------------|-------------------|\n| CCASS | CCASS | CNY | -52,680.00 | 52,680.00 |\n| CCASS | CCASS | USD | -90.00 | 90.00 |\n\n> 注:CCASS 欠我们的结算现金(CNY 52,680 + USD 90)。HKD 余额为正(+4,202,811.87)表示我们欠 CCASS,不属于\"未履行交收义务\"范畴。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q5", "question_zh": "列出今日发出的所有追收保证金通知及其回复状态。", "sql": "-- 查询今日追缴保证金记录 (参考日期: 2026-04-02)\n-- 使用 MCAccount 的账户级追缴字段(CallForDepositDate = 追缴通知发出日期)\n-- 注意:HCAccountMarginCallForDeposit 是追缴每日快照表,不含\"通知发出日\"字段\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.CallForDeposit AS CallAmount,\n a.CallForDepositDate AS IssueDate,\n a.CallForDepositDays AS OverdueDays\nFROM MCAccount a\nWHERE a.CallForDepositDate = '2026-04-02'\nORDER BY a.CallForDeposit DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.CallForDepositDate` = 追缴通知发出日期,`CallForDeposit` = 追缴金额\n- `CallForDeposit < 0` 表示已结清;如查活跃追缴,应筛选 `CallForDeposit > 0`\n- `HCAccountMarginCallForDeposit` 是追缴每日历史快照,不适合查询\"今日发出的通知\"\n- `CallStatus` 字段在 `HCAccountMarginCallForDeposit` 中不存在", "question_en": "Show all margin calls issued today and their response status.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果(测试环境中 MCAccount 的 `CallForDepositDate` 最新到 2025-10-31,2026-04-02 无追缴通知记录)\n\n> 注:`MCAccount.CallForDeposit` 当前所有值均为负数(已结清的追缴金额),无活跃追缴记录。", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q8", "question_zh": "明天生效的公司行动涉及哪些证券?", "sql": "-- 查询明日生效的公司行动 (固定日期: 2026-04-03)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate AS EffectiveDate,\n e.BalanceExDate AS ExDate,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE (e.BalanceDate = '2026-04-03' OR e.BalanceExDate = '2026-04-03')\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录权益分派信息\n- `BalanceDate` 权益登记日期\n- `BalanceExDate` 除权日期", "question_en": "Which instruments have corporate actions effective tomorrow?", "rating": 8, "execution_result": null, "result_note": "当前查询无结果(2026-04-03无公司行动)", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q9", "question_zh": "今日预计收到哪些股息?", "sql": "-- 查询今日预计收到的股息 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceDate AS PaymentDate,\n e.BalanceType,\n e.Remark AS DividendDetails\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.BalanceDate = '2026-04-02'\n AND e.BalanceType = 'C'\n AND e.State = 'A'\nORDER BY i.Name;", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派\n- `BalanceType = 'C'` 表示现金股息\n- `BalanceDate` 股息支付日期", "question_en": "What dividends are expected to be received today?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q10", "question_zh": "列出所有进行中IPO的待缴申购金额。", "sql": "-- 查询进行中IPO的待缴金额\nSELECT \n ip.InstrumentID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.SubscriptionAmount,\n ip.SubscriptionPrice,\n ip.Quantity AS SubscriptionLot,\n ip.CreatedOn AS SubscriptionDate,\n ip.State AS SubscriptionStatus\nFROM MCInstrumentIPODetail ip\nINNER JOIN MCInstrument i ON ip.InstrumentID = i.InstrumentID\nWHERE ip.State = 'A'\n AND i.State = 'A'\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "ip.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MCInstrumentIPODetail` 记录IPO认购详情\n- `SubscriptionAmount` 申购金额\n- `State = 'A'` 表示进行中", "question_en": "Show all pending subscription amounts for ongoing IPOs.", "rating": 8, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | SubscriptionAmount | SubscriptionPrice | Quantity |\n|--------------|----------------|-------------------|-------------------|----------|\n| 02259 | HKEX | 7,231.19 | 36.16 | 100 |\n| 02259 | HKEX | 14,462.40 | 36.16 | 200 |\n\n> 注:MCInstrumentIPODetail 共 586 条进行中 IPO 记录(State='A'),以上为示例行。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q11", "question_zh": "哪些客户尚未缴纳IPO申购款?", "sql": "-- 查询未缴纳IPO申购款的账户\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.TotalBoughtQuantity AS SubscriptionLot,\n bi.TotalBoughtConsideration AS SubscriptionAmount\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nINNER JOIN MCInstrumentIPODetail ip ON bi.InstrumentID = ip.InstrumentID\nWHERE ip.State = 'A'\n AND bi.TotalBoughtConsideration > 0\n AND bi.TotalBoughtFee = 0\nORDER BY bi.TotalBoughtConsideration DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}, {"type": "INNER", "on": "bi.InstrumentID = ip.InstrumentID"}]}, "explanation": "- 通过比对买卖数量和手续费判断是否缴款\n- `TotalBoughtConsideration > 0` 有认购\n- `TotalBoughtFee = 0` 未支付手续费", "question_en": "Which clients have not yet paid their IPO subscription money?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q12", "question_zh": "本月累计的结算失败罚金是多少?", "sql": "-- 查询本月结算失败罚金 (2026年4月)\n-- 注意:BCAccountInstrument 无 FailedTradePenalty 字段,需另找结算罚金存储位置\n-- 目前未找到结算失败罚金字段,建议通过 TSBBrokerContract 或 BCBrokerCash 进一步排查\nSELECT \n COUNT(*) AS FailedTradeCount,\n MarketID\nFROM BCAccountInstrument\nWHERE YEAR(LastTransactionDate) = 2026\n AND MONTH(LastTransactionDate) = 4\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 无 `FailedTradePenalty` 字段,原始 SQL 会报错\n- `TSBBrokerContract` 或 `BCBrokerCash` 中可能存储结算相关费用,需进一步确认", "question_en": "What is the total failed trade penalty incurred this month?", "rating": 3, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 表无 FailedTradePenalty 字段,无法统计结算罚金)", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q13", "question_zh": "列出今日到期的所有股票借贷交易。", "sql": "-- 查询今日到期的股票借贷 (参考日期: 2026-04-02)\n-- MCMarket 表仅包含 HKEX/OSL/SETW,无独立借贷市场标识\n-- 股票借贷可能在持仓变动类型 HoldType 中标识,或在专门的 StockBorrowing 表中\n-- 此 SQL 按 DueBuy/DueSell 显示当前待交收证券,作为借贷合约的近似替代\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.DueBuy AS BorrowQuantity,\n bi.DueSell AS LoanQuantity,\n bi.DueBuy - bi.DueSell AS NetBorrow\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE bi.DueBuy > 0 OR bi.DueSell > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `DueBuy/DueSell` 表示待交收数量(借款买入/借出卖出),反映股票借贷待到期情况\n- `MCMarket` 表无 `MarketType='BORROW'` 标识,现有市场仅为 HKEX/OSL/SETW\n- 股票借贷详情可能存储在 `TSBAccountLoanContract` 或其他借贷专用表中,需业务确认", "question_en": "List all stock borrow/loan transactions due to settle today.", "rating": 5, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 中 DueBuy/DueSell 均为 0,无待交收证券)", "tags": ["join", "trade", "filter"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q14", "question_zh": "列出已申请但尚未处理的客户提款请求。", "sql": "-- 查询待处理提款申请 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.TodayOut AS WithdrawalAmount,\n c.BankAvailableBalance AS AvailableBalance,\n c.State AS RecordStatus\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.TodayOut > 0\nORDER BY c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `TodayOut` 今日计划支出\n- 待处理提款申请应体现在此字段", "question_en": "Show all client withdrawals requested but not yet processed.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q16", "question_zh": "列出T+2交易缺少结算指令的账户。", "sql": "-- 查询T+2待交收但缺少指令的账户\n-- 注意:BCAccountInstrumentInstruction 表不存在,需改用 TSBSettlementInstruction\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.NextDayDueBuy AS T2DueBuy,\n bi.NextDayDueSell AS T2DueSell\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE (bi.NextDayDueBuy > 0 OR bi.NextDayDueSell > 0)\n AND NOT EXISTS (\n SELECT 1 FROM TSBSettlementInstruction si\n WHERE si.AccountOrBrokerID = bi.AccountID\n AND si.InstrumentID = bi.InstrumentID\n AND si.State IN ('P','A')\n );", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "TSBSettlementInstruction": {"alias": "si", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `NextDayDueBuy/NextDayDueSell` T+2交收数量(次日到期)\n- `TSBSettlementInstruction` 是结算指令表,通过 `NOT EXISTS` 排除已有指令的记录\n- `BCAccountInstrumentInstruction` 表不存在,不能使用", "question_en": "Show accounts where settlement instruction is missing for T+2 trades.", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "trade"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q17", "question_zh": "哪些客户已超过其信用额度?", "sql": "-- 查询超信用额度客户(使用 MarginLimit)\n-- 注意:MCAccount 无 CreditLimit 字段,应用 MarginLimit\nSELECT \n AccountID,\n Name AS AccountName,\n MarginLimit,\n MarketValue,\n MarginLimit - MarketValue AS AvailableCredit,\n CASE \n WHEN MarketValue > MarginLimit THEN '超额'\n WHEN MarketValue > MarginLimit * 0.8 THEN '接近限额'\n ELSE '正常'\n END AS Status\nFROM MCAccount\nWHERE MarginLimit > 0\n AND MarketValue > MarginLimit * 0.8\nORDER BY (MarketValue - MarginLimit) DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.MarginLimit` 保证金/信用限额(注意:无 `CreditLimit` 字段)\n- `MCAccount.MarketValue` 持仓市值\n- `MarginBLimit` 是单独借款限额,注意区分", "question_en": "Which clients have exceeded their credit limit?", "rating": 7, "execution_result": null, "result_note": "当前查询无超限客户", "tags": ["account", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q18", "question_zh": "今日收到的未清算支票总额是多少?", "sql": "-- 查询未清算支票 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.UnderdueCheque AS OverdueCheque,\n c.NextDayDueCheque AS PendingCheque,\n c.UnderdueCheque + c.NextDayDueCheque AS TotalUncleared\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.UnderdueCheque > 0 OR c.NextDayDueCheque > 0) -- 注意:BCAccountCash 无这两个字段,需确认实际表\nORDER BY TotalUncleared DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 无 `UnderdueCheque` / `NextDayDueCheque` 字段,需业务确认支票数据存储位置", "question_en": "What is the total amount of uncleared cheques received today?", "rating": 4, "execution_result": null, "result_note": "当前查询无未清算支票", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q19", "question_zh": "列出今日起保证金比例发生变动的所有证券。", "sql": "-- 查询保证金比例变动的证券 (固定日期: 2026-04-02)\nSELECT \n i.InstrumentID,\n i.Name AS InstrumentName,\n i.MarginPercentage AS CurrentMarginRatio,\n i.MarginChangeDate\nFROM MCInstrument i\nWHERE i.MarginChangeDate = '2026-04-02'\nORDER BY i.MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument.MarginPercentage` 当前保证金比例\n- `MarginChangeDate` 变动日期", "question_en": "List all instruments with margin ratio changes effective today.", "rating": 7, "execution_result": null, "result_note": "当前查询无保证金比例变动记录", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q20", "question_zh": "哪些公司行动的选择指令需在今日结束前提交?", "sql": "-- 查询今日截止的公司行动选择 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.ElectionPeriodTo AS Deadline,\n e.ElectionOptionFlag,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.ElectionPeriodTo = '2026-04-02'\n AND e.ElectionOptionFlag = 'Y'\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `ElectionPeriodTo` 选择截止日期\n- `ElectionOptionFlag = 'Y'` 需要选择", "question_en": "Which corporate action elections are due by end of day?", "rating": 8, "execution_result": null, "result_note": "当前查询无今日截止的选择", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q24", "question_zh": "有多少客户报表已生成成功但尚未发送?", "sql": "-- 查询已生成但未发送的报表\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.LastDailyStatementDate,\n a.LastMonthlyStatementDate,\n a.LastStatementGenerateTime,\n a.LastStatementSendTime,\n CASE \n WHEN a.LastStatementGenerateTime > a.LastStatementSendTime THEN '未发送'\n ELSE '已发送'\n END AS DeliveryStatus\nFROM MCAccount a\nWHERE a.LastStatementGenerateTime IS NOT NULL\n AND (a.LastStatementSendTime IS NULL \n OR a.LastStatementGenerateTime > a.LastStatementSendTime)\nORDER BY a.LastStatementGenerateTime DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `LastStatementGenerateTime` 报表生成时间\n- `LastStatementSendTime` 报表发送时间", "question_en": "How many client statements were successfully generated but not yet delivered?", "rating": 7, "execution_result": null, "result_note": "当前查询无未发送报表", "tags": ["ordering", "account", "filter", "statement"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q25", "question_zh": "列出所有待批准的股票转移请求。", "sql": "-- 查询待批准的股票转移\nSELECT \n AccountID,\n InstrumentID,\n MovementType,\n MovementQuantity,\n FromAccountID,\n ToAccountID,\n RequestDate,\n ApprovalStatus,\n RequestedBy\nFROM BCAccountInstrumentMovement\nWHERE ApprovalStatus = 'P'\nORDER BY RequestDate DESC;", "schema_info": {"tables": {"BCAccountInstrumentMovement": {"alias": "BCAccountInstrumentMovement", "columns": []}}, "joins": []}, "explanation": "- 股票转移请求存储在专用表中\n- `ApprovalStatus = 'P'` 待批准状态", "question_en": "Show all pending stock movement requests that require approval.", "rating": 6, "execution_result": null, "result_note": "当前查询无待批准请求", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q28", "question_zh": "哪些客户已请求更改结算货币?", "sql": "-- 查询请求更改结算货币的客户\n-- 说明:经查 MCAccountCurrency 表无 CurrencyChangeStatus/RequestedCurrencyID 等字段\n-- 数据库中没有专门的货币变更请求表\n-- 此查询基于账户货币历史时间表(HCAccountCurrencyTimetable)展示货币生效安排\nSELECT \n act.AccountID,\n mca.Name AS AccountName,\n act.CurrencyID AS Currency,\n act.EffectiveDateFrom,\n act.EffectiveDateTo,\n act.AccountTypeID\nFROM HCAccountCurrencyTimetable act\nINNER JOIN MCAccount mca ON act.AccountID = mca.AccountID\nWHERE act.EffectiveDateTo IS NULL\nORDER BY act.EffectiveDateFrom DESC;", "schema_info": {"tables": {"HCAccountCurrencyTimetable": {"alias": "act", "columns": []}, "MCAccount": {"alias": "mca", "columns": []}}, "joins": [{"type": "INNER", "on": "act.AccountID = mca.AccountID"}]}, "explanation": "- `HCAccountCurrencyTimetable` 记录账户货币生效时间表\n- `EffectiveDateTo IS NULL` 表示当前生效的货币配置\n- 如需货币变更请求功能,建议在系统中扩展相关表结构", "question_en": "Which clients have requested a change in settlement currency?", "rating": 4, "execution_result": null, "result_note": "**说明:** 数据库中没有专门的货币变更请求表。`MCAccountCurrency` 表仅记录账户当前货币配置,无变更请求字段。", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q33", "question_zh": "今日是否有追缴保证金通知?", "sql": "-- 查询今日追缴保证金记录 (固定日期: 2026-04-02)\nSELECT \n m.AccountID,\n a.Name AS AccountName,\n m.CallForDeposit,\n m.CallForDepositDate,\n m.CallForDepositDays\nFROM HCAccountMarginCallForDeposit m\nINNER JOIN MCAccount a ON m.AccountID = a.AccountID\nWHERE m.CallForDepositDate = '2026-04-02';", "schema_info": {"tables": {"HCAccountMarginCallForDeposit": {"alias": "m", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "m.AccountID = a.AccountID"}]}, "explanation": "- `HCAccountMarginCallForDeposit` 记录追缴保证金信息\n- `CallForDeposit` 追缴金额", "question_en": "Have there been any margin calls today?", "rating": 9, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "margin", "filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q36", "question_zh": "各市场(如港股T+1和T+2、美股T+1)的待结算金额是多少?", "sql": "-- 按市场统计待结算金额\nSELECT \n MarketID,\n SUM(DueBuy + DueSell) AS TotalPendingQty,\n SUM(NextDayDueBuy + NextDayDueSell) AS T2TotalPendingQty\nFROM BCAccountInstrument\nWHERE DueBuy > 0 OR DueSell > 0 OR NextDayDueBuy > 0 OR NextDayDueSell > 0\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录各市场的待交收数量\n- `DueBuy/DueSell` 对应T+1交收", "question_en": "What is the pending settlement amount for each market (e.g., HK T+1 & T+2, US T+1)?", "rating": 7, "execution_result": null, "result_note": "| MarketID | TotalPendingQty | T2TotalPendingQty |\n|----------|----------------|------------------|\n| HKEX | 0 | 0 |\n| SETW | 0 | 0 |", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q38", "question_zh": "本月IPO事件的总佣金、手续费和保证金利息是多少?", "sql": "-- IPO相关费用汇总 (2026年4月)\nSELECT \n 'IPO Commission' AS FeeType,\n SUM(ip.SubscriptionAmount * 0.001) AS EstimatedFee\nFROM MCInstrumentIPODetail ip\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": []}, "explanation": "- IPO相关费用分散在多个表中\n- 需要关联 `BCAccountInstrument` 获取IPO交易费用", "question_en": "What are the total commission, handling fees, and margin interest for IPO events this month?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["margin", "aggregation", "filter", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q39", "question_zh": "列出今日需要执行的公司行动(如权益分派)。", "sql": "-- 查询今日生效的公司行动 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.MarketID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate,\n e.BalanceExDate,\n e.Remark,\n e.State\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.State = 'A'\n AND (\n e.BalanceDate = '2026-04-02'\n OR e.BalanceExDate = '2026-04-02'\n OR e.AutoHoldDate = '2026-04-02'\n OR e.AutoDeductDate = '2026-04-02'\n );", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派信息\n- `BalanceDate` 权益登记日期", "question_en": "List corporate actions (e.g., entitlements) that need to be performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q40", "question_zh": "列出今日执行的所有股票转移/移动。", "sql": "-- 今日股票转入/转出 (固定日期: 2026-04-02)\nSELECT \n AccountID,\n MarketID,\n InstrumentID,\n TodayIn AS QuantityIn,\n TodayOut AS QuantityOut,\n HoldForDeposit AS PendingDeposit,\n HoldForWithdrawal AS PendingWithdrawal\nFROM BCAccountInstrument\nWHERE TodayIn > 0 OR TodayOut > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录股票持仓变动\n- `TodayIn` 今日转入数量", "question_en": "List all stock movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q41", "question_zh": "列出今日执行的所有现金划转。", "sql": "-- 今日现金变动汇总 (固定日期: 2026-04-02)\nSELECT \n b.AccountID,\n a.Name AS AccountName,\n b.TodayIn AS CashIn,\n b.TodayOut AS CashOut,\n b.BankAvailableBalance\nFROM BCAccountCash b\nINNER JOIN MCAccount a ON b.AccountID = a.AccountID\nWHERE b.TodayIn > 0 OR b.TodayOut > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "b", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "b.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 记录账户现金变动\n- `TodayIn` 今日现金流入", "question_en": "List all cash movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "cash"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q42", "question_zh": "列出后台系统中的活跃用户。", "sql": "-- 查询后台系统活跃用户\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n u.DepartmentID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储系统用户信息\n- `State = 'A'` 用户状态正常", "question_en": "List active users in the back office.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | AEType |\n|--------|----------|--------|-----------------|---------------|--------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-03 18:53:04 | 个人 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 16:47:11 | 个人 |", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q47", "question_zh": "今日/指定日期发放了多少份日结单或月结单?", "sql": "-- 查询报表发放统计 (2026-04-02)\nSELECT \n 'Daily Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastDailyStatementDate = '2026-04-02'\nUNION ALL\nSELECT \n 'Monthly Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastMonthlyStatementDate = '2026-04-02';", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 记录账户报表发送日期\n- `LastDailyStatementDate` 最近日结单发送日期", "question_en": "How many daily or monthly statements were issued today / on a specific date?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["union", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q50", "question_zh": "", "sql": "SELECT TOP\n\t15 c.ContractID,\n\tc.TradeDate,\n\tLTRIM( RTRIM( c.AccountID ) ) AS AccountID,\n\tLTRIM( RTRIM( c.MarketID ) ) AS MarketID,\n\tc.InstrumentID,\n\tc.BuySell,\n\tc.Quantity,\n\tc.Consideration,\n\tLTRIM( RTRIM( c.CurrencyID ) ) AS CurrencyID \nFROM\n\tdbo.TSBAccountContract c\n\tINNER JOIN dbo.MCAccount a ON c.AccountID = a.AccountID \nWHERE\n\tCAST ( c.TradeDate AS DATE ) = '2026-04-02' \n\tAND LTRIM( RTRIM( a.HouseAccountFlag ) ) = 'N' \nORDER BY\n\tc.ContractID;", "schema_info": {"tables": {"TSBAccountContract": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "", "question_en": "列出今天所有客戶的交易記錄", "rating": null, "execution_result": null, "result_note": "| ContractID | TradeDate | AccountID | MarketID | InstrumentID | BuySell | Quantity | Consideration | CurrencyID |\n| :--------- | :--------- | :-------- | :------- | :----------- | :------ | :------- | :------------ | :--------- |\n| B02403846 | 2026-04-02 | P011206 | HKEX | 01810 | B | 600 | 18360 | HKD |\n| B02403847 | 2026-04-02 | P011202 | HKEX | 01810 | B | 200 | 6120 | HKD |\n\n\n\n\n\n---\n\n## 附录:关键表说明\n\n| 表名 | 说明 | 关键字段 |\n|------|------|---------|\n| MCAccount | 账户主表 | AccountID, Name, MarketValue, MarginFacilityFlag |\n| MCUser | 用户表 | UserID, Name, Status, LogonEnableFlag |\n| BCAccountInstrument | 账户持仓 | AccountID, MarketID, Settled, DueBuy, DueSell |\n| BCAccountCash | 账户现金 | AccountID, TodayIn, TodayOut, AccruedInterest |\n| HCAccountMarginCallForDeposit | 追缴保证金 | AccountID, CallForDeposit, CallForDepositDate |\n| MCInstrument | 证券表 | InstrumentID, Name, MarginPercentage |\n| MCInstrumentIPODetail | IPO详情 | InstrumentID, Quantity, SubscriptionAmount |\n| MSBEntitlement | 权益分派 | EntitlementID, BalanceDate, BalanceExDate |\n| MCBroker | 经纪商 | BrokerID, Name |\n| MCLocation | 托管位置 | LocationID, Name, CCASSFlag |\n| HCExchangeRate | 汇率 | ExchangeRateID, ValueDate |\n| HCAccountCashRecord | 现金记录 | AccountID, ValueDate, LedgerBalance |\n\n---\n\n**文档结束**", "tags": ["join", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q38", "question_zh": "本月IPO事件的总佣金、手续费和保证金利息是多少?", "sql": "-- IPO相关费用汇总 (2026年4月)\nSELECT \n 'IPO Commission' AS FeeType,\n SUM(ip.SubscriptionAmount * 0.001) AS EstimatedFee\nFROM MCInstrumentIPODetail ip\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": []}, "explanation": "- IPO相关费用分散在多个表中\n- 需要关联 `BCAccountInstrument` 获取IPO交易费用", "question_en": "What are the total commission, handling fees, and margin interest for IPO events this month?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["margin", "aggregation", "filter", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q44", "question_zh": "本月收到/支付的利息金额是多少(如客户借款利息和存款利息)?", "sql": "-- 本月利息收支汇总 (2026年4月)\nSELECT \n SUM(AccruedCreditInterest) AS CreditInterest,\n SUM(AccruedDebitInterest) AS DebitInterest,\n SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetInterest\nFROM BCAccountCash;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金和利息信息\n- `AccruedCreditInterest` 应计存款利息", "question_en": "What is the interest amount received/paid this month (e.g., client debit interest and credit interest)?", "rating": 7, "execution_result": null, "result_note": "| CreditInterest | DebitInterest | NetInterest |\n|----------------|---------------|-------------|\n| 464.15 | 2,022.70 | -1,558.55 |", "tags": ["account", "aggregation", "interest"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q4", "question_zh": "哪些券商尚有未履行的交收义务?", "sql": "-- 查询有未履行交收义务的经纪商(截至 2026-04-02)\n-- Settled < 0 表示经纪商欠我们的现金(未履行交收义务)\n-- 注意:BCBrokerCash.Settled 是经纪商在我们账户的已结算现金余额,负数=经纪商欠款\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n b.CurrencyID,\n b.Settled AS OwedToUsAmount,\n ABS(b.Settled) AS OutstandingAmount\nFROM BCBrokerCash b\nINNER JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.Settled < 0\nORDER BY ABS(b.Settled) DESC;", "schema_info": {"tables": {"BCBrokerCash": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `BCBrokerCash` 记录经纪商在我们账户的结算现金,`Settled < 0` = 经纪商欠我们(未履行交收义务)\n- 关联 `MCBroker` 获取经纪商名称\n- 按欠款金额降序排列,优先处理大额欠款", "question_en": "Which brokers have outstanding delivery obligations to us?", "rating": 8, "execution_result": null, "result_note": "| BrokerID | BrokerName | CurrencyID | OwedToUsAmount | OutstandingAmount |\n|----------|-----------|------------|-----------------|-------------------|\n| CCASS | CCASS | CNY | -52,680.00 | 52,680.00 |\n| CCASS | CCASS | USD | -90.00 | 90.00 |\n\n> 注:CCASS 欠我们的结算现金(CNY 52,680 + USD 90)。HKD 余额为正(+4,202,811.87)表示我们欠 CCASS,不属于\"未履行交收义务\"范畴。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q8", "question_zh": "明天生效的公司行动涉及哪些证券?", "sql": "-- 查询明日生效的公司行动 (固定日期: 2026-04-03)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate AS EffectiveDate,\n e.BalanceExDate AS ExDate,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE (e.BalanceDate = '2026-04-03' OR e.BalanceExDate = '2026-04-03')\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录权益分派信息\n- `BalanceDate` 权益登记日期\n- `BalanceExDate` 除权日期", "question_en": "Which instruments have corporate actions effective tomorrow?", "rating": 8, "execution_result": null, "result_note": "当前查询无结果(2026-04-03无公司行动)", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q9", "question_zh": "今日预计收到哪些股息?", "sql": "-- 查询今日预计收到的股息 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceDate AS PaymentDate,\n e.BalanceType,\n e.Remark AS DividendDetails\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.BalanceDate = '2026-04-02'\n AND e.BalanceType = 'C'\n AND e.State = 'A'\nORDER BY i.Name;", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派\n- `BalanceType = 'C'` 表示现金股息\n- `BalanceDate` 股息支付日期", "question_en": "What dividends are expected to be received today?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q10", "question_zh": "列出所有进行中IPO的待缴申购金额。", "sql": "-- 查询进行中IPO的待缴金额\nSELECT \n ip.InstrumentID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.SubscriptionAmount,\n ip.SubscriptionPrice,\n ip.Quantity AS SubscriptionLot,\n ip.CreatedOn AS SubscriptionDate,\n ip.State AS SubscriptionStatus\nFROM MCInstrumentIPODetail ip\nINNER JOIN MCInstrument i ON ip.InstrumentID = i.InstrumentID\nWHERE ip.State = 'A'\n AND i.State = 'A'\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "ip.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MCInstrumentIPODetail` 记录IPO认购详情\n- `SubscriptionAmount` 申购金额\n- `State = 'A'` 表示进行中", "question_en": "Show all pending subscription amounts for ongoing IPOs.", "rating": 8, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | SubscriptionAmount | SubscriptionPrice | Quantity |\n|--------------|----------------|-------------------|-------------------|----------|\n| 02259 | HKEX | 7,231.19 | 36.16 | 100 |\n| 02259 | HKEX | 14,462.40 | 36.16 | 200 |\n\n> 注:MCInstrumentIPODetail 共 586 条进行中 IPO 记录(State='A'),以上为示例行。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q11", "question_zh": "哪些客户尚未缴纳IPO申购款?", "sql": "-- 查询未缴纳IPO申购款的账户\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.TotalBoughtQuantity AS SubscriptionLot,\n bi.TotalBoughtConsideration AS SubscriptionAmount\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nINNER JOIN MCInstrumentIPODetail ip ON bi.InstrumentID = ip.InstrumentID\nWHERE ip.State = 'A'\n AND bi.TotalBoughtConsideration > 0\n AND bi.TotalBoughtFee = 0\nORDER BY bi.TotalBoughtConsideration DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}, {"type": "INNER", "on": "bi.InstrumentID = ip.InstrumentID"}]}, "explanation": "- 通过比对买卖数量和手续费判断是否缴款\n- `TotalBoughtConsideration > 0` 有认购\n- `TotalBoughtFee = 0` 未支付手续费", "question_en": "Which clients have not yet paid their IPO subscription money?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q13", "question_zh": "列出今日到期的所有股票借贷交易。", "sql": "-- 查询今日到期的股票借贷 (参考日期: 2026-04-02)\n-- MCMarket 表仅包含 HKEX/OSL/SETW,无独立借贷市场标识\n-- 股票借贷可能在持仓变动类型 HoldType 中标识,或在专门的 StockBorrowing 表中\n-- 此 SQL 按 DueBuy/DueSell 显示当前待交收证券,作为借贷合约的近似替代\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.DueBuy AS BorrowQuantity,\n bi.DueSell AS LoanQuantity,\n bi.DueBuy - bi.DueSell AS NetBorrow\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE bi.DueBuy > 0 OR bi.DueSell > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `DueBuy/DueSell` 表示待交收数量(借款买入/借出卖出),反映股票借贷待到期情况\n- `MCMarket` 表无 `MarketType='BORROW'` 标识,现有市场仅为 HKEX/OSL/SETW\n- 股票借贷详情可能存储在 `TSBAccountLoanContract` 或其他借贷专用表中,需业务确认", "question_en": "List all stock borrow/loan transactions due to settle today.", "rating": 5, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 中 DueBuy/DueSell 均为 0,无待交收证券)", "tags": ["join", "trade", "filter"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q14", "question_zh": "列出已申请但尚未处理的客户提款请求。", "sql": "-- 查询待处理提款申请 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.TodayOut AS WithdrawalAmount,\n c.BankAvailableBalance AS AvailableBalance,\n c.State AS RecordStatus\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.TodayOut > 0\nORDER BY c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `TodayOut` 今日计划支出\n- 待处理提款申请应体现在此字段", "question_en": "Show all client withdrawals requested but not yet processed.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q16", "question_zh": "列出T+2交易缺少结算指令的账户。", "sql": "-- 查询T+2待交收但缺少指令的账户\n-- 注意:BCAccountInstrumentInstruction 表不存在,需改用 TSBSettlementInstruction\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.NextDayDueBuy AS T2DueBuy,\n bi.NextDayDueSell AS T2DueSell\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE (bi.NextDayDueBuy > 0 OR bi.NextDayDueSell > 0)\n AND NOT EXISTS (\n SELECT 1 FROM TSBSettlementInstruction si\n WHERE si.AccountOrBrokerID = bi.AccountID\n AND si.InstrumentID = bi.InstrumentID\n AND si.State IN ('P','A')\n );", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "TSBSettlementInstruction": {"alias": "si", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `NextDayDueBuy/NextDayDueSell` T+2交收数量(次日到期)\n- `TSBSettlementInstruction` 是结算指令表,通过 `NOT EXISTS` 排除已有指令的记录\n- `BCAccountInstrumentInstruction` 表不存在,不能使用", "question_en": "Show accounts where settlement instruction is missing for T+2 trades.", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "trade"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q18", "question_zh": "今日收到的未清算支票总额是多少?", "sql": "-- 查询未清算支票 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.UnderdueCheque AS OverdueCheque,\n c.NextDayDueCheque AS PendingCheque,\n c.UnderdueCheque + c.NextDayDueCheque AS TotalUncleared\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.UnderdueCheque > 0 OR c.NextDayDueCheque > 0) -- 注意:BCAccountCash 无这两个字段,需确认实际表\nORDER BY TotalUncleared DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 无 `UnderdueCheque` / `NextDayDueCheque` 字段,需业务确认支票数据存储位置", "question_en": "What is the total amount of uncleared cheques received today?", "rating": 4, "execution_result": null, "result_note": "当前查询无未清算支票", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q20", "question_zh": "哪些公司行动的选择指令需在今日结束前提交?", "sql": "-- 查询今日截止的公司行动选择 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.ElectionPeriodTo AS Deadline,\n e.ElectionOptionFlag,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.ElectionPeriodTo = '2026-04-02'\n AND e.ElectionOptionFlag = 'Y'\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `ElectionPeriodTo` 选择截止日期\n- `ElectionOptionFlag = 'Y'` 需要选择", "question_en": "Which corporate action elections are due by end of day?", "rating": 8, "execution_result": null, "result_note": "当前查询无今日截止的选择", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q28", "question_zh": "哪些客户已请求更改结算货币?", "sql": "-- 查询请求更改结算货币的客户\n-- 说明:经查 MCAccountCurrency 表无 CurrencyChangeStatus/RequestedCurrencyID 等字段\n-- 数据库中没有专门的货币变更请求表\n-- 此查询基于账户货币历史时间表(HCAccountCurrencyTimetable)展示货币生效安排\nSELECT \n act.AccountID,\n mca.Name AS AccountName,\n act.CurrencyID AS Currency,\n act.EffectiveDateFrom,\n act.EffectiveDateTo,\n act.AccountTypeID\nFROM HCAccountCurrencyTimetable act\nINNER JOIN MCAccount mca ON act.AccountID = mca.AccountID\nWHERE act.EffectiveDateTo IS NULL\nORDER BY act.EffectiveDateFrom DESC;", "schema_info": {"tables": {"HCAccountCurrencyTimetable": {"alias": "act", "columns": []}, "MCAccount": {"alias": "mca", "columns": []}}, "joins": [{"type": "INNER", "on": "act.AccountID = mca.AccountID"}]}, "explanation": "- `HCAccountCurrencyTimetable` 记录账户货币生效时间表\n- `EffectiveDateTo IS NULL` 表示当前生效的货币配置\n- 如需货币变更请求功能,建议在系统中扩展相关表结构", "question_en": "Which clients have requested a change in settlement currency?", "rating": 4, "execution_result": null, "result_note": "**说明:** 数据库中没有专门的货币变更请求表。`MCAccountCurrency` 表仅记录账户当前货币配置,无变更请求字段。", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q33", "question_zh": "今日是否有追缴保证金通知?", "sql": "-- 查询今日追缴保证金记录 (固定日期: 2026-04-02)\nSELECT \n m.AccountID,\n a.Name AS AccountName,\n m.CallForDeposit,\n m.CallForDepositDate,\n m.CallForDepositDays\nFROM HCAccountMarginCallForDeposit m\nINNER JOIN MCAccount a ON m.AccountID = a.AccountID\nWHERE m.CallForDepositDate = '2026-04-02';", "schema_info": {"tables": {"HCAccountMarginCallForDeposit": {"alias": "m", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "m.AccountID = a.AccountID"}]}, "explanation": "- `HCAccountMarginCallForDeposit` 记录追缴保证金信息\n- `CallForDeposit` 追缴金额", "question_en": "Have there been any margin calls today?", "rating": 9, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "margin", "filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q39", "question_zh": "列出今日需要执行的公司行动(如权益分派)。", "sql": "-- 查询今日生效的公司行动 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.MarketID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate,\n e.BalanceExDate,\n e.Remark,\n e.State\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.State = 'A'\n AND (\n e.BalanceDate = '2026-04-02'\n OR e.BalanceExDate = '2026-04-02'\n OR e.AutoHoldDate = '2026-04-02'\n OR e.AutoDeductDate = '2026-04-02'\n );", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派信息\n- `BalanceDate` 权益登记日期", "question_en": "List corporate actions (e.g., entitlements) that need to be performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q41", "question_zh": "列出今日执行的所有现金划转。", "sql": "-- 今日现金变动汇总 (固定日期: 2026-04-02)\nSELECT \n b.AccountID,\n a.Name AS AccountName,\n b.TodayIn AS CashIn,\n b.TodayOut AS CashOut,\n b.BankAvailableBalance\nFROM BCAccountCash b\nINNER JOIN MCAccount a ON b.AccountID = a.AccountID\nWHERE b.TodayIn > 0 OR b.TodayOut > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "b", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "b.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 记录账户现金变动\n- `TodayIn` 今日现金流入", "question_en": "List all cash movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "cash"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q50", "question_zh": "", "sql": "SELECT TOP\n\t15 c.ContractID,\n\tc.TradeDate,\n\tLTRIM( RTRIM( c.AccountID ) ) AS AccountID,\n\tLTRIM( RTRIM( c.MarketID ) ) AS MarketID,\n\tc.InstrumentID,\n\tc.BuySell,\n\tc.Quantity,\n\tc.Consideration,\n\tLTRIM( RTRIM( c.CurrencyID ) ) AS CurrencyID \nFROM\n\tdbo.TSBAccountContract c\n\tINNER JOIN dbo.MCAccount a ON c.AccountID = a.AccountID \nWHERE\n\tCAST ( c.TradeDate AS DATE ) = '2026-04-02' \n\tAND LTRIM( RTRIM( a.HouseAccountFlag ) ) = 'N' \nORDER BY\n\tc.ContractID;", "schema_info": {"tables": {"TSBAccountContract": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "", "question_en": "列出今天所有客戶的交易記錄", "rating": null, "execution_result": null, "result_note": "| ContractID | TradeDate | AccountID | MarketID | InstrumentID | BuySell | Quantity | Consideration | CurrencyID |\n| :--------- | :--------- | :-------- | :------- | :----------- | :------ | :------- | :------------ | :--------- |\n| B02403846 | 2026-04-02 | P011206 | HKEX | 01810 | B | 600 | 18360 | HKD |\n| B02403847 | 2026-04-02 | P011202 | HKEX | 01810 | B | 200 | 6120 | HKD |\n\n\n\n\n\n---\n\n## 附录:关键表说明\n\n| 表名 | 说明 | 关键字段 |\n|------|------|---------|\n| MCAccount | 账户主表 | AccountID, Name, MarketValue, MarginFacilityFlag |\n| MCUser | 用户表 | UserID, Name, Status, LogonEnableFlag |\n| BCAccountInstrument | 账户持仓 | AccountID, MarketID, Settled, DueBuy, DueSell |\n| BCAccountCash | 账户现金 | AccountID, TodayIn, TodayOut, AccruedInterest |\n| HCAccountMarginCallForDeposit | 追缴保证金 | AccountID, CallForDeposit, CallForDepositDate |\n| MCInstrument | 证券表 | InstrumentID, Name, MarginPercentage |\n| MCInstrumentIPODetail | IPO详情 | InstrumentID, Quantity, SubscriptionAmount |\n| MSBEntitlement | 权益分派 | EntitlementID, BalanceDate, BalanceExDate |\n| MCBroker | 经纪商 | BrokerID, Name |\n| MCLocation | 托管位置 | LocationID, Name, CCASSFlag |\n| HCExchangeRate | 汇率 | ExchangeRateID, ValueDate |\n| HCAccountCashRecord | 现金记录 | AccountID, ValueDate, LedgerBalance |\n\n---\n\n**文档结束**", "tags": ["join", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{"qid": "Q17", "question_zh": "哪些客户已超过其信用额度?", "sql": "-- 查询超信用额度客户(使用 MarginLimit)\n-- 注意:MCAccount 无 CreditLimit 字段,应用 MarginLimit\nSELECT \n AccountID,\n Name AS AccountName,\n MarginLimit,\n MarketValue,\n MarginLimit - MarketValue AS AvailableCredit,\n CASE \n WHEN MarketValue > MarginLimit THEN '超额'\n WHEN MarketValue > MarginLimit * 0.8 THEN '接近限额'\n ELSE '正常'\n END AS Status\nFROM MCAccount\nWHERE MarginLimit > 0\n AND MarketValue > MarginLimit * 0.8\nORDER BY (MarketValue - MarginLimit) DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.MarginLimit` 保证金/信用限额(注意:无 `CreditLimit` 字段)\n- `MCAccount.MarketValue` 持仓市值\n- `MarginBLimit` 是单独借款限额,注意区分", "question_en": "Which clients have exceeded their credit limit?", "rating": 7, "execution_result": null, "result_note": "当前查询无超限客户", "tags": ["account", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q50", "question_zh": "", "sql": "SELECT TOP\n\t15 c.ContractID,\n\tc.TradeDate,\n\tLTRIM( RTRIM( c.AccountID ) ) AS AccountID,\n\tLTRIM( RTRIM( c.MarketID ) ) AS MarketID,\n\tc.InstrumentID,\n\tc.BuySell,\n\tc.Quantity,\n\tc.Consideration,\n\tLTRIM( RTRIM( c.CurrencyID ) ) AS CurrencyID \nFROM\n\tdbo.TSBAccountContract c\n\tINNER JOIN dbo.MCAccount a ON c.AccountID = a.AccountID \nWHERE\n\tCAST ( c.TradeDate AS DATE ) = '2026-04-02' \n\tAND LTRIM( RTRIM( a.HouseAccountFlag ) ) = 'N' \nORDER BY\n\tc.ContractID;", "schema_info": {"tables": {"TSBAccountContract": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "", "question_en": "列出今天所有客戶的交易記錄", "rating": null, "execution_result": null, "result_note": "| ContractID | TradeDate | AccountID | MarketID | InstrumentID | BuySell | Quantity | Consideration | CurrencyID |\n| :--------- | :--------- | :-------- | :------- | :----------- | :------ | :------- | :------------ | :--------- |\n| B02403846 | 2026-04-02 | P011206 | HKEX | 01810 | B | 600 | 18360 | HKD |\n| B02403847 | 2026-04-02 | P011202 | HKEX | 01810 | B | 200 | 6120 | HKD |\n\n\n\n\n\n---\n\n## 附录:关键表说明\n\n| 表名 | 说明 | 关键字段 |\n|------|------|---------|\n| MCAccount | 账户主表 | AccountID, Name, MarketValue, MarginFacilityFlag |\n| MCUser | 用户表 | UserID, Name, Status, LogonEnableFlag |\n| BCAccountInstrument | 账户持仓 | AccountID, MarketID, Settled, DueBuy, DueSell |\n| BCAccountCash | 账户现金 | AccountID, TodayIn, TodayOut, AccruedInterest |\n| HCAccountMarginCallForDeposit | 追缴保证金 | AccountID, CallForDeposit, CallForDepositDate |\n| MCInstrument | 证券表 | InstrumentID, Name, MarginPercentage |\n| MCInstrumentIPODetail | IPO详情 | InstrumentID, Quantity, SubscriptionAmount |\n| MSBEntitlement | 权益分派 | EntitlementID, BalanceDate, BalanceExDate |\n| MCBroker | 经纪商 | BrokerID, Name |\n| MCLocation | 托管位置 | LocationID, Name, CCASSFlag |\n| HCExchangeRate | 汇率 | ExchangeRateID, ValueDate |\n| HCAccountCashRecord | 现金记录 | AccountID, ValueDate, LedgerBalance |\n\n---\n\n**文档结束**", "tags": ["join", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{"qid": "Q5", "question_zh": "列出今日发出的所有追收保证金通知及其回复状态。", "sql": "-- 查询今日追缴保证金记录 (参考日期: 2026-04-02)\n-- 使用 MCAccount 的账户级追缴字段(CallForDepositDate = 追缴通知发出日期)\n-- 注意:HCAccountMarginCallForDeposit 是追缴每日快照表,不含\"通知发出日\"字段\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.CallForDeposit AS CallAmount,\n a.CallForDepositDate AS IssueDate,\n a.CallForDepositDays AS OverdueDays\nFROM MCAccount a\nWHERE a.CallForDepositDate = '2026-04-02'\nORDER BY a.CallForDeposit DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.CallForDepositDate` = 追缴通知发出日期,`CallForDeposit` = 追缴金额\n- `CallForDeposit < 0` 表示已结清;如查活跃追缴,应筛选 `CallForDeposit > 0`\n- `HCAccountMarginCallForDeposit` 是追缴每日历史快照,不适合查询\"今日发出的通知\"\n- `CallStatus` 字段在 `HCAccountMarginCallForDeposit` 中不存在", "question_en": "Show all margin calls issued today and their response status.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果(测试环境中 MCAccount 的 `CallForDepositDate` 最新到 2025-10-31,2026-04-02 无追缴通知记录)\n\n> 注:`MCAccount.CallForDeposit` 当前所有值均为负数(已结清的追缴金额),无活跃追缴记录。", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q19", "question_zh": "列出今日起保证金比例发生变动的所有证券。", "sql": "-- 查询保证金比例变动的证券 (固定日期: 2026-04-02)\nSELECT \n i.InstrumentID,\n i.Name AS InstrumentName,\n i.MarginPercentage AS CurrentMarginRatio,\n i.MarginChangeDate\nFROM MCInstrument i\nWHERE i.MarginChangeDate = '2026-04-02'\nORDER BY i.MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument.MarginPercentage` 当前保证金比例\n- `MarginChangeDate` 变动日期", "question_en": "List all instruments with margin ratio changes effective today.", "rating": 7, "execution_result": null, "result_note": "当前查询无保证金比例变动记录", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q33", "question_zh": "今日是否有追缴保证金通知?", "sql": "-- 查询今日追缴保证金记录 (固定日期: 2026-04-02)\nSELECT \n m.AccountID,\n a.Name AS AccountName,\n m.CallForDeposit,\n m.CallForDepositDate,\n m.CallForDepositDays\nFROM HCAccountMarginCallForDeposit m\nINNER JOIN MCAccount a ON m.AccountID = a.AccountID\nWHERE m.CallForDepositDate = '2026-04-02';", "schema_info": {"tables": {"HCAccountMarginCallForDeposit": {"alias": "m", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "m.AccountID = a.AccountID"}]}, "explanation": "- `HCAccountMarginCallForDeposit` 记录追缴保证金信息\n- `CallForDeposit` 追缴金额", "question_en": "Have there been any margin calls today?", "rating": 9, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "margin", "filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q38", "question_zh": "本月IPO事件的总佣金、手续费和保证金利息是多少?", "sql": "-- IPO相关费用汇总 (2026年4月)\nSELECT \n 'IPO Commission' AS FeeType,\n SUM(ip.SubscriptionAmount * 0.001) AS EstimatedFee\nFROM MCInstrumentIPODetail ip\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": []}, "explanation": "- IPO相关费用分散在多个表中\n- 需要关联 `BCAccountInstrument` 获取IPO交易费用", "question_en": "What are the total commission, handling fees, and margin interest for IPO events this month?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["margin", "aggregation", "filter", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q3", "question_zh": "今日结算使用的汇率是多少?", "sql": "-- 查询汇率类型列表 (固定日期: 2026-04-02)\nSELECT \n ExchangeRateID, \n Name, \n NameEx\nFROM HCExchangeRate\nWHERE ValueDate = '2026-04-02'\nORDER BY ExchangeRateID;", "schema_info": {"tables": {"HCExchangeRate": {"alias": "HCExchangeRate", "columns": []}}, "joins": []}, "explanation": "- `HCExchangeRate` 存储汇率类型定义\n- `HCExchangeRateDetail` 存储各货币对的汇率值\n- 两个表通过 `ExchangeRateID` + `ValueDate` 关联\n- `ExchangeRateDirection` = 'D' 表示直接汇率", "question_en": "What are the currency exchange rates used for today's settlement?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["exchange_rate", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q4", "question_zh": "哪些券商尚有未履行的交收义务?", "sql": "-- 查询有未履行交收义务的经纪商(截至 2026-04-02)\n-- Settled < 0 表示经纪商欠我们的现金(未履行交收义务)\n-- 注意:BCBrokerCash.Settled 是经纪商在我们账户的已结算现金余额,负数=经纪商欠款\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n b.CurrencyID,\n b.Settled AS OwedToUsAmount,\n ABS(b.Settled) AS OutstandingAmount\nFROM BCBrokerCash b\nINNER JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.Settled < 0\nORDER BY ABS(b.Settled) DESC;", "schema_info": {"tables": {"BCBrokerCash": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `BCBrokerCash` 记录经纪商在我们账户的结算现金,`Settled < 0` = 经纪商欠我们(未履行交收义务)\n- 关联 `MCBroker` 获取经纪商名称\n- 按欠款金额降序排列,优先处理大额欠款", "question_en": "Which brokers have outstanding delivery obligations to us?", "rating": 8, "execution_result": null, "result_note": "| BrokerID | BrokerName | CurrencyID | OwedToUsAmount | OutstandingAmount |\n|----------|-----------|------------|-----------------|-------------------|\n| CCASS | CCASS | CNY | -52,680.00 | 52,680.00 |\n| CCASS | CCASS | USD | -90.00 | 90.00 |\n\n> 注:CCASS 欠我们的结算现金(CNY 52,680 + USD 90)。HKD 余额为正(+4,202,811.87)表示我们欠 CCASS,不属于\"未履行交收义务\"范畴。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q5", "question_zh": "列出今日发出的所有追收保证金通知及其回复状态。", "sql": "-- 查询今日追缴保证金记录 (参考日期: 2026-04-02)\n-- 使用 MCAccount 的账户级追缴字段(CallForDepositDate = 追缴通知发出日期)\n-- 注意:HCAccountMarginCallForDeposit 是追缴每日快照表,不含\"通知发出日\"字段\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.CallForDeposit AS CallAmount,\n a.CallForDepositDate AS IssueDate,\n a.CallForDepositDays AS OverdueDays\nFROM MCAccount a\nWHERE a.CallForDepositDate = '2026-04-02'\nORDER BY a.CallForDeposit DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.CallForDepositDate` = 追缴通知发出日期,`CallForDeposit` = 追缴金额\n- `CallForDeposit < 0` 表示已结清;如查活跃追缴,应筛选 `CallForDeposit > 0`\n- `HCAccountMarginCallForDeposit` 是追缴每日历史快照,不适合查询\"今日发出的通知\"\n- `CallStatus` 字段在 `HCAccountMarginCallForDeposit` 中不存在", "question_en": "Show all margin calls issued today and their response status.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果(测试环境中 MCAccount 的 `CallForDepositDate` 最新到 2025-10-31,2026-04-02 无追缴通知记录)\n\n> 注:`MCAccount.CallForDeposit` 当前所有值均为负数(已结清的追缴金额),无活跃追缴记录。", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q9", "question_zh": "今日预计收到哪些股息?", "sql": "-- 查询今日预计收到的股息 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceDate AS PaymentDate,\n e.BalanceType,\n e.Remark AS DividendDetails\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.BalanceDate = '2026-04-02'\n AND e.BalanceType = 'C'\n AND e.State = 'A'\nORDER BY i.Name;", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派\n- `BalanceType = 'C'` 表示现金股息\n- `BalanceDate` 股息支付日期", "question_en": "What dividends are expected to be received today?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q10", "question_zh": "列出所有进行中IPO的待缴申购金额。", "sql": "-- 查询进行中IPO的待缴金额\nSELECT \n ip.InstrumentID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.SubscriptionAmount,\n ip.SubscriptionPrice,\n ip.Quantity AS SubscriptionLot,\n ip.CreatedOn AS SubscriptionDate,\n ip.State AS SubscriptionStatus\nFROM MCInstrumentIPODetail ip\nINNER JOIN MCInstrument i ON ip.InstrumentID = i.InstrumentID\nWHERE ip.State = 'A'\n AND i.State = 'A'\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "ip.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MCInstrumentIPODetail` 记录IPO认购详情\n- `SubscriptionAmount` 申购金额\n- `State = 'A'` 表示进行中", "question_en": "Show all pending subscription amounts for ongoing IPOs.", "rating": 8, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | SubscriptionAmount | SubscriptionPrice | Quantity |\n|--------------|----------------|-------------------|-------------------|----------|\n| 02259 | HKEX | 7,231.19 | 36.16 | 100 |\n| 02259 | HKEX | 14,462.40 | 36.16 | 200 |\n\n> 注:MCInstrumentIPODetail 共 586 条进行中 IPO 记录(State='A'),以上为示例行。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q11", "question_zh": "哪些客户尚未缴纳IPO申购款?", "sql": "-- 查询未缴纳IPO申购款的账户\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.TotalBoughtQuantity AS SubscriptionLot,\n bi.TotalBoughtConsideration AS SubscriptionAmount\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nINNER JOIN MCInstrumentIPODetail ip ON bi.InstrumentID = ip.InstrumentID\nWHERE ip.State = 'A'\n AND bi.TotalBoughtConsideration > 0\n AND bi.TotalBoughtFee = 0\nORDER BY bi.TotalBoughtConsideration DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}, {"type": "INNER", "on": "bi.InstrumentID = ip.InstrumentID"}]}, "explanation": "- 通过比对买卖数量和手续费判断是否缴款\n- `TotalBoughtConsideration > 0` 有认购\n- `TotalBoughtFee = 0` 未支付手续费", "question_en": "Which clients have not yet paid their IPO subscription money?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q14", "question_zh": "列出已申请但尚未处理的客户提款请求。", "sql": "-- 查询待处理提款申请 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.TodayOut AS WithdrawalAmount,\n c.BankAvailableBalance AS AvailableBalance,\n c.State AS RecordStatus\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.TodayOut > 0\nORDER BY c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `TodayOut` 今日计划支出\n- 待处理提款申请应体现在此字段", "question_en": "Show all client withdrawals requested but not yet processed.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q17", "question_zh": "哪些客户已超过其信用额度?", "sql": "-- 查询超信用额度客户(使用 MarginLimit)\n-- 注意:MCAccount 无 CreditLimit 字段,应用 MarginLimit\nSELECT \n AccountID,\n Name AS AccountName,\n MarginLimit,\n MarketValue,\n MarginLimit - MarketValue AS AvailableCredit,\n CASE \n WHEN MarketValue > MarginLimit THEN '超额'\n WHEN MarketValue > MarginLimit * 0.8 THEN '接近限额'\n ELSE '正常'\n END AS Status\nFROM MCAccount\nWHERE MarginLimit > 0\n AND MarketValue > MarginLimit * 0.8\nORDER BY (MarketValue - MarginLimit) DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.MarginLimit` 保证金/信用限额(注意:无 `CreditLimit` 字段)\n- `MCAccount.MarketValue` 持仓市值\n- `MarginBLimit` 是单独借款限额,注意区分", "question_en": "Which clients have exceeded their credit limit?", "rating": 7, "execution_result": null, "result_note": "当前查询无超限客户", "tags": ["account", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q18", "question_zh": "今日收到的未清算支票总额是多少?", "sql": "-- 查询未清算支票 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.UnderdueCheque AS OverdueCheque,\n c.NextDayDueCheque AS PendingCheque,\n c.UnderdueCheque + c.NextDayDueCheque AS TotalUncleared\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.UnderdueCheque > 0 OR c.NextDayDueCheque > 0) -- 注意:BCAccountCash 无这两个字段,需确认实际表\nORDER BY TotalUncleared DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 无 `UnderdueCheque` / `NextDayDueCheque` 字段,需业务确认支票数据存储位置", "question_en": "What is the total amount of uncleared cheques received today?", "rating": 4, "execution_result": null, "result_note": "当前查询无未清算支票", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q19", "question_zh": "列出今日起保证金比例发生变动的所有证券。", "sql": "-- 查询保证金比例变动的证券 (固定日期: 2026-04-02)\nSELECT \n i.InstrumentID,\n i.Name AS InstrumentName,\n i.MarginPercentage AS CurrentMarginRatio,\n i.MarginChangeDate\nFROM MCInstrument i\nWHERE i.MarginChangeDate = '2026-04-02'\nORDER BY i.MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument.MarginPercentage` 当前保证金比例\n- `MarginChangeDate` 变动日期", "question_en": "List all instruments with margin ratio changes effective today.", "rating": 7, "execution_result": null, "result_note": "当前查询无保证金比例变动记录", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q24", "question_zh": "有多少客户报表已生成成功但尚未发送?", "sql": "-- 查询已生成但未发送的报表\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.LastDailyStatementDate,\n a.LastMonthlyStatementDate,\n a.LastStatementGenerateTime,\n a.LastStatementSendTime,\n CASE \n WHEN a.LastStatementGenerateTime > a.LastStatementSendTime THEN '未发送'\n ELSE '已发送'\n END AS DeliveryStatus\nFROM MCAccount a\nWHERE a.LastStatementGenerateTime IS NOT NULL\n AND (a.LastStatementSendTime IS NULL \n OR a.LastStatementGenerateTime > a.LastStatementSendTime)\nORDER BY a.LastStatementGenerateTime DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `LastStatementGenerateTime` 报表生成时间\n- `LastStatementSendTime` 报表发送时间", "question_en": "How many client statements were successfully generated but not yet delivered?", "rating": 7, "execution_result": null, "result_note": "当前查询无未发送报表", "tags": ["ordering", "account", "filter", "statement"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q25", "question_zh": "列出所有待批准的股票转移请求。", "sql": "-- 查询待批准的股票转移\nSELECT \n AccountID,\n InstrumentID,\n MovementType,\n MovementQuantity,\n FromAccountID,\n ToAccountID,\n RequestDate,\n ApprovalStatus,\n RequestedBy\nFROM BCAccountInstrumentMovement\nWHERE ApprovalStatus = 'P'\nORDER BY RequestDate DESC;", "schema_info": {"tables": {"BCAccountInstrumentMovement": {"alias": "BCAccountInstrumentMovement", "columns": []}}, "joins": []}, "explanation": "- 股票转移请求存储在专用表中\n- `ApprovalStatus = 'P'` 待批准状态", "question_en": "Show all pending stock movement requests that require approval.", "rating": 6, "execution_result": null, "result_note": "当前查询无待批准请求", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q28", "question_zh": "哪些客户已请求更改结算货币?", "sql": "-- 查询请求更改结算货币的客户\n-- 说明:经查 MCAccountCurrency 表无 CurrencyChangeStatus/RequestedCurrencyID 等字段\n-- 数据库中没有专门的货币变更请求表\n-- 此查询基于账户货币历史时间表(HCAccountCurrencyTimetable)展示货币生效安排\nSELECT \n act.AccountID,\n mca.Name AS AccountName,\n act.CurrencyID AS Currency,\n act.EffectiveDateFrom,\n act.EffectiveDateTo,\n act.AccountTypeID\nFROM HCAccountCurrencyTimetable act\nINNER JOIN MCAccount mca ON act.AccountID = mca.AccountID\nWHERE act.EffectiveDateTo IS NULL\nORDER BY act.EffectiveDateFrom DESC;", "schema_info": {"tables": {"HCAccountCurrencyTimetable": {"alias": "act", "columns": []}, "MCAccount": {"alias": "mca", "columns": []}}, "joins": [{"type": "INNER", "on": "act.AccountID = mca.AccountID"}]}, "explanation": "- `HCAccountCurrencyTimetable` 记录账户货币生效时间表\n- `EffectiveDateTo IS NULL` 表示当前生效的货币配置\n- 如需货币变更请求功能,建议在系统中扩展相关表结构", "question_en": "Which clients have requested a change in settlement currency?", "rating": 4, "execution_result": null, "result_note": "**说明:** 数据库中没有专门的货币变更请求表。`MCAccountCurrency` 表仅记录账户当前货币配置,无变更请求字段。", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q42", "question_zh": "列出后台系统中的活跃用户。", "sql": "-- 查询后台系统活跃用户\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n u.DepartmentID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储系统用户信息\n- `State = 'A'` 用户状态正常", "question_en": "List active users in the back office.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | AEType |\n|--------|----------|--------|-----------------|---------------|--------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-03 18:53:04 | 个人 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 16:47:11 | 个人 |", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q50", "question_zh": "", "sql": "SELECT TOP\n\t15 c.ContractID,\n\tc.TradeDate,\n\tLTRIM( RTRIM( c.AccountID ) ) AS AccountID,\n\tLTRIM( RTRIM( c.MarketID ) ) AS MarketID,\n\tc.InstrumentID,\n\tc.BuySell,\n\tc.Quantity,\n\tc.Consideration,\n\tLTRIM( RTRIM( c.CurrencyID ) ) AS CurrencyID \nFROM\n\tdbo.TSBAccountContract c\n\tINNER JOIN dbo.MCAccount a ON c.AccountID = a.AccountID \nWHERE\n\tCAST ( c.TradeDate AS DATE ) = '2026-04-02' \n\tAND LTRIM( RTRIM( a.HouseAccountFlag ) ) = 'N' \nORDER BY\n\tc.ContractID;", "schema_info": {"tables": {"TSBAccountContract": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "", "question_en": "列出今天所有客戶的交易記錄", "rating": null, "execution_result": null, "result_note": "| ContractID | TradeDate | AccountID | MarketID | InstrumentID | BuySell | Quantity | Consideration | CurrencyID |\n| :--------- | :--------- | :-------- | :------- | :----------- | :------ | :------- | :------------ | :--------- |\n| B02403846 | 2026-04-02 | P011206 | HKEX | 01810 | B | 600 | 18360 | HKD |\n| B02403847 | 2026-04-02 | P011202 | HKEX | 01810 | B | 200 | 6120 | HKD |\n\n\n\n\n\n---\n\n## 附录:关键表说明\n\n| 表名 | 说明 | 关键字段 |\n|------|------|---------|\n| MCAccount | 账户主表 | AccountID, Name, MarketValue, MarginFacilityFlag |\n| MCUser | 用户表 | UserID, Name, Status, LogonEnableFlag |\n| BCAccountInstrument | 账户持仓 | AccountID, MarketID, Settled, DueBuy, DueSell |\n| BCAccountCash | 账户现金 | AccountID, TodayIn, TodayOut, AccruedInterest |\n| HCAccountMarginCallForDeposit | 追缴保证金 | AccountID, CallForDeposit, CallForDepositDate |\n| MCInstrument | 证券表 | InstrumentID, Name, MarginPercentage |\n| MCInstrumentIPODetail | IPO详情 | InstrumentID, Quantity, SubscriptionAmount |\n| MSBEntitlement | 权益分派 | EntitlementID, BalanceDate, BalanceExDate |\n| MCBroker | 经纪商 | BrokerID, Name |\n| MCLocation | 托管位置 | LocationID, Name, CCASSFlag |\n| HCExchangeRate | 汇率 | ExchangeRateID, ValueDate |\n| HCAccountCashRecord | 现金记录 | AccountID, ValueDate, LedgerBalance |\n\n---\n\n**文档结束**", "tags": ["join", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"qid": "Q24", "question_zh": "有多少客户报表已生成成功但尚未发送?", "sql": "-- 查询已生成但未发送的报表\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.LastDailyStatementDate,\n a.LastMonthlyStatementDate,\n a.LastStatementGenerateTime,\n a.LastStatementSendTime,\n CASE \n WHEN a.LastStatementGenerateTime > a.LastStatementSendTime THEN '未发送'\n ELSE '已发送'\n END AS DeliveryStatus\nFROM MCAccount a\nWHERE a.LastStatementGenerateTime IS NOT NULL\n AND (a.LastStatementSendTime IS NULL \n OR a.LastStatementGenerateTime > a.LastStatementSendTime)\nORDER BY a.LastStatementGenerateTime DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `LastStatementGenerateTime` 报表生成时间\n- `LastStatementSendTime` 报表发送时间", "question_en": "How many client statements were successfully generated but not yet delivered?", "rating": 7, "execution_result": null, "result_note": "当前查询无未发送报表", "tags": ["ordering", "account", "filter", "statement"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q13", "question_zh": "列出今日到期的所有股票借贷交易。", "sql": "-- 查询今日到期的股票借贷 (参考日期: 2026-04-02)\n-- MCMarket 表仅包含 HKEX/OSL/SETW,无独立借贷市场标识\n-- 股票借贷可能在持仓变动类型 HoldType 中标识,或在专门的 StockBorrowing 表中\n-- 此 SQL 按 DueBuy/DueSell 显示当前待交收证券,作为借贷合约的近似替代\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.DueBuy AS BorrowQuantity,\n bi.DueSell AS LoanQuantity,\n bi.DueBuy - bi.DueSell AS NetBorrow\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE bi.DueBuy > 0 OR bi.DueSell > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `DueBuy/DueSell` 表示待交收数量(借款买入/借出卖出),反映股票借贷待到期情况\n- `MCMarket` 表无 `MarketType='BORROW'` 标识,现有市场仅为 HKEX/OSL/SETW\n- 股票借贷详情可能存储在 `TSBAccountLoanContract` 或其他借贷专用表中,需业务确认", "question_en": "List all stock borrow/loan transactions due to settle today.", "rating": 5, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 中 DueBuy/DueSell 均为 0,无待交收证券)", "tags": ["join", "trade", "filter"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q16", "question_zh": "列出T+2交易缺少结算指令的账户。", "sql": "-- 查询T+2待交收但缺少指令的账户\n-- 注意:BCAccountInstrumentInstruction 表不存在,需改用 TSBSettlementInstruction\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.NextDayDueBuy AS T2DueBuy,\n bi.NextDayDueSell AS T2DueSell\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE (bi.NextDayDueBuy > 0 OR bi.NextDayDueSell > 0)\n AND NOT EXISTS (\n SELECT 1 FROM TSBSettlementInstruction si\n WHERE si.AccountOrBrokerID = bi.AccountID\n AND si.InstrumentID = bi.InstrumentID\n AND si.State IN ('P','A')\n );", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "TSBSettlementInstruction": {"alias": "si", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `NextDayDueBuy/NextDayDueSell` T+2交收数量(次日到期)\n- `TSBSettlementInstruction` 是结算指令表,通过 `NOT EXISTS` 排除已有指令的记录\n- `BCAccountInstrumentInstruction` 表不存在,不能使用", "question_en": "Show accounts where settlement instruction is missing for T+2 trades.", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "trade"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q31", "question_zh": "指定日期或日期范围内收到的交易量(如成交金额)及相应佣金是多少?", "sql": "-- 交易量和佣金统计\nSELECT \n SUM(TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(TotalBoughtFee) AS TotalBuyCommission,\n SUM(TotalSoldConsideration) AS TotalSellConsideration,\n SUM(TotalSoldFee) AS TotalSellCommission,\n SUM(TotalBoughtConsideration) + SUM(TotalSoldConsideration) AS TotalConsideration,\n SUM(TotalBoughtFee) + SUM(TotalSoldFee) AS TotalCommission\nFROM BCAccountInstrument;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录了账户所有历史买卖交易\n- `TotalBoughtConsideration / TotalSoldConsideration`: 买卖成交金额", "question_en": "What is the trading volume (e.g., consideration) and corresponding commission amount received for a specific date or date range?", "rating": 9, "execution_result": null, "result_note": "| TotalBuyConsideration | TotalBuyCommission | TotalSellConsideration | TotalSellCommission | TotalConsideration | TotalCommission |\n|---------------------|-------------------|----------------------|--------------------|-------------------|-----------------|\n| 945,480,833.78 | 793,149.07 | 278,693,891.64 | 809,248.89 | 1,224,174,725.42 | 1,602,397.96 |", "tags": ["trade", "aggregation"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q47", "question_zh": "今日/指定日期发放了多少份日结单或月结单?", "sql": "-- 查询报表发放统计 (2026-04-02)\nSELECT \n 'Daily Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastDailyStatementDate = '2026-04-02'\nUNION ALL\nSELECT \n 'Monthly Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastMonthlyStatementDate = '2026-04-02';", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 记录账户报表发送日期\n- `LastDailyStatementDate` 最近日结单发送日期", "question_en": "How many daily or monthly statements were issued today / on a specific date?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["union", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{"qid": "Q3", "question_zh": "今日结算使用的汇率是多少?", "sql": "-- 查询汇率类型列表 (固定日期: 2026-04-02)\nSELECT \n ExchangeRateID, \n Name, \n NameEx\nFROM HCExchangeRate\nWHERE ValueDate = '2026-04-02'\nORDER BY ExchangeRateID;", "schema_info": {"tables": {"HCExchangeRate": {"alias": "HCExchangeRate", "columns": []}}, "joins": []}, "explanation": "- `HCExchangeRate` 存储汇率类型定义\n- `HCExchangeRateDetail` 存储各货币对的汇率值\n- 两个表通过 `ExchangeRateID` + `ValueDate` 关联\n- `ExchangeRateDirection` = 'D' 表示直接汇率", "question_en": "What are the currency exchange rates used for today's settlement?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["exchange_rate", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q4", "question_zh": "哪些券商尚有未履行的交收义务?", "sql": "-- 查询有未履行交收义务的经纪商(截至 2026-04-02)\n-- Settled < 0 表示经纪商欠我们的现金(未履行交收义务)\n-- 注意:BCBrokerCash.Settled 是经纪商在我们账户的已结算现金余额,负数=经纪商欠款\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n b.CurrencyID,\n b.Settled AS OwedToUsAmount,\n ABS(b.Settled) AS OutstandingAmount\nFROM BCBrokerCash b\nINNER JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.Settled < 0\nORDER BY ABS(b.Settled) DESC;", "schema_info": {"tables": {"BCBrokerCash": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `BCBrokerCash` 记录经纪商在我们账户的结算现金,`Settled < 0` = 经纪商欠我们(未履行交收义务)\n- 关联 `MCBroker` 获取经纪商名称\n- 按欠款金额降序排列,优先处理大额欠款", "question_en": "Which brokers have outstanding delivery obligations to us?", "rating": 8, "execution_result": null, "result_note": "| BrokerID | BrokerName | CurrencyID | OwedToUsAmount | OutstandingAmount |\n|----------|-----------|------------|-----------------|-------------------|\n| CCASS | CCASS | CNY | -52,680.00 | 52,680.00 |\n| CCASS | CCASS | USD | -90.00 | 90.00 |\n\n> 注:CCASS 欠我们的结算现金(CNY 52,680 + USD 90)。HKD 余额为正(+4,202,811.87)表示我们欠 CCASS,不属于\"未履行交收义务\"范畴。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q5", "question_zh": "列出今日发出的所有追收保证金通知及其回复状态。", "sql": "-- 查询今日追缴保证金记录 (参考日期: 2026-04-02)\n-- 使用 MCAccount 的账户级追缴字段(CallForDepositDate = 追缴通知发出日期)\n-- 注意:HCAccountMarginCallForDeposit 是追缴每日快照表,不含\"通知发出日\"字段\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.CallForDeposit AS CallAmount,\n a.CallForDepositDate AS IssueDate,\n a.CallForDepositDays AS OverdueDays\nFROM MCAccount a\nWHERE a.CallForDepositDate = '2026-04-02'\nORDER BY a.CallForDeposit DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.CallForDepositDate` = 追缴通知发出日期,`CallForDeposit` = 追缴金额\n- `CallForDeposit < 0` 表示已结清;如查活跃追缴,应筛选 `CallForDeposit > 0`\n- `HCAccountMarginCallForDeposit` 是追缴每日历史快照,不适合查询\"今日发出的通知\"\n- `CallStatus` 字段在 `HCAccountMarginCallForDeposit` 中不存在", "question_en": "Show all margin calls issued today and their response status.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果(测试环境中 MCAccount 的 `CallForDepositDate` 最新到 2025-10-31,2026-04-02 无追缴通知记录)\n\n> 注:`MCAccount.CallForDeposit` 当前所有值均为负数(已结清的追缴金额),无活跃追缴记录。", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q8", "question_zh": "明天生效的公司行动涉及哪些证券?", "sql": "-- 查询明日生效的公司行动 (固定日期: 2026-04-03)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate AS EffectiveDate,\n e.BalanceExDate AS ExDate,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE (e.BalanceDate = '2026-04-03' OR e.BalanceExDate = '2026-04-03')\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录权益分派信息\n- `BalanceDate` 权益登记日期\n- `BalanceExDate` 除权日期", "question_en": "Which instruments have corporate actions effective tomorrow?", "rating": 8, "execution_result": null, "result_note": "当前查询无结果(2026-04-03无公司行动)", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q9", "question_zh": "今日预计收到哪些股息?", "sql": "-- 查询今日预计收到的股息 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceDate AS PaymentDate,\n e.BalanceType,\n e.Remark AS DividendDetails\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.BalanceDate = '2026-04-02'\n AND e.BalanceType = 'C'\n AND e.State = 'A'\nORDER BY i.Name;", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派\n- `BalanceType = 'C'` 表示现金股息\n- `BalanceDate` 股息支付日期", "question_en": "What dividends are expected to be received today?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q10", "question_zh": "列出所有进行中IPO的待缴申购金额。", "sql": "-- 查询进行中IPO的待缴金额\nSELECT \n ip.InstrumentID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.SubscriptionAmount,\n ip.SubscriptionPrice,\n ip.Quantity AS SubscriptionLot,\n ip.CreatedOn AS SubscriptionDate,\n ip.State AS SubscriptionStatus\nFROM MCInstrumentIPODetail ip\nINNER JOIN MCInstrument i ON ip.InstrumentID = i.InstrumentID\nWHERE ip.State = 'A'\n AND i.State = 'A'\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "ip.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MCInstrumentIPODetail` 记录IPO认购详情\n- `SubscriptionAmount` 申购金额\n- `State = 'A'` 表示进行中", "question_en": "Show all pending subscription amounts for ongoing IPOs.", "rating": 8, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | SubscriptionAmount | SubscriptionPrice | Quantity |\n|--------------|----------------|-------------------|-------------------|----------|\n| 02259 | HKEX | 7,231.19 | 36.16 | 100 |\n| 02259 | HKEX | 14,462.40 | 36.16 | 200 |\n\n> 注:MCInstrumentIPODetail 共 586 条进行中 IPO 记录(State='A'),以上为示例行。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q14", "question_zh": "列出已申请但尚未处理的客户提款请求。", "sql": "-- 查询待处理提款申请 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.TodayOut AS WithdrawalAmount,\n c.BankAvailableBalance AS AvailableBalance,\n c.State AS RecordStatus\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.TodayOut > 0\nORDER BY c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `TodayOut` 今日计划支出\n- 待处理提款申请应体现在此字段", "question_en": "Show all client withdrawals requested but not yet processed.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q17", "question_zh": "哪些客户已超过其信用额度?", "sql": "-- 查询超信用额度客户(使用 MarginLimit)\n-- 注意:MCAccount 无 CreditLimit 字段,应用 MarginLimit\nSELECT \n AccountID,\n Name AS AccountName,\n MarginLimit,\n MarketValue,\n MarginLimit - MarketValue AS AvailableCredit,\n CASE \n WHEN MarketValue > MarginLimit THEN '超额'\n WHEN MarketValue > MarginLimit * 0.8 THEN '接近限额'\n ELSE '正常'\n END AS Status\nFROM MCAccount\nWHERE MarginLimit > 0\n AND MarketValue > MarginLimit * 0.8\nORDER BY (MarketValue - MarginLimit) DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.MarginLimit` 保证金/信用限额(注意:无 `CreditLimit` 字段)\n- `MCAccount.MarketValue` 持仓市值\n- `MarginBLimit` 是单独借款限额,注意区分", "question_en": "Which clients have exceeded their credit limit?", "rating": 7, "execution_result": null, "result_note": "当前查询无超限客户", "tags": ["account", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q18", "question_zh": "今日收到的未清算支票总额是多少?", "sql": "-- 查询未清算支票 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.UnderdueCheque AS OverdueCheque,\n c.NextDayDueCheque AS PendingCheque,\n c.UnderdueCheque + c.NextDayDueCheque AS TotalUncleared\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.UnderdueCheque > 0 OR c.NextDayDueCheque > 0) -- 注意:BCAccountCash 无这两个字段,需确认实际表\nORDER BY TotalUncleared DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 无 `UnderdueCheque` / `NextDayDueCheque` 字段,需业务确认支票数据存储位置", "question_en": "What is the total amount of uncleared cheques received today?", "rating": 4, "execution_result": null, "result_note": "当前查询无未清算支票", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q19", "question_zh": "列出今日起保证金比例发生变动的所有证券。", "sql": "-- 查询保证金比例变动的证券 (固定日期: 2026-04-02)\nSELECT \n i.InstrumentID,\n i.Name AS InstrumentName,\n i.MarginPercentage AS CurrentMarginRatio,\n i.MarginChangeDate\nFROM MCInstrument i\nWHERE i.MarginChangeDate = '2026-04-02'\nORDER BY i.MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument.MarginPercentage` 当前保证金比例\n- `MarginChangeDate` 变动日期", "question_en": "List all instruments with margin ratio changes effective today.", "rating": 7, "execution_result": null, "result_note": "当前查询无保证金比例变动记录", "tags": ["margin", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q20", "question_zh": "哪些公司行动的选择指令需在今日结束前提交?", "sql": "-- 查询今日截止的公司行动选择 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.ElectionPeriodTo AS Deadline,\n e.ElectionOptionFlag,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.ElectionPeriodTo = '2026-04-02'\n AND e.ElectionOptionFlag = 'Y'\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `ElectionPeriodTo` 选择截止日期\n- `ElectionOptionFlag = 'Y'` 需要选择", "question_en": "Which corporate action elections are due by end of day?", "rating": 8, "execution_result": null, "result_note": "当前查询无今日截止的选择", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q24", "question_zh": "有多少客户报表已生成成功但尚未发送?", "sql": "-- 查询已生成但未发送的报表\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n a.LastDailyStatementDate,\n a.LastMonthlyStatementDate,\n a.LastStatementGenerateTime,\n a.LastStatementSendTime,\n CASE \n WHEN a.LastStatementGenerateTime > a.LastStatementSendTime THEN '未发送'\n ELSE '已发送'\n END AS DeliveryStatus\nFROM MCAccount a\nWHERE a.LastStatementGenerateTime IS NOT NULL\n AND (a.LastStatementSendTime IS NULL \n OR a.LastStatementGenerateTime > a.LastStatementSendTime)\nORDER BY a.LastStatementGenerateTime DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `LastStatementGenerateTime` 报表生成时间\n- `LastStatementSendTime` 报表发送时间", "question_en": "How many client statements were successfully generated but not yet delivered?", "rating": 7, "execution_result": null, "result_note": "当前查询无未发送报表", "tags": ["ordering", "account", "filter", "statement"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q25", "question_zh": "列出所有待批准的股票转移请求。", "sql": "-- 查询待批准的股票转移\nSELECT \n AccountID,\n InstrumentID,\n MovementType,\n MovementQuantity,\n FromAccountID,\n ToAccountID,\n RequestDate,\n ApprovalStatus,\n RequestedBy\nFROM BCAccountInstrumentMovement\nWHERE ApprovalStatus = 'P'\nORDER BY RequestDate DESC;", "schema_info": {"tables": {"BCAccountInstrumentMovement": {"alias": "BCAccountInstrumentMovement", "columns": []}}, "joins": []}, "explanation": "- 股票转移请求存储在专用表中\n- `ApprovalStatus = 'P'` 待批准状态", "question_en": "Show all pending stock movement requests that require approval.", "rating": 6, "execution_result": null, "result_note": "当前查询无待批准请求", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q28", "question_zh": "哪些客户已请求更改结算货币?", "sql": "-- 查询请求更改结算货币的客户\n-- 说明:经查 MCAccountCurrency 表无 CurrencyChangeStatus/RequestedCurrencyID 等字段\n-- 数据库中没有专门的货币变更请求表\n-- 此查询基于账户货币历史时间表(HCAccountCurrencyTimetable)展示货币生效安排\nSELECT \n act.AccountID,\n mca.Name AS AccountName,\n act.CurrencyID AS Currency,\n act.EffectiveDateFrom,\n act.EffectiveDateTo,\n act.AccountTypeID\nFROM HCAccountCurrencyTimetable act\nINNER JOIN MCAccount mca ON act.AccountID = mca.AccountID\nWHERE act.EffectiveDateTo IS NULL\nORDER BY act.EffectiveDateFrom DESC;", "schema_info": {"tables": {"HCAccountCurrencyTimetable": {"alias": "act", "columns": []}, "MCAccount": {"alias": "mca", "columns": []}}, "joins": [{"type": "INNER", "on": "act.AccountID = mca.AccountID"}]}, "explanation": "- `HCAccountCurrencyTimetable` 记录账户货币生效时间表\n- `EffectiveDateTo IS NULL` 表示当前生效的货币配置\n- 如需货币变更请求功能,建议在系统中扩展相关表结构", "question_en": "Which clients have requested a change in settlement currency?", "rating": 4, "execution_result": null, "result_note": "**说明:** 数据库中没有专门的货币变更请求表。`MCAccountCurrency` 表仅记录账户当前货币配置,无变更请求字段。", "tags": ["join", "account", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q31", "question_zh": "指定日期或日期范围内收到的交易量(如成交金额)及相应佣金是多少?", "sql": "-- 交易量和佣金统计\nSELECT \n SUM(TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(TotalBoughtFee) AS TotalBuyCommission,\n SUM(TotalSoldConsideration) AS TotalSellConsideration,\n SUM(TotalSoldFee) AS TotalSellCommission,\n SUM(TotalBoughtConsideration) + SUM(TotalSoldConsideration) AS TotalConsideration,\n SUM(TotalBoughtFee) + SUM(TotalSoldFee) AS TotalCommission\nFROM BCAccountInstrument;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录了账户所有历史买卖交易\n- `TotalBoughtConsideration / TotalSoldConsideration`: 买卖成交金额", "question_en": "What is the trading volume (e.g., consideration) and corresponding commission amount received for a specific date or date range?", "rating": 9, "execution_result": null, "result_note": "| TotalBuyConsideration | TotalBuyCommission | TotalSellConsideration | TotalSellCommission | TotalConsideration | TotalCommission |\n|---------------------|-------------------|----------------------|--------------------|-------------------|-----------------|\n| 945,480,833.78 | 793,149.07 | 278,693,891.64 | 809,248.89 | 1,224,174,725.42 | 1,602,397.96 |", "tags": ["trade", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q33", "question_zh": "今日是否有追缴保证金通知?", "sql": "-- 查询今日追缴保证金记录 (固定日期: 2026-04-02)\nSELECT \n m.AccountID,\n a.Name AS AccountName,\n m.CallForDeposit,\n m.CallForDepositDate,\n m.CallForDepositDays\nFROM HCAccountMarginCallForDeposit m\nINNER JOIN MCAccount a ON m.AccountID = a.AccountID\nWHERE m.CallForDepositDate = '2026-04-02';", "schema_info": {"tables": {"HCAccountMarginCallForDeposit": {"alias": "m", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "m.AccountID = a.AccountID"}]}, "explanation": "- `HCAccountMarginCallForDeposit` 记录追缴保证金信息\n- `CallForDeposit` 追缴金额", "question_en": "Have there been any margin calls today?", "rating": 9, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "margin", "filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q38", "question_zh": "本月IPO事件的总佣金、手续费和保证金利息是多少?", "sql": "-- IPO相关费用汇总 (2026年4月)\nSELECT \n 'IPO Commission' AS FeeType,\n SUM(ip.SubscriptionAmount * 0.001) AS EstimatedFee\nFROM MCInstrumentIPODetail ip\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": []}, "explanation": "- IPO相关费用分散在多个表中\n- 需要关联 `BCAccountInstrument` 获取IPO交易费用", "question_en": "What are the total commission, handling fees, and margin interest for IPO events this month?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["margin", "aggregation", "filter", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q39", "question_zh": "列出今日需要执行的公司行动(如权益分派)。", "sql": "-- 查询今日生效的公司行动 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.MarketID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate,\n e.BalanceExDate,\n e.Remark,\n e.State\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.State = 'A'\n AND (\n e.BalanceDate = '2026-04-02'\n OR e.BalanceExDate = '2026-04-02'\n OR e.AutoHoldDate = '2026-04-02'\n OR e.AutoDeductDate = '2026-04-02'\n );", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派信息\n- `BalanceDate` 权益登记日期", "question_en": "List corporate actions (e.g., entitlements) that need to be performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q40", "question_zh": "列出今日执行的所有股票转移/移动。", "sql": "-- 今日股票转入/转出 (固定日期: 2026-04-02)\nSELECT \n AccountID,\n MarketID,\n InstrumentID,\n TodayIn AS QuantityIn,\n TodayOut AS QuantityOut,\n HoldForDeposit AS PendingDeposit,\n HoldForWithdrawal AS PendingWithdrawal\nFROM BCAccountInstrument\nWHERE TodayIn > 0 OR TodayOut > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录股票持仓变动\n- `TodayIn` 今日转入数量", "question_en": "List all stock movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q41", "question_zh": "列出今日执行的所有现金划转。", "sql": "-- 今日现金变动汇总 (固定日期: 2026-04-02)\nSELECT \n b.AccountID,\n a.Name AS AccountName,\n b.TodayIn AS CashIn,\n b.TodayOut AS CashOut,\n b.BankAvailableBalance\nFROM BCAccountCash b\nINNER JOIN MCAccount a ON b.AccountID = a.AccountID\nWHERE b.TodayIn > 0 OR b.TodayOut > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "b", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "b.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 记录账户现金变动\n- `TodayIn` 今日现金流入", "question_en": "List all cash movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "cash"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q42", "question_zh": "列出后台系统中的活跃用户。", "sql": "-- 查询后台系统活跃用户\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n u.DepartmentID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储系统用户信息\n- `State = 'A'` 用户状态正常", "question_en": "List active users in the back office.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | AEType |\n|--------|----------|--------|-----------------|---------------|--------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-03 18:53:04 | 个人 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 16:47:11 | 个人 |", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q44", "question_zh": "本月收到/支付的利息金额是多少(如客户借款利息和存款利息)?", "sql": "-- 本月利息收支汇总 (2026年4月)\nSELECT \n SUM(AccruedCreditInterest) AS CreditInterest,\n SUM(AccruedDebitInterest) AS DebitInterest,\n SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetInterest\nFROM BCAccountCash;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金和利息信息\n- `AccruedCreditInterest` 应计存款利息", "question_en": "What is the interest amount received/paid this month (e.g., client debit interest and credit interest)?", "rating": 7, "execution_result": null, "result_note": "| CreditInterest | DebitInterest | NetInterest |\n|----------------|---------------|-------------|\n| 464.15 | 2,022.70 | -1,558.55 |", "tags": ["account", "aggregation", "interest"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q50", "question_zh": "", "sql": "SELECT TOP\n\t15 c.ContractID,\n\tc.TradeDate,\n\tLTRIM( RTRIM( c.AccountID ) ) AS AccountID,\n\tLTRIM( RTRIM( c.MarketID ) ) AS MarketID,\n\tc.InstrumentID,\n\tc.BuySell,\n\tc.Quantity,\n\tc.Consideration,\n\tLTRIM( RTRIM( c.CurrencyID ) ) AS CurrencyID \nFROM\n\tdbo.TSBAccountContract c\n\tINNER JOIN dbo.MCAccount a ON c.AccountID = a.AccountID \nWHERE\n\tCAST ( c.TradeDate AS DATE ) = '2026-04-02' \n\tAND LTRIM( RTRIM( a.HouseAccountFlag ) ) = 'N' \nORDER BY\n\tc.ContractID;", "schema_info": {"tables": {"TSBAccountContract": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "", "question_en": "列出今天所有客戶的交易記錄", "rating": null, "execution_result": null, "result_note": "| ContractID | TradeDate | AccountID | MarketID | InstrumentID | BuySell | Quantity | Consideration | CurrencyID |\n| :--------- | :--------- | :-------- | :------- | :----------- | :------ | :------- | :------------ | :--------- |\n| B02403846 | 2026-04-02 | P011206 | HKEX | 01810 | B | 600 | 18360 | HKD |\n| B02403847 | 2026-04-02 | P011202 | HKEX | 01810 | B | 200 | 6120 | HKD |\n\n\n\n\n\n---\n\n## 附录:关键表说明\n\n| 表名 | 说明 | 关键字段 |\n|------|------|---------|\n| MCAccount | 账户主表 | AccountID, Name, MarketValue, MarginFacilityFlag |\n| MCUser | 用户表 | UserID, Name, Status, LogonEnableFlag |\n| BCAccountInstrument | 账户持仓 | AccountID, MarketID, Settled, DueBuy, DueSell |\n| BCAccountCash | 账户现金 | AccountID, TodayIn, TodayOut, AccruedInterest |\n| HCAccountMarginCallForDeposit | 追缴保证金 | AccountID, CallForDeposit, CallForDepositDate |\n| MCInstrument | 证券表 | InstrumentID, Name, MarginPercentage |\n| MCInstrumentIPODetail | IPO详情 | InstrumentID, Quantity, SubscriptionAmount |\n| MSBEntitlement | 权益分派 | EntitlementID, BalanceDate, BalanceExDate |\n| MCBroker | 经纪商 | BrokerID, Name |\n| MCLocation | 托管位置 | LocationID, Name, CCASSFlag |\n| HCExchangeRate | 汇率 | ExchangeRateID, ValueDate |\n| HCAccountCashRecord | 现金记录 | AccountID, ValueDate, LedgerBalance |\n\n---\n\n**文档结束**", "tags": ["join", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q11", "question_zh": "哪些客户尚未缴纳IPO申购款?", "sql": "-- 查询未缴纳IPO申购款的账户\nSELECT \n a.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.TotalBoughtQuantity AS SubscriptionLot,\n bi.TotalBoughtConsideration AS SubscriptionAmount\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nINNER JOIN MCInstrumentIPODetail ip ON bi.InstrumentID = ip.InstrumentID\nWHERE ip.State = 'A'\n AND bi.TotalBoughtConsideration > 0\n AND bi.TotalBoughtFee = 0\nORDER BY bi.TotalBoughtConsideration DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}, {"type": "INNER", "on": "bi.InstrumentID = ip.InstrumentID"}]}, "explanation": "- 通过比对买卖数量和手续费判断是否缴款\n- `TotalBoughtConsideration > 0` 有认购\n- `TotalBoughtFee = 0` 未支付手续费", "question_en": "Which clients have not yet paid their IPO subscription money?", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "ordering"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q15", "question_zh": "本月应付给介绍经纪人的佣金返佣总额是多少?", "sql": "-- 查询本月应付返佣 (2026年4月)\n-- 注意:BCAccountInstrument 无 UpdatedAt 字段,用 LastTransactionDate 过滤本月有交易的账户\n-- 说明:TotalBoughtFee/TotalSoldFee 为历史累计字段,此处统计近期活跃账户的累计佣金供参考\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE YEAR(bi.LastTransactionDate) = 2026\n AND MONTH(bi.LastTransactionDate) = 4\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee + bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCRebateScheme` 可能存储返佣规则\n- 按AE汇总佣金", "question_en": "What is the total commission rebate payable to introducing brokers this month?", "rating": 6, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q22", "question_zh": "列出今日所有公司账户与客户账户之间的现金划转。", "sql": "-- 查询公司账户与客户账户现金划转 (固定日期: 2026-04-02)\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n a.AccountType,\n c.CurrencyID,\n c.TodayIn AS CashInflow,\n c.TodayOut AS CashOutflow,\n c.Settled AS CurrentBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE (c.TodayIn > 0 OR c.TodayOut > 0)\nORDER BY c.TodayIn + c.TodayOut DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- 公司账户与客户账户通过 `AccountType` 区分\n- `TodayIn/TodayOut` 记录现金流动", "question_en": "List all cash movements between house and client accounts today.", "rating": 7, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q45", "question_zh": "不同经纪商(如非中央结算系统持仓)的客户总持仓是多少?", "sql": "-- 按托管位置统计持仓(非CCASS)\nSELECT \n loc.LocationID,\n loc.Name AS LocationName,\n COUNT(DISTINCT bi.AccountID) AS AccountCount,\n SUM(bi.Settled) AS TotalQuantity,\n SUM(bi.CostAmount) AS TotalCostAmount\nFROM BCAccountLocationInstrument bi\nINNER JOIN MCLocation loc ON bi.LocationID = loc.LocationID\nWHERE loc.CCASSFlag = 'N'\nGROUP BY loc.LocationID, loc.Name\nORDER BY TotalQuantity DESC;", "schema_info": {"tables": {"BCAccountLocationInstrument": {"alias": "bi", "columns": []}, "MCLocation": {"alias": "loc", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.LocationID = loc.LocationID"}]}, "explanation": "- `BCAccountLocationInstrument` 记录按托管位置分类的持仓\n- `MCLocation` 存储托管位置信息", "question_en": "What is the total client holding at different broker firms (e.g., non-CCASS locations)?", "rating": 7, "execution_result": null, "result_note": "当前查询需要验证 Location 表关联", "tags": ["join", "ordering", "holding", "broker", "filter", "account", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q46", "question_zh": "支付给其他经纪商的佣金是多少?", "sql": "-- 查询支付给其他经纪商的佣金\nSELECT \n b.BrokerID,\n b.Name AS BrokerName,\n SUM(bc.Commission) AS TotalCommission,\n SUM(bc.Consideration) AS TotalConsideration\nFROM MCBroker b\nINNER JOIN BCBrokerCash bc ON b.BrokerID = bc.BrokerID\nWHERE bc.Commission > 0\nGROUP BY b.BrokerID, b.Name\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCBroker": {"alias": "b", "columns": []}, "BCBrokerCash": {"alias": "bc", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = bc.BrokerID"}]}, "explanation": "- `MCBroker` 存储经纪商信息\n- `BCBrokerCash` 可能包含经纪商现金和佣金数据", "question_en": "How much commission has been paid to other brokers?", "rating": 6, "execution_result": null, "result_note": "当前查询需要验证经纪商佣金表关联", "tags": ["join", "ordering", "broker", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q47", "question_zh": "今日/指定日期发放了多少份日结单或月结单?", "sql": "-- 查询报表发放统计 (2026-04-02)\nSELECT \n 'Daily Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastDailyStatementDate = '2026-04-02'\nUNION ALL\nSELECT \n 'Monthly Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastMonthlyStatementDate = '2026-04-02';", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 记录账户报表发送日期\n- `LastDailyStatementDate` 最近日结单发送日期", "question_en": "How many daily or monthly statements were issued today / on a specific date?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["union", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q12", "question_zh": "本月累计的结算失败罚金是多少?", "sql": "-- 查询本月结算失败罚金 (2026年4月)\n-- 注意:BCAccountInstrument 无 FailedTradePenalty 字段,需另找结算罚金存储位置\n-- 目前未找到结算失败罚金字段,建议通过 TSBBrokerContract 或 BCBrokerCash 进一步排查\nSELECT \n COUNT(*) AS FailedTradeCount,\n MarketID\nFROM BCAccountInstrument\nWHERE YEAR(LastTransactionDate) = 2026\n AND MONTH(LastTransactionDate) = 4\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 无 `FailedTradePenalty` 字段,原始 SQL 会报错\n- `TSBBrokerContract` 或 `BCBrokerCash` 中可能存储结算相关费用,需进一步确认", "question_en": "What is the total failed trade penalty incurred this month?", "rating": 3, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 表无 FailedTradePenalty 字段,无法统计结算罚金)", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q13", "question_zh": "列出今日到期的所有股票借贷交易。", "sql": "-- 查询今日到期的股票借贷 (参考日期: 2026-04-02)\n-- MCMarket 表仅包含 HKEX/OSL/SETW,无独立借贷市场标识\n-- 股票借贷可能在持仓变动类型 HoldType 中标识,或在专门的 StockBorrowing 表中\n-- 此 SQL 按 DueBuy/DueSell 显示当前待交收证券,作为借贷合约的近似替代\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.DueBuy AS BorrowQuantity,\n bi.DueSell AS LoanQuantity,\n bi.DueBuy - bi.DueSell AS NetBorrow\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE bi.DueBuy > 0 OR bi.DueSell > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `DueBuy/DueSell` 表示待交收数量(借款买入/借出卖出),反映股票借贷待到期情况\n- `MCMarket` 表无 `MarketType='BORROW'` 标识,现有市场仅为 HKEX/OSL/SETW\n- 股票借贷详情可能存储在 `TSBAccountLoanContract` 或其他借贷专用表中,需业务确认", "question_en": "List all stock borrow/loan transactions due to settle today.", "rating": 5, "execution_result": null, "result_note": "当前查询无结果(BCAccountInstrument 中 DueBuy/DueSell 均为 0,无待交收证券)", "tags": ["join", "trade", "filter"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q16", "question_zh": "列出T+2交易缺少结算指令的账户。", "sql": "-- 查询T+2待交收但缺少指令的账户\n-- 注意:BCAccountInstrumentInstruction 表不存在,需改用 TSBSettlementInstruction\nSELECT \n bi.AccountID,\n a.Name AS AccountName,\n bi.InstrumentID,\n i.Name AS InstrumentName,\n bi.NextDayDueBuy AS T2DueBuy,\n bi.NextDayDueSell AS T2DueSell\nFROM BCAccountInstrument bi\nINNER JOIN MCAccount a ON bi.AccountID = a.AccountID\nINNER JOIN MCInstrument i ON bi.InstrumentID = i.InstrumentID\nWHERE (bi.NextDayDueBuy > 0 OR bi.NextDayDueSell > 0)\n AND NOT EXISTS (\n SELECT 1 FROM TSBSettlementInstruction si\n WHERE si.AccountOrBrokerID = bi.AccountID\n AND si.InstrumentID = bi.InstrumentID\n AND si.State IN ('P','A')\n );", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCAccount": {"alias": "a", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}, "TSBSettlementInstruction": {"alias": "si", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.AccountID = a.AccountID"}, {"type": "INNER", "on": "bi.InstrumentID = i.InstrumentID"}]}, "explanation": "- `NextDayDueBuy/NextDayDueSell` T+2交收数量(次日到期)\n- `TSBSettlementInstruction` 是结算指令表,通过 `NOT EXISTS` 排除已有指令的记录\n- `BCAccountInstrumentInstruction` 表不存在,不能使用", "question_en": "Show accounts where settlement instruction is missing for T+2 trades.", "rating": 6, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "account", "filter", "trade"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q36", "question_zh": "各市场(如港股T+1和T+2、美股T+1)的待结算金额是多少?", "sql": "-- 按市场统计待结算金额\nSELECT \n MarketID,\n SUM(DueBuy + DueSell) AS TotalPendingQty,\n SUM(NextDayDueBuy + NextDayDueSell) AS T2TotalPendingQty\nFROM BCAccountInstrument\nWHERE DueBuy > 0 OR DueSell > 0 OR NextDayDueBuy > 0 OR NextDayDueSell > 0\nGROUP BY MarketID;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录各市场的待交收数量\n- `DueBuy/DueSell` 对应T+1交收", "question_en": "What is the pending settlement amount for each market (e.g., HK T+1 & T+2, US T+1)?", "rating": 7, "execution_result": null, "result_note": "| MarketID | TotalPendingQty | T2TotalPendingQty |\n|----------|----------------|------------------|\n| HKEX | 0 | 0 |\n| SETW | 0 | 0 |", "tags": ["filter", "aggregation"], "difficulty": "medium"}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
|||||||
|
{"qid": "Q1", "question_zh": "按对手方列出截至[日期]的所有未结算交易。", "sql": "-- 按对手方统计截至 2026-04-02 的未结算交易\n-- 使用 TSBBrokerContract(经纪商合约表),按 BrokerID(对手方经纪商)分组\n-- SettleStatus='U' 表示未结算;CashSettleDate <= '2026-04-02' 筛选截至日\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n COUNT(*) AS UnsettledTradeCount,\n SUM(b.SettleQuantity) AS TotalUnsettledQty,\n SUM(b.SettleAmount) AS TotalUnsettledAmount,\n MIN(b.BuySell) + '~' + MAX(b.BuySell) AS BuySellRange,\n MIN(b.CashSettleDate) AS EarliestSettleDate,\n MAX(b.CashSettleDate) AS LatestSettleDate\nFROM TSBBrokerContract b\nLEFT JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.SettleStatus = 'U'\n AND b.CashSettleDate <= '2026-04-02'\nGROUP BY b.BrokerID, m.Name\nORDER BY TotalUnsettledAmount DESC;", "schema_info": {"tables": {"TSBBrokerContract": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "LEFT", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `TSBBrokerContract` 是经纪商/对手方合约表,记录与外部对手方的交易合约,`BrokerID` 即对手方标识\n- `SettleStatus = 'U'` 表示未结算;`CashSettleDate <= '2026-04-02'` 筛选截至日未结清的合约\n- 关联 `MCBroker` 获取对手方经纪商名称\n- 按 `BrokerID` 分组汇总未结算数量和金额", "question_en": "Show all unsettled trades as of [date] grouped by counterparty.", "rating": 9, "execution_result": null, "result_note": "| BrokerID | BrokerName | UnsettledTradeCount | TotalUnsettledQty | TotalUnsettledAmount | BuySellRange | EarliestSettleDate | LatestSettleDate |\n|----------|-----------|---------------------|-------------------|---------------------|--------------|---------------------|-------------------|\n| CCASS | CCASS | 622 | 40,459,253 | 44,173,571.06 | B~S | 2025-07-30 | 2026-04-02 |\n\n> **注**:CCASS(Hong Kong Securities Clearing Company Limited)是香港交易所的中央结算机构,作为交易所交易的核心对手方。622笔未结算合约均为 HKEX 市场。", "tags": ["join", "ordering", "broker", "filter", "trade", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q2", "question_zh": "列出今日所有客户账户之间的股票转移记录。", "sql": "-- 查询今日客户账户间股票转移(参考日期:2026-04-02)\n-- 使用 TSBAccountInstrumentMovement,MovementType='T' 表示账户间转移\n-- ValueDate 为转移生效日期,关联 MCInstrument 获取证券名称\nSELECT \n m.MovementID,\n m.AccountID AS FromAccountID,\n m.TransferToAccountID,\n m.InstrumentID,\n i.Name AS InstrumentSymbol,\n m.MovementType,\n m.Quantity AS TransferQuantity,\n m.ValueDate AS TransferDate\nFROM TSBAccountInstrumentMovement m\nLEFT JOIN MCInstrument i ON m.InstrumentID = i.InstrumentID\nWHERE m.MovementType = 'T'\n AND m.ValueDate = '2026-04-02'\nORDER BY m.ValueDate;", "schema_info": {"tables": {"TSBAccountInstrumentMovement": {"alias": "m", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "LEFT", "on": "m.InstrumentID = i.InstrumentID"}]}, "explanation": "- `TSBAccountInstrumentMovement` 是持仓变动流水表,记录所有证券变动事件\n- `MovementType = 'T'`(Transfer)特指**账户间股票转移**,对应 `TransferToAccountID` 记录目标账户\n- `ValueDate` 是转移生效日期,可按日期过滤\n- 关联 `MCInstrument` 获取证券代码/名称\n- 注意:`BCAccountInstrument` 的 `TodayIn/TodayOut` 是**实时快照**,不带历史日期维度,不能用于查历史转移", "question_en": "List all stock transfers between client accounts performed today.", "rating": 9, "execution_result": null, "result_note": "当前查询无结果(测试环境中 `TSBAccountInstrumentMovement` 的 `MovementType='T'` 最新记录为 2025-10-21,无 2026-04-02 的账户间转移数据)", "tags": ["join", "ordering", "filter", "account", "left_join"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q3", "question_zh": "今日结算使用的汇率是多少?", "sql": "-- 查询汇率类型列表 (固定日期: 2026-04-02)\nSELECT \n ExchangeRateID, \n Name, \n NameEx\nFROM HCExchangeRate\nWHERE ValueDate = '2026-04-02'\nORDER BY ExchangeRateID;", "schema_info": {"tables": {"HCExchangeRate": {"alias": "HCExchangeRate", "columns": []}}, "joins": []}, "explanation": "- `HCExchangeRate` 存储汇率类型定义\n- `HCExchangeRateDetail` 存储各货币对的汇率值\n- 两个表通过 `ExchangeRateID` + `ValueDate` 关联\n- `ExchangeRateDirection` = 'D' 表示直接汇率", "question_en": "What are the currency exchange rates used for today's settlement?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["exchange_rate", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q4", "question_zh": "哪些券商尚有未履行的交收义务?", "sql": "-- 查询有未履行交收义务的经纪商(截至 2026-04-02)\n-- Settled < 0 表示经纪商欠我们的现金(未履行交收义务)\n-- 注意:BCBrokerCash.Settled 是经纪商在我们账户的已结算现金余额,负数=经纪商欠款\nSELECT \n b.BrokerID,\n m.Name AS BrokerName,\n b.CurrencyID,\n b.Settled AS OwedToUsAmount,\n ABS(b.Settled) AS OutstandingAmount\nFROM BCBrokerCash b\nINNER JOIN MCBroker m ON b.BrokerID = m.BrokerID\nWHERE b.Settled < 0\nORDER BY ABS(b.Settled) DESC;", "schema_info": {"tables": {"BCBrokerCash": {"alias": "b", "columns": []}, "MCBroker": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "b.BrokerID = m.BrokerID"}]}, "explanation": "- `BCBrokerCash` 记录经纪商在我们账户的结算现金,`Settled < 0` = 经纪商欠我们(未履行交收义务)\n- 关联 `MCBroker` 获取经纪商名称\n- 按欠款金额降序排列,优先处理大额欠款", "question_en": "Which brokers have outstanding delivery obligations to us?", "rating": 8, "execution_result": null, "result_note": "| BrokerID | BrokerName | CurrencyID | OwedToUsAmount | OutstandingAmount |\n|----------|-----------|------------|-----------------|-------------------|\n| CCASS | CCASS | CNY | -52,680.00 | 52,680.00 |\n| CCASS | CCASS | USD | -90.00 | 90.00 |\n\n> 注:CCASS 欠我们的结算现金(CNY 52,680 + USD 90)。HKD 余额为正(+4,202,811.87)表示我们欠 CCASS,不属于\"未履行交收义务\"范畴。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q6", "question_zh": "所有货币的客户现金总余额是多少?", "sql": "-- 按货币统计客户现金余额\nSELECT \n CurrencyID,\n COUNT(DISTINCT AccountID) AS AccountCount,\n SUM(Settled) AS TotalSettledBalance,\n SUM(AccruedCreditInterest) AS TotalCreditInterest,\n SUM(AccruedDebitInterest) AS TotalDebitInterest,\n SUM(Settled) + SUM(AccruedCreditInterest) - SUM(AccruedDebitInterest) AS NetBalance\nFROM BCAccountCash\nGROUP BY CurrencyID\nORDER BY NetBalance DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `BCAccountCash` 记录账户现金余额\n- `Settled` 已结算余额\n- `AccruedCreditInterest` 应计存款利息\n- `AccruedDebitInterest` 应计借款利息", "question_en": "What is the total client cash balance across all currencies?", "rating": 9, "execution_result": null, "result_note": "| CurrencyID | AccountCount | TotalSettledBalance | NetBalance |\n|------------|-------------|--------------------|------------|\n| HKD | 2,906 | 52,195,305.77 | 52,195,305.77 |\n| TWD | 2 | -27,676.60 | -27,676.60 |\n| USD | 175 | 0 | 0 |\n| CNY | 95 | 0 | 0 |\n| GBP | 1 | 0 | 0 |", "tags": ["ordering", "account", "cash", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q7", "question_zh": "今日结算后现金余额为负的账户列表。", "sql": "-- 查询现金余额为负的账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.CurrencyID,\n c.Settled AS CashBalance,\n c.UnderdueCheque,\n c.BankAvailableBalance\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.Settled < 0\nORDER BY c.Settled ASC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash.Settled` 表示已结算现金余额\n- 负数表示账户透支\n- 关联 `MCAccount` 获取账户名称", "question_en": "List accounts with negative cash balance after today's settlement.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | CurrencyID | CashBalance |\n|-----------|------------|------------|-------------|\n| M050009 | NG SUN SHU*** | HKD | -4,678,978.42 |\n| P050667 | WONG YIM HUNG | HKD | -780,196.94 |\n| M050521 | LO CHUN YIP | HKD | -290,487.54 |\n\n(共55个账户现金余额为负)", "tags": ["join", "ordering", "filter", "account", "cash"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q8", "question_zh": "明天生效的公司行动涉及哪些证券?", "sql": "-- 查询明日生效的公司行动 (固定日期: 2026-04-03)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate AS EffectiveDate,\n e.BalanceExDate AS ExDate,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE (e.BalanceDate = '2026-04-03' OR e.BalanceExDate = '2026-04-03')\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录权益分派信息\n- `BalanceDate` 权益登记日期\n- `BalanceExDate` 除权日期", "question_en": "Which instruments have corporate actions effective tomorrow?", "rating": 8, "execution_result": null, "result_note": "当前查询无结果(2026-04-03无公司行动)", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q9", "question_zh": "今日预计收到哪些股息?", "sql": "-- 查询今日预计收到的股息 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceDate AS PaymentDate,\n e.BalanceType,\n e.Remark AS DividendDetails\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.BalanceDate = '2026-04-02'\n AND e.BalanceType = 'C'\n AND e.State = 'A'\nORDER BY i.Name;", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派\n- `BalanceType = 'C'` 表示现金股息\n- `BalanceDate` 股息支付日期", "question_en": "What dividends are expected to be received today?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q10", "question_zh": "列出所有进行中IPO的待缴申购金额。", "sql": "-- 查询进行中IPO的待缴金额\nSELECT \n ip.InstrumentID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.SubscriptionAmount,\n ip.SubscriptionPrice,\n ip.Quantity AS SubscriptionLot,\n ip.CreatedOn AS SubscriptionDate,\n ip.State AS SubscriptionStatus\nFROM MCInstrumentIPODetail ip\nINNER JOIN MCInstrument i ON ip.InstrumentID = i.InstrumentID\nWHERE ip.State = 'A'\n AND i.State = 'A'\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrumentIPODetail": {"alias": "ip", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "ip.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MCInstrumentIPODetail` 记录IPO认购详情\n- `SubscriptionAmount` 申购金额\n- `State = 'A'` 表示进行中", "question_en": "Show all pending subscription amounts for ongoing IPOs.", "rating": 8, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | SubscriptionAmount | SubscriptionPrice | Quantity |\n|--------------|----------------|-------------------|-------------------|----------|\n| 02259 | HKEX | 7,231.19 | 36.16 | 100 |\n| 02259 | HKEX | 14,462.40 | 36.16 | 200 |\n\n> 注:MCInstrumentIPODetail 共 586 条进行中 IPO 记录(State='A'),以上为示例行。", "tags": ["join", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q20", "question_zh": "哪些公司行动的选择指令需在今日结束前提交?", "sql": "-- 查询今日截止的公司行动选择 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.ElectionPeriodTo AS Deadline,\n e.ElectionOptionFlag,\n e.Remark AS Description\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.ElectionPeriodTo = '2026-04-02'\n AND e.ElectionOptionFlag = 'Y'\n AND e.State = 'A';", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `ElectionPeriodTo` 选择截止日期\n- `ElectionOptionFlag = 'Y'` 需要选择", "question_en": "Which corporate action elections are due by end of day?", "rating": 8, "execution_result": null, "result_note": "当前查询无今日截止的选择", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q21", "question_zh": "截至昨日的客户借方余额应计利息是多少?", "sql": "-- 查询客户借方余额应计利息 (截至2026-04-01)\nSELECT \n SUM(AccruedDebitInterest) AS TotalDebitInterestAccrued,\n SUM(AccruedCreditInterest) AS TotalCreditInterestAccrued,\n SUM(AccruedDebitInterest) - SUM(AccruedCreditInterest) AS NetDebitInterest,\n COUNT(DISTINCT AccountID) AS AccountCount\nFROM BCAccountCash\nWHERE AccruedDebitInterest > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "BCAccountCash", "columns": []}}, "joins": []}, "explanation": "- `AccruedDebitInterest` 借款应计利息\n- `AccruedCreditInterest` 存款应计利息", "question_en": "What is the interest accrued on client debit balances as of yesterday?", "rating": 8, "execution_result": null, "result_note": "| TotalDebitInterestAccrued | TotalCreditInterestAccrued | NetDebitInterest | AccountCount |\n|---------------------------|---------------------------|-----------------|-------------|\n| 2,022.70 | 464.15 | 1,558.55 | 55 |", "tags": ["filter", "account", "interest", "cash", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q23", "question_zh": "列出所有逾期未付保证金利息的账户。", "sql": "-- 查询逾期保证金利息账户\nSELECT \n c.AccountID,\n a.Name AS AccountName,\n c.LoanInterestMarginOverdue AS MarginOverdueInterest,\n c.LoanInterestMarginBOverdue AS MarginBOverdueInterest,\n c.LoanInterestOverdue AS GeneralOverdueInterest\nFROM BCAccountCash c\nINNER JOIN MCAccount a ON c.AccountID = a.AccountID\nWHERE c.LoanInterestMarginOverdue > 0 \n OR c.LoanInterestMarginBOverdue > 0\n OR c.LoanInterestOverdue > 0\nORDER BY (c.LoanInterestMarginOverdue + c.LoanInterestMarginBOverdue + c.LoanInterestOverdue) DESC;", "schema_info": {"tables": {"BCAccountCash": {"alias": "c", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "c.AccountID = a.AccountID"}]}, "explanation": "- `LoanInterestMarginOverdue` 保证金账户逾期利息\n- `LoanInterestMarginBOverdue` B类保证金逾期利息", "question_en": "Show all accounts with overdue margin interest payments.", "rating": 8, "execution_result": null, "result_note": "当前查询无逾期利息记录", "tags": ["join", "ordering", "margin", "filter", "account", "interest"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q26", "question_zh": "本月各交易所的结算费用明细如何?", "sql": "-- 按交易所统计结算费用 (2026年4月)\n-- 说明:BCAccountInstrument 表中 TotalBoughtFee/TotalSoldFee 是持仓级别的交易费用字段\n-- LastTransactionDate 表示该持仓最后交易日期\n-- 查询逻辑:筛选最后交易日期在本月内的持仓记录,按交易所汇总交易费用\nSELECT \n bi.MarketID,\n m.MarketName,\n SUM(bi.TotalBoughtFee) AS TotalBuyFee,\n SUM(bi.TotalSoldFee) AS TotalSellFee,\n SUM(bi.TotalBoughtFee + bi.TotalSoldFee) AS TotalSettlementFee,\n COUNT(DISTINCT bi.AccountID) AS TradingAccountCount\nFROM BCAccountInstrument bi\nINNER JOIN MCMarket m ON bi.MarketID = m.MarketID\nWHERE bi.LastTransactionDate >= '2026-04-01' AND bi.LastTransactionDate < '2026-05-01'\nGROUP BY bi.MarketID, m.MarketName\nORDER BY TotalSettlementFee DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "bi", "columns": []}, "MCMarket": {"alias": "m", "columns": []}}, "joins": [{"type": "INNER", "on": "bi.MarketID = m.MarketID"}]}, "explanation": "- `BCAccountInstrument` 存储持仓级别信息,包含各持仓的交易费用累计\n- `MCMarket` 存储市场信息,提供市场名称\n- `LastTransactionDate` 用于过滤本月有交易的持仓记录\n- 按市场分组汇总结算费用", "question_en": "What is the breakdown of settlement fees by exchange for this month?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "ordering", "filter", "trade", "aggregation", "date_range"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q27", "question_zh": "列出所有超过3天的暂记账户分录。", "sql": "-- 查询超过3天的暂记账户分录 (2026-04-02)\n-- 说明:TCAccountCashMovement 是现金流水表\n-- NeedHoldFlag='Y' 表示暂记分录\n-- Amount - ReleasedAmount 表示未释放金额(暂记余额)\n-- 筛选未释放且超过3天的暂记分录\nSELECT \n cm.AccountID,\n cm.MovementID,\n cm.ValueDate,\n cm.CurrencyID,\n cm.Amount - ISNULL(cm.ReleasedAmount, 0) AS PendingBalance,\n cm.Remark,\n DATEDIFF(DAY, cm.ValueDate, '2026-04-02') AS DaysOutstanding\nFROM TCAccountCashMovement cm\nWHERE cm.NeedHoldFlag = 'Y'\n AND cm.Amount - ISNULL(cm.ReleasedAmount, 0) != 0\n AND DATEDIFF(DAY, cm.ValueDate, '2026-04-02') > 3\nORDER BY DaysOutstanding DESC, PendingBalance DESC;", "schema_info": {"tables": {"TCAccountCashMovement": {"alias": "cm", "columns": []}}, "joins": []}, "explanation": "- `TCAccountCashMovement` 存储现金流水记录\n- `NeedHoldFlag = 'Y'` 标识暂记(待确认)分录\n- `Amount - ReleasedAmount` 计算未释放的暂记余额\n- `DATEDIFF` 计算距今天数\n- `DATEDIFF > 3` 筛选超过3天的记录", "question_en": "List all suspense account entries older than 3 days.", "rating": 9, "execution_result": null, "result_note": "**查询结果:0条记录**\n\n当前没有超过3天未释放的暂记账户分录。所有暂记分录的 ReleasedAmount = Amount,已全部释放。", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q29", "question_zh": "列出后台系统中所有活跃用户账户及其最后登录日期。", "sql": "-- 查询活跃用户及其登录情况\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType,\n CASE \n WHEN u.LastLogonTime >= DATEADD(DAY, -7, GETDATE()) THEN '近7天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -30, GETDATE()) THEN '近30天活跃'\n WHEN u.LastLogonTime >= DATEADD(DAY, -90, GETDATE()) THEN '近90天活跃'\n ELSE '长期未登录'\n END AS ActivityStatus\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储用户信息\n- `LastLogonTime` 最后登录时间", "question_en": "List all active user accounts in the back office system with last login date.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | ActivityStatus |\n|--------|----------|--------|-----------------|---------------|----------------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-08 | 近7天活跃 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 | 长期未登录 |\n| SING | WONG LO SING | N | Y | 2025-10-21 | 长期未登录 |\n| LEONNG | LEON NG | N | Y | 2025-10-21 | 长期未登录 |", "tags": ["account", "date_function", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q30", "question_zh": "按结算日期划分的未结算交易总风险敞口是多少?", "sql": "-- 按结算日期分组的未结算交易敞口\nSELECT \n SettlementType,\n SUM(Amount) AS TotalExposure\nFROM (\n SELECT 'DueBuy_T1' AS SettlementType, SUM(DueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'DueSell_T1' AS SettlementType, SUM(DueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueBuy' AS SettlementType, SUM(NextDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextDayDueSell' AS SettlementType, SUM(NextDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueBuy' AS SettlementType, SUM(NextTwoDayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'NextTwoDayDueSell' AS SettlementType, SUM(NextTwoDayDueSell) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueBuy' AS SettlementType, SUM(TodayDueBuy) AS Amount FROM BCAccountInstrument\n UNION ALL\n SELECT 'TodayDueSell' AS SettlementType, SUM(TodayDueSell) AS Amount FROM BCAccountInstrument\n) t\nGROUP BY SettlementType\nORDER BY SettlementType;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表包含所有账户的证券持仓和交收信息\n- `DueBuy/DueSell` 表示T+1到期的买卖数量", "question_en": "What is the total exposure from unsettled trades by settlement date?", "rating": 8, "execution_result": null, "result_note": "| SettlementType | TotalExposure |\n|----------------|---------------|\n| DueBuy_T1 | 0 |\n| DueSell_T1 | 0 |\n| NextDayDueBuy | 0 |\n| NextDayDueSell | 0 |\n| NextTwoDayDueBuy | 0 |\n| NextTwoDayDueSell | 0 |\n| TodayDueBuy | 1,800 |\n| TodayDueSell | 1,000 |\n\n**注:** `DueBuy/DueSell` 等字段表示应交收数量(T+1/T+2/今日),非金额敞口。如需金额敞口,需乘以价格。", "tags": ["union", "trade", "ordering", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q31", "question_zh": "指定日期或日期范围内收到的交易量(如成交金额)及相应佣金是多少?", "sql": "-- 交易量和佣金统计\nSELECT \n SUM(TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(TotalBoughtFee) AS TotalBuyCommission,\n SUM(TotalSoldConsideration) AS TotalSellConsideration,\n SUM(TotalSoldFee) AS TotalSellCommission,\n SUM(TotalBoughtConsideration) + SUM(TotalSoldConsideration) AS TotalConsideration,\n SUM(TotalBoughtFee) + SUM(TotalSoldFee) AS TotalCommission\nFROM BCAccountInstrument;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录了账户所有历史买卖交易\n- `TotalBoughtConsideration / TotalSoldConsideration`: 买卖成交金额", "question_en": "What is the trading volume (e.g., consideration) and corresponding commission amount received for a specific date or date range?", "rating": 9, "execution_result": null, "result_note": "| TotalBuyConsideration | TotalBuyCommission | TotalSellConsideration | TotalSellCommission | TotalConsideration | TotalCommission |\n|---------------------|-------------------|----------------------|--------------------|-------------------|-----------------|\n| 945,480,833.78 | 793,149.07 | 278,693,891.64 | 809,248.89 | 1,224,174,725.42 | 1,602,397.96 |", "tags": ["trade", "aggregation"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q32", "question_zh": "各市场客户不同资产(如股票、债券、基金、虚拟资产)的总持仓是多少?", "sql": "-- 按市场统计客户持仓\nSELECT \n b.MarketID,\n COUNT(DISTINCT b.AccountID) AS AccountCount,\n SUM(b.Settled) AS TotalQuantity,\n SUM(b.CostAmount) AS TotalCostAmount,\n SUM(b.TotalBoughtQuantity) AS TotalBoughtQty,\n SUM(b.TotalSoldQuantity) AS TotalSoldQty\nFROM BCAccountInstrument b\nGROUP BY b.MarketID\nORDER BY TotalCostAmount DESC;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "b", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 表记录账户在各市场的持仓\n- `MarketID` 区分不同市场", "question_en": "What is the total client holding of different assets (e.g., stocks, bonds, funds, virtual assets) by each market?", "rating": 8, "execution_result": null, "result_note": "| MarketID | AccountCount | TotalQuantity | TotalCostAmount | TotalBoughtQty | TotalSoldQty |\n|----------|-------------|---------------|-----------------|---------------|-------------|\n| HKEX | 2,102 | 717,471,548 | 87,813,213.45 | 1,015,923,906 | 298,452,798 |\n| SETW | 2 | 3,100 | 32,298.50 | 4,100 | 1,000 |", "tags": ["account", "holding", "ordering", "aggregation"], "difficulty": "medium"}
|
||||||
|
{"qid": "Q33", "question_zh": "今日是否有追缴保证金通知?", "sql": "-- 查询今日追缴保证金记录 (固定日期: 2026-04-02)\nSELECT \n m.AccountID,\n a.Name AS AccountName,\n m.CallForDeposit,\n m.CallForDepositDate,\n m.CallForDepositDays\nFROM HCAccountMarginCallForDeposit m\nINNER JOIN MCAccount a ON m.AccountID = a.AccountID\nWHERE m.CallForDepositDate = '2026-04-02';", "schema_info": {"tables": {"HCAccountMarginCallForDeposit": {"alias": "m", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "m.AccountID = a.AccountID"}]}, "explanation": "- `HCAccountMarginCallForDeposit` 记录追缴保证金信息\n- `CallForDeposit` 追缴金额", "question_en": "Have there been any margin calls today?", "rating": 9, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "margin", "filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q34", "question_zh": "保证金账户前20大持仓(按市值)是多少?", "sql": "-- 保证金账户按市值排序的前20持仓\nSELECT TOP 20\n a.AccountID,\n a.Name AS AccountName,\n a.MarketValue,\n a.MarginDebitRatio AS MarginDebitBalanceRatio,\n a.MarginPosition,\n a.CallForDeposit,\n a.MarginFacilityFlag\nFROM MCAccount a\nWHERE a.MarginFacilityFlag = 'Y'\n AND a.State = 'A'\n AND a.MarketValue > 0\nORDER BY a.MarketValue DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 表包含所有账户信息\n- `MarginFacilityFlag = 'Y'` 筛选保证金账户", "question_en": "What are the top 20 holdings (based on market value) for margin accounts?", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginPosition |\n|-----------|-------------|------------|----------------|\n| M050009 | NG SUN SHU*** | 57,420,000 | 35,515,021.58 |\n| M050013 | NG CHUNG WAI*** | 19,504,520 | 10,918,958.78 |\n| M050193 | WONG KING YIU*** ^ | 11,682,920 | 3,340,099.05 |\n| P020160 | LAW SHIU MING DENNIS | 7,520,370 | 0 |\n| P030078 | MAK SUI BING | 7,120,309.94 | 0 |\n| P051101 | CHUNG CHUNG YEE | 7,117,808.04 | 0 |\n| M050170 | FUNG LAI FONG ANNA | 7,009,227.25 | 4,136,857 |\n| P220005 | LAI KAM SUI # | 5,837,102.10 | 97.97 |\n| P280628 | CHAN KWOK MO # | 5,761,997.48 | 62,508.23 |\n| P280455 | LEUNG KA CHI # | 5,487,971.20 | -64,981.22 |\n\n(共20条记录,以上展示前10条)", "tags": ["ordering", "holding", "margin", "filter", "limit", "account"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q35", "question_zh": "列出本月按客户主任分组的佣金总额,以及相应的成交金额和返佣金额。", "sql": "-- 按经纪商/客户主任分组的佣金统计\nSELECT \n a.AEID,\n u.Name AS AEName,\n COUNT(DISTINCT a.AccountID) AS AccountCount,\n SUM(bi.TotalBoughtFee) AS TotalBuyCommission,\n SUM(bi.TotalSoldFee) AS TotalSellCommission,\n SUM(bi.TotalBoughtConsideration) AS TotalBuyConsideration,\n SUM(bi.TotalSoldConsideration) AS TotalSellConsideration,\n SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) AS TotalCommission\nFROM MCAccount a\nLEFT JOIN MCUser u ON a.AEID = u.UserID\nLEFT JOIN BCAccountInstrument bi ON a.AccountID = bi.AccountID\nWHERE a.State = 'A'\nGROUP BY a.AEID, u.Name\nHAVING SUM(bi.TotalBoughtFee) + SUM(bi.TotalSoldFee) > 0\nORDER BY TotalCommission DESC;", "schema_info": {"tables": {"MCAccount": {"alias": "a", "columns": []}, "MCUser": {"alias": "u", "columns": []}, "BCAccountInstrument": {"alias": "bi", "columns": []}}, "joins": [{"type": "LEFT", "on": "a.AEID = u.UserID"}, {"type": "LEFT", "on": "a.AccountID = bi.AccountID"}]}, "explanation": "- `MCAccount.AEID` 关联经纪商/客户主任\n- `BCAccountInstrument` 包含交易费用数据", "question_en": "List the total commission received this month by AE, along with corresponding consideration and rebate amounts.", "rating": 8, "execution_result": null, "result_note": "| AEID | AEName | AccountCount | TotalBuyCommission | TotalSellCommission | TotalCommission |\n|------|--------|-------------|-------------------|--------------------|----------------|\n| C01 | CHAN YUET HUNG | 628 | 183,244.64 | 187,220.59 | 370,465.23 |\n| C28 | CHAN SHU SHAN | 405 | 179,275.89 | 176,760.39 | 356,036.28 |\n| C05 | CENTRAL | 517 | 97,368.51 | 102,819.88 | 200,188.39 |\n| C33 | LEON NG | 13 | 71,541.06 | 72,620.60 | 144,161.66 |\n| C07 | CHAN WING PAI | 75 | 73,255.68 | 69,115.58 | 142,371.26 |\n| C10 | TOKWAWAN / KOWLOON CITY | 369 | 51,059.78 | 57,288.86 | 108,348.64 |\n| C08 | WONG HON KONG | 380 | 47,505.15 | 46,300.39 | 93,805.54 |\n| C36 | Name Of C36 | 146 | 40,945.66 | 35,682.86 | 76,628.52 |\n| C66 | CHEUNG YUK LING IVA | 17 | 24,458.82 | 22,723.18 | 47,182.00 |\n| C19 | WONG LO SING | 60 | 13,337.69 | 13,912.30 | 27,249.99 |\n\n(共15个AE有佣金记录,以上展示前10条)\n\n**注:** 当前SQL无日期条件,统计所有历史累计佣金。如需\"本月\"佣金,需添加 `LastTransactionDate >= '2026-04-01'` 条件。", "tags": ["join", "ordering", "filter", "account", "left_join", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q37", "question_zh": "是否有正在进行的IPO事件,其认购状态如何?", "sql": "-- 查询进行中的IPO (2026年4月)\nSELECT \n i.InstrumentID,\n i.MarketID,\n i.Name AS InstrumentName,\n i.State AS InstrumentStatus,\n ip.Quantity AS SubscriptionLot,\n ip.SubscriptionAmount,\n ip.CreatedOn AS SubscriptionDate\nFROM MCInstrument i\nINNER JOIN MCInstrumentIPODetail ip ON i.InstrumentID = ip.InstrumentID\nWHERE YEAR(ip.CreatedOn) = 2026\n AND MONTH(ip.CreatedOn) = 4\nORDER BY ip.CreatedOn DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "i", "columns": []}, "MCInstrumentIPODetail": {"alias": "ip", "columns": []}}, "joins": [{"type": "INNER", "on": "i.InstrumentID = ip.InstrumentID"}]}, "explanation": "- `MCInstrument` 表记录证券信息\n- `MCInstrumentIPODetail` 记录IPO认购详情", "question_en": "Are there any IPO events currently in progress, and what is their subscription status?", "rating": 8, "execution_result": null, "result_note": "", "tags": ["join", "filter", "ipo", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q39", "question_zh": "列出今日需要执行的公司行动(如权益分派)。", "sql": "-- 查询今日生效的公司行动 (固定日期: 2026-04-02)\nSELECT \n e.EntitlementID,\n e.MarketID,\n e.InstrumentID,\n i.Name AS InstrumentName,\n e.BalanceType,\n e.BalanceDate,\n e.BalanceExDate,\n e.Remark,\n e.State\nFROM MSBEntitlement e\nINNER JOIN MCInstrument i ON e.InstrumentID = i.InstrumentID\nWHERE e.State = 'A'\n AND (\n e.BalanceDate = '2026-04-02'\n OR e.BalanceExDate = '2026-04-02'\n OR e.AutoHoldDate = '2026-04-02'\n OR e.AutoDeductDate = '2026-04-02'\n );", "schema_info": {"tables": {"MSBEntitlement": {"alias": "e", "columns": []}, "MCInstrument": {"alias": "i", "columns": []}}, "joins": [{"type": "INNER", "on": "e.InstrumentID = i.InstrumentID"}]}, "explanation": "- `MSBEntitlement` 记录所有权益分派信息\n- `BalanceDate` 权益登记日期", "question_en": "List corporate actions (e.g., entitlements) that need to be performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "corporate_action"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q40", "question_zh": "列出今日执行的所有股票转移/移动。", "sql": "-- 今日股票转入/转出 (固定日期: 2026-04-02)\nSELECT \n AccountID,\n MarketID,\n InstrumentID,\n TodayIn AS QuantityIn,\n TodayOut AS QuantityOut,\n HoldForDeposit AS PendingDeposit,\n HoldForWithdrawal AS PendingWithdrawal\nFROM BCAccountInstrument\nWHERE TodayIn > 0 OR TodayOut > 0;", "schema_info": {"tables": {"BCAccountInstrument": {"alias": "BCAccountInstrument", "columns": []}}, "joins": []}, "explanation": "- `BCAccountInstrument` 记录股票持仓变动\n- `TodayIn` 今日转入数量", "question_en": "List all stock movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["filter"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q41", "question_zh": "列出今日执行的所有现金划转。", "sql": "-- 今日现金变动汇总 (固定日期: 2026-04-02)\nSELECT \n b.AccountID,\n a.Name AS AccountName,\n b.TodayIn AS CashIn,\n b.TodayOut AS CashOut,\n b.BankAvailableBalance\nFROM BCAccountCash b\nINNER JOIN MCAccount a ON b.AccountID = a.AccountID\nWHERE b.TodayIn > 0 OR b.TodayOut > 0;", "schema_info": {"tables": {"BCAccountCash": {"alias": "b", "columns": []}, "MCAccount": {"alias": "a", "columns": []}}, "joins": [{"type": "INNER", "on": "b.AccountID = a.AccountID"}]}, "explanation": "- `BCAccountCash` 记录账户现金变动\n- `TodayIn` 今日现金流入", "question_en": "List all cash movements performed today.", "rating": 8, "execution_result": null, "result_note": "当前查询无结果", "tags": ["join", "filter", "cash"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q42", "question_zh": "列出后台系统中的活跃用户。", "sql": "-- 查询后台系统活跃用户\nSELECT \n u.UserID,\n u.Name AS UserName,\n u.Status,\n u.LogonEnableFlag,\n u.LastLogonTime,\n u.BranchID,\n u.DepartmentID,\n CASE u.CompanyAEFlag WHEN 'Y' THEN '公司经纪' ELSE '个人' END AS AEType\nFROM MCUser u\nWHERE u.State = 'A'\n AND u.LogonEnableFlag = 'Y'\nORDER BY u.LastLogonTime DESC;", "schema_info": {"tables": {"MCUser": {"alias": "u", "columns": []}}, "joins": []}, "explanation": "- `MCUser` 存储系统用户信息\n- `State = 'A'` 用户状态正常", "question_en": "List active users in the back office.", "rating": 9, "execution_result": null, "result_note": "| UserID | UserName | Status | LogonEnableFlag | LastLogonTime | AEType |\n|--------|----------|--------|-----------------|---------------|--------|\n| SUPPORT | SUPPORT | N | Y | 2026-04-03 18:53:04 | 个人 |\n| ROSANNA | ROSANNA | N | Y | 2025-10-21 16:47:11 | 个人 |", "tags": ["filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q43", "question_zh": "列出持仓为负数(透支)的账户。", "sql": "-- 查询透支持仓账户\nSELECT \n AccountID,\n Name AS AccountName,\n MarketValue,\n AssetBalance,\n LiabilityBalance,\n MarginPosition,\n MarginFacilityFlag\nFROM MCAccount\nWHERE MarketValue < 0\n AND State = 'A'\nORDER BY MarketValue ASC;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MarketValue < 0` 筛选持仓为负的账户\n- 持仓为负通常表示账户存在透支或结算问题", "question_en": "List accounts with overdrawn positions.", "rating": 9, "execution_result": null, "result_note": "| AccountID | AccountName | MarketValue | MarginFacilityFlag |\n|-----------|-------------|-------------|-------------------|\n| P030012 | LAI YEE TAK HELEN | -112,196.60 | N |\n| P350022 | CHAN YAU KING*** | -92,950.00 | N |\n| P011212 | NG SIU PING | -12,352.00 | N |", "tags": ["account", "holding", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q47", "question_zh": "今日/指定日期发放了多少份日结单或月结单?", "sql": "-- 查询报表发放统计 (2026-04-02)\nSELECT \n 'Daily Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastDailyStatementDate = '2026-04-02'\nUNION ALL\nSELECT \n 'Monthly Statement' AS StatementType,\n COUNT(*) AS StatementCount\nFROM MCAccount\nWHERE LastMonthlyStatementDate = '2026-04-02';", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount` 记录账户报表发送日期\n- `LastDailyStatementDate` 最近日结单发送日期", "question_en": "How many daily or monthly statements were issued today / on a specific date?", "rating": 9, "execution_result": null, "result_note": "", "tags": ["union", "filter", "aggregation"], "difficulty": "hard"}
|
||||||
|
{"qid": "Q48", "question_zh": "列出所有支持保证金的证券及其相应的保证金比例。", "sql": "-- 查询支持保证金的证券\nSELECT \n InstrumentID,\n Name AS InstrumentName,\n MarginPercentage AS MarginRatio,\n CASE \n WHEN MarginPercentage >= 0.7 THEN '高'\n WHEN MarginPercentage >= 0.5 THEN '中'\n WHEN MarginPercentage > 0 THEN '低'\n ELSE 'N/A'\n END AS RiskLevel,\n MarginLimitPerInstrument,\n CollateralRatio\nFROM MCInstrument\nWHERE MarginPercentage IS NOT NULL\n AND MarginPercentage > 0\n AND State = 'A'\nORDER BY MarginPercentage DESC;", "schema_info": {"tables": {"MCInstrument": {"alias": "MCInstrument", "columns": []}}, "joins": []}, "explanation": "- `MCInstrument` 存储证券信息\n- `MarginPercentage` 保证金比例", "question_en": "List all instruments that support margin and their corresponding margin ratios.", "rating": 9, "execution_result": null, "result_note": "| InstrumentID | InstrumentName | MarginRatio | RiskLevel |\n|-------------|----------------|-------------|-----------|\n| 00001 | CKH HOLDINGS(S) | 0.70 | 高 |\n| 00002 | CLP HOLDINGS(S) | 0.70 | 高 |\n| 00003 | HK & CHINA GAS(S) | 0.70 | 高 |\n\n(共767条记录)", "tags": ["margin", "limit", "filter", "ordering"], "difficulty": "easy"}
|
||||||
|
{"qid": "Q49", "question_zh": "本月、本季度和今年分别开了多少账户?", "sql": "-- 开户统计 (2026年4月)\nSELECT \n COUNT(CASE WHEN MONTH(OpenDate) = 4 AND YEAR(OpenDate) = 2026 THEN 1 END) AS April2026,\n COUNT(CASE WHEN DATEPART(QUARTER, OpenDate) = 2 AND YEAR(OpenDate) = 2026 THEN 1 END) AS Q2_2026,\n COUNT(CASE WHEN YEAR(OpenDate) = 2026 THEN 1 END) AS Year2026,\n COUNT(*) AS TotalAccounts,\n MIN(OpenDate) AS EarliestOpenDate,\n MAX(OpenDate) AS LatestOpenDate\nFROM MCAccount\nWHERE OpenDate IS NOT NULL;", "schema_info": {"tables": {"MCAccount": {"alias": "MCAccount", "columns": []}}, "joins": []}, "explanation": "- `MCAccount.OpenDate` 账户开户日期\n- 按月、季度、年分别统计新开户数量", "question_en": "How many accounts were opened this month, this quarter, and this year?", "rating": 9, "execution_result": null, "result_note": "| April2026 | Q2_2026 | Year2026 | TotalAccounts | EarliestOpenDate | LatestOpenDate |\n|-----------|---------|----------|---------------|------------------|----------------|\n| 0 | 0 | 0 | 1,951 | 1993-03-05 | 2025-10-16 |", "tags": ["account", "limit", "filter", "aggregation"], "difficulty": "easy"}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
# llm 包初始化
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,304 @@
|
|||||||
|
"""
|
||||||
|
DeepSeek API 客户端封装
|
||||||
|
支持同步/异步调用,与CAMEL AI兼容
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Optional, Any, Union
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from openai import OpenAI, AsyncOpenAI
|
||||||
|
from openai.types.chat import ChatCompletion, ChatCompletionMessage
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeepSeekConfig:
|
||||||
|
"""DeepSeek API配置"""
|
||||||
|
api_key: str
|
||||||
|
base_url: str = "https://api.deepseek.com"
|
||||||
|
model_name: str = "deepseek-chat"
|
||||||
|
temperature: float = 0.3
|
||||||
|
max_tokens: int = 4096
|
||||||
|
top_p: float = 0.9
|
||||||
|
frequency_penalty: float = 0.0
|
||||||
|
presence_penalty: float = 0.0
|
||||||
|
timeout: float = 60.0
|
||||||
|
extra_headers: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
class DeepSeekClient:
|
||||||
|
"""
|
||||||
|
DeepSeek API 客户端(同步)
|
||||||
|
|
||||||
|
使用 OpenAI 兼容接口调用 DeepSeek 模型
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: DeepSeekConfig):
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
# 初始化OpenAI客户端(DeepSeek兼容OpenAI协议)
|
||||||
|
self.client = OpenAI(
|
||||||
|
api_key=config.api_key,
|
||||||
|
base_url=config.base_url,
|
||||||
|
timeout=config.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[OK] DeepSeekClient初始化: model={config.model_name}, "
|
||||||
|
f"base_url={config.base_url}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def chat(
|
||||||
|
self,
|
||||||
|
messages: List[Dict[str, str]],
|
||||||
|
**kwargs
|
||||||
|
) -> ChatCompletionMessage:
|
||||||
|
"""
|
||||||
|
发送聊天请求
|
||||||
|
|
||||||
|
Args:
|
||||||
|
messages: 消息列表,格式为 [{"role": "user", "content": "..."}, ...]
|
||||||
|
**kwargs: 覆盖默认参数的额外参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ChatCompletionMessage对象(包含.role和.content属性)
|
||||||
|
"""
|
||||||
|
# 合并参数
|
||||||
|
params = {
|
||||||
|
"model": self.config.model_name,
|
||||||
|
"messages": messages,
|
||||||
|
"temperature": kwargs.get("temperature", self.config.temperature),
|
||||||
|
"max_tokens": kwargs.get("max_tokens", self.config.max_tokens),
|
||||||
|
"top_p": kwargs.get("top_p", self.config.top_p),
|
||||||
|
"frequency_penalty": kwargs.get(
|
||||||
|
"frequency_penalty", self.config.frequency_penalty
|
||||||
|
),
|
||||||
|
"presence_penalty": kwargs.get(
|
||||||
|
"presence_penalty", self.config.presence_penalty
|
||||||
|
),
|
||||||
|
"timeout": kwargs.get("timeout", self.config.timeout),
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.config.extra_headers:
|
||||||
|
params["extra_headers"] = self.config.extra_headers
|
||||||
|
|
||||||
|
try:
|
||||||
|
response: ChatCompletion = self.client.chat.completions.create(**params)
|
||||||
|
message = response.choices[0].message
|
||||||
|
|
||||||
|
# 记录使用情况
|
||||||
|
usage = response.usage
|
||||||
|
logger.debug(
|
||||||
|
f"DeepSeek调用完成: "
|
||||||
|
f"prompt_tokens={usage.prompt_tokens}, "
|
||||||
|
f"completion_tokens={usage.completion_tokens}, "
|
||||||
|
f"total_tokens={usage.total_tokens}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"DeepSeek API调用失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def chat_with_json(
|
||||||
|
self,
|
||||||
|
messages: List[Dict[str, str]],
|
||||||
|
**kwargs
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
发送聊天请求并期望JSON格式返回
|
||||||
|
|
||||||
|
Args:
|
||||||
|
messages: 消息列表
|
||||||
|
**kwargs: 额外参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
解析后的JSON字典
|
||||||
|
"""
|
||||||
|
message = self.chat(messages, **kwargs)
|
||||||
|
content = message.content.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 尝试解析JSON(可能包含markdown代码块)
|
||||||
|
if "```json" in content:
|
||||||
|
# 提取```json```之间的内容
|
||||||
|
start = content.find("```json") + 7
|
||||||
|
end = content.find("```", start)
|
||||||
|
content = content[start:end].strip()
|
||||||
|
elif "```" in content:
|
||||||
|
# 仅```包裹
|
||||||
|
start = content.find("```") + 3
|
||||||
|
end = content.find("```", start)
|
||||||
|
content = content[start:end].strip()
|
||||||
|
|
||||||
|
return json.loads(content)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.warning(f"JSON解析失败,返回原始内容: {e}")
|
||||||
|
return {"raw_content": content}
|
||||||
|
|
||||||
|
def generate_sql(
|
||||||
|
self,
|
||||||
|
prompt: str,
|
||||||
|
schema: str,
|
||||||
|
dialect: str = "tsql",
|
||||||
|
**kwargs
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
便捷方法:生成SQL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: 用户问题
|
||||||
|
schema: Schema描述
|
||||||
|
dialect: SQL方言(默认 T-SQL / SQL Server)
|
||||||
|
**kwargs: 额外参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SQL字符串
|
||||||
|
"""
|
||||||
|
from config.prompts import SQL_GENERATOR_SYSTEM, SQL_GENERATOR_USER
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": SQL_GENERATOR_SYSTEM},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": SQL_GENERATOR_USER.format(
|
||||||
|
schema=schema,
|
||||||
|
question=prompt,
|
||||||
|
dialect=dialect
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.chat(messages, **kwargs)
|
||||||
|
content = response.content.strip()
|
||||||
|
|
||||||
|
# 提取SQL(移除可能的markdown代码块标记)
|
||||||
|
if "```sql" in content:
|
||||||
|
start = content.find("```sql") + 6
|
||||||
|
end = content.find("```", start)
|
||||||
|
content = content[start:end].strip()
|
||||||
|
elif "```" in content:
|
||||||
|
# 通用代码块
|
||||||
|
start = content.find("```") + 3
|
||||||
|
end = content.find("```", start)
|
||||||
|
content = content[start:end].strip()
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def validate_sql(
|
||||||
|
self,
|
||||||
|
sql: str,
|
||||||
|
schema: str,
|
||||||
|
**kwargs
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
便捷方法:验证SQL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema: Schema描述
|
||||||
|
**kwargs: 额外参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
验证结果字典
|
||||||
|
"""
|
||||||
|
from config.prompts import VALIDATOR_SYSTEM, VALIDATOR_USER
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": VALIDATOR_SYSTEM},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": VALIDATOR_USER.format(sql=sql, schema=schema)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return self.chat_with_json(messages, **kwargs)
|
||||||
|
|
||||||
|
def select_tables(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
table_list: str,
|
||||||
|
**kwargs
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
便捷方法:选择相关表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
table_list: 可用表列表字符串
|
||||||
|
**kwargs: 额外参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
包含relevant_tables和reasoning的字典
|
||||||
|
"""
|
||||||
|
from config.prompts import SCHEMA_LINKER_SYSTEM, SCHEMA_LINKER_USER
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": SCHEMA_LINKER_SYSTEM},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": SCHEMA_LINKER_USER.format(
|
||||||
|
question=question,
|
||||||
|
table_list=table_list
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return self.chat_with_json(messages, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncDeepSeekClient:
|
||||||
|
"""
|
||||||
|
DeepSeek API 客户端(异步)
|
||||||
|
|
||||||
|
注意:CAMEL AI当前版本主要支持同步Agent,
|
||||||
|
异步客户端适用于自定义异步流程。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: DeepSeekConfig):
|
||||||
|
self.config = config
|
||||||
|
self.client = AsyncOpenAI(
|
||||||
|
api_key=config.api_key,
|
||||||
|
base_url=config.base_url,
|
||||||
|
timeout=config.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def chat(self, messages: List[Dict[str, str]], **kwargs) -> ChatCompletionMessage:
|
||||||
|
params = {
|
||||||
|
"model": self.config.model_name,
|
||||||
|
"messages": messages,
|
||||||
|
"temperature": kwargs.get("temperature", self.config.temperature),
|
||||||
|
"max_tokens": kwargs.get("max_tokens", self.config.max_tokens),
|
||||||
|
}
|
||||||
|
response = await self.client.chat.completions.create(**params)
|
||||||
|
return response.choices[0].message
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷工厂函数
|
||||||
|
def create_deepseek_client(
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
**kwargs
|
||||||
|
) -> DeepSeekClient:
|
||||||
|
"""
|
||||||
|
创建DeepSeek客户端
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key: API密钥(可从环境变量DEEPSEEK_API_KEY读取)
|
||||||
|
**kwargs: 覆盖默认配置的参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeepSeekClient实例
|
||||||
|
"""
|
||||||
|
if api_key is None:
|
||||||
|
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
raise ValueError(
|
||||||
|
"未提供api_key且环境变量DEEPSEEK_API_KEY未设置。"
|
||||||
|
)
|
||||||
|
|
||||||
|
config = DeepSeekConfig(api_key=api_key, **kwargs)
|
||||||
|
return DeepSeekClient(config)
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Text2SQL 多智能体系统 - CLI演示入口
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python main.py "查询2024年1月销售额最高的前5个产品"
|
||||||
|
python main.py --question "查询所有状态为Active的账户数量"
|
||||||
|
python main.py --interactive # 交互式模式
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DEFAULT_EMBEDDING_PATH = "./data/models/Qwen3-Embedding-0.6B"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_project_env():
|
||||||
|
"""加载项目根目录 .env(与 main.py 同目录),供后续 os.getenv 使用。"""
|
||||||
|
load_dotenv(Path(__file__).resolve().parent / ".env")
|
||||||
|
|
||||||
|
|
||||||
|
def _embedding_model_path() -> str:
|
||||||
|
return os.getenv("EMBEDDING_MODEL_PATH", _DEFAULT_EMBEDDING_PATH).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_sql_dialect(name: str) -> str:
|
||||||
|
"""CLI / 配置中的方言别名统一为 sqlglot 方言名(SQL Server -> tsql)。"""
|
||||||
|
n = (name or "sqlserver").lower().strip()
|
||||||
|
if n in ("sqlserver", "mssql"):
|
||||||
|
return "tsql"
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
def setup_environment():
|
||||||
|
"""环境检查"""
|
||||||
|
_load_project_env()
|
||||||
|
use_local_emb = os.getenv("USE_LOCAL_EMBEDDING", "true").strip().lower() in (
|
||||||
|
"1", "true", "yes", "on",
|
||||||
|
)
|
||||||
|
if use_local_emb:
|
||||||
|
# 与 .env 中 EMBEDDING_MODEL_PATH 及 utils.embedding 一致
|
||||||
|
model_path = Path(_embedding_model_path())
|
||||||
|
if not model_path.exists():
|
||||||
|
logger.warning(f"Embedding模型不存在: {model_path}")
|
||||||
|
logger.info("请先下载模型:")
|
||||||
|
logger.info(" modelscope download --model 'Qwen/Qwen3-Embedding-0.6B' "
|
||||||
|
f"--local_dir '{model_path}'")
|
||||||
|
logger.info("或使用远程 Embedding API:USE_LOCAL_EMBEDDING=false,并配置 "
|
||||||
|
"MODELSCOPE_API_KEY、或 OPENAI_API_KEY+OPENAI_EMBEDDING_MODEL"
|
||||||
|
"(及可选 OPENAI_BASE_URL)、或 DASHSCOPE_*(百炼)")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
ms_key = os.getenv("MODELSCOPE_API_KEY", "").strip()
|
||||||
|
oa_key = os.getenv("OPENAI_API_KEY", "").strip()
|
||||||
|
oa_key_ok = oa_key and not (
|
||||||
|
oa_key.startswith("http://") or oa_key.startswith("https://")
|
||||||
|
)
|
||||||
|
if ms_key:
|
||||||
|
pass # ModelScope:BASE_URL / MODEL 有默认值,仅需 KEY
|
||||||
|
elif oa_key_ok:
|
||||||
|
if not os.getenv("OPENAI_EMBEDDING_MODEL", "").strip():
|
||||||
|
logger.warning("USE_LOCAL_EMBEDDING=false 但未设置 OPENAI_EMBEDDING_MODEL")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if not os.getenv("DASHSCOPE_API_KEY", "").strip():
|
||||||
|
logger.warning(
|
||||||
|
"USE_LOCAL_EMBEDDING=false 但未设置 MODELSCOPE_API_KEY、"
|
||||||
|
"OPENAI_API_KEY+OPENAI_EMBEDDING_MODEL 或 DASHSCOPE_API_KEY"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
base = (
|
||||||
|
os.getenv("DASHSCOPE_BASE_URL") or os.getenv("DASHSCOPE_base_url", "")
|
||||||
|
).strip()
|
||||||
|
if not base:
|
||||||
|
logger.warning("未设置 DASHSCOPE_BASE_URL(或 DASHSCOPE_base_url)")
|
||||||
|
return False
|
||||||
|
if not os.getenv("DASHSCOPE_MODEL", "").strip():
|
||||||
|
logger.warning("未设置 DASHSCOPE_MODEL")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查Schema文件
|
||||||
|
schema_path = Path("./data/schemas/G3SB_MCDataDictionary_table_structure.json")
|
||||||
|
if not schema_path.exists():
|
||||||
|
logger.warning(f"Schema文件不存在: {schema_path}")
|
||||||
|
logger.info("请将G3SB Schema文件放置在 ./data/schemas/ 目录")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查API Key
|
||||||
|
if not os.getenv("DEEPSEEK_API_KEY"):
|
||||||
|
logger.warning("环境变量 DEEPSEEK_API_KEY 未设置")
|
||||||
|
logger.info("请在 .env 文件中配置,或 export DEEPSEEK_API_KEY=your_key")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _default_g3sb_meta_path(structure_path: str) -> Optional[str]:
|
||||||
|
"""若存在与 table_structure 同名的 table_meta 文件则返回其路径。"""
|
||||||
|
p = Path(structure_path)
|
||||||
|
if "table_structure" not in p.name:
|
||||||
|
return None
|
||||||
|
cand = p.parent / p.name.replace("table_structure", "table_meta")
|
||||||
|
return str(cand) if cand.is_file() else None
|
||||||
|
|
||||||
|
|
||||||
|
def load_schema(schema_path: str, schema_meta_path: Optional[str] = None):
|
||||||
|
"""加载 Schema;G3SB structure JSON 会自动尝试配对 table_meta(可用 --schema-meta 指定)。"""
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
|
||||||
|
meta = (
|
||||||
|
schema_meta_path
|
||||||
|
if schema_meta_path is not None
|
||||||
|
else _default_g3sb_meta_path(schema_path)
|
||||||
|
)
|
||||||
|
logger.info(f"加载Schema: {schema_path}")
|
||||||
|
if meta:
|
||||||
|
logger.info(f" 表注释(meta): {meta}")
|
||||||
|
|
||||||
|
schema_mgr = SchemaManager.load_from_json(
|
||||||
|
schema_path, g3sb_meta_path=meta
|
||||||
|
)
|
||||||
|
|
||||||
|
stats = schema_mgr.get_statistics()
|
||||||
|
logger.info(
|
||||||
|
f"[OK] Schema加载完成: {stats['database']}, "
|
||||||
|
f"共{stats['total_tables']}张表, {stats['total_columns']}个字段"
|
||||||
|
)
|
||||||
|
|
||||||
|
return schema_mgr
|
||||||
|
|
||||||
|
|
||||||
|
def create_orchestrator(schema_mgr, args):
|
||||||
|
"""创建编排器"""
|
||||||
|
from agents.orchestrator import Text2SQLOrchestrator
|
||||||
|
from llm.deepseek_client import DeepSeekConfig
|
||||||
|
|
||||||
|
api_key = (args.api_key or os.getenv("DEEPSEEK_API_KEY") or "").strip()
|
||||||
|
if not api_key:
|
||||||
|
raise ValueError(
|
||||||
|
"未配置 DeepSeek API Key:请在 .env 中设置 DEEPSEEK_API_KEY,"
|
||||||
|
"或使用命令行参数 --api-key"
|
||||||
|
)
|
||||||
|
base_url = (os.getenv("DEEPSEEK_BASE_URL") or "https://api.deepseek.com").strip()
|
||||||
|
|
||||||
|
config = DeepSeekConfig(
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
model_name=args.model or "deepseek-chat",
|
||||||
|
temperature=args.temperature,
|
||||||
|
max_tokens=args.max_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
|
orchestrator = Text2SQLOrchestrator(
|
||||||
|
schema_manager=schema_mgr,
|
||||||
|
deepseek_config=config,
|
||||||
|
embedding_model_path=args.embedding_model,
|
||||||
|
vector_db_path=args.vector_db,
|
||||||
|
max_retry=args.max_retry,
|
||||||
|
use_vector_search=not args.no_vector_search,
|
||||||
|
# Few-shot配置
|
||||||
|
fewshot_enabled=not args.no_fewshot,
|
||||||
|
fewshot_top_k=args.fewshot_top_k,
|
||||||
|
fewshot_min_rating=args.fewshot_min_rating,
|
||||||
|
)
|
||||||
|
|
||||||
|
return orchestrator
|
||||||
|
|
||||||
|
|
||||||
|
def single_query(orchestrator, question: str, dialect: str = "tsql"):
|
||||||
|
"""单次查询"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
logger.info(f"[Q] 问题: {question}")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
result = orchestrator.generate(
|
||||||
|
question=question,
|
||||||
|
dialect=dialect,
|
||||||
|
top_k_candidates=20
|
||||||
|
)
|
||||||
|
elapsed = time.time() - start
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("生成结果:")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
if result.valid:
|
||||||
|
print(f"[OK] SQL (耗时 {elapsed:.2f}s, 尝试 {result.attempts} 次):\n")
|
||||||
|
print(result.sql)
|
||||||
|
else:
|
||||||
|
print(f"[FAIL] 生成失败 (尝试 {result.attempts} 次)")
|
||||||
|
for err in result.errors:
|
||||||
|
print(f" - {err}")
|
||||||
|
|
||||||
|
if result.warnings:
|
||||||
|
print("\n[WARN] 警告:")
|
||||||
|
for w in result.warnings:
|
||||||
|
print(f" - {w}")
|
||||||
|
|
||||||
|
print(f"\n使用表: {result.tables_used}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_mode(orchestrator, dialect: str = "tsql"):
|
||||||
|
"""交互式模式"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("Text2SQL 交互模式(输入 'quit' 或 'exit' 退出)")
|
||||||
|
print("=" * 60 + "\n")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
question = input("❓ 请输入问题: ").strip()
|
||||||
|
if question.lower() in ('quit', 'exit', 'q'):
|
||||||
|
print("再见!")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not question:
|
||||||
|
continue
|
||||||
|
|
||||||
|
result = single_query(orchestrator, question, dialect)
|
||||||
|
print()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n再见!")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"查询失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def batch_mode(orchestrator, questions: list, dialect: str = "tsql"):
|
||||||
|
"""批量查询模式"""
|
||||||
|
print(f"\n批量模式:共 {len(questions)} 个问题\n")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for i, question in enumerate(questions, 1):
|
||||||
|
print(f"[{i}/{len(questions)}] {question}")
|
||||||
|
result = single_query(orchestrator, question, dialect)
|
||||||
|
results.append(result)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 统计
|
||||||
|
success_count = sum(1 for r in results if r.valid)
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"统计: {success_count}/{len(questions)} 成功 "
|
||||||
|
f"({success_count/len(questions)*100:.1f}%)")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Text2SQL 多智能体系统 - 自然语言生成SQL"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"question",
|
||||||
|
nargs="?",
|
||||||
|
help="自然语言问题(如不提供则进入交互模式)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--schema", "-s",
|
||||||
|
default="./data/schemas/G3SB_MCDataDictionary_table_structure.json",
|
||||||
|
help="Schema文件路径(默认: ./data/schemas/G3SB_MCDataDictionary_table_structure.json)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--schema-meta",
|
||||||
|
default=None,
|
||||||
|
help="G3SB table_meta.json;默认自动使用同目录下文件名含 table_meta 的配对文件",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dialect", "-d",
|
||||||
|
default=os.getenv("TEXT2SQL_DIALECT", "sqlserver").strip(),
|
||||||
|
choices=["mysql", "postgresql", "sqlite", "tsql", "sqlserver", "mssql"],
|
||||||
|
help="SQL方言(默认: sqlserver / T-SQL;可用环境变量 TEXT2SQL_DIALECT 覆盖)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--api-key",
|
||||||
|
help="DeepSeek API Key(默认从DEEPSEEK_API_KEY环境变量读取)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--model", "-m",
|
||||||
|
default="deepseek-chat",
|
||||||
|
help="DeepSeek模型名称(默认: deepseek-chat)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--temperature", "-t",
|
||||||
|
type=float,
|
||||||
|
default=0.3,
|
||||||
|
help="生成温度(默认: 0.3)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-tokens",
|
||||||
|
type=int,
|
||||||
|
default=4096,
|
||||||
|
help="最大token数(默认: 4096)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-retry",
|
||||||
|
type=int,
|
||||||
|
default=2,
|
||||||
|
help="最大重试次数(默认: 2)"
|
||||||
|
)
|
||||||
|
_load_project_env()
|
||||||
|
parser.add_argument(
|
||||||
|
"--embedding-model",
|
||||||
|
default=_embedding_model_path(),
|
||||||
|
help="Embedding模型路径(默认来自环境变量 EMBEDDING_MODEL_PATH)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--vector-db",
|
||||||
|
default=os.getenv("VECTOR_DB_PATH", "./data/embeddings/chroma").strip(),
|
||||||
|
help="向量数据库路径(默认来自环境变量 VECTOR_DB_PATH)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-vector-search",
|
||||||
|
action="store_true",
|
||||||
|
help="禁用向量检索(使用所有表)"
|
||||||
|
)
|
||||||
|
# Few-shot 配置
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-fewshot",
|
||||||
|
action="store_true",
|
||||||
|
help="禁用few-shot示例增强"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--fewshot-top-k",
|
||||||
|
type=int,
|
||||||
|
default=int(os.getenv("FEWSHOT_TOP_K", "3")),
|
||||||
|
help="每次使用的few-shot示例数量(默认: 3)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--fewshot-min-rating",
|
||||||
|
type=int,
|
||||||
|
default=int(os.getenv("FEWSHOT_MIN_RATING", "7")),
|
||||||
|
help="few-shot示例最低评分(默认: 7)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--interactive", "-i",
|
||||||
|
action="store_true",
|
||||||
|
help="交互模式"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--batch", "-b",
|
||||||
|
help="批量文件路径(每行一个问题)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose", "-v",
|
||||||
|
action="store_true",
|
||||||
|
help="详细日志"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.dialect = resolve_sql_dialect(args.dialect)
|
||||||
|
|
||||||
|
# 日志级别
|
||||||
|
if args.verbose:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# 环境检查
|
||||||
|
if not setup_environment():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 加载Schema
|
||||||
|
try:
|
||||||
|
schema_mgr = load_schema(args.schema, args.schema_meta)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Schema加载失败: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 创建Orchestrator
|
||||||
|
try:
|
||||||
|
orchestrator = create_orchestrator(schema_mgr, args)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Orchestrator创建失败: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 根据参数选择模式
|
||||||
|
if args.interactive or (not args.question and not args.batch):
|
||||||
|
interactive_mode(orchestrator, args.dialect)
|
||||||
|
elif args.batch:
|
||||||
|
with open(args.batch, 'r', encoding='utf-8') as f:
|
||||||
|
questions = [line.strip() for line in f if line.strip()]
|
||||||
|
batch_mode(orchestrator, questions, args.dialect)
|
||||||
|
elif args.question:
|
||||||
|
single_query(orchestrator, args.question, args.dialect)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "text2sql-agent-camel"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Text-to-SQL Multi-Agent System based on CAMEL AI"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "Backman Team"}
|
||||||
|
]
|
||||||
|
keywords = ["text2sql", "llm", "agent", "camel-ai", "deepseek"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
# CAMEL AI 核心
|
||||||
|
"camel-ai>=0.2.0",
|
||||||
|
"camel-ai[all]>=0.2.0", # 包含所有扩展
|
||||||
|
|
||||||
|
# LLM API
|
||||||
|
"openai>=1.0.0",
|
||||||
|
|
||||||
|
# 向量数据库
|
||||||
|
"chromadb>=0.4.0",
|
||||||
|
|
||||||
|
# Embedding 模型
|
||||||
|
"transformers>=4.36.0",
|
||||||
|
"torch>=2.0.0",
|
||||||
|
"sentencepiece>=0.1.99",
|
||||||
|
"accelerate>=0.20.0",
|
||||||
|
"safetensors>=0.4.0",
|
||||||
|
|
||||||
|
# SQL 处理
|
||||||
|
"sqlglot>=20.0.0",
|
||||||
|
"sqlparse>=0.4.0",
|
||||||
|
|
||||||
|
# 配置与验证
|
||||||
|
"pydantic>=2.0.0",
|
||||||
|
"pydantic-settings>=2.0.0",
|
||||||
|
"python-dotenv>=1.0.0",
|
||||||
|
|
||||||
|
# 工具库
|
||||||
|
"tenacity>=8.0.0",
|
||||||
|
"structlog>=23.0.0",
|
||||||
|
"tqdm>=4.65.0",
|
||||||
|
|
||||||
|
# 中文分词(可选,用于关键词提取)
|
||||||
|
"jieba>=0.42.0",
|
||||||
|
"numpy>=1.24.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"pytest-asyncio>=0.21.0",
|
||||||
|
"pytest-cov>=4.0.0",
|
||||||
|
"black>=23.0.0",
|
||||||
|
"isort>=5.12.0",
|
||||||
|
"mypy>=1.0.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
]
|
||||||
|
docs = [
|
||||||
|
"sphinx>=7.0.0",
|
||||||
|
"sphinx-rtd-theme>=1.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
text2sql = "text2sql_agent_camel.main:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["text2sql_agent_camel*"]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 100
|
||||||
|
target-version = ["py310", "py311", "py312"]
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
line_length = 100
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle
|
||||||
|
"W", # pycodestyle
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
ignore = []
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = "-v --tb=short"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Text2SQL Agent 依赖清单
|
||||||
|
# 使用 uv 或 pip 安装:pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 核心框架
|
||||||
|
camel-ai>=0.2.0
|
||||||
|
|
||||||
|
# LLM API
|
||||||
|
openai>=1.0.0
|
||||||
|
|
||||||
|
# 向量数据库
|
||||||
|
chromadb>=0.4.0
|
||||||
|
sentencepiece>=0.1.99
|
||||||
|
accelerate>=0.20.0
|
||||||
|
safetensors>=0.4.0
|
||||||
|
|
||||||
|
# SQL 处理
|
||||||
|
sqlglot>=20.0.0
|
||||||
|
sqlparse>=0.4.0
|
||||||
|
|
||||||
|
# 配置与验证
|
||||||
|
pydantic>=2.0.0
|
||||||
|
pydantic-settings>=2.0.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
|
||||||
|
# 工具库
|
||||||
|
tenacity>=8.0.0
|
||||||
|
structlog>=23.0.0
|
||||||
|
tqdm>=4.65.0
|
||||||
|
jieba>=0.42.0
|
||||||
|
numpy>=1.24.0,<2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# schema 包初始化
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,229 @@
|
|||||||
|
"""
|
||||||
|
Schema向量索引构建器 - 基于ChromaDB
|
||||||
|
"""
|
||||||
|
|
||||||
|
import chromadb
|
||||||
|
from chromadb.config import Settings as ChromaSettings
|
||||||
|
from typing import Any, List, Dict, Optional
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaIndexer:
|
||||||
|
"""
|
||||||
|
Schema向量索引器
|
||||||
|
|
||||||
|
功能:
|
||||||
|
1. 为表结构构建向量索引
|
||||||
|
2. 基于问题的表检索
|
||||||
|
3. 持久化存储和加载
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
embedder: Any,
|
||||||
|
persist_dir: str = "./data/embeddings",
|
||||||
|
collection_name: str = "schema_tables",
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化索引器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
embedder: Embedding模型实例
|
||||||
|
persist_dir: 向量数据库持久化目录
|
||||||
|
collection_name: 集合名称
|
||||||
|
"""
|
||||||
|
self.embedder = embedder
|
||||||
|
self.persist_dir = Path(persist_dir)
|
||||||
|
self.persist_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 初始化ChromaDB客户端
|
||||||
|
self.client = chromadb.PersistentClient(
|
||||||
|
path=str(self.persist_dir),
|
||||||
|
settings=ChromaSettings(anonymized_telemetry=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取或创建集合
|
||||||
|
self.collection = self.client.get_or_create_collection(
|
||||||
|
name=collection_name,
|
||||||
|
metadata={"hnsw:space": "cosine"}, # 使用余弦相似度
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"[OK] 初始化SchemaIndexer: collection={collection_name}")
|
||||||
|
|
||||||
|
def build_index(
|
||||||
|
self,
|
||||||
|
schema_manager: SchemaManager,
|
||||||
|
batch_size: int = 32,
|
||||||
|
force_rebuild: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
为Schema构建向量索引
|
||||||
|
|
||||||
|
Args:
|
||||||
|
schema_manager: SchemaManager实例
|
||||||
|
batch_size: 批处理大小
|
||||||
|
force_rebuild: 是否强制重建(默认为False,增量更新)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True=成功,False=已存在且未强制重建
|
||||||
|
"""
|
||||||
|
existing_ids = set(self.collection.get()["ids"]) if self.collection.count() > 0 else set()
|
||||||
|
|
||||||
|
if not force_rebuild and existing_ids:
|
||||||
|
logger.info(f"索引已存在({len(existing_ids)}条记录),跳过构建")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if force_rebuild and existing_ids:
|
||||||
|
logger.info(f"强制重建索引,删除{len(existing_ids)}条旧记录")
|
||||||
|
self.collection.delete()
|
||||||
|
|
||||||
|
# 准备数据
|
||||||
|
table_texts = []
|
||||||
|
table_ids = []
|
||||||
|
table_metadatas = []
|
||||||
|
|
||||||
|
for table_info in schema_manager.get_table_for_embedding():
|
||||||
|
table_texts.append(table_info["text"])
|
||||||
|
table_ids.append(table_info["name"])
|
||||||
|
table_metadatas.append({
|
||||||
|
"table_name": table_info["name"],
|
||||||
|
"column_count": len(schema_manager.get_table(table_info["name"]).columns),
|
||||||
|
})
|
||||||
|
|
||||||
|
# 批量计算embedding
|
||||||
|
logger.info(f"计算{len(table_texts)}张表的embedding...")
|
||||||
|
embeddings = self.embedder.encode(table_texts, batch_size=batch_size)
|
||||||
|
|
||||||
|
# 存入ChromaDB
|
||||||
|
self.collection.add(
|
||||||
|
embeddings=embeddings.tolist(),
|
||||||
|
documents=table_texts,
|
||||||
|
metadatas=table_metadatas,
|
||||||
|
ids=table_ids,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"[OK] 索引构建完成,共{len(table_ids)}张表")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def search(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
top_k: int = 20,
|
||||||
|
score_threshold: float = 0.3,
|
||||||
|
) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
检索相关表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: 查询文本(用户问题)
|
||||||
|
top_k: 返回前K个结果
|
||||||
|
score_threshold: 相似度阈值(0-1),低于此值的结果会被过滤
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
检索结果列表,按相似度降序排列
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"table_name": "表名",
|
||||||
|
"score": 0.95,
|
||||||
|
"document": "表描述文本",
|
||||||
|
"metadata": {...}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
# 编码查询文本
|
||||||
|
query_embedding = self.embedder.encode([query])
|
||||||
|
|
||||||
|
# 执行检索
|
||||||
|
results = self.collection.query(
|
||||||
|
query_embeddings=query_embedding.tolist(),
|
||||||
|
n_results=min(top_k, self.collection.count()),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 格式化结果
|
||||||
|
formatted = []
|
||||||
|
if results["ids"] and len(results["ids"][0]) > 0:
|
||||||
|
for idx, (table_id, distance, metadata, document) in enumerate(zip(
|
||||||
|
results["ids"][0],
|
||||||
|
results["distances"][0],
|
||||||
|
results["metadatas"][0],
|
||||||
|
results["documents"][0],
|
||||||
|
)):
|
||||||
|
score = 1 - distance # 余弦相似度(已归一化,转换为0-1,越大越相似)
|
||||||
|
if score >= score_threshold:
|
||||||
|
formatted.append({
|
||||||
|
"table_name": table_id,
|
||||||
|
"score": float(score),
|
||||||
|
"document": document,
|
||||||
|
"metadata": metadata,
|
||||||
|
"rank": idx + 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.debug(f"检索 '{query[:50]}...' -> 找到{len(formatted)}个相关表(阈值={score_threshold})")
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def search_by_table_names(self, table_names: List[str]) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
直接通过表名获取表信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table_names: 表名列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
表信息列表
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
for name in table_names:
|
||||||
|
try:
|
||||||
|
result = self.collection.get(ids=[name], include=["metadatas", "documents"])
|
||||||
|
if result["ids"]:
|
||||||
|
results.append({
|
||||||
|
"table_name": name,
|
||||||
|
"score": 1.0,
|
||||||
|
"document": result["documents"][0],
|
||||||
|
"metadata": result["metadatas"][0],
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取表信息失败 {name}: {e}")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def get_all_tables(self) -> List[str]:
|
||||||
|
"""获取索引中的所有表名"""
|
||||||
|
result = self.collection.get()
|
||||||
|
return result["ids"] if result["ids"] else []
|
||||||
|
|
||||||
|
def delete_table(self, table_name: str):
|
||||||
|
"""从索引中删除表"""
|
||||||
|
self.collection.delete(ids=[table_name])
|
||||||
|
logger.info(f"已删除表索引: {table_name}")
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""清空索引"""
|
||||||
|
self.collection.delete()
|
||||||
|
logger.info("索引已清空")
|
||||||
|
|
||||||
|
def count(self) -> int:
|
||||||
|
"""获取索引中的表数量"""
|
||||||
|
return self.collection.count()
|
||||||
|
|
||||||
|
# ========== 统计与调试 ==========
|
||||||
|
|
||||||
|
def get_statistics(self) -> Dict:
|
||||||
|
"""获取索引统计信息"""
|
||||||
|
count = self.collection.count()
|
||||||
|
result = self.collection.get(include=["metadatas"])
|
||||||
|
|
||||||
|
total_columns = sum(m.get("column_count", 0) for m in result["metadatas"])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"indexed_tables": count,
|
||||||
|
"total_columns": total_columns,
|
||||||
|
"avg_columns": total_columns / count if count > 0 else 0,
|
||||||
|
"persist_dir": str(self.persist_dir),
|
||||||
|
}
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
"""
|
||||||
|
Schema加载器 - 解析JSON/DDL格式的数据库Schema
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional, Tuple
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .models import Table, Column, ForeignKey, DatabaseSchema
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaLoader:
|
||||||
|
"""Schema加载器 - 支持JSON和DDL格式"""
|
||||||
|
|
||||||
|
def __init__(self, schema_dir: str = None):
|
||||||
|
"""
|
||||||
|
初始化Schema加载器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
schema_dir: Schema文件目录路径
|
||||||
|
"""
|
||||||
|
self.schema_dir = Path(schema_dir) if schema_dir else None
|
||||||
|
|
||||||
|
def load_from_json(
|
||||||
|
self, json_path: str, g3sb_meta_path: Optional[str] = None
|
||||||
|
) -> DatabaseSchema:
|
||||||
|
"""
|
||||||
|
从JSON文件加载Schema
|
||||||
|
|
||||||
|
JSON格式示例:
|
||||||
|
{
|
||||||
|
"database": "db_name",
|
||||||
|
"tables": [
|
||||||
|
{
|
||||||
|
"name": "table1",
|
||||||
|
"comment": "表描述",
|
||||||
|
"columns": [
|
||||||
|
{"name": "col1", "type": "INT", "comment": "...", "nullable": false}
|
||||||
|
],
|
||||||
|
"primary_keys": ["col1"],
|
||||||
|
"foreign_keys": [
|
||||||
|
{"columns": ["col2"], "ref_table": "table2", "ref_columns": ["col1"]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
亦支持 G3SB table_structure.json:顶层为 ``schemas`` 字典(与 ``tables`` 数组二选一
|
||||||
|
时优先使用非空的 ``schemas``)。可选 ``g3sb_meta_path`` 指向 table_meta.json
|
||||||
|
以合并表级注释。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path: JSON文件路径
|
||||||
|
g3sb_meta_path: 可选,G3SB table_meta.json(含 tables.{表名}.comment)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DatabaseSchema对象
|
||||||
|
"""
|
||||||
|
path = Path(json_path)
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(f"Schema文件不存在: {json_path}")
|
||||||
|
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 提取数据库名
|
||||||
|
db_name = data.get("database", path.stem)
|
||||||
|
|
||||||
|
schemas_map = data.get("schemas")
|
||||||
|
tables_data = data.get("tables")
|
||||||
|
|
||||||
|
# 优先非空 schemas(G3SB 结构字典),避免误把其它 truthy 的 tables 键当好列表解析
|
||||||
|
if isinstance(schemas_map, dict) and schemas_map:
|
||||||
|
table_meta: Dict = {}
|
||||||
|
if g3sb_meta_path:
|
||||||
|
mp = Path(g3sb_meta_path)
|
||||||
|
if mp.is_file():
|
||||||
|
with open(mp, 'r', encoding='utf-8') as mf:
|
||||||
|
meta_data = json.load(mf)
|
||||||
|
table_meta = meta_data.get("tables", {}) or {}
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"G3SB meta 文件不存在,将跳过表注释: %s", g3sb_meta_path
|
||||||
|
)
|
||||||
|
|
||||||
|
tables = []
|
||||||
|
for table_name, structure_str in schemas_map.items():
|
||||||
|
if not isinstance(structure_str, str):
|
||||||
|
continue
|
||||||
|
comment = None
|
||||||
|
if table_meta and isinstance(table_meta.get(table_name), dict):
|
||||||
|
comment = table_meta[table_name].get("comment")
|
||||||
|
columns = self._parse_g3sb_table_structure(structure_str)
|
||||||
|
tables.append(
|
||||||
|
Table(
|
||||||
|
name=table_name,
|
||||||
|
comment=comment,
|
||||||
|
columns=columns,
|
||||||
|
primary_keys=self._extract_primary_keys(columns),
|
||||||
|
foreign_keys=[],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"[OK] 加载Schema完成(G3SB schemas): %s, 共%d张表", db_name, len(tables)
|
||||||
|
)
|
||||||
|
return DatabaseSchema(name=db_name, tables=tables)
|
||||||
|
|
||||||
|
if isinstance(tables_data, list) and tables_data:
|
||||||
|
tables = [self._parse_table(table_data) for table_data in tables_data]
|
||||||
|
logger.info(f"[OK] 加载Schema完成: {db_name}, 共{len(tables)}张表")
|
||||||
|
return DatabaseSchema(name=db_name, tables=tables)
|
||||||
|
|
||||||
|
tables = []
|
||||||
|
logger.info(f"[OK] 加载Schema完成: {db_name}, 共{len(tables)}张表")
|
||||||
|
return DatabaseSchema(name=db_name, tables=tables)
|
||||||
|
|
||||||
|
def load_from_g3sb_format(self, meta_path: str, structure_path: str) -> DatabaseSchema:
|
||||||
|
"""
|
||||||
|
加载G3SB系统的数据字典格式(两个JSON文件)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
meta_path: table_meta.json路径(表注释)
|
||||||
|
structure_path: table_structure.json路径(表结构)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DatabaseSchema对象
|
||||||
|
"""
|
||||||
|
# 1. 加载表元数据(表注释)
|
||||||
|
with open(meta_path, 'r', encoding='utf-8') as f:
|
||||||
|
meta_data = json.load(f)
|
||||||
|
table_meta = meta_data.get("tables", {})
|
||||||
|
|
||||||
|
# 2. 加载表结构
|
||||||
|
with open(structure_path, 'r', encoding='utf-8') as f:
|
||||||
|
structure_data = json.load(f)
|
||||||
|
table_structures = structure_data.get("schemas", {})
|
||||||
|
|
||||||
|
# 3. 解析所有表
|
||||||
|
tables = []
|
||||||
|
for table_name, structure_str in table_structures.items():
|
||||||
|
comment = table_meta.get(table_name, {}).get("comment")
|
||||||
|
|
||||||
|
# 解析表结构字符串
|
||||||
|
# 格式: "TABLE TableName (col1:TYPE -- comment, col2:TYPE, ...)"
|
||||||
|
columns = self._parse_g3sb_table_structure(structure_str)
|
||||||
|
|
||||||
|
table = Table(
|
||||||
|
name=table_name,
|
||||||
|
comment=comment,
|
||||||
|
columns=columns,
|
||||||
|
primary_keys=self._extract_primary_keys(columns),
|
||||||
|
foreign_keys=[] # G3SB格式没有外键信息,后续可补充
|
||||||
|
)
|
||||||
|
tables.append(table)
|
||||||
|
|
||||||
|
logger.info(f"[OK] 加载G3SB Schema完成: 共{len(tables)}张表")
|
||||||
|
return DatabaseSchema(name="G3SB_DB", tables=tables)
|
||||||
|
|
||||||
|
def _parse_table(self, data: Dict) -> Table:
|
||||||
|
"""解析单表JSON数据"""
|
||||||
|
columns = []
|
||||||
|
for col_data in data.get("columns", []):
|
||||||
|
col = Column(
|
||||||
|
name=col_data["name"],
|
||||||
|
data_type=col_data["type"],
|
||||||
|
comment=col_data.get("comment"),
|
||||||
|
nullable=col_data.get("nullable", True),
|
||||||
|
is_primary_key=col_data.get("is_primary_key", False),
|
||||||
|
)
|
||||||
|
columns.append(col)
|
||||||
|
|
||||||
|
# 外键解析
|
||||||
|
foreign_keys = []
|
||||||
|
for fk_data in data.get("foreign_keys", []):
|
||||||
|
fk = ForeignKey(
|
||||||
|
columns=fk_data["columns"],
|
||||||
|
ref_table=fk_data["ref_table"],
|
||||||
|
ref_columns=fk_data["ref_columns"],
|
||||||
|
)
|
||||||
|
foreign_keys.append(fk)
|
||||||
|
|
||||||
|
return Table(
|
||||||
|
name=data["name"],
|
||||||
|
comment=data.get("comment"),
|
||||||
|
columns=columns,
|
||||||
|
primary_keys=data.get("primary_keys", []),
|
||||||
|
foreign_keys=foreign_keys,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _parse_g3sb_table_structure(self, structure_str: str) -> List[Column]:
|
||||||
|
"""
|
||||||
|
解析G3SB格式的表结构字符串
|
||||||
|
|
||||||
|
示例:
|
||||||
|
"TABLE BCAccountCash (AccountID:NCHAR, RegionID:NCHAR, CurrencyID:NCHAR, Settled:DECIMAL -- Settled balance, ...)"
|
||||||
|
"""
|
||||||
|
# 提取括号内的内容
|
||||||
|
match = re.search(r'\((.*)\)', structure_str)
|
||||||
|
if not match:
|
||||||
|
logger.warning(f"无法解析表结构: {structure_str[:100]}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
inner = match.group(1)
|
||||||
|
|
||||||
|
columns = []
|
||||||
|
# 按逗号分割字段(注意注释中可能包含逗号)
|
||||||
|
parts = self._split_columns(inner)
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
part = part.strip()
|
||||||
|
if not part:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 解析字段定义:name:TYPE [-- comment]
|
||||||
|
# 支持格式: "FieldName:DATATYPE" 或 "FieldName:DATATYPE -- comment"
|
||||||
|
col_match = re.match(r'^(\w+)\s*:\s*([A-Za-z0-9()]+)', part)
|
||||||
|
if not col_match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
col_name = col_match.group(1).strip()
|
||||||
|
col_type = col_match.group(2).strip()
|
||||||
|
|
||||||
|
# 提取注释(ASCII -- 或 G3SB 常用的 Unicode 长破折号 — U+2014)
|
||||||
|
comment = None
|
||||||
|
comment_match = re.search(r'(?:--|\u2014)\s*(.+)', part)
|
||||||
|
if comment_match:
|
||||||
|
comment = comment_match.group(1).strip()
|
||||||
|
|
||||||
|
# 判断是否可为空(通常有默认值或未标注NOT NULL即为NULL)
|
||||||
|
nullable = True # G3SB格式默认允许NULL
|
||||||
|
|
||||||
|
column = Column(
|
||||||
|
name=col_name,
|
||||||
|
data_type=col_type,
|
||||||
|
comment=comment,
|
||||||
|
nullable=nullable,
|
||||||
|
)
|
||||||
|
columns.append(column)
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
def _split_columns(self, inner: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
按字段边界拆分(G3SB 注释里常有英文逗号,且用 — 而非 --)。
|
||||||
|
|
||||||
|
仅在「后面紧跟 标识符: 」的逗号处切分,这样注释内的逗号不会误拆列。
|
||||||
|
"""
|
||||||
|
if not inner or not inner.strip():
|
||||||
|
return []
|
||||||
|
# 下一列以 Name:TYPE 开头;避免在括号嵌套里误匹配可再收紧(当前 G3SB 类型无顶层逗号)
|
||||||
|
parts = re.split(r",\s*(?=\w+\s*:)", inner)
|
||||||
|
return [p.strip() for p in parts if p.strip()]
|
||||||
|
|
||||||
|
def _extract_primary_keys(self, columns: List[Column]) -> List[str]:
|
||||||
|
"""从字段列表中提取主键(简单启发式:字段名包含ID或明确标记)"""
|
||||||
|
pk_candidates = []
|
||||||
|
for col in columns:
|
||||||
|
# 简单规则:字段名以ID结尾,或名称包含key/id
|
||||||
|
if col.name.upper().endswith('ID') or 'KEY' in col.name.upper():
|
||||||
|
pk_candidates.append(col.name)
|
||||||
|
return pk_candidates[:1] # 暂时只取一个主键(简化)
|
||||||
|
|
||||||
|
def load_all_schemas(self) -> List[DatabaseSchema]:
|
||||||
|
"""
|
||||||
|
加载schema_dir下的所有Schema文件
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DatabaseSchema列表
|
||||||
|
"""
|
||||||
|
if not self.schema_dir:
|
||||||
|
raise ValueError("未指定schema_dir")
|
||||||
|
|
||||||
|
schemas = []
|
||||||
|
for json_file in self.schema_dir.glob("*.json"):
|
||||||
|
try:
|
||||||
|
schema = self.load_from_json(str(json_file))
|
||||||
|
schemas.append(schema)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"加载Schema失败 {json_file}: {e}")
|
||||||
|
|
||||||
|
logger.info(f"[OK] 共加载{len(schemas)}个Schema")
|
||||||
|
return schemas
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数
|
||||||
|
def load_schema_from_g3sb(meta_path: str, structure_path: str) -> DatabaseSchema:
|
||||||
|
"""
|
||||||
|
从G3SB格式加载Schema的便捷函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
meta_path: table_meta.json路径
|
||||||
|
structure_path: table_structure.json路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DatabaseSchema对象
|
||||||
|
"""
|
||||||
|
loader = SchemaLoader()
|
||||||
|
return loader.load_from_g3sb_format(meta_path, structure_path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_schema_from_json(
|
||||||
|
json_path: str, g3sb_meta_path: Optional[str] = None
|
||||||
|
) -> DatabaseSchema:
|
||||||
|
"""
|
||||||
|
从标准JSON加载Schema的便捷函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path: JSON文件路径
|
||||||
|
g3sb_meta_path: 可选,G3SB table_meta.json
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DatabaseSchema对象
|
||||||
|
"""
|
||||||
|
loader = SchemaLoader()
|
||||||
|
return loader.load_from_json(json_path, g3sb_meta_path=g3sb_meta_path)
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
"""
|
||||||
|
Schema管理器 - 管理数据库Schema的加载、索引和检索
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional, Tuple
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .models import DatabaseSchema, Table
|
||||||
|
from .loader import SchemaLoader, load_schema_from_json, load_schema_from_g3sb
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaManager:
|
||||||
|
"""
|
||||||
|
Schema管理器
|
||||||
|
|
||||||
|
功能:
|
||||||
|
1. 加载和解析Schema文件
|
||||||
|
2. 管理表关系
|
||||||
|
3. 提供表检索接口
|
||||||
|
4. 生成紧凑的Schema描述
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, schema: DatabaseSchema):
|
||||||
|
"""
|
||||||
|
初始化Schema管理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
schema: DatabaseSchema对象
|
||||||
|
"""
|
||||||
|
self.schema = schema
|
||||||
|
self._table_dict = {tbl.name: tbl for tbl in schema.tables}
|
||||||
|
self._embedding_index = None # 向量索引(延迟初始化)
|
||||||
|
self._cache = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_json(
|
||||||
|
cls, json_path: str, g3sb_meta_path: Optional[str] = None
|
||||||
|
) -> "SchemaManager":
|
||||||
|
"""
|
||||||
|
从JSON文件加载Schema
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path: JSON文件路径
|
||||||
|
g3sb_meta_path: 可选,G3SB table_meta.json(与 table_structure 配对)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SchemaManager实例
|
||||||
|
"""
|
||||||
|
schema = load_schema_from_json(json_path, g3sb_meta_path=g3sb_meta_path)
|
||||||
|
return cls(schema)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_g3sb(cls, meta_path: str, structure_path: str) -> "SchemaManager":
|
||||||
|
"""
|
||||||
|
从G3SB格式加载Schema
|
||||||
|
|
||||||
|
Args:
|
||||||
|
meta_path: table_meta.json路径
|
||||||
|
structure_path: table_structure.json路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SchemaManager实例
|
||||||
|
"""
|
||||||
|
schema = load_schema_from_g3sb(meta_path, structure_path)
|
||||||
|
return cls(schema)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Dict) -> "SchemaManager":
|
||||||
|
"""
|
||||||
|
从字典创建SchemaManager
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: 包含database和tables的字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SchemaManager实例
|
||||||
|
"""
|
||||||
|
loader = SchemaLoader()
|
||||||
|
schema = loader._parse_table(data) if isinstance(data, dict) else None
|
||||||
|
if not schema:
|
||||||
|
raise ValueError("Invalid schema data")
|
||||||
|
return cls(schema)
|
||||||
|
|
||||||
|
# ========== 查询接口 ==========
|
||||||
|
|
||||||
|
def get_table(self, table_name: str) -> Optional[Table]:
|
||||||
|
"""获取指定表"""
|
||||||
|
return self._table_dict.get(table_name)
|
||||||
|
|
||||||
|
def list_tables(self) -> List[str]:
|
||||||
|
"""获取所有表名"""
|
||||||
|
return list(self._table_dict.keys())
|
||||||
|
|
||||||
|
def get_tables(self) -> List[Table]:
|
||||||
|
"""获取所有Table对象"""
|
||||||
|
return list(self._table_dict.values())
|
||||||
|
|
||||||
|
def get_related_tables(self, table_name: str, depth: int = 1) -> List[Table]:
|
||||||
|
"""
|
||||||
|
获取关联表(通过外键关系)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table_name: 起始表名
|
||||||
|
depth: 递归深度(1=直接关联,2=间接关联...)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
相关表列表
|
||||||
|
"""
|
||||||
|
result = set()
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
def _traverse(tname: str, current_depth: int):
|
||||||
|
if tname not in self._table_dict or current_depth > depth:
|
||||||
|
return
|
||||||
|
if tname in visited:
|
||||||
|
return
|
||||||
|
visited.add(tname)
|
||||||
|
|
||||||
|
table = self._table_dict[tname]
|
||||||
|
for fk in table.foreign_keys:
|
||||||
|
result.add(fk.ref_table)
|
||||||
|
_traverse(fk.ref_table, current_depth + 1)
|
||||||
|
|
||||||
|
# 反向外键(被引用的表)
|
||||||
|
for other_table in self._table_dict.values():
|
||||||
|
for fk in other_table.foreign_keys:
|
||||||
|
if fk.ref_table == tname:
|
||||||
|
result.add(other_table.name)
|
||||||
|
_traverse(other_table.name, current_depth + 1)
|
||||||
|
|
||||||
|
_traverse(table_name, 0)
|
||||||
|
return [self._table_dict[t] for t in result if t in self._table_dict]
|
||||||
|
|
||||||
|
# ========== Schema生成 ==========
|
||||||
|
|
||||||
|
def to_compact_string(
|
||||||
|
self,
|
||||||
|
table_names: List[str] = None,
|
||||||
|
include_columns: bool = True,
|
||||||
|
max_columns_per_table: int = 15,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
生成紧凑的Schema描述字符串(用于LLM输入)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table_names: 指定包含的表(None表示所有表)
|
||||||
|
include_columns: 是否包含字段详情
|
||||||
|
max_columns_per_table: 每表最多显示字段数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Schema描述字符串
|
||||||
|
"""
|
||||||
|
if table_names is None:
|
||||||
|
tables = self.get_tables()
|
||||||
|
else:
|
||||||
|
missing = [n for n in table_names if n not in self._table_dict]
|
||||||
|
if missing:
|
||||||
|
logger.warning(
|
||||||
|
"以下表名不在已加载Schema中,已从本次Schema片段中省略: %s",
|
||||||
|
missing,
|
||||||
|
)
|
||||||
|
tables = [t for t in self.get_tables() if t.name in table_names]
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
lines.append(f"数据库: {self.schema.name}")
|
||||||
|
lines.append(f"涉及表数: {len(tables)}")
|
||||||
|
lines.append("=" * 60)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
for table in tables:
|
||||||
|
lines.append(f"【表】{table.name}")
|
||||||
|
if table.comment:
|
||||||
|
lines.append(f" 描述: {table.comment}")
|
||||||
|
|
||||||
|
if include_columns and table.columns:
|
||||||
|
lines.append(f" 字段:")
|
||||||
|
for col in table.columns[:max_columns_per_table]:
|
||||||
|
pk_marker = " [PK]" if col.is_primary_key else ""
|
||||||
|
null_marker = " NOT NULL" if not col.nullable else ""
|
||||||
|
comment = f" -- {col.comment}" if col.comment else ""
|
||||||
|
lines.append(f" {col.name}: {col.data_type}{pk_marker}{null_marker}{comment}")
|
||||||
|
|
||||||
|
if len(table.columns) > max_columns_per_table:
|
||||||
|
lines.append(f" ... 还有 {len(table.columns) - max_columns_per_table} 个字段")
|
||||||
|
|
||||||
|
# 外键关系
|
||||||
|
if table.foreign_keys:
|
||||||
|
lines.append(" 外键:")
|
||||||
|
for fk in table.foreign_keys[:5]:
|
||||||
|
lines.append(
|
||||||
|
f" {', '.join(fk.columns)} → {fk.ref_table}({', '.join(fk.ref_columns)})"
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def to_filtered_schema(self, table_names: List[str]) -> DatabaseSchema:
|
||||||
|
"""
|
||||||
|
根据表名过滤,生成子集Schema
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table_names: 要保留的表名列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
新的DatabaseSchema对象
|
||||||
|
"""
|
||||||
|
filtered_tables = [t for t in self.get_tables() if t.name in table_names]
|
||||||
|
return DatabaseSchema(
|
||||||
|
name=f"{self.schema.name}_filtered",
|
||||||
|
tables=filtered_tables,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 向量索引支持 ==========
|
||||||
|
|
||||||
|
def set_embeddings(self, table_embeddings: Dict[str, List[float]]):
|
||||||
|
"""
|
||||||
|
为表设置预计算的embedding向量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table_embeddings: {table_name: embedding_vector}
|
||||||
|
"""
|
||||||
|
for table in self.get_tables():
|
||||||
|
if table.name in table_embeddings:
|
||||||
|
table.embedding = table_embeddings[table.name]
|
||||||
|
|
||||||
|
def get_table_for_embedding(
|
||||||
|
self,
|
||||||
|
max_columns: int = 48,
|
||||||
|
max_col_comment_chars: int = 160,
|
||||||
|
max_total_chars: int = 8000,
|
||||||
|
) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
获取用于embedding的表信息列表(单条文本同时承载 meta + structure 的可检索信息)。
|
||||||
|
|
||||||
|
- 表级:表名、表注释(来自 table_meta 合并后的 Table.comment)
|
||||||
|
- 列级:字段名、类型、主键标记、列注释(来自 table_structure 解析后的 Column)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
[{"name": "...", "text": "..."}, ...]
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for table in self.get_tables():
|
||||||
|
text_parts = [f"表名: {table.name}"]
|
||||||
|
if table.comment:
|
||||||
|
text_parts.append(f"描述: {table.comment}")
|
||||||
|
|
||||||
|
col_segments: List[str] = []
|
||||||
|
shown = table.columns[:max_columns]
|
||||||
|
for col in shown:
|
||||||
|
seg = f"{col.name} {col.data_type}"
|
||||||
|
if col.is_primary_key:
|
||||||
|
seg += " PK"
|
||||||
|
if col.comment:
|
||||||
|
c = col.comment.strip().replace("\n", " ")
|
||||||
|
if len(c) > max_col_comment_chars:
|
||||||
|
c = c[: max_col_comment_chars - 1] + "…"
|
||||||
|
seg += f" — {c}"
|
||||||
|
col_segments.append(seg)
|
||||||
|
|
||||||
|
if col_segments:
|
||||||
|
text_parts.append("字段: " + ";".join(col_segments))
|
||||||
|
if len(table.columns) > max_columns:
|
||||||
|
rest = len(table.columns) - max_columns
|
||||||
|
text_parts.append(f"另有{rest}个字段未列出(共{len(table.columns)}列)")
|
||||||
|
|
||||||
|
text = "。".join(text_parts)
|
||||||
|
if len(text) > max_total_chars:
|
||||||
|
text = text[: max_total_chars - 1] + "…"
|
||||||
|
|
||||||
|
result.append({
|
||||||
|
"name": table.name,
|
||||||
|
"text": text,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
# ========== 缓存支持 ==========
|
||||||
|
|
||||||
|
def get_cached_result(self, key: str) -> Optional[str]:
|
||||||
|
"""获取缓存结果"""
|
||||||
|
return self._cache.get(key)
|
||||||
|
|
||||||
|
def set_cached_result(self, key: str, value: str, ttl: int = 3600):
|
||||||
|
"""设置缓存结果"""
|
||||||
|
self._cache[key] = value
|
||||||
|
# TODO: 可扩展为Redis缓存
|
||||||
|
|
||||||
|
# ========== 统计信息 ==========
|
||||||
|
|
||||||
|
def get_statistics(self) -> Dict:
|
||||||
|
"""获取Schema统计信息"""
|
||||||
|
total_columns = sum(len(t.columns) for t in self.get_tables())
|
||||||
|
tables_with_pk = sum(1 for t in self.get_tables() if t.primary_keys)
|
||||||
|
tables_with_fk = sum(1 for t in self.get_tables() if t.foreign_keys)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"database": self.schema.name,
|
||||||
|
"total_tables": len(self.get_tables()),
|
||||||
|
"total_columns": total_columns,
|
||||||
|
"avg_columns_per_table": total_columns / len(self.get_tables()) if self.get_tables() else 0,
|
||||||
|
"tables_with_primary_key": tables_with_pk,
|
||||||
|
"tables_with_foreign_key": tables_with_fk,
|
||||||
|
"table_names": self.list_tables(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.get_tables())
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"SchemaManager(database={self.schema.name}, tables={len(self)})"
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
Schema数据模型定义
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Column:
|
||||||
|
"""列字段定义"""
|
||||||
|
name: str
|
||||||
|
data_type: str
|
||||||
|
comment: Optional[str] = None
|
||||||
|
nullable: bool = True
|
||||||
|
is_primary_key: bool = False
|
||||||
|
is_foreign_key: bool = False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
null_str = "NULL" if self.nullable else "NOT NULL"
|
||||||
|
pk_str = " PK" if self.is_primary_key else ""
|
||||||
|
comment_str = f" -- {self.comment}" if self.comment else ""
|
||||||
|
return f"{self.name} {self.data_type}{null_str}{pk_str}{comment_str}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ForeignKey:
|
||||||
|
"""外键关系"""
|
||||||
|
columns: List[str] # 本表字段
|
||||||
|
ref_table: str # 引用表
|
||||||
|
ref_columns: List[str] # 引用字段
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Table:
|
||||||
|
"""数据表定义"""
|
||||||
|
name: str
|
||||||
|
comment: Optional[str] = None
|
||||||
|
columns: List[Column] = field(default_factory=list)
|
||||||
|
primary_keys: List[str] = field(default_factory=list)
|
||||||
|
foreign_keys: List[ForeignKey] = field(default_factory=list)
|
||||||
|
# 向量embedding(可选,用于检索)
|
||||||
|
embedding: Optional[Any] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def column_dict(self) -> Dict[str, Column]:
|
||||||
|
"""字段名到Column对象的映射"""
|
||||||
|
return {col.name: col for col in self.columns}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def column_names(self) -> List[str]:
|
||||||
|
"""所有字段名列表"""
|
||||||
|
return [col.name for col in self.columns]
|
||||||
|
|
||||||
|
def to_compact_string(self, max_columns: int = 20) -> str:
|
||||||
|
"""
|
||||||
|
生成紧凑的Schema字符串(用于LLM输入)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_columns: 最大字段数,超过时截断
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
紧凑的Schema描述字符串
|
||||||
|
"""
|
||||||
|
parts = [f"TABLE {self.name} ("]
|
||||||
|
|
||||||
|
# 只显示关键字段(主键、常见字段)
|
||||||
|
display_columns = self.columns[:max_columns] if len(self.columns) > max_columns else self.columns
|
||||||
|
|
||||||
|
col_strs = []
|
||||||
|
for col in display_columns:
|
||||||
|
col_str = f" {col.name}: {col.data_type}"
|
||||||
|
if col.is_primary_key:
|
||||||
|
col_str += " [PK]"
|
||||||
|
if col.comment:
|
||||||
|
col_str += f" -- {col.comment}"
|
||||||
|
col_strs.append(col_str)
|
||||||
|
|
||||||
|
parts.append(",\n".join(col_strs))
|
||||||
|
|
||||||
|
if len(self.columns) > max_columns:
|
||||||
|
parts.append(f"\n -- 还有 {len(self.columns) - max_columns} 个字段未显示")
|
||||||
|
|
||||||
|
parts.append(")")
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict:
|
||||||
|
"""转换为字典格式"""
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"comment": self.comment,
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": col.name,
|
||||||
|
"data_type": col.data_type,
|
||||||
|
"comment": col.comment,
|
||||||
|
"nullable": col.nullable,
|
||||||
|
"is_primary_key": col.is_primary_key,
|
||||||
|
}
|
||||||
|
for col in self.columns
|
||||||
|
],
|
||||||
|
"primary_keys": self.primary_keys,
|
||||||
|
"foreign_keys": [
|
||||||
|
{
|
||||||
|
"columns": fk.columns,
|
||||||
|
"ref_table": fk.ref_table,
|
||||||
|
"ref_columns": fk.ref_columns,
|
||||||
|
}
|
||||||
|
for fk in self.foreign_keys
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DatabaseSchema:
|
||||||
|
"""数据库Schema(多个表的集合)"""
|
||||||
|
name: str
|
||||||
|
tables: List[Table] = field(default_factory=list)
|
||||||
|
created_at: datetime = field(default_factory=datetime.now)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def table_dict(self) -> Dict[str, Table]:
|
||||||
|
"""表名字典"""
|
||||||
|
return {tbl.name: tbl for tbl in self.tables}
|
||||||
|
|
||||||
|
def get_table(self, table_name: str) -> Optional[Table]:
|
||||||
|
"""获取指定表"""
|
||||||
|
return self.table_dict.get(table_name)
|
||||||
|
|
||||||
|
def to_summary_string(self, include_columns: bool = True) -> str:
|
||||||
|
"""
|
||||||
|
生成Schema摘要字符串(用于LLM输入)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
include_columns: 是否包含字段信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Schema描述字符串
|
||||||
|
"""
|
||||||
|
lines = [f"数据库: {self.name}\n"]
|
||||||
|
lines.append(f"表总数: {len(self.tables)}\n")
|
||||||
|
lines.append("=" * 60 + "\n")
|
||||||
|
|
||||||
|
for table in self.tables:
|
||||||
|
lines.append(f"表名: {table.name}")
|
||||||
|
if table.comment:
|
||||||
|
lines.append(f"描述: {table.comment}")
|
||||||
|
lines.append(f"字段数: {len(table.columns)}")
|
||||||
|
|
||||||
|
if include_columns and table.columns:
|
||||||
|
lines.append("字段列表:")
|
||||||
|
for col in table.columns[:30]: # 限制字段数
|
||||||
|
pk_marker = " [PK]" if col.is_primary_key else ""
|
||||||
|
null_marker = " NULL" if col.nullable else " NOT NULL"
|
||||||
|
comment = f" -- {col.comment}" if col.comment else ""
|
||||||
|
lines.append(f" {col.name}: {col.data_type}{pk_marker}{null_marker}{comment}")
|
||||||
|
|
||||||
|
if len(table.columns) > 30:
|
||||||
|
lines.append(f" ... 还有 {len(table.columns) - 30} 个字段")
|
||||||
|
|
||||||
|
# 外键关系
|
||||||
|
if table.foreign_keys:
|
||||||
|
lines.append("外键关系:")
|
||||||
|
for fk in table.foreign_keys[:5]:
|
||||||
|
lines.append(
|
||||||
|
f" {', '.join(fk.columns)} -> {fk.ref_table}({', '.join(fk.ref_columns)})"
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.append("") # 空行分隔
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def to_compact_dict(self) -> Dict:
|
||||||
|
"""紧凑字典格式(用于序列化)"""
|
||||||
|
return {
|
||||||
|
"database": self.name,
|
||||||
|
"tables": [tbl.to_dict() for tbl in self.tables],
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
"""
|
||||||
|
Text2SQL Few-shot 集成示例
|
||||||
|
|
||||||
|
展示如何在现有系统中集成经验数据集few-shot功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
def enhance_orchestrator_with_fewshot(orchestrator_class):
|
||||||
|
"""
|
||||||
|
装饰器:为Text2SQLOrchestrator添加few-shot功能
|
||||||
|
|
||||||
|
用法:
|
||||||
|
from agents.orchestrator import Text2SQLOrchestrator
|
||||||
|
EnhancedOrchestrator = enhance_orchestrator_with_fewshot(Text2SQLOrchestrator)
|
||||||
|
orch = EnhancedOrchestrator(..., fewshot_enabled=True, fewshot_top_k=3)
|
||||||
|
"""
|
||||||
|
class FewShotEnhancedOrchestrator(orchestrator_class):
|
||||||
|
"""增强版Orchestrator,支持few-shot"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
fewshot_enabled: bool = True,
|
||||||
|
fewshot_samples_path: Optional[str] = None,
|
||||||
|
fewshot_top_k: int = 3,
|
||||||
|
fewshot_min_rating: int = 7,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.fewshot_enabled = fewshot_enabled
|
||||||
|
self.fewshot_top_k = fewshot_top_k
|
||||||
|
self.fewshot_min_rating = fewshot_min_rating
|
||||||
|
self.fewshot_selector = None
|
||||||
|
|
||||||
|
if fewshot_enabled:
|
||||||
|
from utils.fewshot_selector import FewShotSelector
|
||||||
|
path = fewshot_samples_path or "data/experiences/all_samples.jsonl"
|
||||||
|
self.fewshot_selector = FewShotSelector(path)
|
||||||
|
print(f"✅ Few-shot已启用: top_k={fewshot_top_k}, min_rating={fewshot_min_rating}")
|
||||||
|
|
||||||
|
def _generate_sql_with_fewshot(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
schema_str: str,
|
||||||
|
dialect: str = "tsql"
|
||||||
|
) -> str:
|
||||||
|
"""带few-shot的SQL生成"""
|
||||||
|
from llm.deepseek_client import DeepSeekConfig
|
||||||
|
|
||||||
|
# 获取few-shot示例
|
||||||
|
examples_prompt = ""
|
||||||
|
if self.fewshot_selector:
|
||||||
|
examples = self.fewshot_selector.select(
|
||||||
|
question=question,
|
||||||
|
top_k=self.fewshot_top_k,
|
||||||
|
min_rating=self.fewshot_min_rating,
|
||||||
|
exclude_qids=[] # 可排除当前问题(如果已存在)
|
||||||
|
)
|
||||||
|
if examples:
|
||||||
|
examples_prompt = "\n\n".join([
|
||||||
|
f"示例 {i+1}:\n问题:{ex.question_zh}\nSQL:\n{ex.sql}"
|
||||||
|
for i, ex in enumerate(examples)
|
||||||
|
])
|
||||||
|
examples_prompt = f"参考以下相似示例:\n\n{examples_prompt}\n\n"
|
||||||
|
|
||||||
|
# 构造增强的Prompt
|
||||||
|
from config.prompts import SQL_GENERATOR_SYSTEM, SQL_GENERATOR_USER
|
||||||
|
|
||||||
|
user_content = f"""{examples_prompt}
|
||||||
|
当前问题:{question}
|
||||||
|
|
||||||
|
Schema信息:
|
||||||
|
{schema_str}
|
||||||
|
|
||||||
|
数据库方言:{dialect}
|
||||||
|
|
||||||
|
请参考上述示例的SQL编写风格,生成**有用 SQL**:
|
||||||
|
- 与示例保持相同的版式(大写关键字、4空格缩进、PascalCase别名)
|
||||||
|
- 根据当前Schema选择正确的表和字段
|
||||||
|
- 输出纯SQL,不要其他内容"""
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": SQL_GENERATOR_SYSTEM},
|
||||||
|
{"role": "user", "content": user_content}
|
||||||
|
]
|
||||||
|
|
||||||
|
# 调用LLM
|
||||||
|
config = DeepSeekConfig(
|
||||||
|
api_key=self.deepseek.config.api_key,
|
||||||
|
base_url=self.deepseek.config.base_url,
|
||||||
|
model_name=self.deepseek.config.model_name,
|
||||||
|
temperature=0.3,
|
||||||
|
max_tokens=4096
|
||||||
|
)
|
||||||
|
|
||||||
|
from llm.deepseek_client import DeepSeekClient
|
||||||
|
client = DeepSeekClient(config)
|
||||||
|
response = client.chat(messages)
|
||||||
|
sql = response.content.strip()
|
||||||
|
|
||||||
|
# 清理代码块
|
||||||
|
if "```sql" in sql:
|
||||||
|
sql = sql[sql.find("```sql")+6:sql.find("```", sql.find("```sql")+6)].strip()
|
||||||
|
elif "```" in sql:
|
||||||
|
sql = sql[sql.find("```")+3:sql.find("```", sql.find("```")+3)].strip()
|
||||||
|
|
||||||
|
from utils.sql_parser import normalize_sql_for_dialect
|
||||||
|
return normalize_sql_for_dialect(sql, dialect)
|
||||||
|
|
||||||
|
# 重写 _generate_sql 方法
|
||||||
|
def _generate_sql(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
schema_str: str,
|
||||||
|
dialect: str = "tsql"
|
||||||
|
) -> str:
|
||||||
|
if self.fewshot_enabled and self.fewshot_selector:
|
||||||
|
return self._generate_sql_with_fewshot(question, schema_str, dialect)
|
||||||
|
else:
|
||||||
|
return super()._generate_sql(question, schema_str, dialect)
|
||||||
|
|
||||||
|
return FewShotEnhancedOrchestrator
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 集成步骤 ==========
|
||||||
|
|
||||||
|
def integrate_fewshot():
|
||||||
|
"""
|
||||||
|
集成few-shot功能的完整步骤
|
||||||
|
"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("Few-shot 集成指南")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
steps = """
|
||||||
|
**步骤1:确保经验数据集已生成**
|
||||||
|
```bash
|
||||||
|
cd text2sql_agent_camel
|
||||||
|
python scripts/parse_examples.py
|
||||||
|
```
|
||||||
|
生成: data/experiences/all_samples.jsonl
|
||||||
|
|
||||||
|
**步骤2:修改 agents/orchestrator.py**
|
||||||
|
|
||||||
|
在文件开头添加:
|
||||||
|
```python
|
||||||
|
from utils.fewshot_selector import FewShotSelector
|
||||||
|
```
|
||||||
|
|
||||||
|
修改 Text2SQLOrchestrator.__init__:
|
||||||
|
```python
|
||||||
|
def __init__(self, ..., fewshot_enabled=True, fewshot_top_k=3, ...):
|
||||||
|
# ... 原有代码 ...
|
||||||
|
self.fewshot_enabled = fewshot_enabled
|
||||||
|
self.fewshot_top_k = fewshot_top_k
|
||||||
|
self.fewshot_selector = None
|
||||||
|
|
||||||
|
if fewshot_enabled:
|
||||||
|
self.fewshot_selector = FewShotSelector(
|
||||||
|
os.getenv("FEWSHOT_DATA_PATH", "data/experiences/all_samples.jsonl")
|
||||||
|
)
|
||||||
|
logger.info(f"Few-shot已启用: top_k={fewshot_top_k}")
|
||||||
|
```
|
||||||
|
|
||||||
|
修改 _generate_sql 方法(约第251行):
|
||||||
|
```python
|
||||||
|
def _generate_sql(self, question: str, schema_str: str, dialect: str = "tsql") -> str:
|
||||||
|
# 添加few-shot增强
|
||||||
|
if self.fewshot_enabled and self.fewshot_selector:
|
||||||
|
examples = self.fewshot_selector.select(
|
||||||
|
question=question,
|
||||||
|
top_k=self.fewshot_top_k,
|
||||||
|
min_rating=7,
|
||||||
|
exclude_qids=[] # 可选:排除当前问题对应的QID
|
||||||
|
)
|
||||||
|
if examples:
|
||||||
|
examples_prompt = "\\n\\n".join([
|
||||||
|
f"示例({ex.qid}):\\n问题:{ex.question_zh}\\nSQL:\\n{ex.sql}"
|
||||||
|
for ex in examples
|
||||||
|
])
|
||||||
|
# 将examples_prompt注入到用户消息中
|
||||||
|
# (需要调整 deepseek_client 调用逻辑)
|
||||||
|
# ... 原有生成逻辑 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤3:配置环境变量(可选)**
|
||||||
|
```bash
|
||||||
|
# .env 文件添加
|
||||||
|
FEWSHOT_ENABLED=true
|
||||||
|
FEWSHOT_TOP_K=3
|
||||||
|
FEWSHOT_MIN_RATING=7
|
||||||
|
FEWSHOT_DATA_PATH=./data/experiences/all_samples.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤4:测试效果**
|
||||||
|
```bash
|
||||||
|
# 对比测试
|
||||||
|
python main.py "查询2024年1月的销售额" --verbose
|
||||||
|
# 观察日志中的 "Few-shot已选择: Q31, Q6, Q44"
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤5:A/B测试评估**
|
||||||
|
```python
|
||||||
|
# 创建对比脚本
|
||||||
|
from agents.orchestrator import Text2SQLOrchestrator
|
||||||
|
|
||||||
|
# 无few-shot
|
||||||
|
orch_baseline = Text2SQLOrchestrator(..., fewshot_enabled=False)
|
||||||
|
result1 = orch_baseline.generate(question)
|
||||||
|
|
||||||
|
# 有few-shot
|
||||||
|
orch_fewshot = Text2SQLOrchestrator(..., fewshot_enabled=True, fewshot_top_k=3)
|
||||||
|
result2 = orch_fewshot.generate(question)
|
||||||
|
|
||||||
|
print(f"Baseline: {result1.sql[:100]}")
|
||||||
|
print(f"Few-shot: {result2.sql[:100]}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
print(steps)
|
||||||
|
|
||||||
|
|
||||||
|
def quick_test_fewshot():
|
||||||
|
"""快速测试few-shot选择器"""
|
||||||
|
from utils.fewshot_selector import FewShotSelector
|
||||||
|
|
||||||
|
selector = FewShotSelector("data/experiences/all_samples.jsonl")
|
||||||
|
|
||||||
|
test_questions = [
|
||||||
|
"查询2024年1月的销售额",
|
||||||
|
"统计所有活跃账户数量",
|
||||||
|
"按对手方列出未结算交易",
|
||||||
|
"查询今天的现金余额",
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\nFew-shot选择测试:\n")
|
||||||
|
for q in test_questions:
|
||||||
|
print(f"问题: {q}")
|
||||||
|
examples = selector.select(q, top_k=2, min_rating=8)
|
||||||
|
for ex in examples:
|
||||||
|
print(f" [{ex.qid}] {ex.question_zh[:40]}... (rating={ex.rating})")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
integrate_fewshot()
|
||||||
|
# quick_test_fewshot()
|
||||||
@@ -0,0 +1,439 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
将 Example_text2sql.md 解析为结构化数据集
|
||||||
|
|
||||||
|
输出格式:
|
||||||
|
1. JSONL格式(每行一个样本)
|
||||||
|
2. 按难度/类别分类
|
||||||
|
3. 包含Schema、SQL、执行结果、评分
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional, Any
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Text2SQLSample:
|
||||||
|
"""单条Text2SQL样本"""
|
||||||
|
qid: str # 问题ID,如 "Q1"
|
||||||
|
question_zh: str # 中文问题
|
||||||
|
sql: str # SQL语句
|
||||||
|
schema_info: Dict[str, Any] # Schema信息(表、字段)
|
||||||
|
explanation: str = "" # SQL理由
|
||||||
|
question_en: Optional[str] = None # 英文问题(如有)
|
||||||
|
rating: Optional[int] = None # 准确度评分 1-10
|
||||||
|
execution_result: Optional[List[Dict]] = None # 执行结果(表格)
|
||||||
|
result_note: str = "" # 结果说明(如"当前查询无结果")
|
||||||
|
tags: List[str] = None # 标签(如["聚合", "JOIN", "日期筛选"])
|
||||||
|
difficulty: str = "medium" # 难度:easy/medium/hard
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.tags is None:
|
||||||
|
self.tags = []
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
def to_fewshot_example(self) -> str:
|
||||||
|
"""转换为few-shot示例字符串"""
|
||||||
|
return f"""问题:{self.question_zh}
|
||||||
|
Schema:{json.dumps(self.schema_info, ensure_ascii=False)[:500]}
|
||||||
|
SQL:
|
||||||
|
{self.sql}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleText2SQLParser:
|
||||||
|
"""解析 Example_text2sql.md 文件"""
|
||||||
|
|
||||||
|
def __init__(self, md_path: str):
|
||||||
|
self.md_path = Path(md_path)
|
||||||
|
self.content = self.md_path.read_text(encoding='utf-8')
|
||||||
|
self.samples: List[Text2SQLSample] = []
|
||||||
|
|
||||||
|
def parse(self) -> List[Text2SQLSample]:
|
||||||
|
"""解析整个MD文件"""
|
||||||
|
# 按问题分割(## Qxx. 标题)
|
||||||
|
q_blocks = re.split(r'\n## Q(\d+)\. ', self.content)
|
||||||
|
|
||||||
|
# q_blocks[0] 是文件头(目录、固定日期说明等),跳过
|
||||||
|
for i in range(1, len(q_blocks)):
|
||||||
|
qid_num = q_blocks[i].split('\n')[0].strip() # 从分割中获取问题号
|
||||||
|
# 实际qid应为 "Q{num}"
|
||||||
|
# 重新用正则匹配完整的Q标题
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 更稳健的方法:直接用正则查找所有Q标题块
|
||||||
|
pattern = r'(## Q(\d+)\. .*?)(?=\n## Q\d+\. |\Z)'
|
||||||
|
matches = re.findall(pattern, self.content, re.DOTALL)
|
||||||
|
|
||||||
|
print(f"找到 {len(matches)} 个问答对")
|
||||||
|
|
||||||
|
for full_match, qid in matches:
|
||||||
|
sample = self._parse_single_block(qid, full_match)
|
||||||
|
if sample:
|
||||||
|
self.samples.append(sample)
|
||||||
|
|
||||||
|
return self.samples
|
||||||
|
|
||||||
|
def _parse_single_block(self, qid: str, block: str) -> Optional[Text2SQLSample]:
|
||||||
|
"""解析单个问答块"""
|
||||||
|
try:
|
||||||
|
lines = block.strip().split('\n')
|
||||||
|
|
||||||
|
# 提取问题(支持中英文双语)
|
||||||
|
# 格式:### 问题 后跟英文段落,空行,然后中文段落
|
||||||
|
question_zh = ""
|
||||||
|
question_en = ""
|
||||||
|
found_question_header = False
|
||||||
|
paragraph_lines = []
|
||||||
|
paragraphs = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('### 问题'):
|
||||||
|
found_question_header = True
|
||||||
|
continue
|
||||||
|
if not found_question_header:
|
||||||
|
continue
|
||||||
|
if line.startswith('###'):
|
||||||
|
# 遇到下一节,停止
|
||||||
|
break
|
||||||
|
if line.strip() == '':
|
||||||
|
# 空行:段落结束
|
||||||
|
if paragraph_lines:
|
||||||
|
paragraphs.append(' '.join(paragraph_lines).strip())
|
||||||
|
paragraph_lines = []
|
||||||
|
else:
|
||||||
|
paragraph_lines.append(line.strip())
|
||||||
|
|
||||||
|
# 最后一个段落
|
||||||
|
if paragraph_lines:
|
||||||
|
paragraphs.append(' '.join(paragraph_lines).strip())
|
||||||
|
|
||||||
|
# 第一段为英文,第二段为中文
|
||||||
|
if len(paragraphs) >= 1:
|
||||||
|
question_en = paragraphs[0]
|
||||||
|
if len(paragraphs) >= 2:
|
||||||
|
question_zh = paragraphs[1]
|
||||||
|
|
||||||
|
# 提取SQL
|
||||||
|
sql_match = re.search(r'```sql\n(.*?)\n```', block, re.DOTALL)
|
||||||
|
if not sql_match:
|
||||||
|
return None
|
||||||
|
sql = sql_match.group(1).strip()
|
||||||
|
|
||||||
|
# 提取SQL理由(### SQL理由 后面的内容)
|
||||||
|
reason_match = re.search(r'### SQL理由\n\n(.*?)(?=\n### |\Z)', block, re.DOTALL)
|
||||||
|
explanation = reason_match.group(1).strip() if reason_match else ""
|
||||||
|
|
||||||
|
# 提取评分(### SQL准确度评分)
|
||||||
|
rating_match = re.search(r'\*\*评分:\s*(\d+)/10\*\*', block)
|
||||||
|
rating = int(rating_match.group(1)) if rating_match else None
|
||||||
|
|
||||||
|
# 提取结果说明(### SQL执行结果 后面)
|
||||||
|
result_match = re.search(r'### SQL执行结果\n\n(.*?)(?=\n### |\Z)', block, re.DOTALL)
|
||||||
|
result_note = result_match.group(1).strip() if result_match else ""
|
||||||
|
|
||||||
|
# 推断标签
|
||||||
|
tags = self._infer_tags(question_zh, sql)
|
||||||
|
|
||||||
|
# 推断难度
|
||||||
|
difficulty = self._infer_difficulty(sql, tags)
|
||||||
|
|
||||||
|
# 构建Schema信息(从SQL中提取的表和字段)
|
||||||
|
schema_info = self._extract_schema_from_sql(sql)
|
||||||
|
|
||||||
|
return Text2SQLSample(
|
||||||
|
qid=f"Q{qid}",
|
||||||
|
question_zh=question_zh.strip(),
|
||||||
|
question_en=question_en.strip() if question_en else None,
|
||||||
|
sql=sql,
|
||||||
|
schema_info=schema_info,
|
||||||
|
explanation=explanation,
|
||||||
|
rating=rating,
|
||||||
|
result_note=result_note,
|
||||||
|
tags=tags,
|
||||||
|
difficulty=difficulty
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"解析 {qid} 失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _infer_tags(self, question: str, sql: str) -> List[str]:
|
||||||
|
"""推断问题标签"""
|
||||||
|
tags = []
|
||||||
|
q_lower = question.lower()
|
||||||
|
sql_upper = sql.upper()
|
||||||
|
|
||||||
|
# 按业务域
|
||||||
|
if any(k in q_lower for k in ['对手方', 'broker', '经纪商']):
|
||||||
|
tags.append('broker')
|
||||||
|
if any(k in q_lower for k in ['账户', 'account', '客户']):
|
||||||
|
tags.append('account')
|
||||||
|
if any(k in q_lower for k in ['现金', 'cash', '余额']):
|
||||||
|
tags.append('cash')
|
||||||
|
if any(k in q_lower for k in ['持仓', 'holding', 'position']):
|
||||||
|
tags.append('holding')
|
||||||
|
if any(k in q_lower for k in ['交易', 'trade', 'commission']):
|
||||||
|
tags.append('trade')
|
||||||
|
if any(k in q_lower for k in ['IPO', '认购', 'subscription']):
|
||||||
|
tags.append('ipo')
|
||||||
|
if any(k in q_lower for k in ['公司行动', 'corporate action', '权益']):
|
||||||
|
tags.append('corporate_action')
|
||||||
|
if any(k in q_lower for k in ['汇率', 'exchange rate']):
|
||||||
|
tags.append('exchange_rate')
|
||||||
|
if any(k in q_lower for k in ['保证金', 'margin']):
|
||||||
|
tags.append('margin')
|
||||||
|
if any(k in q_lower for k in ['利息', 'interest']):
|
||||||
|
tags.append('interest')
|
||||||
|
if any(k in q_lower for k in ['报表', 'statement']):
|
||||||
|
tags.append('statement')
|
||||||
|
|
||||||
|
# 按SQL特征
|
||||||
|
if 'GROUP BY' in sql_upper:
|
||||||
|
tags.append('aggregation')
|
||||||
|
if 'JOIN' in sql_upper:
|
||||||
|
tags.append('join')
|
||||||
|
if 'WHERE' in sql_upper:
|
||||||
|
tags.append('filter')
|
||||||
|
if re.search(r'DATE.*BETWEEN|>=.*AND', sql_upper) or 'BETWEEN' in sql_upper:
|
||||||
|
tags.append('date_range')
|
||||||
|
if re.search(r'\bCOUNT\b|\bSUM\b|\bAVG\b|\bMAX\b|\bMIN\b', sql_upper):
|
||||||
|
tags.append('aggregation')
|
||||||
|
if 'ORDER BY' in sql_upper:
|
||||||
|
tags.append('ordering')
|
||||||
|
if 'TOP' in sql_upper or 'LIMIT' in sql_upper:
|
||||||
|
tags.append('limit')
|
||||||
|
if 'LEFT JOIN' in sql_upper:
|
||||||
|
tags.append('left_join')
|
||||||
|
if 'UNION' in sql_upper:
|
||||||
|
tags.append('union')
|
||||||
|
if re.search(r'DATEADD|DATEDIFF|CAST.*GETDATE', sql_upper):
|
||||||
|
tags.append('date_function')
|
||||||
|
|
||||||
|
return list(set(tags))
|
||||||
|
|
||||||
|
def _infer_difficulty(self, sql: str, tags: List[str]) -> str:
|
||||||
|
"""推断难度"""
|
||||||
|
sql_upper = sql.upper()
|
||||||
|
|
||||||
|
# 简单规则
|
||||||
|
if 'UNION' in sql_upper or 'EXCEPT' in sql_upper or 'INTERSECT' in sql_upper:
|
||||||
|
return 'hard'
|
||||||
|
if sql.count('JOIN') >= 3:
|
||||||
|
return 'hard'
|
||||||
|
if 'GROUP BY' in sql_upper and 'HAVING' in sql_upper:
|
||||||
|
return 'hard'
|
||||||
|
if re.search(r'SUBQUERY|EXISTS.*SELECT', sql_upper, re.IGNORECASE):
|
||||||
|
return 'hard'
|
||||||
|
if len(tags) >= 5:
|
||||||
|
return 'hard'
|
||||||
|
if sql.count('JOIN') == 2 or 'GROUP BY' in sql_upper:
|
||||||
|
return 'medium'
|
||||||
|
return 'easy'
|
||||||
|
|
||||||
|
def _extract_schema_from_sql(self, sql: str) -> Dict[str, Any]:
|
||||||
|
"""从SQL中提取表结构信息"""
|
||||||
|
import sqlglot
|
||||||
|
from sqlglot import exp
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
"tables": {},
|
||||||
|
"joins": []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = sqlglot.parse_one(sql, dialect='tsql')
|
||||||
|
|
||||||
|
# 提取所有表
|
||||||
|
for table in parsed.find_all(exp.Table):
|
||||||
|
table_name = table.name
|
||||||
|
alias = table.alias_or_name
|
||||||
|
schema["tables"][table_name] = {
|
||||||
|
"alias": alias,
|
||||||
|
"columns": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 提取所有列引用
|
||||||
|
for col in parsed.find_all(exp.Column):
|
||||||
|
table_name = col.table
|
||||||
|
col_name = col.name
|
||||||
|
if table_name in schema["tables"]:
|
||||||
|
if col_name not in schema["tables"][table_name]["columns"]:
|
||||||
|
schema["tables"][table_name]["columns"].append(col_name)
|
||||||
|
|
||||||
|
# 提取JOIN关系
|
||||||
|
for join in parsed.find_all(exp.Join):
|
||||||
|
schema["joins"].append({
|
||||||
|
"type": join.args.get('side', 'INNER'),
|
||||||
|
"on": str(join.args.get('on'))
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"SQL解析失败: {e}")
|
||||||
|
|
||||||
|
return schema
|
||||||
|
|
||||||
|
def save_jsonl(self, output_path: Path):
|
||||||
|
"""保存为JSONL格式"""
|
||||||
|
with output_path.open('w', encoding='utf-8') as f:
|
||||||
|
for sample in self.samples:
|
||||||
|
f.write(json.dumps(sample.to_dict(), ensure_ascii=False) + '\n')
|
||||||
|
print(f"[OK] 保存 {len(self.samples)} 条样本到 {output_path}")
|
||||||
|
|
||||||
|
def save_fewshot_prompt(self, output_path: Path, limit_per_tag: int = 2):
|
||||||
|
"""生成few-shot提示文件"""
|
||||||
|
examples_by_tag = {}
|
||||||
|
for sample in self.samples:
|
||||||
|
for tag in sample.tags:
|
||||||
|
if tag not in examples_by_tag:
|
||||||
|
examples_by_tag[tag] = []
|
||||||
|
if len(examples_by_tag[tag]) < limit_per_tag:
|
||||||
|
examples_by_tag[tag].append(sample)
|
||||||
|
|
||||||
|
with output_path.open('w', encoding='utf-8') as f:
|
||||||
|
f.write("# Few-shot Examples for Text2SQL\n\n")
|
||||||
|
f.write("以下是从经验数据集中精选的示例,用于辅助SQL生成。\n\n")
|
||||||
|
|
||||||
|
for tag, samples in sorted(examples_by_tag.items()):
|
||||||
|
f.write(f"## 标签: {tag}\n\n")
|
||||||
|
for i, sample in enumerate(samples, 1):
|
||||||
|
f.write(f"### 示例 {i}\n")
|
||||||
|
f.write(f"**问题**:{sample.question_zh}\n\n")
|
||||||
|
f.write(f"**SQL**:\n```sql\n{sample.sql}\n```\n\n")
|
||||||
|
f.write(f"**说明**:{sample.explanation[:200]}...\n\n")
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
print(f"[OK] Few-shot示例已保存到 {output_path}")
|
||||||
|
|
||||||
|
def save_by_category(self, output_dir: Path):
|
||||||
|
"""按标签分类保存"""
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
by_tag = {}
|
||||||
|
for sample in self.samples:
|
||||||
|
for tag in sample.tags:
|
||||||
|
if tag not in by_tag:
|
||||||
|
by_tag[tag] = []
|
||||||
|
by_tag[tag].append(sample)
|
||||||
|
|
||||||
|
for tag, samples in by_tag.items():
|
||||||
|
tag_file = output_dir / f"{tag}.jsonl"
|
||||||
|
with tag_file.open('w', encoding='utf-8') as f:
|
||||||
|
for sample in samples:
|
||||||
|
f.write(json.dumps(sample.to_dict(), ensure_ascii=False) + '\n')
|
||||||
|
|
||||||
|
print(f"[OK] 按标签分类保存到 {output_dir}({len(by_tag)}个类别)")
|
||||||
|
|
||||||
|
def generate_stats(self) -> Dict:
|
||||||
|
"""生成数据集统计"""
|
||||||
|
stats = {
|
||||||
|
"total_samples": len(self.samples),
|
||||||
|
"by_difficulty": {},
|
||||||
|
"by_tag": {},
|
||||||
|
"by_rating": {},
|
||||||
|
"avg_rating": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 难度分布
|
||||||
|
for diff in ['easy', 'medium', 'hard']:
|
||||||
|
stats["by_difficulty"][diff] = sum(
|
||||||
|
1 for s in self.samples if s.difficulty == diff
|
||||||
|
)
|
||||||
|
|
||||||
|
# 标签分布
|
||||||
|
tag_counts = {}
|
||||||
|
for sample in self.samples:
|
||||||
|
for tag in sample.tags:
|
||||||
|
tag_counts[tag] = tag_counts.get(tag, 0) + 1
|
||||||
|
stats["by_tag"] = tag_counts
|
||||||
|
|
||||||
|
# 评分分布
|
||||||
|
ratings = [s.rating for s in self.samples if s.rating is not None]
|
||||||
|
if ratings:
|
||||||
|
stats["avg_rating"] = sum(ratings) / len(ratings)
|
||||||
|
for r in range(1, 11):
|
||||||
|
stats["by_rating"][r] = ratings.count(r)
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
md_path = Path(__file__).parent.parent / "data" / "Example" / "Example_text2sql.md"
|
||||||
|
output_dir = Path(__file__).parent.parent / "data" / "experiences"
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("Text2SQL 经验数据集构建工具")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
parser = ExampleText2SQLParser(md_path)
|
||||||
|
samples = parser.parse()
|
||||||
|
|
||||||
|
print(f"\n[OK] 解析完成,共 {len(samples)} 个样本")
|
||||||
|
|
||||||
|
# 生成统计
|
||||||
|
stats = parser.generate_stats()
|
||||||
|
print(f"\n[INFO] 数据集统计:")
|
||||||
|
print(f" 总样本数: {stats['total_samples']}")
|
||||||
|
print(f" 难度分布: {stats['by_difficulty']}")
|
||||||
|
print(f" 平均评分: {stats['avg_rating']:.1f}/10")
|
||||||
|
print(f"\n 标签分布(Top 10):")
|
||||||
|
sorted_tags = sorted(stats["by_tag"].items(), key=lambda x: -x[1])[:10]
|
||||||
|
for tag, count in sorted_tags:
|
||||||
|
print(f" {tag}: {count}")
|
||||||
|
|
||||||
|
# 保存输出
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 1. JSONL格式(所有样本)
|
||||||
|
parser.save_jsonl(output_dir / "all_samples.jsonl")
|
||||||
|
|
||||||
|
# 2. 按难度分类
|
||||||
|
for diff in ['easy', 'medium', 'hard']:
|
||||||
|
diff_samples = [s for s in samples if s.difficulty == diff]
|
||||||
|
if diff_samples:
|
||||||
|
with (output_dir / f"difficulty_{diff}.jsonl").open('w', encoding='utf-8') as f:
|
||||||
|
for s in diff_samples:
|
||||||
|
f.write(json.dumps(s.to_dict(), ensure_ascii=False) + '\n')
|
||||||
|
|
||||||
|
# 3. Few-shot提示文件
|
||||||
|
parser.save_fewshot_prompt(output_dir / "fewshot_examples.md")
|
||||||
|
|
||||||
|
# 4. 按标签分类
|
||||||
|
parser.save_by_category(output_dir / "by_tag")
|
||||||
|
|
||||||
|
# 5. 高评分样本(rating >= 8)
|
||||||
|
high_rating = [s for s in samples if s.rating and s.rating >= 8]
|
||||||
|
with (output_dir / "high_rating_samples.jsonl").open('w', encoding='utf-8') as f:
|
||||||
|
for s in high_rating:
|
||||||
|
f.write(json.dumps(s.to_dict(), ensure_ascii=False) + '\n')
|
||||||
|
print(f"\n[OK] 高评分样本(rating>=8): {len(high_rating)} 条")
|
||||||
|
|
||||||
|
# 6. 生成README
|
||||||
|
with (output_dir / "README.md").open('w', encoding='utf-8') as f:
|
||||||
|
f.write("# Text2SQL 经验数据集\n\n")
|
||||||
|
f.write(f"**来源**: `Example_text2sql.md`\n")
|
||||||
|
f.write(f"**样本数**: {len(samples)}\n")
|
||||||
|
f.write(f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||||
|
f.write("## 文件说明\n\n")
|
||||||
|
f.write("- `all_samples.jsonl` - 所有样本(JSONL格式)\n")
|
||||||
|
f.write("- `difficulty_*.jsonl` - 按难度分类\n")
|
||||||
|
f.write("- `by_tag/` - 按标签分类(每个标签一个文件)\n")
|
||||||
|
f.write("- `fewshot_examples.md` - Few-shot示例(Markdown格式)\n")
|
||||||
|
f.write("- `high_rating_samples.jsonl` - 高评分样本(rating≥8)\n\n")
|
||||||
|
f.write("## 使用建议\n\n")
|
||||||
|
f.write("1. **Few-shot prompting**: 使用 `fewshot_examples.md` 中的示例\n")
|
||||||
|
f.write("2. **训练数据**: 使用 `all_samples.jsonl` 进行模型微调\n")
|
||||||
|
f.write("3. **评估测试**: 使用 `high_rating_samples.jsonl` 作为测试集\n")
|
||||||
|
f.write("4. **领域适应**: 按标签选择相关示例(如broker相关查询)\n")
|
||||||
|
|
||||||
|
print(f"\n[OK] 所有文件已保存到: {output_dir}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
自动集成few-shot功能到 Text2SQL 系统
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python scripts/patch_fewshot.py --enable --top-k 3 --min-rating 7
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def patch_orchestrator(
|
||||||
|
enable: bool = True,
|
||||||
|
top_k: int = 3,
|
||||||
|
min_rating: int = 7,
|
||||||
|
data_path: str = "data/experiences/all_samples.jsonl"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
修改 agents/orchestrator.py 添加few-shot支持
|
||||||
|
"""
|
||||||
|
orch_path = Path(__file__).parent.parent / "agents" / "orchestrator.py"
|
||||||
|
|
||||||
|
if not orch_path.exists():
|
||||||
|
print(f"❌ 文件不存在: {orch_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
content = orch_path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
# 1. 在文件头部添加import
|
||||||
|
import_line = "from utils.fewshot_selector import FewShotSelector"
|
||||||
|
if import_line not in content:
|
||||||
|
# 找到合适的插入点(在其他import之后)
|
||||||
|
last_import = content.rfind("import ")
|
||||||
|
if last_import != -1:
|
||||||
|
insert_pos = content.find("\n", last_import) + 1
|
||||||
|
content = content[:insert_pos] + import_line + "\n" + content[insert_pos:]
|
||||||
|
print("✅ 添加 import: from utils.fewshot_selector import FewShotSelector")
|
||||||
|
|
||||||
|
# 2. 修改 __init__ 方法
|
||||||
|
init_pattern = r'def __init__\([\s\S]*?\):'
|
||||||
|
init_match = re.search(init_pattern, content)
|
||||||
|
|
||||||
|
if init_match:
|
||||||
|
init_text = content[init_match.start():init_match.end()]
|
||||||
|
|
||||||
|
# 检查是否已添加few-shot参数
|
||||||
|
if "fewshot_enabled" not in init_text:
|
||||||
|
# 在参数列表末尾添加(在max_retry之后)
|
||||||
|
new_params = """,
|
||||||
|
# Few-shot 配置
|
||||||
|
fewshot_enabled: bool = True,
|
||||||
|
fewshot_samples_path: Optional[str] = None,
|
||||||
|
fewshot_top_k: int = 3,
|
||||||
|
fewshot_min_rating: int = 7,"""
|
||||||
|
|
||||||
|
# 找到max_retry参数后插入
|
||||||
|
max_retry_pos = init_text.find("max_retry:")
|
||||||
|
if max_retry_pos != -1:
|
||||||
|
comma_pos = init_text.find(",", max_retry_pos) + 1
|
||||||
|
new_init = (
|
||||||
|
init_text[:comma_pos] +
|
||||||
|
new_params +
|
||||||
|
init_text[comma_pos:]
|
||||||
|
)
|
||||||
|
content = content[:init_match.start()] + new_init + content[init_match.end():]
|
||||||
|
print("✅ 修改 __init__: 添加few-shot参数")
|
||||||
|
|
||||||
|
# 在__init__方法体内添加few-shot初始化代码
|
||||||
|
# 找到"self.max_retry = max_retry"这一行
|
||||||
|
max_retry_assign = "self.max_retry = max_retry"
|
||||||
|
if max_retry_assign in content[init_match.start():init_match.end()]:
|
||||||
|
init_body_start = init_match.end()
|
||||||
|
init_body = content[init_body_start:init_body_start + 2000] # 读取前2000字符
|
||||||
|
|
||||||
|
if "self.fewshot_selector" not in init_body:
|
||||||
|
fewshot_init = '''
|
||||||
|
|
||||||
|
# Few-shot 初始化
|
||||||
|
self.fewshot_enabled = fewshot_enabled
|
||||||
|
self.fewshot_top_k = fewshot_top_k
|
||||||
|
self.fewshot_min_rating = fewshot_min_rating
|
||||||
|
self.fewshot_selector = None
|
||||||
|
|
||||||
|
if self.fewshot_enabled:
|
||||||
|
try:
|
||||||
|
from utils.fewshot_selector import FewShotSelector
|
||||||
|
path = fewshot_samples_path or os.getenv(
|
||||||
|
"FEWSHOT_DATA_PATH",
|
||||||
|
"./data/experiences/all_samples.jsonl"
|
||||||
|
)
|
||||||
|
self.fewshot_selector = FewShotSelector(path)
|
||||||
|
logger.info(
|
||||||
|
f"Few-shot已启用: top_k={fewshot_top_k}, "
|
||||||
|
f"min_rating={fewshot_min_rating}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Few-shot加载失败: {e},将使用标准生成")
|
||||||
|
self.fewshot_enabled = False
|
||||||
|
'''
|
||||||
|
# 插入到self.max_retry赋值之后
|
||||||
|
insert_pos = content[init_match.start():].find(max_retry_assign)
|
||||||
|
if insert_pos != -1:
|
||||||
|
absolute_pos = init_match.start() + insert_pos + len(max_retry_assign) + 1
|
||||||
|
content = content[:absolute_pos] + fewshot_init + content[absolute_pos:]
|
||||||
|
print("✅ 修改 __init__: 添加few-shot初始化")
|
||||||
|
|
||||||
|
# 3. 修改 _generate_sql 方法
|
||||||
|
generate_pattern = r'def _generate_sql\([\s\S]*?\) -> str:'
|
||||||
|
generate_match = re.search(generate_pattern, content)
|
||||||
|
|
||||||
|
if generate_match:
|
||||||
|
generate_text = content[generate_match.start():generate_match.end()]
|
||||||
|
|
||||||
|
if "if self.fewshot_enabled" not in generate_text:
|
||||||
|
# 在方法开头注入few-shot逻辑
|
||||||
|
fewshot_logic = ''' # Few-shot 增强
|
||||||
|
if self.fewshot_enabled and self.fewshot_selector:
|
||||||
|
try:
|
||||||
|
examples = self.fewshot_selector.select(
|
||||||
|
question=question,
|
||||||
|
top_k=self.fewshot_top_k,
|
||||||
|
min_rating=self.fewshot_min_rating
|
||||||
|
)
|
||||||
|
if examples:
|
||||||
|
examples_prompt = "\\n".join([
|
||||||
|
f"示例 {i+1}:\\n问题:{ex.question_zh}\\nSQL:\\n{ex.sql}"
|
||||||
|
for i, ex in enumerate(examples)
|
||||||
|
])
|
||||||
|
examples_prompt = (
|
||||||
|
f"参考以下相似问题的SQL示例:\\n\\n{examples_prompt}\\n\\n"
|
||||||
|
f"请学习上述示例的SQL编写风格和模式,为当前问题生成SQL。"
|
||||||
|
)
|
||||||
|
# 注入到schema_str前或user_content中
|
||||||
|
# 这里选择在schema_str前添加
|
||||||
|
schema_str = f"{examples_prompt}Schema信息:\\n{schema_str}"
|
||||||
|
logger.debug(f"已注入 {len(examples)} 个few-shot示例")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Few-shot检索失败: {e}")
|
||||||
|
|
||||||
|
'''
|
||||||
|
# 插入到方法开头("dialect_label = dialect" 之前)
|
||||||
|
dialect_pos = content[generate_match.start():].find("dialect_label = dialect")
|
||||||
|
if dialect_pos != -1:
|
||||||
|
absolute_pos = generate_match.start() + dialect_pos
|
||||||
|
content = content[:absolute_pos] + fewshot_logic + content[absolute_pos:]
|
||||||
|
print("✅ 修改 _generate_sql: 注入few-shot逻辑")
|
||||||
|
|
||||||
|
# 保存修改
|
||||||
|
backup_path = orch_path.with_suffix('.py.bak')
|
||||||
|
orch_path.write_text(content, encoding='utf-8')
|
||||||
|
print(f"✅ 已修改 {orch_path}")
|
||||||
|
print(f" 备份文件: {backup_path}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create_env_config(top_k: int, min_rating: int):
|
||||||
|
"""更新.env文件添加few-shot配置"""
|
||||||
|
env_path = Path(__file__).parent.parent / ".env"
|
||||||
|
|
||||||
|
if not env_path.exists():
|
||||||
|
print(f"⚠️ .env文件不存在: {env_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
content = env_path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
additions = f"""
|
||||||
|
# Few-shot 配置
|
||||||
|
FEWSHOT_ENABLED=true
|
||||||
|
FEWSHOT_TOP_K={top_k}
|
||||||
|
FEWSHOT_MIN_RATING={min_rating}
|
||||||
|
FEWSHOT_DATA_PATH=./data/experiences/all_samples.jsonl
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 检查是否已存在
|
||||||
|
if "FEWSHOT_ENABLED" not in content:
|
||||||
|
with env_path.open('a', encoding='utf-8') as f:
|
||||||
|
f.write(additions)
|
||||||
|
print(f"✅ 已添加Few-shot配置到 .env")
|
||||||
|
else:
|
||||||
|
print(f"ℹ️ .env 中已存在FEWSHOT配置,跳过")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="集成Few-shot功能到Text2SQL系统")
|
||||||
|
parser.add_argument("--enable", action="store_true", help="启用few-shot")
|
||||||
|
parser.add_argument("--top-k", type=int, default=3, help="每次使用的示例数量")
|
||||||
|
parser.add_argument("--min-rating", type=int, default=7, help="示例最低评分")
|
||||||
|
parser.add_argument("--env-only", action="store_true", help="仅修改.env")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.enable and not args.env_only:
|
||||||
|
print("请使用 --enable 启用few-shot,或 --env-only 仅修改环境变量")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("Text2SQL Few-shot 集成工具")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
if args.env_only:
|
||||||
|
create_env_config(args.top_k, args.min_rating)
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.enable:
|
||||||
|
print(f"\n配置: top_k={args.top_k}, min_rating={args.min_rating}")
|
||||||
|
|
||||||
|
# 1. 修改orchestrator.py
|
||||||
|
success = patch_orchestrator(
|
||||||
|
enable=True,
|
||||||
|
top_k=args.top_k,
|
||||||
|
min_rating=args.min_rating
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# 2. 更新.env
|
||||||
|
create_env_config(args.top_k, args.min_rating)
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("✅ Few-shot集成完成!")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"\n下一步:")
|
||||||
|
print(f"1. 确保经验数据集已生成:")
|
||||||
|
print(f" python scripts/parse_examples.py")
|
||||||
|
print(f"\n2. 测试系统:")
|
||||||
|
print(f" python main.py \"查询2024年1月的销售额\" --verbose")
|
||||||
|
print(f"\n3. 查看日志中的few-shot选择:")
|
||||||
|
print(f" Few-shot已选择: Q31, Q6, Q44")
|
||||||
|
print(f"\n4. 调整参数:")
|
||||||
|
print(f" 编辑 .env 修改 FEWSHOT_TOP_K 和 FEWSHOT_MIN_RATING")
|
||||||
|
else:
|
||||||
|
print("❌ 集成失败")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# utils 包初始化
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,530 @@
|
|||||||
|
"""
|
||||||
|
Embedding 封装:本地 Qwen3-Embedding,或兼容 OpenAI /v1/embeddings 的远程 API
|
||||||
|
(ModelScope 推理、阿里云 DashScope 等)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Union, List, Optional, Any
|
||||||
|
import numpy as np
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
from transformers import AutoModel, AutoTokenizer
|
||||||
|
import torch
|
||||||
|
_TRANSFORMERS_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
_TRANSFORMERS_AVAILABLE = False
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _RemoteEmbeddingEnv:
|
||||||
|
"""从环境变量解析出的远程 OpenAI-Compatible Embedding 配置。"""
|
||||||
|
|
||||||
|
api_key: str
|
||||||
|
base_url: str
|
||||||
|
model: str
|
||||||
|
max_batch: int
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
|
def _remote_embedding_from_env() -> _RemoteEmbeddingEnv:
|
||||||
|
"""
|
||||||
|
优先 ModelScope(MODELSCOPE_*);未配置时回退 DashScope(DASHSCOPE_*)。
|
||||||
|
"""
|
||||||
|
ms_key = os.getenv("MODELSCOPE_API_KEY", "").strip()
|
||||||
|
ms_base = os.getenv("MODELSCOPE_BASE_URL", "").strip()
|
||||||
|
ds_key = os.getenv("DASHSCOPE_API_KEY", "").strip()
|
||||||
|
ds_base = (
|
||||||
|
os.getenv("DASHSCOPE_BASE_URL") or os.getenv("DASHSCOPE_base_url", "")
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
# 仅当配置了 API Key 时走 ModelScope(避免仅有 BASE_URL 时误判、阻断 DashScope)
|
||||||
|
if ms_key:
|
||||||
|
base_url = ms_base or "https://api-inference.modelscope.cn/v1"
|
||||||
|
model = (
|
||||||
|
os.getenv("MODELSCOPE_EMBEDDING_MODEL")
|
||||||
|
or os.getenv("MODELSCOPE_MODEL", "Qwen/Qwen3-Embedding-8B")
|
||||||
|
).strip()
|
||||||
|
mb = os.getenv("MODELSCOPE_EMBEDDING_MAX_BATCH", "32").strip()
|
||||||
|
max_batch = max(1, int(mb)) if mb.isdigit() else 32
|
||||||
|
if not model:
|
||||||
|
raise ValueError("未配置 MODELSCOPE_EMBEDDING_MODEL(或 MODELSCOPE_MODEL)")
|
||||||
|
return _RemoteEmbeddingEnv(
|
||||||
|
api_key=ms_key,
|
||||||
|
base_url=base_url.rstrip("/"),
|
||||||
|
model=model,
|
||||||
|
max_batch=max_batch,
|
||||||
|
label="ModelScope",
|
||||||
|
)
|
||||||
|
|
||||||
|
# OpenAI 官方或兼容网关:OPENAI_API_KEY、OPENAI_EMBEDDING_MODEL、可选 OPENAI_BASE_URL
|
||||||
|
oa_key = os.getenv("OPENAI_API_KEY", "").strip()
|
||||||
|
if oa_key:
|
||||||
|
if oa_key.startswith("http://") or oa_key.startswith("https://"):
|
||||||
|
raise ValueError(
|
||||||
|
"OPENAI_API_KEY 不能填写为 URL:请将网关地址写到 OPENAI_BASE_URL"
|
||||||
|
"(例如 http://host:9080/v1),密钥单独写在 OPENAI_API_KEY"
|
||||||
|
)
|
||||||
|
oa_base = (
|
||||||
|
os.getenv("OPENAI_BASE_URL", "").strip() or "https://api.openai.com/v1"
|
||||||
|
)
|
||||||
|
oa_model = os.getenv("OPENAI_EMBEDDING_MODEL", "").strip()
|
||||||
|
if not oa_model:
|
||||||
|
raise ValueError(
|
||||||
|
"使用 OpenAI 兼容 Embedding 时请设置 OPENAI_EMBEDDING_MODEL"
|
||||||
|
)
|
||||||
|
mb = os.getenv("OPENAI_EMBEDDING_MAX_BATCH", "100").strip()
|
||||||
|
max_batch = max(1, int(mb)) if mb.isdigit() else 100
|
||||||
|
return _RemoteEmbeddingEnv(
|
||||||
|
api_key=oa_key,
|
||||||
|
base_url=oa_base.rstrip("/"),
|
||||||
|
model=oa_model,
|
||||||
|
max_batch=max_batch,
|
||||||
|
label="OpenAI",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not ds_key:
|
||||||
|
raise ValueError(
|
||||||
|
"远程 Embedding 未配置:请设置 MODELSCOPE_API_KEY(及可选 BASE_URL),"
|
||||||
|
"或 OPENAI_API_KEY / OPENAI_EMBEDDING_MODEL(及可选 OPENAI_BASE_URL),"
|
||||||
|
"或 DASHSCOPE_API_KEY / DASHSCOPE_BASE_URL / DASHSCOPE_MODEL"
|
||||||
|
)
|
||||||
|
if not ds_base:
|
||||||
|
raise ValueError("未配置 DASHSCOPE_BASE_URL(或 DASHSCOPE_base_url)")
|
||||||
|
model = os.getenv("DASHSCOPE_MODEL", "").strip()
|
||||||
|
if not model:
|
||||||
|
raise ValueError("未配置 DASHSCOPE_MODEL")
|
||||||
|
mb = os.getenv("DASHSCOPE_EMBEDDING_MAX_BATCH", "10").strip()
|
||||||
|
max_batch = max(1, int(mb)) if mb.isdigit() else 10
|
||||||
|
return _RemoteEmbeddingEnv(
|
||||||
|
api_key=ds_key,
|
||||||
|
base_url=ds_base.rstrip("/"),
|
||||||
|
model=model,
|
||||||
|
max_batch=max_batch,
|
||||||
|
label="DashScope",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Qwen3Embedding:
|
||||||
|
"""
|
||||||
|
Qwen3-Embedding-0.6B 向量化封装
|
||||||
|
|
||||||
|
使用 Mean Pooling 将token embeddings聚合为句子向量,
|
||||||
|
并进行L2归一化以支持余弦相似度计算。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model_path: Optional[str] = None,
|
||||||
|
device: Optional[str] = None,
|
||||||
|
use_fp16: bool = False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化 embedding 模型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_path: 本地模型路径,若为None则从环境变量或默认路径加载
|
||||||
|
device: 推理设备('cpu', 'cuda', 'cuda:0'等),None则自动选择
|
||||||
|
use_fp16: 是否使用FP16混合精度(GPU可用时建议开启,速度更快)
|
||||||
|
"""
|
||||||
|
if not _TRANSFORMERS_AVAILABLE:
|
||||||
|
raise ImportError(
|
||||||
|
"transformers 和 torch 未安装。请运行:\n"
|
||||||
|
"pip install transformers torch sentencepiece accelerate"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确定模型路径
|
||||||
|
if model_path is None:
|
||||||
|
model_path = os.getenv(
|
||||||
|
"EMBEDDING_MODEL_PATH",
|
||||||
|
"./data/models/Qwen3-Embedding-0.6B"
|
||||||
|
)
|
||||||
|
|
||||||
|
model_path = Path(model_path)
|
||||||
|
if not model_path.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"模型目录不存在:{model_path}\n"
|
||||||
|
"请先下载模型:\n"
|
||||||
|
" modelscope download --model 'Qwen/Qwen3-Embedding-0.6B' "
|
||||||
|
f"--local_dir '{model_path}'\n"
|
||||||
|
"或从Hugging Face下载:git lfs install && git clone "
|
||||||
|
f"https://huggingface.co/Qwen/Qwen3-Embedding-0.6B {model_path}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确定设备
|
||||||
|
if device is None:
|
||||||
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
|
||||||
|
self.device = device
|
||||||
|
logger.info(f"加载Qwen3-Embedding模型:{model_path},设备:{device}")
|
||||||
|
|
||||||
|
# 加载 tokenizer:fast(Rust) 解析 tokenizer.json 需较新 tokenizers;
|
||||||
|
# 旧版本会报 ModelWrapper / untagged enum,回退到慢速 tokenizer 可恢复。
|
||||||
|
try:
|
||||||
|
self.tokenizer = AutoTokenizer.from_pretrained(
|
||||||
|
str(model_path), trust_remote_code=True
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
err = str(e).lower()
|
||||||
|
if "modelwrapper" in err or "untagged enum" in err:
|
||||||
|
logger.warning(
|
||||||
|
"快速 tokenizer 解析 tokenizer.json 失败(多为 tokenizers 过旧),"
|
||||||
|
"改用 use_fast=False:%s",
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
self.tokenizer = AutoTokenizer.from_pretrained(
|
||||||
|
str(model_path), use_fast=False, trust_remote_code=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.model = AutoModel.from_pretrained(
|
||||||
|
str(model_path), trust_remote_code=True
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
msg = str(e)
|
||||||
|
if "qwen3" in msg.lower() or "does not recognize this architecture" in msg:
|
||||||
|
raise RuntimeError(
|
||||||
|
"当前 transformers 版本不支持 Qwen3(model_type=qwen3)。"
|
||||||
|
"请升级:pip install \"transformers>=4.51.0\" \"tokenizers>=0.21\""
|
||||||
|
) from e
|
||||||
|
raise
|
||||||
|
|
||||||
|
# 设置为评估模式并移动设备
|
||||||
|
self.model.eval()
|
||||||
|
self.model.to(device)
|
||||||
|
|
||||||
|
# 混合精度(仅GPU)
|
||||||
|
self.use_fp16 = use_fp16 and device != "cpu"
|
||||||
|
if self.use_fp16:
|
||||||
|
self.model.half()
|
||||||
|
|
||||||
|
# 嵌入维度
|
||||||
|
self.embedding_dim = self.model.config.hidden_size
|
||||||
|
logger.info(f"[OK] 模型加载完成,嵌入维度:{self.embedding_dim}")
|
||||||
|
|
||||||
|
def encode(
|
||||||
|
self,
|
||||||
|
texts: Union[str, List[str]],
|
||||||
|
batch_size: int = 32,
|
||||||
|
normalize: bool = True,
|
||||||
|
max_length: int = 8192,
|
||||||
|
show_progress: bool = False
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
编码文本为向量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
texts: 单个文本或文本列表
|
||||||
|
batch_size: 批处理大小(根据显存调整)
|
||||||
|
normalize: 是否L2归一化(余弦相似度必需)
|
||||||
|
max_length: 最大序列长度(模型支持8192,建议512-1024平衡速度与精度)
|
||||||
|
show_progress: 是否显示进度条(需安装tqdm)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
numpy数组,shape=(len(texts), embedding_dim)
|
||||||
|
"""
|
||||||
|
if isinstance(texts, str):
|
||||||
|
texts = [texts]
|
||||||
|
|
||||||
|
if not texts:
|
||||||
|
return np.empty((0, self.embedding_dim), dtype=np.float32)
|
||||||
|
|
||||||
|
all_embeddings = []
|
||||||
|
|
||||||
|
# 可选进度条
|
||||||
|
iterator = range(0, len(texts), batch_size)
|
||||||
|
if show_progress:
|
||||||
|
try:
|
||||||
|
from tqdm import tqdm
|
||||||
|
iterator = tqdm(iterator, desc="Embedding")
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for i in iterator:
|
||||||
|
batch = texts[i:i + batch_size]
|
||||||
|
|
||||||
|
# Tokenize
|
||||||
|
inputs = self.tokenizer(
|
||||||
|
batch,
|
||||||
|
padding=True,
|
||||||
|
truncation=True,
|
||||||
|
max_length=max_length,
|
||||||
|
return_tensors="pt"
|
||||||
|
).to(self.device)
|
||||||
|
|
||||||
|
# Inference
|
||||||
|
with torch.no_grad():
|
||||||
|
outputs = self.model(**inputs)
|
||||||
|
# Mean Pooling: 取序列维度的平均值
|
||||||
|
# outputs.last_hidden_state shape: (batch, seq_len, hidden_size)
|
||||||
|
embeddings = outputs.last_hidden_state.mean(dim=1)
|
||||||
|
|
||||||
|
# 转换为numpy(保持在CPU)
|
||||||
|
if self.device != "cpu":
|
||||||
|
embeddings = embeddings.cpu()
|
||||||
|
embeddings = embeddings.numpy()
|
||||||
|
|
||||||
|
if normalize:
|
||||||
|
# L2归一化(余弦相似度必需)
|
||||||
|
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
|
||||||
|
embeddings = embeddings / (norms + 1e-10)
|
||||||
|
|
||||||
|
all_embeddings.append(embeddings)
|
||||||
|
|
||||||
|
return np.vstack(all_embeddings).astype(np.float32)
|
||||||
|
|
||||||
|
def similarity(
|
||||||
|
self,
|
||||||
|
emb1: np.ndarray,
|
||||||
|
emb2: np.ndarray
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
计算两组embedding的余弦相似度
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emb1: 第一组向量 (n, dim)
|
||||||
|
emb2: 第二组向量 (m, dim)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
相似度矩阵 (n, m),值域[-1, 1](若已归一化则为[0, 1])
|
||||||
|
"""
|
||||||
|
# 确保已归一化
|
||||||
|
return np.dot(emb1, emb2.T)
|
||||||
|
|
||||||
|
def encode_and_search(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
documents: List[str],
|
||||||
|
top_k: int = 5
|
||||||
|
) -> List[dict]:
|
||||||
|
"""
|
||||||
|
便捷方法:编码查询并检索最相似的文档
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: 查询文本
|
||||||
|
documents: 候选文档列表
|
||||||
|
top_k: 返回前K个结果
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
[{"score": float, "document": str, "index": int}, ...]
|
||||||
|
"""
|
||||||
|
query_emb = self.encode([query], normalize=True)
|
||||||
|
doc_embs = self.encode(documents, normalize=True)
|
||||||
|
|
||||||
|
scores = self.similarity(query_emb, doc_embs)[0]
|
||||||
|
|
||||||
|
# 获取top_k
|
||||||
|
top_indices = np.argsort(scores)[::-1][:top_k]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for idx in top_indices:
|
||||||
|
results.append({
|
||||||
|
"score": float(scores[idx]),
|
||||||
|
"document": documents[idx],
|
||||||
|
"index": int(idx)
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def _env_flag(name: str, default: str = "true") -> bool:
|
||||||
|
return os.getenv(name, default).strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAICompatibleRemoteEmbedding:
|
||||||
|
"""
|
||||||
|
通过 OpenAI 兼容接口获取文本向量(POST /v1/embeddings)。
|
||||||
|
|
||||||
|
环境变量(优先级:ModelScope → OpenAI 兼容 → DashScope):
|
||||||
|
- ModelScope:MODELSCOPE_API_KEY、可选 MODELSCOPE_BASE_URL(默认
|
||||||
|
https://api-inference.modelscope.cn/v1)、MODELSCOPE_EMBEDDING_MODEL
|
||||||
|
或 MODELSCOPE_MODEL、可选 MODELSCOPE_EMBEDDING_MAX_BATCH
|
||||||
|
- OpenAI 兼容:OPENAI_API_KEY、OPENAI_EMBEDDING_MODEL、可选 OPENAI_BASE_URL
|
||||||
|
(默认 https://api.openai.com/v1)、可选 OPENAI_EMBEDDING_MAX_BATCH
|
||||||
|
- DashScope:DASHSCOPE_API_KEY、DASHSCOPE_BASE_URL、DASHSCOPE_MODEL、
|
||||||
|
可选 DASHSCOPE_EMBEDDING_MAX_BATCH
|
||||||
|
|
||||||
|
可选 VECTOR_DIM:在首次请求前确定空列表返回的维度。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
base_url: Optional[str] = None,
|
||||||
|
model: Optional[str] = None,
|
||||||
|
max_batch: Optional[int] = None,
|
||||||
|
provider_label: Optional[str] = None,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
from openai import OpenAI
|
||||||
|
except ImportError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"使用远程 Embedding 需要安装 openai:pip install openai"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
if api_key is not None and base_url is not None and model is not None:
|
||||||
|
cfg = _RemoteEmbeddingEnv(
|
||||||
|
api_key=api_key.strip(),
|
||||||
|
base_url=base_url.strip().rstrip("/"),
|
||||||
|
model=model.strip(),
|
||||||
|
max_batch=max(1, int(max_batch)) if max_batch is not None else 32,
|
||||||
|
label=provider_label or "custom",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cfg = _remote_embedding_from_env()
|
||||||
|
|
||||||
|
self.api_key = cfg.api_key
|
||||||
|
self.base_url = cfg.base_url
|
||||||
|
self.model = cfg.model
|
||||||
|
self._api_max_batch = cfg.max_batch
|
||||||
|
self._provider_label = cfg.label
|
||||||
|
|
||||||
|
self._client = OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||||
|
vd = os.getenv("VECTOR_DIM", "").strip()
|
||||||
|
self._embedding_dim: Optional[int] = int(vd) if vd.isdigit() else None
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"使用 %s Embedding API:model=%s,base_url=%s,max_batch=%s",
|
||||||
|
self._provider_label,
|
||||||
|
self.model,
|
||||||
|
self.base_url,
|
||||||
|
self._api_max_batch,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embedding_dim(self) -> int:
|
||||||
|
if self._embedding_dim is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"尚未获知向量维度:请先执行一次 encode,或在 .env 中设置 VECTOR_DIM"
|
||||||
|
)
|
||||||
|
return self._embedding_dim
|
||||||
|
|
||||||
|
def _set_dim_from_vector(self, vec: List[float]) -> None:
|
||||||
|
if self._embedding_dim is None:
|
||||||
|
self._embedding_dim = len(vec)
|
||||||
|
logger.info("[OK] Embedding 向量维度:%s", self._embedding_dim)
|
||||||
|
|
||||||
|
def encode(
|
||||||
|
self,
|
||||||
|
texts: Union[str, List[str]],
|
||||||
|
batch_size: int = 10,
|
||||||
|
normalize: bool = True,
|
||||||
|
max_length: int = 8192,
|
||||||
|
show_progress: bool = False,
|
||||||
|
) -> np.ndarray:
|
||||||
|
del max_length # API 侧截断,此处仅保持签名与本地实现一致
|
||||||
|
|
||||||
|
if isinstance(texts, str):
|
||||||
|
texts = [texts]
|
||||||
|
|
||||||
|
if not texts:
|
||||||
|
dim = self._embedding_dim
|
||||||
|
if dim is None:
|
||||||
|
vd = os.getenv("VECTOR_DIM", "").strip()
|
||||||
|
dim = int(vd) if vd.isdigit() else 1024
|
||||||
|
return np.empty((0, dim), dtype=np.float32)
|
||||||
|
|
||||||
|
# 无论调用方传多大,不能超过远端接口单次条数上限
|
||||||
|
step = max(1, min(int(batch_size), self._api_max_batch))
|
||||||
|
|
||||||
|
all_embeddings: List[np.ndarray] = []
|
||||||
|
iterator = range(0, len(texts), step)
|
||||||
|
if show_progress:
|
||||||
|
try:
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
iterator = tqdm(iterator, desc="Embedding (API)")
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for i in iterator:
|
||||||
|
batch = texts[i : i + step]
|
||||||
|
resp = self._client.embeddings.create(
|
||||||
|
model=self.model,
|
||||||
|
input=batch,
|
||||||
|
encoding_format="float",
|
||||||
|
)
|
||||||
|
rows = sorted(
|
||||||
|
[(d.index, d.embedding) for d in resp.data],
|
||||||
|
key=lambda x: x[0],
|
||||||
|
)
|
||||||
|
batch_embs = np.array([e for _, e in rows], dtype=np.float32)
|
||||||
|
if batch_embs.size > 0:
|
||||||
|
self._set_dim_from_vector(batch_embs[0].tolist())
|
||||||
|
|
||||||
|
if normalize:
|
||||||
|
norms = np.linalg.norm(batch_embs, axis=1, keepdims=True)
|
||||||
|
batch_embs = batch_embs / (norms + 1e-10)
|
||||||
|
|
||||||
|
all_embeddings.append(batch_embs)
|
||||||
|
|
||||||
|
return np.vstack(all_embeddings).astype(np.float32)
|
||||||
|
|
||||||
|
def similarity(self, emb1: np.ndarray, emb2: np.ndarray) -> np.ndarray:
|
||||||
|
return np.dot(emb1, emb2.T)
|
||||||
|
|
||||||
|
def encode_and_search(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
documents: List[str],
|
||||||
|
top_k: int = 5,
|
||||||
|
) -> List[dict]:
|
||||||
|
query_emb = self.encode([query], normalize=True)
|
||||||
|
doc_embs = self.encode(documents, normalize=True)
|
||||||
|
scores = self.similarity(query_emb, doc_embs)[0]
|
||||||
|
top_indices = np.argsort(scores)[::-1][:top_k]
|
||||||
|
return [
|
||||||
|
{"score": float(scores[idx]), "document": documents[idx], "index": int(idx)}
|
||||||
|
for idx in top_indices
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# 向后兼容旧名称
|
||||||
|
DashScopeOpenAIEmbedding = OpenAICompatibleRemoteEmbedding
|
||||||
|
|
||||||
|
# 全局单例(避免重复加载模型,节省显存/内存)
|
||||||
|
_embedding_instance: Optional[Any] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_embedder(
|
||||||
|
model_path: Optional[str] = None,
|
||||||
|
device: Optional[str] = None,
|
||||||
|
force_reload: bool = False,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
获取 Embedding 单例:USE_LOCAL_EMBEDDING=true 时用本地 Qwen3,否则用远程
|
||||||
|
OpenAI 兼容 API(优先级见 OpenAICompatibleRemoteEmbedding)。
|
||||||
|
"""
|
||||||
|
global _embedding_instance
|
||||||
|
|
||||||
|
if force_reload or _embedding_instance is None:
|
||||||
|
if _env_flag("USE_LOCAL_EMBEDDING", "true"):
|
||||||
|
_embedding_instance = Qwen3Embedding(
|
||||||
|
model_path=model_path,
|
||||||
|
device=device,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_embedding_instance = OpenAICompatibleRemoteEmbedding()
|
||||||
|
|
||||||
|
return _embedding_instance
|
||||||
|
|
||||||
|
|
||||||
|
def clear_embedder():
|
||||||
|
"""清空单例(用于测试或切换模型)"""
|
||||||
|
global _embedding_instance
|
||||||
|
_embedding_instance = None
|
||||||
|
import gc
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
if _TRANSFORMERS_AVAILABLE:
|
||||||
|
import torch
|
||||||
|
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
torch.cuda.empty_cache()
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
"""
|
||||||
|
Few-shot示例选择器 - 基于经验数据集动态选择相关示例
|
||||||
|
|
||||||
|
与 Schema 向量检索一致,使用 utils.embedding.get_embedder()(本地 Qwen3 或远程 OpenAI 兼容 API,
|
||||||
|
由 USE_LOCAL_EMBEDDING 及 OPENAI_* / MODELSCOPE_* / DASHSCOPE_* 等环境变量决定)。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
from utils.fewshot_selector import FewShotSelector
|
||||||
|
|
||||||
|
selector = FewShotSelector("data/experiences/all_samples.jsonl")
|
||||||
|
examples = selector.select(question="查询2024年1月的销售额", top_k=3)
|
||||||
|
|
||||||
|
# 在Prompt中使用
|
||||||
|
prompt = f"{examples}\n当前问题:{question}\nSchema:{schema}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import numpy as np
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DEFAULT_LOCAL_EMBED_PATH = "./data/models/Qwen3-Embedding-0.6B"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExperienceSample:
|
||||||
|
"""经验数据样本"""
|
||||||
|
qid: str
|
||||||
|
question_zh: str
|
||||||
|
question_en: Optional[str]
|
||||||
|
sql: str
|
||||||
|
explanation: str
|
||||||
|
rating: Optional[int]
|
||||||
|
tags: List[str]
|
||||||
|
difficulty: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> "ExperienceSample":
|
||||||
|
return cls(
|
||||||
|
qid=data.get("qid", ""),
|
||||||
|
question_zh=data.get("question_zh", ""),
|
||||||
|
question_en=data.get("question_en"),
|
||||||
|
sql=data.get("sql", ""),
|
||||||
|
explanation=data.get("explanation", ""),
|
||||||
|
rating=data.get("rating"),
|
||||||
|
tags=data.get("tags", []),
|
||||||
|
difficulty=data.get("difficulty", "medium")
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_fewshot_format(self, include_explanation: bool = True) -> str:
|
||||||
|
"""转换为few-shot格式"""
|
||||||
|
result = f"问题:{self.question_zh}\nSQL:\n{self.sql}"
|
||||||
|
if include_explanation and self.explanation:
|
||||||
|
result += f"\n说明:{self.explanation[:200]}"
|
||||||
|
return result
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"qid": self.qid,
|
||||||
|
"question": self.question_zh,
|
||||||
|
"sql": self.sql,
|
||||||
|
"rating": self.rating,
|
||||||
|
"tags": self.tags,
|
||||||
|
"difficulty": self.difficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FewShotSelector:
|
||||||
|
"""
|
||||||
|
Few-shot示例选择器
|
||||||
|
|
||||||
|
根据用户问题,从经验数据集中检索最相似的示例,
|
||||||
|
用于增强Prompt,提升LLM生成质量。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
samples_path: str,
|
||||||
|
embedding_model_path: Optional[str] = None,
|
||||||
|
use_cache: bool = True,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
samples_path: 样本JSONL文件路径
|
||||||
|
embedding_model_path: 本地 Embedding 模型目录;None 时用环境变量
|
||||||
|
EMBEDDING_MODEL_PATH(仅 USE_LOCAL_EMBEDDING=true 时有效)
|
||||||
|
use_cache: 是否缓存样本向量(按向量维度分文件,换模型会自动重建)
|
||||||
|
"""
|
||||||
|
self.samples_path = Path(samples_path)
|
||||||
|
self.samples: List[ExperienceSample] = []
|
||||||
|
self._embedder = None
|
||||||
|
self.embeddings: Optional[np.ndarray] = None
|
||||||
|
self.use_cache = use_cache
|
||||||
|
self.cache_path: Optional[Path] = None
|
||||||
|
self._embedding_model_path = (
|
||||||
|
embedding_model_path
|
||||||
|
if embedding_model_path is not None
|
||||||
|
else os.getenv("EMBEDDING_MODEL_PATH", _DEFAULT_LOCAL_EMBED_PATH).strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
self._load_samples()
|
||||||
|
self._build_index()
|
||||||
|
|
||||||
|
def _load_samples(self):
|
||||||
|
"""加载样本数据"""
|
||||||
|
if not self.samples_path.exists():
|
||||||
|
raise FileNotFoundError(f"样本文件不存在: {self.samples_path}")
|
||||||
|
|
||||||
|
logger.info(f"加载样本: {self.samples_path}")
|
||||||
|
with self.samples_path.open('r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
data = json.loads(line.strip())
|
||||||
|
self.samples.append(ExperienceSample.from_dict(data))
|
||||||
|
|
||||||
|
logger.info(f"[OK] 加载 {len(self.samples)} 个样本")
|
||||||
|
|
||||||
|
def _build_index(self):
|
||||||
|
"""用项目统一 Embedder 构建语义索引"""
|
||||||
|
from utils.embedding import get_embedder
|
||||||
|
|
||||||
|
self._embedder = get_embedder(self._embedding_model_path)
|
||||||
|
probe = self._embedder.encode(
|
||||||
|
[" "],
|
||||||
|
batch_size=1,
|
||||||
|
normalize=True,
|
||||||
|
show_progress=False,
|
||||||
|
)
|
||||||
|
dim = int(probe.shape[1])
|
||||||
|
self.cache_path = (
|
||||||
|
self.samples_path.parent / f"{self.samples_path.stem}.fewshot_dim{dim}.npy"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.use_cache and self.cache_path.exists():
|
||||||
|
try:
|
||||||
|
self.embeddings = np.load(self.cache_path)
|
||||||
|
if (
|
||||||
|
self.embeddings.shape[0] == len(self.samples)
|
||||||
|
and self.embeddings.shape[1] == dim
|
||||||
|
):
|
||||||
|
logger.info(f"[OK] 加载 Few-shot 向量缓存: {self.cache_path}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Few-shot 缓存加载失败: {e},将重新计算")
|
||||||
|
|
||||||
|
# 远程 API 通常不接受空字符串作 input
|
||||||
|
questions = [
|
||||||
|
(s.question_zh or "").strip() or " "
|
||||||
|
for s in self.samples
|
||||||
|
]
|
||||||
|
if not questions:
|
||||||
|
self.embeddings = np.empty((0, dim), dtype=np.float32)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f"计算 {len(questions)} 个 Few-shot 样本向量...")
|
||||||
|
self.embeddings = self._embedder.encode(
|
||||||
|
questions,
|
||||||
|
batch_size=min(32, len(questions)),
|
||||||
|
normalize=True,
|
||||||
|
show_progress=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.use_cache and self.cache_path is not None:
|
||||||
|
np.save(self.cache_path, self.embeddings)
|
||||||
|
logger.info(f"[OK] Few-shot 向量已缓存: {self.cache_path}")
|
||||||
|
|
||||||
|
def select(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
top_k: int = 3,
|
||||||
|
min_rating: Optional[int] = None,
|
||||||
|
required_tags: Optional[List[str]] = None,
|
||||||
|
max_difficulty: str = "hard",
|
||||||
|
exclude_qids: Optional[List[str]] = None
|
||||||
|
) -> List[ExperienceSample]:
|
||||||
|
"""
|
||||||
|
选择最相关的few-shot示例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: 用户问题
|
||||||
|
top_k: 返回示例数量
|
||||||
|
min_rating: 最低评分(None表示不限制)
|
||||||
|
required_tags: 必须包含的标签(如["aggregation", "join"])
|
||||||
|
max_difficulty: 最大难度(过滤更难的示例)
|
||||||
|
exclude_qids: 排除的QID(避免与当前问题相同)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
排序后的示例列表(最相关优先)
|
||||||
|
"""
|
||||||
|
if self._embedder is None or self.embeddings is None:
|
||||||
|
logger.error("Few-shot 索引未初始化")
|
||||||
|
return []
|
||||||
|
|
||||||
|
if len(self.samples) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
q_emb = self._embedder.encode(
|
||||||
|
[question],
|
||||||
|
batch_size=1,
|
||||||
|
normalize=True,
|
||||||
|
show_progress=False,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
scores = np.dot(self.embeddings, q_emb)
|
||||||
|
|
||||||
|
candidates = []
|
||||||
|
for idx, (score, sample) in enumerate(zip(scores, self.samples)):
|
||||||
|
if exclude_qids and sample.qid in exclude_qids:
|
||||||
|
continue
|
||||||
|
if min_rating and sample.rating and sample.rating < min_rating:
|
||||||
|
continue
|
||||||
|
if max_difficulty == "easy" and sample.difficulty != "easy":
|
||||||
|
continue
|
||||||
|
if max_difficulty == "medium" and sample.difficulty == "hard":
|
||||||
|
continue
|
||||||
|
if required_tags and not all(tag in sample.tags for tag in required_tags):
|
||||||
|
continue
|
||||||
|
|
||||||
|
candidates.append((idx, score, sample))
|
||||||
|
|
||||||
|
candidates.sort(key=lambda x: -x[1])
|
||||||
|
|
||||||
|
selected = [sample for _, _, sample in candidates[:top_k]]
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Few-shot选择: 问题='{question[:30]}...' "
|
||||||
|
f"→ 选中{len(selected)}个示例 (top_k={top_k}, min_rating={min_rating})"
|
||||||
|
)
|
||||||
|
for s in selected:
|
||||||
|
logger.debug(
|
||||||
|
f" [{s.qid}] {s.question_zh[:50]}... (rating={s.rating}, tags={s.tags[:3]})"
|
||||||
|
)
|
||||||
|
|
||||||
|
return selected
|
||||||
|
|
||||||
|
def get_examples_prompt(
|
||||||
|
self,
|
||||||
|
question: str,
|
||||||
|
top_k: int = 3,
|
||||||
|
min_rating: int = 7,
|
||||||
|
**kwargs
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
生成few-shot prompt片段
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式化的示例字符串,可直接插入Prompt
|
||||||
|
"""
|
||||||
|
examples = self.select(question, top_k=top_k, min_rating=min_rating, **kwargs)
|
||||||
|
|
||||||
|
if not examples:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
lines = ["以下为相似问题的参考SQL示例:\n"]
|
||||||
|
for i, ex in enumerate(examples, 1):
|
||||||
|
lines.append(f"示例{i}:")
|
||||||
|
lines.append(f"问题:{ex.question_zh}")
|
||||||
|
lines.append(f"SQL:\n{ex.sql}")
|
||||||
|
if ex.explanation:
|
||||||
|
lines.append(f"说明:{ex.explanation[:150]}...")
|
||||||
|
lines.append("") # 空行分隔
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def get_tagged_examples(self, tags: List[str], top_k_per_tag: int = 2) -> str:
|
||||||
|
"""获取特定标签的示例"""
|
||||||
|
tagged_samples = []
|
||||||
|
for sample in self.samples:
|
||||||
|
if any(tag in sample.tags for tag in tags):
|
||||||
|
tagged_samples.append(sample)
|
||||||
|
|
||||||
|
tagged_samples.sort(key=lambda s: -(s.rating or 0))
|
||||||
|
selected = tagged_samples[:top_k_per_tag * len(tags)]
|
||||||
|
|
||||||
|
lines = [f"# {tags} 相关示例\n"]
|
||||||
|
for ex in selected:
|
||||||
|
lines.append(f"## {ex.qid}. {ex.question_zh[:50]}")
|
||||||
|
lines.append(f"评分: {ex.rating}/10")
|
||||||
|
lines.append(f"标签: {', '.join(ex.tags)}")
|
||||||
|
lines.append(f"```sql\n{ex.sql}\n```\n")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def get_stats(self) -> Dict:
|
||||||
|
"""获取数据集统计"""
|
||||||
|
stats = {
|
||||||
|
"total": len(self.samples),
|
||||||
|
"by_rating": {},
|
||||||
|
"by_difficulty": {},
|
||||||
|
"by_tag": {},
|
||||||
|
"avg_rating": 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
ratings = [s.rating for s in self.samples if s.rating]
|
||||||
|
if ratings:
|
||||||
|
stats["avg_rating"] = sum(ratings) / len(ratings)
|
||||||
|
for r in range(1, 11):
|
||||||
|
stats["by_rating"][r] = sum(1 for s in self.samples if s.rating == r)
|
||||||
|
|
||||||
|
for diff in ["easy", "medium", "hard"]:
|
||||||
|
stats["by_difficulty"][diff] = sum(
|
||||||
|
1 for s in self.samples if s.difficulty == diff
|
||||||
|
)
|
||||||
|
|
||||||
|
tag_counts = {}
|
||||||
|
for s in self.samples:
|
||||||
|
for tag in s.tags:
|
||||||
|
tag_counts[tag] = tag_counts.get(tag, 0) + 1
|
||||||
|
stats["by_tag"] = tag_counts
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
def load_fewshot_selector() -> FewShotSelector:
|
||||||
|
"""加载默认的few-shot选择器"""
|
||||||
|
default_path = Path(__file__).resolve().parent.parent / "data" / "experiences" / "all_samples.jsonl"
|
||||||
|
return FewShotSelector(str(default_path))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Few-shot示例选择器")
|
||||||
|
parser.add_argument("--samples", default="data/experiences/all_samples.jsonl")
|
||||||
|
parser.add_argument("--question", help="测试问题")
|
||||||
|
parser.add_argument("--top-k", type=int, default=3)
|
||||||
|
parser.add_argument("--min-rating", type=int, default=7)
|
||||||
|
parser.add_argument("--stats", action="store_true", help="显示数据集统计")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
selector = FewShotSelector(args.samples)
|
||||||
|
|
||||||
|
if args.stats:
|
||||||
|
stats = selector.get_stats()
|
||||||
|
print("📊 数据集统计:")
|
||||||
|
print(f" 总样本: {stats['total']}")
|
||||||
|
print(f" 平均评分: {stats['avg_rating']:.1f}")
|
||||||
|
print(f" 难度分布: {stats['by_difficulty']}")
|
||||||
|
print(f"\n Top 10 标签:")
|
||||||
|
sorted_tags = sorted(stats["by_tag"].items(), key=lambda x: -x[1])[:10]
|
||||||
|
for tag, count in sorted_tags:
|
||||||
|
print(f" {tag}: {count}")
|
||||||
|
elif args.question:
|
||||||
|
examples = selector.select(args.question, top_k=args.top_k, min_rating=args.min_rating)
|
||||||
|
print(f"\n为问题 '{args.question}' 选择的示例:\n")
|
||||||
|
for ex in examples:
|
||||||
|
print(f"[{ex.qid}] 评分:{ex.rating} 难度:{ex.difficulty}")
|
||||||
|
print(f"问题: {ex.question_zh}")
|
||||||
|
print(f"SQL:\n{ex.sql}\n")
|
||||||
|
else:
|
||||||
|
print("请指定 --question 或 --stats")
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
"""
|
||||||
|
SQL 解析与验证工具(基于 sqlglot)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from typing import List, Tuple, Optional, Dict
|
||||||
|
import sqlglot
|
||||||
|
from sqlglot import exp, parse_one, ParseError
|
||||||
|
|
||||||
|
from schema.manager import SchemaManager
|
||||||
|
from schema.models import Table, Column
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_sql_syntax(sql: str, dialect: str = "tsql") -> Tuple[bool, List[str]]:
|
||||||
|
"""
|
||||||
|
验证SQL语法是否正确
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否有效, 错误信息列表)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 尝试解析
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
|
||||||
|
if parsed is None:
|
||||||
|
errors.append("SQL解析返回空结果")
|
||||||
|
return False, errors
|
||||||
|
|
||||||
|
# 检查是否为只读查询(SELECT/CTE/SHOW等)
|
||||||
|
# 动态获取可用表达式类型(兼容不同sqlglot版本)
|
||||||
|
readable_ops = [exp.Select, exp.Union, exp.Intersect, exp.Except, exp.With]
|
||||||
|
# 可选:添加 Show, Describe, Explain(如果存在)
|
||||||
|
for op_name in ['Show', 'Describe', 'Explain']:
|
||||||
|
if hasattr(exp, op_name):
|
||||||
|
readable_ops.append(getattr(exp, op_name))
|
||||||
|
|
||||||
|
if not isinstance(parsed, tuple(readable_ops)):
|
||||||
|
op_type = type(parsed).__name__
|
||||||
|
errors.append(f"非查询操作({op_type}),只允许SELECT等只读语句")
|
||||||
|
|
||||||
|
return True, []
|
||||||
|
|
||||||
|
except ParseError as e:
|
||||||
|
errors.append(f"SQL语法错误: {str(e)}")
|
||||||
|
return False, errors
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"解析异常: {str(e)}")
|
||||||
|
return False, errors
|
||||||
|
|
||||||
|
|
||||||
|
def extract_tables_from_sql(sql: str, dialect: str = "tsql") -> List[str]:
|
||||||
|
"""
|
||||||
|
从SQL中提取所有表名
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
表名列表(去重)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
tables = []
|
||||||
|
|
||||||
|
# 遍历AST查找所有表名
|
||||||
|
for node in parsed.walk():
|
||||||
|
if isinstance(node, exp.Table):
|
||||||
|
table_name = node.name
|
||||||
|
if table_name and table_name not in tables:
|
||||||
|
tables.append(table_name)
|
||||||
|
|
||||||
|
return tables
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"提取表名失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def build_table_alias_map(parsed: exp.Expression) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
从已解析的 AST 构建「别名/表名 -> 物理表名」映射。
|
||||||
|
|
||||||
|
FROM T a 时 a -> T,且 T -> T,便于将 a.col 解析到表 T 的列。
|
||||||
|
"""
|
||||||
|
alias_map: Dict[str, str] = {}
|
||||||
|
for node in parsed.walk():
|
||||||
|
if not isinstance(node, exp.Table):
|
||||||
|
continue
|
||||||
|
physical = node.name
|
||||||
|
if not physical:
|
||||||
|
continue
|
||||||
|
alias_map[physical] = physical
|
||||||
|
talias = node.args.get("alias")
|
||||||
|
if talias is not None:
|
||||||
|
aname = talias.name
|
||||||
|
if aname:
|
||||||
|
alias_map[aname] = physical
|
||||||
|
return alias_map
|
||||||
|
|
||||||
|
|
||||||
|
def extract_columns_from_sql(sql: str, dialect: str = "tsql") -> List[Tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
从SQL中提取所有字段引用(表.字段)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
[(表名, 字段名), ...] 列表
|
||||||
|
"""
|
||||||
|
columns = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
|
||||||
|
for node in parsed.walk():
|
||||||
|
if isinstance(node, exp.Column):
|
||||||
|
table_name = node.table
|
||||||
|
col_name = node.name
|
||||||
|
if table_name and col_name:
|
||||||
|
columns.append((table_name, col_name))
|
||||||
|
|
||||||
|
return columns
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"提取字段失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def validate_schema_consistency(
|
||||||
|
sql: str,
|
||||||
|
schema_manager: SchemaManager,
|
||||||
|
dialect: str = "tsql"
|
||||||
|
) -> Tuple[bool, List[str]]:
|
||||||
|
"""
|
||||||
|
验证SQL与Schema的一致性
|
||||||
|
|
||||||
|
检查:
|
||||||
|
1. 所有表名存在于Schema
|
||||||
|
2. 所有字段名属于对应的表
|
||||||
|
3. JOIN条件字段存在且类型兼容
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema_manager: Schema管理器
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否一致, 错误信息列表)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Schema一致性检查跳过(解析失败): {e}")
|
||||||
|
return True, []
|
||||||
|
|
||||||
|
alias_map = build_table_alias_map(parsed)
|
||||||
|
|
||||||
|
tables_used: List[str] = []
|
||||||
|
for node in parsed.walk():
|
||||||
|
if isinstance(node, exp.Table):
|
||||||
|
tname = node.name
|
||||||
|
if tname and tname not in tables_used:
|
||||||
|
tables_used.append(tname)
|
||||||
|
|
||||||
|
columns_used: List[Tuple[str, str]] = []
|
||||||
|
for node in parsed.walk():
|
||||||
|
if isinstance(node, exp.Column):
|
||||||
|
tref, cname = node.table, node.name
|
||||||
|
if tref and cname:
|
||||||
|
columns_used.append((tref, cname))
|
||||||
|
|
||||||
|
# 检查表存在性
|
||||||
|
for tbl in tables_used:
|
||||||
|
if not schema_manager.get_table(tbl):
|
||||||
|
errors.append(f"表不存在: '{tbl}'")
|
||||||
|
|
||||||
|
# 检查字段存在性(表引用可为物理表名或别名)
|
||||||
|
for tbl_name, col_name in columns_used:
|
||||||
|
physical = alias_map.get(tbl_name, tbl_name)
|
||||||
|
table = schema_manager.get_table(physical)
|
||||||
|
if not table:
|
||||||
|
errors.append(
|
||||||
|
f"字段不存在: '{tbl_name}.{col_name}'"
|
||||||
|
f"(无法将表引用解析到已加载Schema中的表)"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
col_names = [c.name for c in table.columns]
|
||||||
|
if col_name not in col_names:
|
||||||
|
errors.append(
|
||||||
|
f"字段不存在: '{tbl_name}.{col_name}'"
|
||||||
|
f"(表 '{physical}' 可用字段: {col_names[:5]}...)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查JOIN条件(外键匹配)
|
||||||
|
try:
|
||||||
|
for join in parsed.find_all(exp.Join):
|
||||||
|
# 解析ON条件
|
||||||
|
on_condition = join.args.get("on")
|
||||||
|
if on_condition:
|
||||||
|
# 检查ON条件中涉及的字段
|
||||||
|
for eq in on_condition.find_all(exp.EQ):
|
||||||
|
left = eq.left
|
||||||
|
right = eq.right
|
||||||
|
|
||||||
|
# 提取左右两边的表.字段
|
||||||
|
for side in [left, right]:
|
||||||
|
if isinstance(side, exp.Column):
|
||||||
|
tbl = side.table
|
||||||
|
col = side.name
|
||||||
|
physical = alias_map.get(tbl, tbl)
|
||||||
|
table = schema_manager.get_table(physical)
|
||||||
|
if table and col not in [c.name for c in table.columns]:
|
||||||
|
errors.append(f"JOIN条件字段不存在: {tbl}.{col}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"JOIN条件检查异常: {e}")
|
||||||
|
|
||||||
|
return len(errors) == 0, errors
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_mysql_builtins_for_tsql(sql: str) -> str:
|
||||||
|
"""
|
||||||
|
模型在 T-SQL 目标下仍常输出 MySQL 函数;sqlglot 转写也可能遗漏。
|
||||||
|
SQL Server 无 CURDATE()/NOW(),需替换为 GETDATE 族。
|
||||||
|
"""
|
||||||
|
if not sql:
|
||||||
|
return sql
|
||||||
|
out = sql
|
||||||
|
out = re.sub(
|
||||||
|
r"\bCURDATE\s*\(\s*\)",
|
||||||
|
"CAST(GETDATE() AS DATE)",
|
||||||
|
out,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
out = re.sub(
|
||||||
|
r"\bCURRENT_DATE\b",
|
||||||
|
"CAST(GETDATE() AS DATE)",
|
||||||
|
out,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
out = re.sub(r"\bNOW\s*\(\s*\)", "GETDATE()", out, flags=re.IGNORECASE)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_sql_for_dialect(sql: str, dialect: str) -> str:
|
||||||
|
"""
|
||||||
|
将模型输出的 SQL 规范为目标方言。
|
||||||
|
对于 T-SQL,主要进行 MySQL 函数替换(因为模型仍可能输出 CURDATE() 等)。
|
||||||
|
"""
|
||||||
|
sql = (sql or "").strip()
|
||||||
|
if not sql:
|
||||||
|
return sql
|
||||||
|
|
||||||
|
# 如果目标是 T-SQL,只做函数名替换,不再用 sqlglot 转写
|
||||||
|
if dialect == "tsql":
|
||||||
|
return rewrite_mysql_builtins_for_tsql(sql)
|
||||||
|
|
||||||
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
def format_sql(sql: str, dialect: str = "tsql", indent: int = 2) -> str:
|
||||||
|
"""
|
||||||
|
格式化SQL(可读性)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
indent: 缩进空格数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式化后的SQL
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
return parsed.sql(dialect=dialect, pretty=True, indent=indent)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"SQL格式化失败: {e}")
|
||||||
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_sql(sql: str, dialect: str = "tsql") -> str:
|
||||||
|
"""
|
||||||
|
标准化SQL(用于比较去重)
|
||||||
|
|
||||||
|
去除多余空格、统一引号、移除注释等
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
标准化后的SQL
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 解析后重新生成(会规范化格式)
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
normalized = parsed.sql(dialect=dialect, pretty=False)
|
||||||
|
# 转换为大写关键词
|
||||||
|
return normalized.upper()
|
||||||
|
except Exception:
|
||||||
|
# 降级:简单处理
|
||||||
|
import re
|
||||||
|
# 移除多余空格
|
||||||
|
sql = re.sub(r'\s+', ' ', sql.strip())
|
||||||
|
# 移除注释
|
||||||
|
sql = re.sub(r'--.*?$', '', sql, flags=re.MULTILINE)
|
||||||
|
sql = re.sub(r'/\*.*?\*/', '', sql, flags=re.DOTALL)
|
||||||
|
return sql.upper()
|
||||||
|
|
||||||
|
|
||||||
|
def count_joins(sql: str, dialect: str = "tsql") -> int:
|
||||||
|
"""统计JOIN数量"""
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
joins = list(parsed.find_all(exp.Join))
|
||||||
|
return len(joins)
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def has_subquery(sql: str, dialect: str = "tsql") -> bool:
|
||||||
|
"""检查是否包含子查询"""
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
# 检查嵌套的SELECT
|
||||||
|
for select in parsed.find_all(exp.Select):
|
||||||
|
if select is not parsed: # 不是最外层的SELECT
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_query_complexity(sql: str, dialect: str = "tsql") -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
评估查询复杂度
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
复杂度指标字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"join_count": len(list(parsed.find_all(exp.Join))),
|
||||||
|
"subquery_count": len([s for s in parsed.find_all(exp.Select) if s is not parsed]),
|
||||||
|
"where_conditions": len(list(parsed.find_all(exp.Predicate))),
|
||||||
|
"aggregation_functions": len(list(parsed.find_all(exp.AggFunc))),
|
||||||
|
"column_count": len(list(parsed.find_all(exp.Column))),
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"复杂度评估失败: {e}")
|
||||||
|
return {}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
"""
|
||||||
|
SQL 验证工具集
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
from typing import Tuple, List, Dict
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 危险操作关键词(除非明确允许)
|
||||||
|
DANGEROUS_KEYWORDS = [
|
||||||
|
"DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE",
|
||||||
|
"CREATE", "DROP DATABASE", "DROP TABLE", "DROP INDEX",
|
||||||
|
"GRANT", "REVOKE", "PURGE", "FLUSH", "KILL"
|
||||||
|
]
|
||||||
|
|
||||||
|
# 允许的操作(仅查询)
|
||||||
|
ALLOWED_KEYWORDS = [
|
||||||
|
"SELECT", "WITH", "FROM", "WHERE", "JOIN", "LEFT JOIN", "RIGHT JOIN",
|
||||||
|
"INNER JOIN", "OUTER JOIN", "ON", "USING", "GROUP BY", "HAVING",
|
||||||
|
"ORDER BY", "LIMIT", "OFFSET", "UNION", "UNION ALL", "EXCEPT", "INTERSECT",
|
||||||
|
"AS", "CASE", "WHEN", "THEN", "ELSE", "END",
|
||||||
|
"COUNT", "SUM", "AVG", "MIN", "MAX", "DISTINCT",
|
||||||
|
"AND", "OR", "NOT", "IN", "EXISTS", "BETWEEN", "LIKE", "IS NULL", "IS NOT NULL",
|
||||||
|
"CAST", "COALESCE", "NULLIF", "IFNULL",
|
||||||
|
"DATE", "TIME", "TIMESTAMP", "EXTRACT", "DATE_FORMAT", "STR_TO_DATE",
|
||||||
|
"CURRENT_DATE", "CURRENT_TIMESTAMP",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def check_dangerous_operations(sql: str) -> Tuple[bool, List[str]]:
|
||||||
|
"""
|
||||||
|
检查SQL是否包含危险操作
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句(大小写不敏感)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否安全, 危险关键词列表)
|
||||||
|
"""
|
||||||
|
sql_upper = sql.upper()
|
||||||
|
found_dangers = []
|
||||||
|
|
||||||
|
for keyword in DANGEROUS_KEYWORDS:
|
||||||
|
# 使用正则避免部分匹配(如"DROP"不应匹配"DROPOUT")
|
||||||
|
pattern = r'\b' + re.escape(keyword) + r'\b'
|
||||||
|
if re.search(pattern, sql_upper):
|
||||||
|
found_dangers.append(keyword)
|
||||||
|
|
||||||
|
is_safe = len(found_dangers) == 0
|
||||||
|
|
||||||
|
if not is_safe:
|
||||||
|
logger.warning(f"检测到危险操作: {found_dangers}")
|
||||||
|
|
||||||
|
return is_safe, found_dangers
|
||||||
|
|
||||||
|
|
||||||
|
def validate_no_dml(sql: str) -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
验证SQL不是DML/DDL操作(仅允许SELECT等查询)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否通过, 错误消息)
|
||||||
|
"""
|
||||||
|
sql_upper = sql.strip().upper()
|
||||||
|
|
||||||
|
# 检查是否以危险关键词开头
|
||||||
|
first_word = sql_upper.split()[0] if sql_upper.split() else ""
|
||||||
|
if first_word in ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "TRUNCATE"]:
|
||||||
|
return False, f"禁止的操作: {first_word}"
|
||||||
|
|
||||||
|
is_safe, dangers = check_dangerous_operations(sql)
|
||||||
|
if not is_safe:
|
||||||
|
return False, f"SQL包含危险操作: {', '.join(dangers)}"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
def check_sql_injection_patterns(sql: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
检查明显的SQL注入模式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
发现的注入模式列表
|
||||||
|
"""
|
||||||
|
patterns = {
|
||||||
|
"union_all_injection": r"UNION\s+ALL\s+SELECT",
|
||||||
|
"union_injection": r"UNION\s+SELECT",
|
||||||
|
"comment_injection": r"(--|\#|/\*).*SELECT",
|
||||||
|
"semicolon_injection": r";\s*(DROP|DELETE|UPDATE|INSERT)",
|
||||||
|
"or_true_condition": r"OR\s+['\"]?\s*1\s*['\"]?\s*=\s*1",
|
||||||
|
"always_true": r"1\s*=\s*1",
|
||||||
|
}
|
||||||
|
|
||||||
|
findings = []
|
||||||
|
sql_lower = sql.lower()
|
||||||
|
|
||||||
|
for name, pattern in patterns.items():
|
||||||
|
if re.search(pattern, sql, re.IGNORECASE):
|
||||||
|
findings.append(name)
|
||||||
|
|
||||||
|
return findings
|
||||||
|
|
||||||
|
|
||||||
|
def validate_aggregation_groupby(sql: str, dialect: str = "tsql") -> Tuple[bool, List[str]]:
|
||||||
|
"""
|
||||||
|
验证聚合查询的GROUP BY正确性
|
||||||
|
|
||||||
|
检查:SELECT中的非聚合字段是否都在GROUP BY中
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否有效, 错误列表)
|
||||||
|
"""
|
||||||
|
from utils.sql_parser import parse_one, exp
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
|
||||||
|
# 只检查SELECT语句
|
||||||
|
if not isinstance(parsed, exp.Select):
|
||||||
|
return True, []
|
||||||
|
|
||||||
|
# 获取SELECT列表中的表达式
|
||||||
|
select_exprs = parsed.expressions
|
||||||
|
|
||||||
|
# 获取GROUP BY字段
|
||||||
|
group_by = parsed.args.get("group")
|
||||||
|
if not group_by:
|
||||||
|
# 没有GROUP BY但有聚合函数,通常是错误的
|
||||||
|
has_agg = any(
|
||||||
|
expr.find(exp.AggFunc) is not None
|
||||||
|
for expr in select_exprs
|
||||||
|
)
|
||||||
|
if has_agg:
|
||||||
|
errors.append("包含聚合函数但缺少GROUP BY子句")
|
||||||
|
return len(errors) == 0, errors
|
||||||
|
|
||||||
|
group_by_exprs = group_by.expressions
|
||||||
|
|
||||||
|
# 提取GROUP BY的字段名(简单处理)
|
||||||
|
group_by_cols = set()
|
||||||
|
for expr in group_by_exprs:
|
||||||
|
if isinstance(expr, exp.Column):
|
||||||
|
group_by_cols.add(expr.name)
|
||||||
|
elif isinstance(expr, exp.Ordered):
|
||||||
|
# GROUP BY x ASC/DESC
|
||||||
|
this = expr.this
|
||||||
|
if isinstance(this, exp.Column):
|
||||||
|
group_by_cols.add(this.name)
|
||||||
|
|
||||||
|
# 检查每个SELECT表达式
|
||||||
|
for expr in select_exprs:
|
||||||
|
# 如果是聚合函数,跳过
|
||||||
|
if expr.find(exp.AggFunc):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果是字面量或表达式,跳过
|
||||||
|
if isinstance(expr, exp.Literal):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果是列引用,检查是否在GROUP BY中
|
||||||
|
if isinstance(expr, exp.Column):
|
||||||
|
col_name = expr.name
|
||||||
|
if col_name not in group_by_cols:
|
||||||
|
errors.append(
|
||||||
|
f"字段 '{col_name}' 在SELECT中但不在GROUP BY中"
|
||||||
|
)
|
||||||
|
elif isinstance(expr, exp.Alias):
|
||||||
|
# 别名: column AS alias
|
||||||
|
this = expr.this
|
||||||
|
if isinstance(this, exp.Column):
|
||||||
|
col_name = this.name
|
||||||
|
if col_name not in group_by_cols:
|
||||||
|
errors.append(
|
||||||
|
f"字段 '{col_name}' (别名为'{expr.alias}') 在SELECT中但不在GROUP BY中"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"GROUP BY验证异常: {e}")
|
||||||
|
|
||||||
|
return len(errors) == 0, errors
|
||||||
|
|
||||||
|
|
||||||
|
def check_join_conditions(sql: str, dialect: str = "tsql") -> List[str]:
|
||||||
|
"""
|
||||||
|
检查JOIN条件是否完整
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
问题列表(空表示无问题)
|
||||||
|
"""
|
||||||
|
from utils.sql_parser import parse_one, exp
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
|
||||||
|
# 遍历所有JOIN
|
||||||
|
for join in parsed.find_all(exp.Join):
|
||||||
|
# 检查是否有ON条件
|
||||||
|
on_condition = join.args.get("on")
|
||||||
|
if on_condition is None:
|
||||||
|
# 检查是否使用USING
|
||||||
|
using = join.args.get("using")
|
||||||
|
if using is None:
|
||||||
|
issues.append("JOIN缺少ON条件")
|
||||||
|
else:
|
||||||
|
# ON条件为空表达式
|
||||||
|
if isinstance(on_condition, exp.Empty):
|
||||||
|
issues.append("JOIN的ON条件为空")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"JOIN条件检查异常: {e}")
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def validate_order_by_fields(
|
||||||
|
sql: str,
|
||||||
|
schema_manager,
|
||||||
|
dialect: str = "tsql"
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
验证ORDER BY字段是否存在于对应表中
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema_manager: Schema管理器
|
||||||
|
dialect: SQL方言
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
问题列表
|
||||||
|
"""
|
||||||
|
from utils.sql_parser import parse_one, exp
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = parse_one(sql, dialect=dialect)
|
||||||
|
order = parsed.args.get("order")
|
||||||
|
|
||||||
|
if order:
|
||||||
|
for ordered in order.expressions:
|
||||||
|
expr = ordered.this
|
||||||
|
|
||||||
|
# 提取字段和表
|
||||||
|
if isinstance(expr, exp.Column):
|
||||||
|
tbl_name = expr.table
|
||||||
|
col_name = expr.name
|
||||||
|
|
||||||
|
if tbl_name:
|
||||||
|
table = schema_manager.get_table(tbl_name)
|
||||||
|
if table:
|
||||||
|
col_names = [c.name for c in table.columns]
|
||||||
|
if col_name not in col_names:
|
||||||
|
issues.append(
|
||||||
|
f"ORDER BY字段不存在: {tbl_name}.{col_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"ORDER BY验证异常: {e}")
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def full_validation_pipeline(
|
||||||
|
sql: str,
|
||||||
|
schema_manager,
|
||||||
|
dialect: str = "tsql",
|
||||||
|
check_dangerous: bool = True
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
完整验证流水线
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL语句
|
||||||
|
schema_manager: Schema管理器
|
||||||
|
dialect: SQL方言
|
||||||
|
check_dangerous: 是否检查危险操作
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
验证结果字典
|
||||||
|
"""
|
||||||
|
result = {
|
||||||
|
"valid": True,
|
||||||
|
"errors": [],
|
||||||
|
"warnings": [],
|
||||||
|
"suggestions": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. 语法验证
|
||||||
|
syntax_ok, syntax_errors = validate_sql_syntax(sql, dialect)
|
||||||
|
if not syntax_ok:
|
||||||
|
result["valid"] = False
|
||||||
|
result["errors"].extend(syntax_errors)
|
||||||
|
|
||||||
|
# 2. 危险操作检查
|
||||||
|
if check_dangerous:
|
||||||
|
safe, dangers = check_dangerous_operations(sql)
|
||||||
|
if not safe:
|
||||||
|
result["valid"] = False
|
||||||
|
result["errors"].append(f"包含危险操作: {', '.join(dangers)}")
|
||||||
|
|
||||||
|
# 3. Schema一致性验证
|
||||||
|
schema_ok, schema_errors = validate_schema_consistency(sql, schema_manager, dialect)
|
||||||
|
if not schema_ok:
|
||||||
|
result["valid"] = False
|
||||||
|
result["errors"].extend(schema_errors)
|
||||||
|
|
||||||
|
# 4. GROUP BY验证
|
||||||
|
groupby_ok, groupby_errors = validate_aggregation_groupby(sql, dialect)
|
||||||
|
if not groupby_ok:
|
||||||
|
result["valid"] = False
|
||||||
|
result["errors"].extend(groupby_errors)
|
||||||
|
|
||||||
|
# 5. JOIN条件验证
|
||||||
|
join_issues = check_join_conditions(sql, dialect)
|
||||||
|
if join_issues:
|
||||||
|
result["valid"] = False
|
||||||
|
result["errors"].extend(join_issues)
|
||||||
|
|
||||||
|
# 6. ORDER BY验证
|
||||||
|
order_issues = validate_order_by_fields(sql, schema_manager, dialect)
|
||||||
|
if order_issues:
|
||||||
|
result["warnings"].extend(order_issues)
|
||||||
|
|
||||||
|
# 7. SQL注入模式检查(警告)
|
||||||
|
injection_patterns = check_sql_injection_patterns(sql)
|
||||||
|
if injection_patterns:
|
||||||
|
result["warnings"].append(f"检测到可疑模式: {', '.join(injection_patterns)}")
|
||||||
|
|
||||||
|
return result
|
||||||
Reference in New Issue
Block a user