微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何自动生成SQL表的数据类型

如何解决如何自动生成SQL表的数据类型

我有许多200列以上的非规范化表。这些表位于sql Server中,如果不是总是的话,它们通常具有varchar(100)或nvarchar(100)(字符串)数据类型。但是,大多数列都是整数,浮点数或其他数据类型。对于我来说,不可能遍历每个表和列并选择数据类型。由于多种原因,包括应用程序兼容性,性能,联接和其他原因,我必须将它们转换为适当的(或至少接近适当的)数据类型。有什么我可以使用的工具吗?有没有人创建代码来实现这一目标?不一定是完美的,但是紧密匹配即可。

我尝试了什么:

  1. 我试图将这些表导出到Excel,然后再回到sql。它可以工作,但是比手动完成要花费更多,更多的时间,因为Excel会弄乱您的数据并将其转换为任何感觉(想想科学记数法,以数字标出日期等等)。这非常耗时且失败。如果您选择在Excel中使用“文本”选项,它将仅将所有内容转换回varchar(x)
  2. 我尝试导出到平面文件并使用具有智能数据类型的VS或SSMS新版本。这比Excel效果更好,但不幸的是,即使只有一排冲突也会停止整个过程。该工具比较笨拙,错误很严重,并且不会告诉您引起问题的行。使用这种方法也很糟糕,因为这些表非常庞大,非常耗时。尤其是在考虑工具故障排除时。

感谢您的帮助。如果您不要求我通过说我的设置不正确等来放弃任务,我也将不胜感激。

解决方法

我现在假设您只关心以下字符串列:

  1. 应保留为字符串,但可能定义得比所需的范围宽
  2. 不应为字符串,因为:
    • 它们仅包含日期
    • 它们仅包含数字,在这种情况下:
      • 您将关心长度(以确定潜在的tinyint / int / bigint)
      • 您将关心它们是否包含小数点

您已经看到了用于确定if columns already defined as integers could be made smaller的方法,但是可以使用类似的方法在数据类型当前为字符串但满足上述条件之一的表中查找潜在的候选者。 / p>

假设您有一个这样的表:

CREATE TABLE dbo.foo
(
  a int PRIMARY KEY,h varchar(100),i varchar(100),j varchar(100)
);

