我知道过去曾问过类似的问题,但他们仍然没有给我一个适当的解决方案。
我有一个数据库表(第三方),其varchar
列的datetime
列。{/ p>
它包含以下格式的日期。
11181980
8 18 1960
10/01/1960
04-12-1953
041371
7/29/44
Empty String
NULL
当我选择此列时,我希望在可用时以标准格式(例如mm/dd/yyyy
)显示日期,或者为NULL。
我只能想到一个函数来执行此操作,但我不想做UDF,因为我需要确保它在尝试转换时不会出错。 UDF中没有try / catch。我可以使用CLR功能来使用更强大的.net功能,但我想避免它。
在SQL Server中还有其他更好的方法来处理这种转换吗?另外,如果可能的话,我应该如何进行这种转换。
答案 0 :(得分:2)
对于您所描述的潜在格式集:
DECLARE @x TABLE(y VARCHAR(32))
INSERT @x VALUES
('11181980'),
('8 18 1960'),
('10/01/1960'),
('04-12-1953'),
('041371'),
('7/29/44'),
(''),
(NULL);
SET DATEFORMAT MDY;
SELECT CONVERT(DATETIME, CASE WHEN y LIKE '%/%' THEN y
WHEN LEN(RTRIM(y)) = 0 THEN NULL
WHEN LEN(RTRIM(y)) IN (6,8) AND ISNUMERIC(y) = 1 THEN
STUFF(STUFF(y,3,0,'/'),6,0,'/') END)
FROM (SELECT y = REPLACE(REPLACE(y, ' ', '/'), '-', '/') FROM @x) AS x;
根据服务器设置,这会将7/29/44
解释为2044
,而不是1944
。要确保所有日期都在过去,您可以这样做:
SELECT y = DATEADD(YEAR, CASE WHEN y > GETDATE() THEN -100 ELSE 0 END, y)
FROM
(
SELECT y = CONVERT(DATETIME, CASE WHEN y LIKE '%/%' THEN y
WHEN LEN(RTRIM(y)) = 0 THEN NULL ELSE
STUFF(STUFF(y, 3, 0, '/'),6, 0, '/') END)
FROM (SELECT y = REPLACE(REPLACE(y, ' ', '/'), '-', '/') FROM @x) AS x
) AS z;
这还取决于没有无法按摩到日期的垃圾数据。究竟是什么样的系统进入这种不一致的废话呢?
在SQL Server 2012中,您将能够使用TRY_PARSE或TRY_CONVERT,但是对于那些混乱的格式,您仍然需要进行一些按摩以获得有意义的结果。
答案 1 :(得分:0)
如果您拥有数据库但无法更改它,我将运行一个存储过程,将所有值清理为一种通用格式,并确保只能插入/更新该格式的条目。如果您无法控制CRUD操作,我只会按原样获取“日期”并执行转换为BL层中的DateTime
。
也许不是您问题的答案,但我个人认为所有查询都尽可能简单,只需将转换和其他逻辑保留在数据库之外。
答案 2 :(得分:0)
我建议您执行以下操作:
说实话,数据看起来像垃圾,我怀疑你可以完全依赖它。有可能存在以下值:
这些应该是1990-11-01还是1990-01-11?我认为CLR功能将以最稳定的方式为您提供最多的数据。
答案 3 :(得分:0)
这是我对这个3岁问题的解决方案。我没有任何空格,但您可以使用此作为基础,并在评估时使用替换功能去除它们。你去,互联网。感谢过去10年的所有帮助。这非常特定于SQL数据导入导出,但有望帮助那些陷入手动ETL模式的人。
CASE WHEN DOB LIKE '__/__/____' THEN [DOB] -- PROPER FORMAT
WHEN DOB LIKE '_/__/____' THEN '0'+ [DOB] -- NEED TO ADD A ZERO TO THE MONTH
WHEN DOB LIKE '__/_/____' THEN LEFT(DOB,3)+'0'+RIGHT(DOB,6) -- NEED TO ADD A ZERO TO THE DAY
WHEN DOB LIKE '_/_/____' THEN '0'+LEFT(DOB,2)+'0'+RIGHT(DOB,6) -- NEED TO ADD A ZERO TO THE MONTH AND DAY
WHEN LEN(DOB)=8 AND DOB BETWEEN '1900' AND '2016' THEN LEFT(RIGHT(DOB,4),2) + '/' + RIGHT(DOB,2) +'/'+ LEFT(DOB,4)
WHEN LEN(DOB)=8 AND DOB BETWEEN '01011900' AND '12312016' AND DOB NOT LIKE '%/%' THEN LEFT(DOB,2) + '/' + RIGHT(LEFT(DOB,4),2) +'/'+ RIGHT(DOB,4)
WHEN DOB LIKE '__/__/__' -- CONVERT FROM MM/DD/YY (ADD TWO DIGIT YEAR PREFIX)
THEN CASE WHEN RIGHT(replace(dob,'/',''),2) > RIGHT(YEAR(GETDATE()),2) --WHEN 2-DIGIT YEAR IS WITHIN 100 YEARS AGO USE 19
THEN LEFT(DOB,2)+'/'+LEFT(RIGHT(replace(dob,'/',''),4),2)+'/19'+RIGHT(replace(dob,'/',''),2)
WHEN RIGHT(DOB,2) < RIGHT(YEAR(GETDATE()),2) --WHEN 2-DIGIT YEAR IS MORE THAN 100 YEARS AGO USE 20
THEN LEFT(DOB,2)+'/'+LEFT(RIGHT(replace(dob,'/',''),4),2)+'/20'+RIGHT(replace(dob,'/',''),2)
ELSE NULL END
ELSE NULL END AS [DOB_CONVERTER]
正如Max Vernon所指出的那样,你必须考虑到每个匹配和修复的模式。错误处理可以很好地实现自动化。在那之前查看数据一旦被清理,加载到临时表并使用类似的模式寻找坏人(WHERE NULL以找到非模式匹配)(WHERE RIGHT(LEFT(REPLACE([DOB],&#39; / &#39;,&#39;&#39;),4),2)&gt; 31
模式搜索在microsoft.com上是一个有用的网站 https://technet.microsoft.com/en-us/library/ms187489(v=sql.105).aspx