SQL - 不一致的日期时间转换问题

时间:2016-02-17 16:28:54

标签: sql sql-server datetime

我希望有人可以解释以下涉及将varchar转换为datetime的问题。

以下代码有效:

SELECT
    mt.matter_code,
    ud.uds_type,
    ud.group_no,
    ud.ud_field##2,
    convert(datetime,ud.ud_field##2,103) hearingDate
FROM dbo.matdb mt
    INNER JOIN dbo.matdb_add_in ad
            ON mt.mt_int_code  = ad.mt_int_code
            AND ad.add_in_code = 'OUTAA'
    INNER JOIN dbo.uddetail ud
            ON convert(varchar,ad.mt_add_in_int_code) = ud.owner_code
            AND ud.parent_code = ad.add_in_code
            AND ud.po_type_char = 'A'
            AND ud.uds_type = 'LPR'
            --AND ud.uds_type IN (SELECT s FROM dbo.split(',','LPR'))
WHERE mt.mt_type   = 'MATA'
AND mt.matter_code = '118-1'
ORDER BY ud.ud_field##2 ASC;

但是,如果我们替换

'AND ud.uds_type = 'LPR'

FOR

'AND ud.uds_type IN (SELECT s FROM dbo.split(',','LPR'))'

我收到以下转换错误消息: '从字符串转换日期时转换失败。'

两行代码在不使用转换时返回完全相同的结果。但是,只要在使用dbo.split()时引入转换,我就会收到上面提到的错误消息。

我错过了某种参考,还是这种错误?

编辑: dbo.split功能

USE [Vfile_Dev]
GO

/****** Object:  UserDefinedFunction [dbo].[Split]    Script Date: 02/18/2016 13:28:12 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[Split] (@sep char(1), @s varchar(512))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT pn,
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )
GO
编辑:如果有人感兴趣,我已经提供了一个解决方案,这要归功于Ivan。在ud.ud_field ## 2上进行了额外的检查,以确保没有空白,并且现在将dbo.split结果插入到表变量中。

INSERT INTO @screenTable
SELECT s FROM dbo.split(',','LPR,COU');

SELECT
    mt.matter_code,
    ud.uds_type,
    ud.group_no,
    isnull(nullif(ud.ud_field##2,''),'01/01/1990') as hdNorm,
    convert(datetime, isnull(nullif(ud.ud_field##2,''),'1990-01-01 00:00:00.000')) AS hdConverted
FROM dbo.matdb mt
    INNER JOIN dbo.matdb_add_in ad
            ON mt.mt_int_code  = ad.mt_int_code
            AND ad.add_in_code = 'OUTAA'
    INNER JOIN dbo.uddetail ud
            ON convert(varchar, ad.mt_add_in_int_code) = ud.owner_code
            AND ud.parent_code = ad.add_in_code
            AND ud.uds_type IN (select * from @screenTable)
WHERE mt.mt_type   = 'MATA'
AND mt.matter_code = '118-1'
ORDER BY group_no ASC;

1 个答案:

答案 0 :(得分:1)

我的猜测是ud.ud_field##2不仅包含datetime个数据。 ud.uds_type以及您需要convert(datetime,ud.ud_field##2,103)的原因之一。直接使用WHERE子句时,在调用CONVERT函数之前应用谓词。这意味着SQL Server仅转换可转换的数据。当您使用split函数时,SQL Server将无法再执行此操作。

如果ud.ud_field##2用于存储多种数据类型,那么您就遇到了设计问题。有一些不同的解决方案(例如,创建多个uddetail表 - 每个数据类型一个,向uddetail添加列 - 每个数据类型一个,等等。

这是我的榜样:

CREATE TABLE #DifferentData1
  (
     [DataType]           VARCHAR(20),-- This is our code for the type of data we have in Sometimes_datetime
     [Sometimes_datetime] VARCHAR(20)-- This is datetime or something else
  )

CREATE TABLE #DifferentData2
  (
     [DataType]        VARCHAR(20),-- This is our code for the type of data we have
     [Always_datetime] DATETIME,-- This is ALWAYS datetime
     [Other_data]      VARCHAR(50)
  )

INSERT INTO #DifferentData1
            ([DataType],
             [Sometimes_datetime])
VALUES      ( 'LPR',CONVERT(VARCHAR, GETDATE(), 103)),
            ( 'Almost LPR',CONVERT(VARCHAR, GETDATE(), 120)),
            ('Not LPR','Some other data type')

INSERT INTO #DifferentData2
            ([DataType],
             [Always_datetime],
             [Other_data])
VALUES      ( 'LPR',CONVERT(DATETIME, GETDATE(), 103),NULL),
            ( 'Almost LPR',NULL,CONVERT(VARCHAR, GETDATE(), 120)),
            ('Not LPR',NULL,'Some other data type')

-- Let's see what we have
SELECT [DataType],
       [Sometimes_datetime]
FROM   #DifferentData1

SELECT [DataType],
       [Always_datetime],
       [Other_data]
FROM   #DifferentData2

现在,我们可以使用一个简单的WHERE子句进行过滤,并且对于任何一个表都可以。

SELECT CONVERT(DATETIME, [Sometimes_datetime], 103)
FROM   #DifferentData1
WHERE  [DataType] = 'LPR'

SELECT CONVERT(DATETIME, [Always_datetime], 103)
FROM   #DifferentData2
WHERE  [DataType] = 'LPR'

这适用于#DifferentData1,因为谓词(WHERE子句)在 CONVERT之前应用。但是,在使用split函数时,SQL Sever会在应用谓词后尝试CONVERT 。这导致它失败。 #DifferentData2仍然可以正常运行:

-- This fails
SELECT CONVERT(DATETIME, [Sometimes_datetime], 103)
FROM   #DifferentData1
WHERE  [DataType] IN
       (SELECT [SplitData]
        FROM   [dbo].[SplitString]('LPR', ','))
OPTION (MERGE JOIN) 

-- This works!
SELECT CONVERT(DATETIME, [Always_datetime], 103)
FROM   #DifferentData2
WHERE  [DataType] IN
       (SELECT [SplitData]
        FROM   [dbo].[SplitString]('LPR', ','))
OPTION (MERGE JOIN) 

注意:我必须强制使用MERGE JOIN,因为如果使用LOOP JOIN,则可以在CONVERT之前应用谓词 。如果您的数据集很小,可能只是强制执行LOOP JOIN,但不建议将其作为一般解决方案:

SELECT CONVERT(DATETIME, [Sometimes_datetime], 103)
FROM   #DifferentData1
WHERE  [DataType] IN
       (SELECT [SplitData]
        FROM   [dbo].[SplitString]('LPR', ','))
OPTION (LOOP JOIN)