INSERT dbo.foo VALUES 
(1,'123','20200101 04:00:00'),(2,'456','20200101'),(3,'789',(4,'867','foo',(5,'876',(6,'6.54','654','20200101');

一种方法是确定列定义后的所有元数据(您可以从sys.dm_exec_describe_first_result_set轻松获得),然后从该构建动态SQL来检查每个列对于最长的值(将确定最小的字符串大小),是否存在单个非数字(这意味着您不能转换为数字),是否存在单个非日期(这意味着您不能转换为日期),以及是否有小数点(这意味着您无法转换为int族,但还需要检查精度/小数位数)。

这绝对是一个艰难的,肮脏的开始,但这应该会让您前进。

DECLARE @table nvarchar(513) = N'dbo.foo';


DECLARE @sql nvarchar(max) = N'SELECT ',@un nvarchar(max) = N'',@un_sub nvarchar(max) = N'
  SELECT ColumnName =  MIN([col $c$]),CurrentType = MIN([type $c$]),LongestValue = MAX([len $c$]),[AllNumerics?] = MIN([is_num $c$]),[AllDates?] = MIN([is_date $c$]),[AnyContainDecimal] = MAX([has_dec $c$]) FROM x '

SELECT @sql += N'[col ' + name + '] = ''' + name + ''',[type ' + name + '] = '''
  + system_type_name + ''',' + QUOTENAME(name)
  + ',[len ' + name + '] = LEN(' + QUOTENAME(name) + '),[is_num ' + name + '] = CONVERT(tinyint,ISNUMERIC(' + QUOTENAME(name) + ')),[is_date ' + name + '] = CONVERT(tinyint,ISDATE(' + QUOTENAME(name) + ')),[has_dec ' + name + '] = CASE WHEN ISNUMERIC(' + QUOTENAME(name) + ') = 1
    AND ' + QUOTENAME(name) + ' LIKE N''%.%'' THEN 1 ELSE 0 END,',@un += N'
UNION ALL ' + REPLACE(@un_sub,N'$c$',name)
  
FROM sys.dm_exec_describe_first_result_set('SELECT * FROM ' + @table,NULL,1)
WHERE system_type_name like '%char%'

SELECT @sql += N'[$garbage$]='''' FROM ' + @table;

SELECT @sql = N';WITH x AS (
' + @sql + N'
) ' + STUFF(@un,1,10,'');

EXEC sys.sp_executesql @sql;

要消化的东西很多...动态SQL功能强大,但是它确实很丑陋,而且不完全面向对象。

结果(try it out in this fiddle):

enter image description here

您可以在那里看到

  1. h是所有数字,最长值为4,但是至少一个值包含小数点,因此此处的最佳类型为decimal(something,something)
  2. i包含至少一个非数字和至少一个非日期,因此只能是一个字符串,但是由于最长的值只有3个字符,因此varchar(100)太多了。无论您是要使用varchar(3)还是char(3)还是需要稍作填充以适应未来的发展,这实际上只是一个问题,可以根据您的数据模型,现在和以后的业务需求等来定性地回答。
  3. j包含所有日期类型,但是您在这里不能从最大长度中得到很多解释(因为您不知道日期的实际存储方式,因为它们以字符串形式存储,并且包含许多字符串表格可以解释为有效日期)。因此,您可能足够知道说j应该是datetime的某种味道,但是您需要仔细看一下值才能了解其中的实际情况。

您可以更改此查询的结果(尤其是对于具有很多列的表),以仅返回值得调查的值,在这种情况下,我返回了所有行以进行演示(无论如何,在我的示例中所有行都具有潜在的解决方案) 。只需在联合周围添加另一个CTE,然后根据这些列(或您添加的其他列)进行过滤即可。

当然,在大表上,这可能会扫描每个列,因此不要指望它很快,并且如果您对此不满意,请不要期望它占用大量内存。同样,这可能很明显,但这不能保护您避免选择以后会伤害您的类型。假设该列正在收集整数,并且刚好达到99,那么您将类型更改为tinyint,因为没有小数,并且最长长度为2。然后有人插入256和繁荣。

您还可以添加其他增强功能,例如获得最小长度(如果它们都是字符串,则可能是varchar,但可能是char),或者检查是否有任何字符在外面ASCII码(也许您有nvarchar,但可能是varchar),小数点两边的位数(以更精确的方式表示十进制类型)或最大值(以提高精度)确定整数的类型)。我将把它们留做练习。

,

您可能在SQL Server中有一个更简单的解决方案。只需尝试转换值并选择最合适的类型即可。对于单列处理整数,日期和时间非常简单:

select (case when count(try_convert(tinyint,col)) = count(col) then 'tinyint'
             when count(try_convert(int,col)) = count(col) then 'int'
             when count(try_convert(bigint,col)) = count(col) then 'bigint'
             when count(try_convert(date,col)) = count(col) then 'date'
             when count(try_convert(time,col)) = count(col) then 'time'
             when count(try_convert(datetime,col)) = count(col) then 'datetime'
             else 'varchar(255)'  -- or whatever default
        end)
from t
where col is not null;

这需要以两种方式扩展。一种是用于更多列,第二种是用于其他类型的数字。首先很简单:

select colname,(case when count(try_convert(tinyint,col)) = count(col) then 'datetime'
             else 'varchar(255)'  -- or whatever default
        end)
from t cross apply
     (values ('col1',col1),('col2',col2),. . . ) v(colname,col)
where col is not null
group by colname;

注意:如果所有值均为NULL,则无法检查。

带小数点的数字的问题是值不明确-您要数字还是浮点数?一种可能性是您要考虑数据类型。因此,您可能知道所有数字都可能是数字,所以numeric(20,4)因为它们表示货币金额-您可以在上面将它们包括在内。

或者您可以测试小数位在哪里,然后使用该信息来推导类型。我认为最简单的解决方案可能是这样的:

select colname,col)) = count(col) then 'datetime'
             when count(try_convert(numeric(20,4),col)) = count(col) and
                  sum(case when col like '%._____' then 1 else 0 end) = 0
             then 'numeric(20,4)'
             when count(try_convert(float,col) = count(col)
             then 'float'
             else 'varchar(255)'  -- or whatever default
        end)
from t cross apply
     (values ('col1',col)
where col is not null
group by colname;
,

您可以考虑使用表格上的视图,该视图使用带有转换功能的选择(如

select someFunction(colA),someOtherFunction(colB) ... from tableName

例如对于sqlserver

CREATE VIEW myView
as
select CAST(colA AS int) as colA,CAST(colB AS text) as colB ... 
from tableName

然后您可以说从myView中选择...

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。