我将采用以下方法:
- 创建识别列类型的功能
- 将列类型映射到文本表示
- 强制转换为保存值所需的最大文本长度
- 加入数据
我在一个数据库的上下文中具有相同的场景并应用这种方法。这是因为从数据库中查询动态列集并对它们执行各种 SQL 操作的功能。
其中一位运营商是UNPIVOT。 T-SQL 语句构建是这样的:
SELECT *
FROM
(
SELCET RowID
,Col001
,Col002
...
,Col00X
FROM ...
) DS
UNPIVOT
(
[value] FOR [column] IN ([Col001], [Col002], ... , [Col00X])
) UNPVT;
问题是UNPIVOT IN 子句中的所有列必须具有相同的类型。当然,最简单的解决方法是将CAST/CONVERT 的所有列都设置为NVARCHAR(MAX),因为它几乎可以存储所有内容,而我们确实做到了,但是查询的执行时间很长。
因此,最好将最大类型保存在 VARCHAR(X) 或 NVARCHNAR(X) 中以将列转换为它。我编写了一个简单的SQL CLR Aggregate 函数,因为我在表中有所有列名和类型,并且希望快速清晰地获得目标类型。它看起来像这样:
using System;
using System.Data;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Linq;
[Serializable]
[
Microsoft.SqlServer.Server.SqlUserDefinedAggregate
(
Microsoft.SqlServer.Server.Format.UserDefined,
IsInvariantToNulls = true,
IsInvariantToDuplicates = false,
IsInvariantToOrder = false,
MaxByteSize = -1
)
]
/// <summary>
/// Returns the data type with highest precedence. The date types comes in the "[system_type_name]" format.
/// </summary>
public class AnalysisCustomRollupsGetHighestDataTypeConverstionValue : Microsoft.SqlServer.Server.IBinarySerialize
{
private Dictionary<string, KeyValuePair<string, int>> dataTypesMapping;
private List<KeyValuePair<string, int>> destinationDataTypes;
private String[] dataTypesWithoutPredifinedLength;
public void Init()
{
// for the following data types the length is extracting from the input value
dataTypesWithoutPredifinedLength = new string[] {"nvarchar", "nchar", "varchar", "char"};
// each data type is mapped to its string corresponding value ("-1" is "MAX", "0" is defined by source)
dataTypesMapping = new Dictionary<string, KeyValuePair<string, int>> {
{"user-defined data types", new KeyValuePair<string, int> ("NVARCHAR", -1)},
{"sql_variant", new KeyValuePair<string, int> ("NVARCHAR", -1)},
{"xml", new KeyValuePair<string, int> ("NVARCHAR", -1)},
{"datetimeoffset", new KeyValuePair<string, int> ("VARCHAR", 34)},
{"datetime2", new KeyValuePair<string, int> ("VARCHAR", 27)},
{"datetime", new KeyValuePair<string, int> ("VARCHAR", 19)},
{"smalldatetime", new KeyValuePair<string, int> ("VARCHAR", 19)},
{"date", new KeyValuePair<string, int> ("VARCHAR", 19)},
{"time", new KeyValuePair<string, int> ("VARCHAR", 16)},
{"float", new KeyValuePair<string, int> ("VARCHAR", 48)},
{"real", new KeyValuePair<string, int> ("VARCHAR", 48)},
{"decimal", new KeyValuePair<string, int> ("VARCHAR", 48)},
{"money", new KeyValuePair<string, int> ("VARCHAR", 19)},
{"smallmoney", new KeyValuePair<string, int> ("VARCHAR", 10)},
{"bigint", new KeyValuePair<string, int> ("VARCHAR", 26)},
{"int", new KeyValuePair<string, int> ("VARCHAR", 14)},
{"smallint", new KeyValuePair<string, int> ("VARCHAR", 7)},
{"tinyint", new KeyValuePair<string, int> ("VARCHAR", 3)},
{"bit", new KeyValuePair<string, int> ("VARCHAR", 1)},
{"ntext", new KeyValuePair<string, int> ("NVARCHAR", -1)},
{"text", new KeyValuePair<string, int> ("VARCHAR", -1)},
{"image", new KeyValuePair<string, int> ("VARCHAR", -1)},
{"timestamp", new KeyValuePair<string, int> ("VARCHAR", 8)},
{"uniqueidentifier", new KeyValuePair<string, int> ("VARCHAR", 36)},
{"nvarchar", new KeyValuePair<string, int> ("NVARCHAR", 0)},
{"nchar", new KeyValuePair<string, int> ("NVARCHAR", 0)},
{"varchar", new KeyValuePair<string, int> ("VARCHAR", 0)},
{"char", new KeyValuePair<string, int> ("VARCHAR", 0)},
{"varbinary", new KeyValuePair<string, int> ("NVARCHAR", -1)},
{"binary", new KeyValuePair<string, int> ("NVARCHAR", -1)}
};
destinationDataTypes = new List<KeyValuePair<string, int>>();
}
public void Accumulate(SqlString value)
{
string[] buffer;
string currentDataTypeName;
int currentDataLength;
if (value.IsNull)
{
return;
}
buffer = value.Value.Split('(', ')');
currentDataTypeName = buffer[0].ToLower();
// length is extracting from the source value
if (dataTypesWithoutPredifinedLength.Contains(currentDataTypeName))
{
if(buffer[1].ToUpper() == "MAX")
{
buffer[1] = "-1";
}
Int32.TryParse(buffer[1], out currentDataLength);
destinationDataTypes.Add(new KeyValuePair<string, int>(currentDataTypeName, currentDataLength));
}
// length is predefined
else
{
destinationDataTypes.Add(new KeyValuePair<string, int>(dataTypesMapping[currentDataTypeName].Key, dataTypesMapping[currentDataTypeName].Value));
}
}
public void Merge(AnalysisCustomRollupsGetHighestDataTypeConverstionValue other)
{
destinationDataTypes = destinationDataTypes.Union(other.destinationDataTypes).ToList();
}
public SqlString Terminate()
{
string output;
string length;
length = (destinationDataTypes.OrderBy(x => x.Value).First().Value == -1 ? "MAX" : destinationDataTypes.OrderByDescending(x => x.Value).First().Value.ToString());
output = (destinationDataTypes.Exists(x => String.Equals(x.Key.ToUpper(), "NVARCHAR")) ? "NVARCHAR" : "VARCHAR") + "(" + length + ")";
return new SqlString(output);
}
public void Read(BinaryReader r)
{
if (r == null) throw new ArgumentNullException("r");
int count = r.ReadInt32();
destinationDataTypes = new List<KeyValuePair<string, int>>(count);
for (int i = 0; i < count; i++)
{
destinationDataTypes.Add(new KeyValuePair<string, int> (r.ReadString(), r.ReadInt32()));
}
}
public void Write(BinaryWriter w)
{
if (w == null) throw new ArgumentNullException("w");
w.Write(destinationDataTypes.Count);
foreach (KeyValuePair<string, int> record in destinationDataTypes)
{
w.Write(record.Key);
w.Write(record.Value);
}
}
}
它允许我这样做:
SELECT [dbo].[AnalysisCustomRollupsGetHighestDataTypeConverstionValue] ([column_type])
FROM
(
VALUES ('VARCHAR(5)')
,('INT')
,('SMALLMONEY')
) DS ([column_type]);
返回VARCHAR(14)。
我想在您的 ETL 过程中这将更容易实现。
更难的是处理日期。在我的上下文中,所有日期都以这种格式YYYY-MM-DDTHH-MM-SS 作为字符串出现。如果您需要加入日期,其中一些以不同格式的字符串形式出现,例如 2010 5th May 或 something crazy enter in input without any validation,您需要先将它们转换为日期,然后再转换为字符串。