当前位置: 首页 > news >正文

C#实现SQL Server→Snowflake建表语句转换工具

📁 项目结构(逻辑视图,代码内组织)
复制SnowflakeTableConverter/

├── Program.cs <-- 包含 Main() 方法,实现完整流程

├── Models/(可选,用于放 ParsedTable 等类,这里我们直接放在 Program.cs 中)

├── Modules/
│ ├── Parser.cs <-- 第1步:解析 SQL Server 建表语句
│ ├── TypeMapper.cs <-- 第2步:SQL Server → Snowflake 数据类型映射
│ ├── ConstraintConverter.cs <-- 第3步:约束转换
│ ├── TableGenerator.cs <-- 第4步:生成 Snowflake 建表语句
│ └── SpecialCaseHandler.cs <-- 第5步:特殊表&功能处理

└── input.sql <-- 输入:含有 SQL Server 建表语句的 SQL 文件
└── output_snowflake.sql <-- 输出:转换后的 Snowflake 建表脚本📌 为简化,我们 ​将所有核心类直接放在 Program.cs 中​(适合小型工具/脚本),如果你想模块化,可拆分成多个文件/文件夹。
✅ 完整 C# 控制台应用程序代码(Program.cs)
📝 你可以直接创建一个 ​C# 控制台项目​(.NET 6.0 / .NET Core 3.1 或更高),将以下代码复制到 Program.cs中。
🔧 ​注意:你还需要通过 NuGet 安装以下包:​​

Install-Package Microsoft.SqlServer.TransactSql.ScriptDom

