开发者

C#实现MySQL中Clickhouse建表语句的转换工具

目录
  • 功能概览
  • 程序结构
  • 完整 C# 控制台程序代码(可直接复制到 Visual Studio 或 VS Code 运行)
  • 示例输出(运行结果)

功能概览

步骤功能是否实现
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 函数)
  • 数据模型类:TableMetaColumnMetaPrimaryKeyMetaIndexMetaForeignKeyMeta

功能函数:

  • 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; }
    }

  js  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: 提取其它不兼容php语法(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 columnSectionjsMatch = 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 engineMatandroidch = 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 去重

到此这篇关于C#实现MySQL中Clickhouse建表语句的转换工具的文章就介绍到这了,更多相关C# MySQL Clickhouse建表语句转换内容请搜索编程客www.devze.com栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