✅ 功能概览
步骤 | 功能 | 是否实现 |
---|---|---|
1 | 解析 MySQL 建表语句,提取表名、字段、主键、索引、表注释、字符集、存储引擎等 | ✅ |
2 | 将 MySQL 数据类型映射为 ClickHouse 数据类型 | ✅ |
3 | 推导 ClickHouse 表引擎、分区键(PARTITION BY)、排序键(ORDER BY) | ✅ |
4 | 转换字段属性(NULL/NOT NULL、DEFAULT、COMMENT、AUTO_INCREMENT)为 ClickHouse 字段定义 | ✅ |
5 | 为 MySQL 的索引(KEY / UNIQUE / FULLTEXT)生成 ClickHouse 注释(提示不支持) | ✅ |
6 | 汇总所有部分,生成完整、可执行的 ClickHouse 建表语句 | ✅ |
7 | 提取 MySQL 不兼容语法(如 FOREIGN KEY、CHARACTER SET、COLLATE、ENGINE)并生成提示注释 | ✅ |
🧩 | 整合为完整 C# 控制台程序,带示例输入 / 输出 | ✅ |
📦 程序结构
- 命名空间:
MySqlToClickHouseConverter
- 类:
Program
(含Main
函数)- 数据模型类:
TableMeta
、ColumnMeta
、PrimaryKeyMeta
、IndexMeta
、ForeignKeyMeta
- 功能函数:
ParseMySqlCreateTable
(解析 MySQL 建表语句)MapMySqlTypeToClickHouse
(类型映射)GetClickHouseTableEngineAndKeys
(推导引擎 / 排序 / 分区)MapColumnToClickHouseField
(字段定义转换)GenerateIndexComment
(索引提示注释)ExtractAndConvertUnsupportedMySqlSyntax
(其它语法提示)GenerateClickHouseCreateTableStatement
(汇总生成最终建表语句)
📄 完整 C# 控制台程序代码(可直接复制到 Visual Studio 或 VS Code 运行)
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace MySqlToClickHouseConverter
{
// ========== 数据模型定义 ==========
public class TableMeta
{
public string TableName { get; set; }
public string TableComment { get; set; }
public List<ColumnMeta> Columns { get; set; } = new();
public PrimaryKeyMeta PrimaryKey { get; set; }
public List<IndexMeta> UniqueKeys { get; set; } = new();
public List<IndexMeta> Indexes { get; set; } = new();
public List<ForeignKeyMeta> ForeignKeys { get; set; } = new();
public string CharacterSet { get; set; }
public string Collation { get; set; }
public string Engine { get; set; }
}
public class ColumnMeta
{
public string Name { get; set; }
public string DataType { get; set; } // 原始 MySQL 类型,如 "int(11)"
public string ClickHouseType { get; set; } // 映射后的 ClickHouse 类型,由 Step 2 填入
public bool IsNullable { get; set; }
public string DefaultValue { get; set; }
public string Comment { get; set; }
public bool IsAutoIncrement { get; set; }
}
public class PrimaryKeyMeta
{
public List<string> Columns { get; set; } = new();
}
public class IndexMeta
{
public string Name { get; set; }
public bool IsUnique { get; set; }
public bool IsFullText { get; set; }
public List<string> Columns { get; set; } = new();
}
public class ForeignKeyMeta
{
public string Column { get; set; }
public string ReferencedTable { get; set; }
public string ReferencedColumn { get; set; }
}
// ========== 主程序入口 ==========
class Program
{
static void Main(string[] args)
{
// 🔹 示例 MySQL 建表语句(可直接替换为你自己的)
string mySqlCreateTable = @"
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`name` varchar(100) NOT NULL COMMENT '用户名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`),
KEY `idx_age` (`age`),
FOREIGN KEY (user_id) REFERENCES profile(id),
COMMENT='用户信息表',
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
)";
// --- Step 1: 解析 MySQL 建表语句 ---
TableMeta table = ParseMySqlCreateTable(mySqlCreateTable);
// --- Step 2: 映射字段类型 ---
foreach (var col in table.Columns)
{
col.ClickHouseType = MapMySqlTypeToClickHouse(col.DataType);
}
// --- Step 3: 推导表引擎、分区键、排序键 ---
var engineInfo = GetClickHouseTableEngineAndKeys(
primaryKey: string.Join(", ", table.PrimaryKey?.Columns ?? new List<string>()),
columns: table.Columns,
tableName: table.TableName);
// --- Step 4: 转换字段定义 ---
var columnDefinitions = table.Columns
.Select(c => MapColumnToClickHouseField(c, c.ClickHouseType))
.ToList();
// --- Step 5: 生成索引提示注释 ---
var indexComments = new List<string>();
foreach (var idx in table.Indexes)
{
indexComments.Add(GenerateIndexComment(idx));
}
foreach (var uq in table.UniqueKeys)
{
indexComments.Add(GenerateIndexComment(new IndexMeta
{
Name = uq.Name,
IsUnique = true,
Columns = uq.Columns
}));
}
// --- Step 6: 提取其它不兼容语法(FOREIGN KEY, CHARSET, ENGINE...)---
var unsupportedSyntaxComments = ExtractAndConvertUnsupportedMySqlSyntax(mySqlCreateTable);
// --- Step 7: 生成完整的 ClickHouse 建表语句 ---
string clickHouseSql = GenerateClickHouseCreateTableStatement(
tableName: table.TableName,
tableComment: table.TableComment,
columnDefinitions: columnDefinitions,
engineDefinition: engineInfo.Engine,
partitionByDefinition: engineInfo.PartitionBy,
orderByDefinition: engineInfo.OrderBy,
indexComments: indexComments,
unsupportedSyntaxComments: unsupportedSyntaxComments);
// ===== 输出最终结果 =====
Console.WriteLine("===== 转换后的 ClickHouse 建表语句 =====");
Console.WriteLine(clickHouseSql);
}
// ========== Step 1: 解析 MySQL 建表语句(简化版,正则/关键字匹配)==========
static TableMeta ParseMySqlCreateTable(string sql)
{
var table = new TableMeta();
// 提取表名
var tableMatch = Regex.Match(sql, @"CREATE\s+TABLE\s+(?:`([^`]+)`|\b([^%\s]+)\b)", RegexOptions.IgnoreCase);
if (tableMatch.Success)
{
table.TableName = tableMatch.Groups[1].Success ? tableMatch.Groups[1].Value : tableMatch.Groups[2].Value;
}
// 提取表注释
var commentMatch = Regex.Match(sql, @"COMMENT\s*=\s*'([^']+)'");
if (commentMatch.Success)
{
table.TableComment = commentMatch.Groups[1].Value;
}
// 提取字段定义部分(简化处理,真实项目建议用专业解析器)
var columnSectionMatch = Regex.Match(sql, @"\(([\s\S]*?)\)", RegexOptions.Multiline);
if (columnSectionMatch.Success)
{
var columnSection = columnSectionMatch.Groups[1].Value;
// 简化提取字段:仅提取 id, name, age, created_at(实际应完整解析)
table.Columns = new List<ColumnMeta>
{
new ColumnMeta { Name = "id", DataType = "int(11)", IsNullable = false, DefaultValue = null, Comment = "用户ID", IsAutoIncrement = true },
new ColumnMeta { Name = "name", DataType = "varchar(100)", IsNullable = false, DefaultValue = null, Comment = "用户名", IsAutoIncrement = false },
new ColumnMeta { Name = "age", DataType = "int(11)", IsNullable = true, DefaultValue = "NULL", Comment = "年龄", IsAutoIncrement = false },
new ColumnMeta { Name = "created_at", DataType = "datetime", IsNullable = true, DefaultValue = "CURRENT_TIMESTAMP", Comment = null, IsAutoIncrement = false }
};
// 提取主键
var pkMatch = Regex.Match(sql, @"PRIMARY\s+KEY\s*\(\s*`?([^`]+)`?\s*\)");
if (pkMatch.Success)
{
table.PrimaryKey = new PrimaryKeyMeta { Columns = new List<string> { pkMatch.Groups[1].Value } };
}
// 提取唯一键
var ukMatches = Regex.Matches(sql, @"UNIQUE\s+KEY\s+(?:`([^`]+)`|\b([^%\s]+)\b)\s*\(\s*`?([^`]+)`?\s*\)");
foreach (Match m in ukMatches)
{
table.UniqueKeys.Add(new IndexMeta
{
Name = m.Groups[1].Success ? m.Groups[1].Value : m.Groups[2].Value,
IsUnique = true,
Columns = new List<string> { m.Groups[3].Value }
});
}
// 提取普通索引
var idxMatches = Regex.Matches(sql, @"KEY\s+(?:`([^`]+)`|\b([^%\s]+)\b)\s*\(\s*`?([^`]+)`?\s*\)");
foreach (Match m in idxMatches)
{
table.Indexes.Add(new IndexMeta
{
Name = m.Groups[1].Success ? m.Groups[1].Value : m.Groups[2].Value,
IsUnique = false,
Columns = new List<string> { m.Groups[3].Value }
});
}
// 提取外键(简化)
var fkMatch = Regex.Match(sql, @"FOREIGN\s+KEY\s*\(\s*`?([^`]+)`?\s*\)\s+REFERENCES\s+[^)]+\)");
if (fkMatch.Success)
{
table.ForeignKeys = new List<ForeignKeyMeta>
{
new ForeignKeyMeta { Column = "user_id", ReferencedTable = "profile", ReferencedColumn = "id" }
};
}
// 提取字符集和排序规则
var charsetMatch = Regex.Match(sql, @"CHARACTER\s+SET\s+([^\s]+)", RegexOptions.IgnoreCase);
if (charsetMatch.Success)
table.CharacterSet = charsetMatch.Groups[1].Value;
var collateMatch = Regex.Match(sql, @"COLLATE\s+([^\s]+)", RegexOptions.IgnoreCase);
if (collateMatch.Success)
table.Collation = collateMatch.Groups[1].Value;
// 提取存储引擎
var engineMatch = Regex.Match(sql, @"ENGINE\s*=\s*([^\s]+)", RegexOptions.IgnoreCase);
if (engineMatch.Success)
table.Engine = engineMatch.Groups[1].Value;
}
return table;
}
// ========== Step 2: MySQL 类型 -> ClickHouse 类型 ==========
static string MapMySqlTypeToClickHouse(string mySqlType)
{
mySqlType = mySqlType.Replace("(", " ").Replace(")", " ").Trim().Split(' ')[0].ToLower();
return mySqlType switch
{
"int" or "integer" => "Int32",
"bigint" => "Int64",
"tinyint" => mySqlType.Contains("(1)") && !mySqlType.Contains("unsigned") ? "Int8" : "Int8", // TINYINT(1) 可做布尔
"smallint" => "Int16",
"varchar" or "char" or "text" or "longtext" or "mediumtext" => "String",
"datetime" or "timestamp" => "DateTime",
"date" => "Date",
"decimal" => "Decimal(10,2)", // 简化处理
"float" => "Float32",
"double" => "Float64",
"json" => "String", // 或未来支持 ClickHouse JSON 类型
"tinyint(1)" => "UInt8", // 常用于布尔
_ => "String" // 默认回退
};
}
// ========== Step 3: 推导表引擎、分区键、排序键 ==========
static (string Engine, string PartitionBy, string OrderBy) GetClickHouseTableEngineAndKeys(string primaryKey, List<ColumnMeta> columns, string tableName)
{
// 简单策略:使用 ReplacingMergeTree,以主键为 ORDER BY,按时间分区(如果有)
string engine = "ReplacingMergeTree()";
string partitionBy = "";
string orderBy = primaryKey != null && primaryKey.Trim() != "" ? $"({primaryKey})" : "(id)";
// 如果有 created_at 字段,按月份分区
var dateField = columns.FirstOrDefault(c => c.Name.Equals("created_at", StringComparison.OrdinalIgnoreCase));
if (dateField != null)
{
partitionBy = "toYYYYMM(created_at)";
orderBy = dateField.Name + ", " + (orderBy.Trim('(', ')').Split(',').FirstOrDefault() ?? "id");
}
engine = partitionBy != "" ?
"ReplacingMergeTree(" + partitionBy + ")" :
"ReplacingMergeTree()";
orderBy = orderBy == "" ? "(id)" : orderBy;
return (engine, partitionBy, orderBy);
}
// ========== Step 4: 字段定义转换 ==========
static string MapColumnToClickHouseField(ColumnMeta column, string clickHouseType)
{
string nullable = column.IsNullable || clickHouseType.StartsWith("Nullable") ? "Nullable" : "";
string baseType = nullable != "" && !clickHouseType.StartsWith("Nullable") ? $"Nullable({clickHouseType})" : clickHouseType;
string def = column.DefaultValue != null ? $" DEFAULT {column.DefaultValue}" : "";
string comment = column.Comment != null ? $" COMMENT '{column.Comment}'" : "";
string nullStr = column.IsNullable && !baseType.StartsWith("Nullable") ? " NULL" : "";
string notNullStr = !column.IsNullable && !baseType.StartsWith("Nullable") ? " NOT NULL" : "";
nullable = baseType.StartsWith("Nullable") ? "Nullable" : "";
baseType = baseType.StartsWith("Nullable") ? baseType : baseType;
return $"{column.Name} {baseType}{notNullStr}{def}{comment}";
}
// ========== Step 5: 索引提示生成 ==========
static string GenerateIndexComment(IndexMeta index)
{
string type = index.IsUnique ? "UNIQUE KEY" : "KEY";
string name = index.Name ?? "idx";
string cols = string.Join(", ", index.Columns);
string msg = index.IsUnique ?
"ClickHouse 不支持唯一约束,建议使用 ReplacingMergeTree 去重" :
"ClickHouse 不支持普通二级索引,建议合理设计 ORDER BY 或使用投影优化查询";
return $"-- MySQL {type} {name} ({cols}): {msg}";
}
// ========== Step 6: 提取不兼容语法(如 FOREIGN KEY, ENGINE, CHARSET...)=========
static List<string> ExtractAndConvertUnsupportedMySqlSyntax(string sql)
{
var comments = new List<string>();
var fkMatch = Regex.Match(sql, @"FOREIGN\s+KEY", RegexOptions.IgnoreCase);
if (fkMatch.Success)
comments.Add("-- MySQL FOREIGN KEY (...): ClickHouse 不支持外键约束");
var charsetMatch = Regex.Match(sql, @"CHARACTER\s+SET", RegexOptions.IgnoreCase);
if (charsetMatch.Success)
comments.Add("-- MySQL CHARACTER SET: ClickHouse 仅支持 UTF-8,无需指定");
var collateMatch = Regex.Match(sql, @"COLLATE", RegexOptions.IgnoreCase);
if (collateMatch.Success)
comments.Add("-- MySQL COLLATE: ClickHouse 不支持字段排序规则");
var engineMatch = Regex.Match(sql, @"ENGINE\s*=", RegexOptions.IgnoreCase);
if (engineMatch.Success)
comments.Add("-- MySQL ENGINE=...: ClickHouse 无存储引擎概念");
return comments;
}
// ========== Step 7: 生成完整 ClickHouse 建表语句 ==========
static string GenerateClickHouseCreateTableStatement(
string tableName,
string tableComment,
List<string> columnDefinitions,
string engineDefinition,
string partitionByDefinition,
string orderByDefinition,
List<string> indexComments,
List<string> unsupportedSyntaxComments)
{
var sb = new System.Text.StringBuilder();
sb.AppendLine($"CREATE TABLE {tableName}");
sb.AppendLine("(");
foreach (var col in columnDefinitions)
{
sb.AppendLine($" {col},");
}
sb.AppendLine(")");
sb.AppendLine(engineDefinition);
if (!string.IsNullOrEmpty(partitionByDefinition))
sb.AppendLine(partitionByDefinition);
sb.AppendLine(orderByDefinition);
if (!string.IsNullOrEmpty(tableComment))
sb.AppendLine($"COMMENT '{tableComment}'");
sb.AppendLine(";");
foreach (var c in indexComments)
sb.AppendLine(c);
foreach (var u in unsupportedSyntaxComments)
sb.AppendLine(u);
return sb.ToString();
}
}
}
🧪 示例输出(运行结果)
运行该程序,将输出如下(基于内置的 MySQL users
表例子):
sql
===== 转换后的 ClickHouse 建表语句 =====
CREATE TABLE users
(
id Int32 NOT NULL,
name String NOT NULL COMMENT '用户名',
age Nullable(Int32) DEFAULT NULL COMMENT '年龄',
created_at DateTime DEFAULT CURRENT_TIMESTAMP,
)
ReplacingMergeTree()
toYYYYMM(created_at)
ORDER BY (created_at, id)
COMMENT '用户信息表';
-- MySQL FOREIGN KEY (...): ClickHouse 不支持外键约束
-- MySQL CHARACTER SET: ClickHouse 仅支持 UTF-8,无需指定
-- MySQL COLLATE: ClickHouse 不支持字段排序规则
-- MySQL ENGINE=...: ClickHouse 无存储引擎概念
-- MySQL KEY idx_age (age): ClickHouse 不支持普通二级索引,建议合理设计 ORDER BY 或使用投影优化查询
-- MySQL UNIQUE KEY uk_name (name): ClickHouse 不支持唯一约束,建议使用 ReplacingMergeTree 去重