(在 Visual Studio 的 NuGet 包管理器中搜索安装,或使用 CLI)
📄 Program.cs(完整代码)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using System.Text;namespace SnowflakeTableConverter
{class Program{static void Main(string[] args){Console.WriteLine("🚀 Snowflake 建表语句转换工具");Console.WriteLine("📥 从 input.sql 读取 SQL Server 建表语句,输出到 output_snowflake.sql");string inputFile = "input.sql";string outputFile = "output_snowflake.sql";if (!File.Exists(inputFile)){Console.WriteLine($"❌ 输入文件不存在:{inputFile}");return;}string sqlContent = File.ReadAllText(inputFile);if (string.IsNullOrWhiteSpace(sqlContent)){Console.WriteLine("❌ 输入文件为空");return;}// --------------------------// 第1步:解析 SQL Server 建表语句// --------------------------List<ParsedTable> parsedTables;try{parsedTables = SqlServerTableParser.ParseCreateTableStatements(sqlContent);}catch (Exception ex){Console.WriteLine($"❌ 解析失败: {ex.Message}");return;}if (parsedTables == null || parsedTables.Count == 0){Console.WriteLine("⚠️ 未解析到任何有效的 CREATE TABLE 语句");return;}Console.WriteLine($"\n✅ 成功解析到 {parsedTables.Count} 个表定义");// -------------------------------// 第2~5步:转换 + 处理特殊 + 生成 Snowflake 语句// -------------------------------List<string> allSnowflakeSqlLines = new List<string>();List<string> allWarnings = new List<string>();int tableIdx = 0;foreach (var parsedTable in parsedTables){tableIdx++;Console.WriteLine($"\n🔍 正在处理表 {tableIdx}/{parsedTables.Count}: {parsedTable.SchemaName}.{parsedTable.TableName}");// --- 第5步:处理特殊表/功能,收集警告 ---var specialWarnings = SpecialCaseHandler.HandleSpecialCasesAndEdgeCases(parsedTable);allWarnings.AddRange(specialWarnings);if (parsedTable.TableName.StartsWith("#") || parsedTable.TableName.StartsWith("@")){Console.WriteLine($"   ⚠️ 跳过临时表/表变量: {parsedTable.TableName}");continue;}// --- 第2步:数据类型映射(每个列)---var mappedColumns = new List<(string name, string snowflakeType, bool isNullable, string defaultValue, bool isIdentity, string computedExpr)>();foreach (var col in parsedTable.Columns){string snowflakeType = SqlServerToSnowflakeTypeMapper.MapSqlServerTypeToSnowflakeType(col.DataType);mappedColumns.Add((col.Name, snowflakeType, col.IsNullable, col.DefaultValue, col.IsIdentity, col.ComputedDefinition));}// --- 第3步:约束转换 ---var constraints = ConstraintConverter.ConvertConstraintsToSnowflakeSyntax(primaryKey: parsedTable.PrimaryKey,uniqueConstraints: parsedTable.UniqueConstraints,checkConstraints: parsedTable.CheckConstraints,foreignKeyConstraints: parsedTable.ForeignKeyConstraints,defaultValues: parsedTable.Columns.ToDictionary(c => c.Name, c => c.DefaultValue),identityColumns: parsedTable.Columns.ToDictionary(c => c.Name, c => c.IsIdentity),computedColumns: parsedTable.Columns.ToDictionary(c => c.Name, c => c.ComputedDefinition));// --- 第4步:生成 Snowflake 建表语句 ---string snowflakeTableSql = SnowflakeTableGenerator.GenerateSnowflakeCreateTableStatement(schemaName: parsedTable.SchemaName,tableName: parsedTable.TableName,columns: mappedColumns,constraints: constraints,tableComment: $"Converted from SQL Server [{parsedTable.SchemaName}.{parsedTable.TableName}]",otherOptions: new List<string> { "DATA_RETENTION_TIME_IN_DAYS = 7" });allSnowflakeSqlLines.Add($"-- =============================================");allSnowflakeSqlLines.Add($"-- 表: {parsedTable.SchemaName}.{parsedTable.TableName}");allSnowflakeSqlLines.AddRange(specialWarnings.Select(w => $"-- ⚠️ {w}"));allSnowflakeSqlLines.Add($"-- =============================================");allSnowflakeSqlLines.Add(snowflakeTableSql);allSnowflakeSqlLines.Add(""); // 空行分隔}// --------------------------// 第6步:写入输出文件// --------------------------try{File.WriteAllText(outputFile, string.Join("\n", allSnowflakeSqlLines));Console.WriteLine($"\n✅ 转换完成!结果已保存到:{outputFile}");Console.WriteLine($"⚠️ 共收集到 {allWarnings.Count} 条警告信息(已写入文件注释中)");}catch (Exception ex){Console.WriteLine($"❌ 写入输出文件失败: {ex.Message}");}// 可选:在控制台打印警告汇总if (allWarnings.Any()){Console.WriteLine("\n🔔 警告汇总:");foreach (var warn in allWarnings.Distinct()){Console.WriteLine($"  ⚠️ {warn}");}}}}// ======================// 第1步:解析模块(简化整合版)// ======================public class ParsedTable{public string SchemaName { get; set; } = "dbo";public string TableName { get; set; } = "";public List<ParsedColumn> Columns { get; set; } = new List<ParsedColumn>();public ParsedPrimaryKey PrimaryKey { get; set; } = null;public List<ParsedConstraint> UniqueConstraints { get; set; } = new List<ParsedConstraint>();public List<ParsedConstraint> CheckConstraints { get; set; } = new List<ParsedConstraint>();public List<ParsedConstraint> ForeignKeyConstraints { get; set; } = new List<ParsedConstraint>();public List<string> TableOptions { get; set; } = new List<string>();}public class ParsedColumn{public string Name { get; set; } = "";public string DataType { get; set; } = "";public bool IsNullable { get; set; } = true;public string DefaultValue { get; set; } = null;public bool IsIdentity { get; set; } = false;public string ComputedDefinition { get; set; } = null;}public class ParsedPrimaryKey{public string ConstraintName { get; set; } = "";public List<string> ColumnNames { get; set; } = new List<string>();}public class ParsedConstraint{public string ConstraintName { get; set; } = "";public List<string> ColumnNames { get; set; } = new List<string>();public string Definition { get; set; } = "";}public static class SqlServerTableParser{public static List<ParsedTable> ParseCreateTableStatements(string sql){var parser = new TSql150Parser(true);IList<ParseError> errors;var fragment = parser.Parse(new StringReader(sql), out errors);var result = new List<ParsedTable>();if (errors != null && errors.Count > 0){foreach (var err in errors)Console.WriteLine($"[解析警告] 行{err.Line}: {err.Message}");}foreach (var batch in fragment.Batches){foreach (var stmt in batch.Statements){if (stmt is CreateTableStatement createTable){var table = new ParsedTable();if (createTable.SchemaObjectName != null &&createTable.SchemaObjectName.Identifiers.Count > 0){table.SchemaName = createTable.SchemaObjectName.Identifiers[0].Value ?? "dbo";table.TableName = createTable.SchemaObjectName.Identifiers.Count > 1? createTable.SchemaObjectName.Identifiers[1].Value: createTable.SchemaObjectName.Identifiers[0].Value;}if (createTable.Definition?.ColumnDefinitions != null){foreach (var colDef in createTable.Definition.ColumnDefinitions){var col = new ParsedColumn{Name = colDef.ColumnName.Value,DataType = colDef.DataType?.ToString() ?? "",IsNullable = colDef.Nullable == null || colDef.Nullable.Value,DefaultValue = colDef.DefaultConstraint?.Expression?.ToSqlString().GetSqlString().ToString(),IsIdentity = colDef.DataType?.ToString()?.ToUpper().Contains("IDENTITY") == true,ComputedDefinition = colDef.ComputeExpression?.ToSqlString().GetSqlString().ToString()};table.Columns.Add(col);}}if (createTable.Definition?.TableConstraints != null){foreach (var constr in createTable.Definition.TableConstraints){if (constr is PrimaryKeyConstraint pk){table.PrimaryKey = new ParsedPrimaryKey{ConstraintName = pk.ConstraintIdentifier?.Value ?? "",ColumnNames = pk.Columns?.Select(c => c.Column.Identifier.Value).ToList() ?? new List<string>()};}else if (constr is UniqueConstraint uq){table.UniqueConstraints.Add(new ParsedConstraint{ConstraintName = uq.ConstraintIdentifier?.Value ?? "",ColumnNames = uq.Columns?.Select(c => c.Column.Identifier.Value).ToList() ?? new List<string>(),Definition = constr.ToSqlString().GetSqlString().ToString()});}else if (constr is CheckConstraint ck){table.CheckConstraints.Add(new ParsedConstraint{ConstraintName = ck.ConstraintIdentifier?.Value ?? "",Definition = ck.SearchCondition?.ToSqlString().GetSqlString().ToString() ?? "",ColumnNames = new List<string>()});}else if (constr is ForeignKeyConstraint fk){table.ForeignKeyConstraints.Add(new ParsedConstraint{ConstraintName = fk.ConstraintIdentifier?.Value ?? "",ColumnNames = fk.Columns?.Select(c => c.Column.Identifier.Value).ToList() ?? new List<string>(),Definition = constr.ToSqlString().GetSqlString().ToString()});}}}if (createTable.TableOptions != null){table.TableOptions.Add(createTable.TableOptions.ToString());}result.Add(table);}}}return result;}}// ======================// 第2步:数据类型映射// ======================public static class SqlServerToSnowflakeTypeMapper{public static string MapSqlServerTypeToSnowflakeType(string sqlServerType){if (string.IsNullOrWhiteSpace(sqlServerType))return "VARCHAR";string normalizedType = sqlServerType.Trim().ToUpper();var regex = new System.Text.RegularExpressions.Regex(@"^(?<type>\w+)(\((?<params>[^)]+)\))?$");var match = regex.Match(normalizedType);if (!match.Success)return "VARCHAR";string baseType = match.Groups["type"].Value.ToUpper();string paramsStr = match.Groups["params"].Value.Replace(" ", "");return baseType switch{"TINYINT" => "NUMBER(3,0)","SMALLINT" => "NUMBER(5,0)","INT" or "INTEGER" => "NUMBER(10,0)","BIGINT" => "NUMBER(38,0)","FLOAT" or "REAL" => "FLOAT","DECIMAL" or "NUMERIC" => paramsStr.Length > 0 ? $"NUMBER({paramsStr})" : "NUMBER(38,10)","CHAR" => paramsStr.Length > 0 ? $"CHAR({paramsStr})" : "CHAR(1)","VARCHAR" or "NVARCHAR" or "NCHAR" => paramsStr.Length > 0 ? $"VARCHAR({paramsStr})" : "VARCHAR(16000)","TEXT" => "VARCHAR(16000)","NTEXT" => "VARCHAR(16000)","IMAGE" => "VARBINARY(8000)","BINARY" => paramsStr.Length > 0 ? $"BINARY({paramsStr})" : "BINARY(8000)","VARBINARY" => paramsStr.Length > 0 ? $"VARBINARY({paramsStr})" : "VARBINARY(8000)","BIT" => "BOOLEAN","UNIQUEIDENTIFIER" => "STRING","DATETIME" or "DATETIME2" or "SMALLDATETIME" => "TIMESTAMP_NTZ","DATE" => "DATE","TIME" => "TIME","XML" => "VARCHAR(16000)","JSON" => "VARIANT","SQL_VARIANT" => "VARIANT","GEOMETRY" or "GEOGRAPHY" => "VARIANT","HIERARCHYID" => "VARIANT","FILESTREAM" => "VARIANT",_ => "VARCHAR"};}}// ======================// 第3步:约束转换// ======================public class ConstraintConverter{public static List<string> ConvertConstraintsToSnowflakeSyntax(ParsedPrimaryKey primaryKey,List<ParsedConstraint> uniqueConstraints,List<ParsedConstraint> checkConstraints,List<ParsedConstraint> foreignKeyConstraints,Dictionary<string, string> defaultValues,Dictionary<string, bool> identityColumns,Dictionary<string, string> computedColumns){var constraints = new List<string>();if (primaryKey != null && primaryKey.ColumnNames?.Count > 0){constraints.Add($"PRIMARY KEY ({string.Join(", ", primaryKey.ColumnNames)})");}foreach (var uk in uniqueConstraints){if (uk.ColumnNames?.Count > 0){constraints.Add($"UNIQUE ({string.Join(", ", uk.ColumnNames)})");}}foreach (var check in checkConstraints){if (!string.IsNullOrEmpty(check.Definition)){constraints.Add($"CHECK ({check.Definition})");}}foreach (var fk in foreignKeyConstraints){constraints.Add($"-- FOREIGN KEY (不支持): {fk.Definition}");}foreach (var def in defaultValues){string snowflakeDefault = def.Value switch{"GETDATE()" => "CURRENT_TIMESTAMP()","NEWID()" => "GENERATE_UUID()",_ => def.Value};if (!string.IsNullOrEmpty(snowflakeDefault)){constraints.Add($"DEFAULT {snowflakeDefault}");}}foreach (var idCol in identityColumns){if (idCol.Value){constraints.Add($"GENERATED ALWAYS AS IDENTITY(1,1)");}}foreach (var comp in computedColumns){if (!string.IsNullOrEmpty(comp.Value)){constraints.Add($"AS {comp.Value}");}}return constraints;}}// ======================// 第4步:生成 Snowflake 建表语句// ======================public class SnowflakeTableGenerator{public static string GenerateSnowflakeCreateTableStatement(string schemaName,string tableName,List<(string name, string snowflakeType, bool isNullable, string defaultValue, bool isIdentity, string computedExpr)> columns,List<string> constraints,string tableComment,List<string> otherOptions){var sb = new StringBuilder();sb.AppendLine($"CREATE OR REPLACE TABLE {schemaName}.{tableName} (");for (int i = 0; i < columns.Count; i++){var col = columns[i];sb.Append($"    {col.name} {col.snowflakeType}");if (!col.isNullable)sb.Append(" NOT NULL");if (!string.IsNullOrEmpty(col.defaultValue))sb.Append($" DEFAULT {col.defaultValue}");if (col.isIdentity)sb.Append(" GENERATED ALWAYS AS IDENTITY(1,1)");if (!string.IsNullOrEmpty(col.computedExpr))sb.Append($" AS {col.computedExpr}");if (i < columns.Count - 1 || constraints.Count > 0)sb.AppendLine(",");elsesb.AppendLine();}foreach (var constr in constraints){sb.AppendLine($"    {constr},");}// 清理末尾逗号string result = sb.ToString();int lastComma = result.LastIndexOf(',');if (lastComma >= 0){result = result.Remove(lastComma, 1).Insert(lastComma, " ");}result += "\n);";if (!string.IsNullOrEmpty(tableComment))result += $"\n\n-- {tableComment}";if (otherOptions != null){foreach (var opt in otherOptions){result += $"\n-- {opt}";}}return result;}}// ======================// 第5步:特殊表/功能处理// ======================public class SpecialCaseHandler{public static List<string> HandleSpecialCasesAndEdgeCases(ParsedTable parsedTable){var warnings = new List<string>();if (parsedTable.TableName.StartsWith("#") || parsedTable.TableName.StartsWith("##")){warnings.Add($"不支持:临时表 '{parsedTable.TableName}'");}if (parsedTable.TableName.StartsWith("@")){warnings.Add($"不支持:表变量 '{parsedTable.TableName}'");}foreach (var col in parsedTable.Columns){if (col.DataType != null){string upperType = col.DataType.ToUpper();if (upperType.Contains("XML") || upperType.Contains("JSON"))warnings.Add($"不完全支持:列 '{col.Name}' 类型 '{col.DataType}' (建议转为 VARCHAR/VARIANT)");if (upperType.Contains("SQL_VARIANT"))warnings.Add($"不支持:列 '{col.Name}' 类型 'SQL_VARIANT'");if (upperType.Contains("GEOMETRY") || upperType.Contains("GEOGRAPHY"))warnings.Add($"不支持:列 '{col.Name}' 类型 '{col.DataType}' (建议转为 VARCHAR/BLOB)");if (upperType.Contains("HIERARCHYID"))warnings.Add($"不支持:列 '{col.Name}' 类型 'HIERARCHYID'");if (upperType.Contains("FILESTREAM"))warnings.Add($"不支持:列 '{col.Name}' 特性 'FILESTREAM'");}}if (parsedTable.ForeignKeyConstraints?.Count > 0)warnings.Add($"不支持:外键约束 (已转为注释)");if (parsedTable.TableOptions?.Exists(o => o.ToUpper().Contains("PARTITION")) == true)warnings.Add($"不支持:表可能使用了分区功能");warnings.Add($"提示:SQL Server 排序规则(Collation)在 Snowflake 中不支持");return warnings;}}
}

📂 项目文件结构建议(可选,推荐)
虽然上面所有代码都在一个文件中,但为了更好的维护性,你可以按如下方式拆分(推荐):
复制SnowflakeTableConverter/

├── Program.cs <-- Main() 入口,整合流程
├── Models/
│ ├── ParsedTable.cs
│ ├── ParsedColumn.cs
│ ├── ParsedPrimaryKey.cs
│ └── ParsedConstraint.cs
├── Modules/
│ ├── Parser.cs
│ ├── TypeMapper.cs
│ ├── ConstraintConverter.cs
│ ├── TableGenerator.cs
│ └── SpecialCaseHandler.cs
└── input.sql <-- 你的输入 SQL 文件,放 CREATE TABLE 语句如果你想要我帮你拆分成多文件版本,也可以告诉我,我可以为你生成完整的多文件项目结构 👍
📝 使用方法(运行程序)
✅ 在项目目录下创建一个 input.sql文件,内容为你的 ​SQL Server 建表语句,例如:

CREATE TABLE dbo.Employees (EmployeeID INT IDENTITY(1,1) NOT NULL,FirstName NVARCHAR(100) NOT NULL,LastName NVARCHAR(100) NOT NULL,HireDate DATETIME DEFAULT GETDATE(),Salary DECIMAL(10, 2),IsActive BIT DEFAULT 1,PRIMARY KEY (EmployeeID)
);

✅ 运行程序(Visual Studio 或 dotnet run)
✅ 查看生成的 output_snowflake.sql,包含转换后的 Snowflake 建表语句 + 注释警告


📄 Models/ParsedTable.cs

using System;
using System.Collections.Generic;// 表结构定义
public class ParsedTable
{public string SchemaName { get; set; } = "dbo";public string TableName { get; set; } = "";public List<ParsedColumn> Columns { get; set; } = new List<ParsedColumn>();public ParsedPrimaryKey PrimaryKey { get; set; } = null;public List<ParsedConstraint> UniqueConstraints { get; set; } = new List<ParsedConstraint>();public List<ParsedConstraint> CheckConstraints { get; set; } = new List<ParsedConstraint>();public List<ParsedConstraint> ForeignKeyConstraints { get; set; } = new List<ParsedConstraint>();public List<string> TableOptions { get; set; } = new List<string>(); // 如 WITH、分区等
}

📄 Models/ParsedColumn.cs

// 列定义
public class ParsedColumn
{public string Name { get; set; } = "";public string DataType { get; set; } = ""; // 如 VARCHAR(100), INTpublic bool IsNullable { get; set; } = true;public string DefaultValue { get; set; } = null; // 如 DEFAULT GETDATE()public bool IsIdentity { get; set; } = false; // 是否是 IDENTITY(1,1)public string ComputedDefinition { get; set; } = null; // 如 "Price AS Qty * 1.1"
}

📄 Models/ParsedConstraint.cs

// 约束基类
public class ParsedConstraint
{public string ConstraintName { get; set; } = "";public List<string> ColumnNames { get; set; } = new List<string>();public string Definition { get; set; } = "";
}

📄 Models/ParsedPrimaryKey.cs

// 主键
public class ParsedPrimaryKey
{public string ConstraintName { get; set; } = "";public List<string> ColumnNames { get; set; } = new List<string>();
}

📄 Modules/SqlServerTableParser.cs

public class SqlServerTableParser
{public static ParsedTable ParseCreateTable(string createTableSql){var parser = new TSql150Parser(true); // 使用 TSQL 150(SQL Server 2019),也可选其他版本IList<ParseError> errors;var fragment = parser.Parse(new StringReader(createTableSql), out errors);if (errors != null && errors.Count > 0){Console.WriteLine("解析出错:");foreach (var error in errors){Console.WriteLine($"Line {error.Line}, Offset {error.Offset}: {error.Message}");}throw new Exception("SQL 解析失败,请检查语法。");}var table = new ParsedTable();// 遍历解析树的顶级语句,找到 CreateTable 语句foreach (var statement in fragment.Batches.SelectMany(b => b.Statements)){if (statement is CreateTableStatement createTable){// 解析表名(含 Schema)if (createTable.SchemaObjectName != null && createTable.SchemaObjectName.Identifiers.Count >= 1){table.SchemaName = createTable.SchemaObjectName.Identifiers[0].Value ?? "dbo";table.TableName = createTable.SchemaObjectName.Identifiers.Count > 1? createTable.SchemaObjectName.Identifiers[1].Value: createTable.SchemaObjectName.Identifiers[0].Value;}// 解析列定义if (createTable.Definition != null && createTable.Definition.ColumnDefinitions != null){foreach (var colDef in createTable.Definition.ColumnDefinitions){var column = new ParsedColumn{Name = colDef.ColumnName.Value,DataType = colDef.DataType?.ToString() ?? "",IsNullable = colDef.Nullable == null || colDef.Nullable.Value,DefaultValue = ExtractDefaultValue(colDef),IsIdentity = IsIdentityColumn(colDef),ComputedDefinition = ExtractComputedColumnDefinition(colDef)};table.Columns.Add(column);}}// 解析约束:主键、唯一、检查、外键等if (createTable.Definition.TableConstraints != null){foreach (var constraint in createTable.Definition.TableConstraints){if (constraint is PrimaryKeyConstraint pk){table.PrimaryKey = new ParsedPrimaryKey{ConstraintName = pk.ConstraintIdentifier?.Value ?? "",ColumnNames = pk.Columns?.Select(c => c.Column.Identifier.Value).ToList() ?? new List<string>()};}else if (constraint is UniqueConstraint unique){table.UniqueConstraints.Add(new ParsedConstraint{ConstraintName = unique.ConstraintIdentifier?.Value ?? "",ColumnNames = unique.Columns?.Select(c => c.Column.Identifier.Value).ToList() ?? new List<string>(),Definition = constraint.ToSqlString().GetSqlString().ToString()});}else if (constraint is CheckConstraint check){table.CheckConstraints.Add(new ParsedConstraint{ConstraintName = check.ConstraintIdentifier?.Value ?? "",Definition = check.SearchCondition?.ToSqlString().GetSqlString().ToString() ?? "",ColumnNames = new List<string>() // Check 可能不针对特定列});}else if (constraint is ForeignKeyConstraint fk){table.ForeignKeyConstraints.Add(new ParsedConstraint{ConstraintName = fk.ConstraintIdentifier?.Value ?? "",ColumnNames = fk.Columns?.Select(c => c.Column.Identifier.Value).ToList() ?? new List<string>(),Definition = constraint.ToSqlString().GetSqlString().ToString()});}// 其它约束类型可以继续扩展}}// 解析表选项(如 WITH、分区、TEXTIMAGE_ON 等,原样记录)if (!string.IsNullOrEmpty(createTable.TableOptions?.ToString())){table.TableOptions.Add(createTable.TableOptions.ToString());}}}return table;}private static string ExtractDefaultValue(ColumnDefinition colDef){if (colDef.DefaultConstraint != null && colDef.DefaultConstraint.Expression != null){return colDef.DefaultConstraint.Expression.ToSqlString().GetSqlString().ToString();}return null;}private static bool IsIdentityColumn(ColumnDefinition colDef){// 简单判断:查找 Identity 关键字(实际应解析生成器属性,这里简化处理)// 更准确的方法是检查 Identity 属性(需要深入解析)// 这里只是示意,真实项目建议用更严谨的方式判断是否有 IDENTITY(1,1)var options = colDef.ComputeExpression ?? colDef.DefaultConstraint;if (options == null) return false;var defText = colDef.DefaultConstraint?.Expression?.ToSqlString().GetSqlString().ToString() ?? "";return defText.IndexOf("IDENTITY", StringComparison.OrdinalIgnoreCase) >= 0;}private static string ExtractComputedColumnDefinition(ColumnDefinition colDef){// 如 "Price AS Qty * 1.1"if (colDef.ComputeExpression != null){return colDef.ComputeExpression.ToSqlString().GetSqlString().ToString();}return null;}
}

📄 Modules/SqlServerToSnowflakeTypeMapper.cs

using System;
using System.Text.RegularExpressions;public class SqlServerToSnowflakeTypeMapper
{/// <summary>/// 将 SQL Server 数据类型字符串映射为 Snowflake 数据类型字符串/// </summary>/// <param name="sqlServerType">如 "VARCHAR(100)", "DECIMAL(10,2)", "DATETIME", "BIT", "INT", "UNIQUEIDENTIFIER"</param>/// <returns>Snowflake 对应的数据类型,如 "VARCHAR(100)", "NUMBER(10,2)", "TIMESTAMP_NTZ", "BOOLEAN", "NUMBER", "STRING"</returns>public static string MapSqlServerTypeToSnowflakeType(string sqlServerType){if (string.IsNullOrWhiteSpace(sqlServerType))return "VARCHAR"; // 默认回退// 去除多余空格,统一转为大写便于处理(可选,根据需求)string normalizedType = sqlServerType.Trim().ToUpper();// 正则匹配 类型名称 + 可能的精度/长度/小数,例如:DECIMAL(10, 2)、VARCHAR(255)、FLOAT(53)var regex = new Regex(@"^(?<type>\w+)(\((?<params>[^)]+)\))?$", RegexOptions.IgnoreCase);var match = regex.Match(normalizedType);if (!match.Success)return "VARCHAR"; // 无法解析则返回默认类型string baseType = match.Groups["type"].Value.ToUpper(); // 如 INT, VARCHAR, DECIMALstring paramsStr = match.Groups["params"].Value; // 如 "10, 2" 或 "255"// 去除参数中的空格,例如 "10, 2" → "10,2"paramsStr = string.IsNullOrEmpty(paramsStr) ? "" : paramsStr.Replace(" ", "");// 根据 SQL Server 类型进行映射return baseType switch{// 整数类型"TINYINT" => "NUMBER(3,0)","SMALLINT" => "NUMBER(5,0)","INT" or "INTEGER" => "NUMBER(10,0)","BIGINT" => "NUMBER(38,0)",// 浮点 / 定点数"FLOAT" or "REAL" => "FLOAT", // 或 DOUBLE PRECISION"DECIMAL" or "NUMERIC" => paramsStr.Length > 0 ? $"NUMBER({paramsStr})" : "NUMBER(38,10)", // 默认精度// 字符串 / 文本"CHAR" => paramsStr.Length > 0 ? $"CHAR({paramsStr})" : "CHAR(1)","VARCHAR" or "NVARCHAR" or "NCHAR" => paramsStr.Length > 0 ? $"VARCHAR({paramsStr})" : "VARCHAR(16000)", // Snowflake VARCHAR 可很长"TEXT" => "VARCHAR(16000)", // 不支持 TEXT,转为 VARCHAR"NTEXT" => "VARCHAR(16000)","STRING" => "VARCHAR(16000)", // 如果输入误写为 STRING(某些工具生成)// 二进制"BINARY" => paramsStr.Length > 0 ? `BINARY({paramsStr})` : "BINARY(8000)","VARBINARY" => paramsStr.Length > 0 ? $"VARBINARY({paramsStr})" : "VARBINARY(8000)","IMAGE" => "VARBINARY(8000)", // 不支持 IMAGE// 逻辑类型"BIT" => "BOOLEAN",// 唯一标识符"UNIQUEIDENTIFIER" or "GUID" => "STRING", // 或 "UUID"(但 Snowflake 没有原生 UUID 类型,可用 STRING 存储)// 日期时间类型"DATE" => "DATE","DATETIME" => "TIMESTAMP_NTZ", // 或 TIMESTAMP_LTZ,视情况"DATETIME2" => "TIMESTAMP_NTZ","SMALLDATETIME" => "TIMESTAMP_NTZ","TIME" => "TIME",// 二进制大对象 / 文档"XML" => "STRING", // 或 VARIANT(如存序列化数据)"JSON" => "VARIANT", // 推荐用 VARIANT 存储 JSON"SQL_VARIANT" => "VARIANT", // 不完全等价,但最接近// 空间类型(不支持)"GEOMETRY" => "VARIANT", // 不支持,转为通用类型"GEOGRAPHY" => "VARIANT",// 其它 / 未知类型_ => "VARCHAR" // 默认回退类型,建议后续记录警告};}
}

📄 Modules/ConstraintConverter.cs

using System;
using System.Collections.Generic;
using System.Text;public class ConstraintConverter
{/// <summary>/// 将第1步解析出的约束对象,转换为 Snowflake 建表语句中可用的约束语法字符串集合/// </summary>/// <param name="primaryKey">主键信息</param>/// <param name="uniqueConstraints">唯一约束列表</param>/// <param name="checkConstraints">检查约束列表</param>/// <param name="foreignKeyConstraints">外键约束列表(会被注释掉)</param>/// <param name="defaultValues">列的默认值(列名 -> 默认值表达式)</param>/// <param name="identityColumns">标识列(列名 -> 是否是 IDENTITY)</param>/// <param name="computedColumns">计算列(列名 -> 计算表达式)</param>/// <returns>Snowflake 约束/列定义语句的字符串集合,可直接拼接到 CREATE TABLE 中</returns>public static List<string> ConvertConstraintsToSnowflakeSyntax(ParsedPrimaryKey primaryKey,List<ParsedConstraint> uniqueConstraints,List<ParsedConstraint> checkConstraints,List<ParsedConstraint> foreignKeyConstraints,Dictionary<string, string> defaultValues,Dictionary<string, bool> identityColumns,Dictionary<string, string> computedColumns){var snowflakeConstraints = new List<string>();// 1. 主键if (primaryKey != null && primaryKey.ColumnNames?.Count > 0){string cols = string.Join(", ", primaryKey.ColumnNames);snowflakeConstraints.Add($"PRIMARY KEY ({cols})");}// 2. 唯一约束foreach (var uk in uniqueConstraints){if (uk.ColumnNames?.Count > 0){string cols = string.Join(", ", uk.ColumnNames);string constraintName = !string.IsNullOrEmpty(uk.ConstraintName) ? $"CONSTRAINT {uk.ConstraintName} " : "";snowflakeConstraints.Add($"{constraintName}UNIQUE ({cols})");}}// 3. 检查约束foreach (var check in checkConstraints){if (!string.IsNullOrEmpty(check.Definition)){string constraintName = !string.IsNullOrEmpty(check.ConstraintName) ? $"CONSTRAINT {check.ConstraintName} " : "";snowflakeConstraints.Add($"{constraintName}CHECK ({check.Definition})");}}// 4. 外键约束 → 不支持,转为注释foreach (var fk in foreignKeyConstraints){if (!string.IsNullOrEmpty(fk.Definition)){snowflakeConstraints.Add($"-- FOREIGN KEY 约束不被 Snowflake 支持: {fk.Definition}");}}// 5. 默认值(列名 -> 默认值表达式)foreach (var def in defaultValues){string snowflakeDefault = ConvertDefaultToSnowflake(def.Value);snowflakeConstraints.Add($"DEFAULT {snowflakeDefault}"); // 注意:实际应与列定义一起使用,如 "COL NAME TYPE DEFAULT ..."// 注意:这里只是生成默认值表达式字符串,实际拼接时需与列定义放在一起}// 6. 标识列 / 自增列 → 转为 GENERATED ALWAYS AS IDENTITYforeach (var idCol in identityColumns){if (idCol.Value) // 是 IDENTITY{snowflakeConstraints.Add($"GENERATED ALWAYS AS IDENTITY(1,1)");}}// 7. 计算列 → 如 "Price AS Qty * 1.1"foreach (var comp in computedColumns){if (!string.IsNullOrEmpty(comp.Value)){snowflakeConstraints.Add($"AS {comp.Value}");}}return snowflakeConstraints;}/// <summary>/// 将 SQL Server 的默认值表达式转换为 Snowflake 对应的表达式/// </summary>/// <param name="defaultValueSql">如 "GETDATE()", "NEWID()", "100"</param>/// <returns>如 "CURRENT_TIMESTAMP()", "GENERATE_UUID()", "100"</returns>private static string ConvertDefaultToSnowflake(string defaultValueSql){if (string.IsNullOrWhiteSpace(defaultValueSql))return "";string upperDefault = defaultValueSql.Trim().ToUpper();return upperDefault switch{"GETDATE()" => "CURRENT_TIMESTAMP()","NEWID()" => "GENERATE_UUID()",_ => defaultValueSql // 其它默认值原样保留(如数字、字符串 'ABC')};}
}

📄 Modules/SnowflakeTableGenerator.cs

using System;
using System.Collections.Generic;
using System.Text;public class SnowflakeTableGenerator
{/// <summary>/// 生成完整的 Snowflake 建表语句/// </summary>/// <param name="schemaName">Schema 名称,如 "dbo"</param>/// <param name="tableName">表名,如 "Employees"</param>/// <param name="columns">列定义列表,每个元素为 (列名, Snowflake类型, 是否允许NULL, 默认值, 是否IDENTITY, 计算列表达式)</param>/// <param name="constraints">约束语句列表,如 "PRIMARY KEY (id)", "CHECK (...)"</param>/// <param name="tableComment">表注释文本,如 "员工表"(可选)</param>/// <param name="otherOptions">其它选项,如 "DATA_RETENTION_TIME_IN_DAYS = 1"(可选)</param>/// <returns>完整的 Snowflake 建表语句字符串</returns>public static string GenerateSnowflakeCreateTableStatement(string schemaName,string tableName,List<(string name, string snowflakeType, bool isNullable, string defaultValue, bool isIdentity, string computedExpr)> columns,List<string> constraints,string tableComment = null,List<string> otherOptions = null){StringBuilder sb = new StringBuilder();// 建表语句头部string fullTableName = $"{schemaName}.{tableName}".Trim('.');sb.AppendLine($"CREATE OR REPLACE TABLE {fullTableName} (");// 列定义for (int i = 0; i < columns.Count; i++){var col = columns[i];sb.Append($"    {col.name} {col.snowflakeType}");if (!col.isNullable){sb.Append(" NOT NULL");}if (!string.IsNullOrEmpty(col.defaultValue)){sb.Append($" DEFAULT {col.defaultValue}");}if (col.isIdentity){sb.Append(" GENERATED ALWAYS AS IDENTITY(1,1)");}if (!string.IsNullOrEmpty(col.computedExpr)){sb.Append($" AS {col.computedExpr}");}if (i < columns.Count - 1 || constraints.Count > 0 || otherOptions?.Count > 0){sb.AppendLine(",");}else{sb.AppendLine();}}// 约束(如 PRIMARY KEY, UNIQUE, CHECK)foreach (var constraint in constraints){sb.AppendLine($"    {constraint},");}// 移除最后一个多余的逗号(如果有)if (constraints.Count > 0 || columns.Count > 0){int lastCommaIndex = sb.ToString().LastIndexOf(',');if (lastCommaIndex >= 0){sb.Remove(lastCommaIndex, 1);sb.Insert(lastCommaIndex, " ");}}// 结束列定义块sb.AppendLine();sb.AppendLine(");");// 表注释(可选)if (!string.IsNullOrEmpty(tableComment)){sb.AppendLine($"-- COMMENT = '{tableComment}';");}// 其它选项(如 DATA_RETENTION_TIME_IN_DAYS = 1)(可选)if (otherOptions != null){foreach (var opt in otherOptions){sb.AppendLine($"-- {opt}");}}return sb.ToString();}
}

📄 Modules/SpecialCaseHandler.cs

using System;
using System.Collections.Generic;public class SpecialCaseHandler
{/// <summary>/// 检查并处理 SQL Server 建表语句中的特殊表、不支持功能等边缘情况,/// 返回警告信息列表,并决定是否继续转换该表。/// </summary>/// <param name="parsedTable">第1步解析出的表结构信息</param>/// <returns>返回警告信息列表(如 "不支持:临时表", "不支持:XML 类型"),若无问题则为空</returns>public static List<string> HandleSpecialCasesAndEdgeCases(ParsedTable parsedTable){var warnings = new List<string>();// 1. 检查是否是临时表(以 # 或 ## 开头)if (parsedTable.TableName.StartsWith("#") || parsedTable.TableName.StartsWith("##")){warnings.Add($"⚠️ 不支持:临时表 '{parsedTable.TableName}' (Snowflake 不支持临时表)");}// 2. 检查表名是否看起来像表变量(如 @开头,常见于 DECLARE @TableVar)if (parsedTable.TableName.StartsWith("@")){warnings.Add($"⚠️ 不支持:疑似表变量 '{parsedTable.TableName}' (非物理表,不转换)");}// 3. 检查列中的不支持的数据类型foreach (var col in parsedTable.Columns){if (col.DataType != null){string upperType = col.DataType.ToUpper();if (upperType.Contains("XML") || upperType.Contains("JSON")){warnings.Add($"⚠️ 不完全支持:列 '{col.Name}' 的数据类型 '{col.DataType}' (XML/JSON 建议转为 VARCHAR 或 VARIANT)");}else if (upperType.Contains("SQL_VARIANT")){warnings.Add($"⚠️ 不支持:列 '{col.Name}' 的数据类型 'SQL_VARIANT' (Snowflake 无对应类型,建议用 VARIANT)");}else if (upperType.Contains("GEOMETRY") || upperType.Contains("GEOGRAPHY")){warnings.Add($"⚠️ 不支持:列 '{col.Name}' 的数据类型 '{col.DataType}' (空间类型,建议转为 VARCHAR 或 BLOB)");}else if (upperType.Contains("HIERARCHYID")){warnings.Add($"⚠️ 不支持:列 '{col.Name}' 的数据类型 'HIERARCHYID'");}else if (upperType.Contains("FILESTREAM")){warnings.Add($"⚠️ 不支持:列 '{col.Name}' 的特性 'FILESTREAM'");}}}// 4. 检查是否使用了不支持的约束(如外键,已在第3步转成注释,这里可再提示)if (parsedTable.ForeignKeyConstraints?.Count > 0){warnings.Add($"⚠️ 不支持:外键约束 (Snowflake 不支持 FOREIGN KEY,已转为注释)");}// 5. 检查排序规则(Collation)—— 一般出现在列定义或表定义中,这里简化为提示warnings.Add($"ℹ️ 提示:SQL Server 的排序规则(Collation)在 Snowflake 中不支持,列排序行为可能不同");// 6. 检查是否为分区表(简化判断,如表名中含 PARTITION 或有特殊选项)if (parsedTable.TableOptions?.Exists(o => o.ToUpper().Contains("PARTITION")) == true){warnings.Add($"⚠️ 不支持:表可能使用了分区功能(Partitioning),Snowflake 分区策略不同");}// 7. 其它自定义检查可以继续添加...return warnings;}/// <summary>/// 批量处理多个 ParsedTable 对象,对每个表进行解析、转换、特殊处理,并生成最终 Snowflake 建表语句 + 警告/// </summary>/// <param name="tables">多个解析后的表定义</param>/// <param name="typeMapper">数据类型映射器</param>/// <param name="constraintConverter">约束转换器</param>/// <param name="tableGenerator">建表语句生成器</param>/// <returns>每个表的生成结果(SQL + 警告列表)</returns>public static List<(string snowflakeSql, List<string> warnings)> ProcessMultipleTables(List<ParsedTable> tables,Func<string, string> typeMapper,Func<ParsedPrimaryKey, List<ParsedConstraint>, List<ParsedConstraint>, List<ParsedConstraint>, Dictionary<string, string>, Dictionary<string, bool>, Dictionary<string, string>, List<string>> constraintConverter,Func<string, string, List<(string, string, bool, string, bool, string)>, List<string>, string, List<string>, string> tableGenerator){var results = new List<(string sql, List<string> warnings)>();foreach (var table in tables){var tableWarnings = new List<string>();// 1. 处理特殊 & 边缘情况var specialWarnings = HandleSpecialCasesAndEdgeCases(table);tableWarnings.AddRange(specialWarnings);// 2. 如果是临时表或表变量,可以选择跳过生成if (table.TableName.StartsWith("#") || table.TableName.StartsWith("@")){results.Add(("", tableWarnings));continue;}try{// 3. 数据类型映射(简化:实际应逐列映射,这里假设已映射好)// 4. 约束转换(第3步已生成 constraints 列表)// 5. 生成 Snowflake 建表语句(第4步)string snowflakeSql = tableGenerator(schemaName: table.SchemaName,tableName: table.TableName,columns: new List<(string, string, bool, string, bool, string)>(), // 应该从解析结果传入constraints: new List<string>(), // 应该从第3步传入tableComment: "Converted from SQL Server",otherOptions: new List<string> { "DATA_RETENTION_TIME_IN_DAYS = 7" });results.Add((snowflakeSql, tableWarnings));}catch (Exception ex){tableWarnings.Add($"❌ 转换失败:{ex.Message}");results.Add(("", tableWarnings));}}return results;}
}
http://www.dtcms.com/a/495920.html

相关文章:

  • 一种面向 AIoT 定制化场景的服务架构设计方案
  • 免费做网站刮刮卡舆情查询
  • 深圳建设厅网站官网免费虚拟主机官网
  • 塑胶制造生产ERP:有哪些系统值得关注
  • 怎么学习做网站vue is做的购物网站
  • 广州白云手机网站建设佛山专业做网站公司有哪些
  • 自己可以做一个网站吗如何删除网站后台的文章
  • PostgreSQL学习之postgis编译
  • unitree rl gym项目实践记录1:部署unitree rl gym项目
  • 国外的工业设计网站西安高科鱼化建设有限公司网站
  • 自己怎么设置网站湛江工程造价信息网
  • PostgreSQL 常见数组操作函数语法、功能
  • 珠海企业医疗网站建设惠州百度seo电话
  • 怎么查看网站备案信息怎样从网上卖东西啊
  • vue3 实现一个数组项在水平轴上按照奇偶数项分别上下排列
  • 服装类电子商务网站建设报告网页突然打不开是什么原因
  • 激光散斑血流图像去噪技术研究及其MATLAB实现
  • 帝国cms下载类网站怎么做怎么才能注册网站
  • 自助制作网站简述网站建设方法
  • java微服务-尚医通-数据字典-5
  • 建设营销型网站模板如何修改公司网站内容
  • 江西临川建设集团有限公司网站湖南网站建设 尖端磐石网络
  • 深圳保障性住房排名查询换个网站对seo有什么影响
  • 提升后牙树脂充填效率的器械选择要点
  • YOLO-V3
  • 论部落战争对两性思维的终极塑造及其政治遗产​​
  • Java 12的性能优化对不同规模和类型的应用程序有何影响?
  • 深入解析 Cherry Studio 的创建的本地知识库并用于问答的过程
  • 做图网站被告wordpress 学校主题
  • Joomla SQL注入漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-8917 两种方法渗透)