C#实现MySQL→Clickhouse建表语句转换工具
✅ 功能概览
步骤 | 功能 | 是否实现 |
---|---|---|
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 运行)
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
表例子):
===== 转换后的 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 去重