2015-05-20 30 views
0

不同的日期格式,我需要VARCHAR值轉換成一個視圖的多個列DATETIME進行排序和格式IsDate函數目的在另一個應用程序SQL Server上(在本地格式顯示)2008年在TSQL

有目前有兩個問題。

  1. VARCHAR值的輸入格式不同(但在 列級別一致)
  2. 也有可能發生故障的值(例如:20..05.2015)

不幸的是,TRY_CONVERT功能是僅適用於SQL Server 2012及更高版本。

ISDATE不起作用,因爲該視圖包含不同的日期格式,我既不能在用戶定義的函數中也不在視圖中設置語言,這會導致ISDATE與德語日期格式一起工作。

有沒有更容易解決我的問題的解決方案? 我首先想到的是寫像

FUNCTION TryConvertStringAsDatetime ( @value VARCHAR(MAX), 
             @format INT 
            ) 
使用了 CONVERT函數的格式數量

功能,但檢查每一個可能的格式手動讓我害怕了一下。

例子:TryConvertStringAsDatetime('20 .05.2015' ,104)(有一些僞代碼)

SET @day = character 1 and 2 
SET @month = character 4 and 5 
SET @year = character 7, 8, 9 and 10 
SET @dateODBCFormat = @year - @month - @day (concatenated with hyphen and not subtracted :) 
IF ISDATE(@dateODBCFormat) = 1 
    RETURN CONVERT(DATETIME, @dateODBCFormat, 120) 
ELSE 
    RETURN CONVERT(DATETIME, 0) (does the job) 
+0

你可以期望遇到哪些的DateFormats? –

+0

沒有簡單的內置解決方案。您必須分析數據以瞭解您需要支持哪些轉換規則,並編寫自定義代碼才能將其轉換。在前端應用程序或CLR過程中可能會更好。 –

+0

目前有格式104(德語)和120(ODBC)的值。 – GoToXY

回答

3

這是我來到了現在的功能了:

CREATE 
FUNCTION TryConvertStringAsDatetime ( @value VARCHAR(MAX), 
             @format INT 
            ) 
RETURNS DATETIME 
AS 
/* 
Tries to convert a given VARCHAR value to DATETIME. 
Returns NULL if no value was specified or the value is not in the correct format. 
*/ 
BEGIN 

    DECLARE @length INT = LEN(@value) 

    IF @length IS NULL OR @length < 10 OR @length > 23 
     RETURN NULL 

    DECLARE @day VARCHAR(2), 
      @month VARCHAR(2), 
      @year VARCHAR(4), 
      @time VARCHAR(9) 

    IF @format = 104 --dd.mm.yyyy hh:mi:ss(24h) 
    BEGIN 
     SET @day = SUBSTRING(@value, 1, 2) 
     SET @month = SUBSTRING(@value, 4, 2) 
     SET @year = SUBSTRING(@value, 7, 4) 
    END 
    ELSE IF @format IN (120, 121) --yyyy-mm-dd hh:mi:ss(24h) 
    BEGIN 
     SET @year = SUBSTRING(@value, 1, 4) 
     SET @month = SUBSTRING(@value, 6, 2) 
     SET @day = SUBSTRING(@value, 9, 2)    
    END 
    ELSE 
     RETURN NULL -- currently only german and ODBC supported 

    IF @length > 11 
     SET @time = SUBSTRING(@value, 12, @length - 11) 

    SET @value = @year + '-' + @month + '-' + @day + ISNULL(' ' + @time, '') 

    IF ISDATE(@value) = 1 
     RETURN CONVERT(DATETIME, @value, 121) 

    RETURN NULL 

END 
+0

不錯!那可能比我的建議更好。 –

1

我可能會像這樣的東西去:

CREATE FUNCTION TryConvertToDate 
(
    @InputString varchar(20) 
) 
RETURNS Datetime 
BEGIN 
    DECLARE @DateTime datetime = NULL 
    SET @DateTime = 
     CASE 
      WHEN LEN(@InputString) = 10 AND PATINDEX('[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]', @InputString)=1 THEN 
       CONVERT(DateTime, @InputString, 104) -- German 
      WHEN LEN(@InputString) = 10 AND PATINDEX('[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]', @InputString)=1 THEN 
       CONVERT(DateTime, @InputString, 120) -- ODBC 
      ELSE 
       NULL -- unsuported format 
     END 
    RETURN @DateTime   
END 

注:用於測試長度和使用patindex只確保通用格式,因此您需要在try塊中調用此函數,以防日期和月份反轉並導致轉換錯誤。
另一方面,向該功能添加支持的格式非常簡單 - 您只需添加when子句以及正確的patindex和長度以及正確的轉換樣式。

另一個選項是確保字符串實際上可以轉換爲日期。
這將使你的函數較爲複雜,因此很難寫,但會更容易些,因爲它會降低到最低限度提高轉換錯誤的機會去工作:

CREATE FUNCTION TryConvertToDate 
(
    @InputString varchar(20) 
) 
RETURNS Datetime 
BEGIN 


    DECLARE @DateValue date, @Days int, @Months int, @Years int 

    IF LEN(@DateString) = 10 AND PATINDEX('[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]', @InputString)=1 -- German format 
     BEGIN 
      SELECT @Days = CAST(LEFT(@InputString, 2) As int), 
        @Months = CAST(SUBSTRING(@InputString, 4, 2) as int), 
        @Years = CAST(RIGHT(@InputString, 4) as int) 
      -- NOTE: you will need to add a condition for leap years 
      IF (@Days < 31 AND @Months IN(4,6,9,12)) OR (@Days < 30 AND @Months = 2) 
       SET @DateValue = convert(date, @InputString, 104) 
     END 

    IF LEN(@InputString) = 10 AND PATINDEX('[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]', @InputString)=1 -- ODBC format 
     BEGIN 
      SELECT @Days = CAST(RIGHT(@InputString, 2) As int), 
        @Months = CAST(SUBSTRING(@InputString, 6, 2) as int), 
        @Years = CAST(LEFT(@InputString, 4) as int) 
      -- NOTE: you will need to add a condition for leap years 
      IF (@Days < 31 AND @Months IN(4,6,9,12)) OR (@Days < 30 AND @Months = 2) 
       SET @DateValue = convert(date, @InputString, 120) 
     END 

    RETURN @DateValue 

END 
+0

據我所知並在互聯網上閱讀,UDF和視圖中不允許TRY/CATCH。 所以我仍然需要調用ISDATE函數。 另外,我將使用一個額外的參數編寫函數,如上所述(ANSI 2和德語4格式在數字分隔和長度方面完全相似)。 非常感謝。 – GoToXY

+0

是的,我注意到了我的錯誤,並編輯了那個函數。但是,如果你可以使用'clr'函數,那麼它可能會做得更好,並且可以做更多的可讀性。 –

0

您可能必須在速度和功能SQLCLR這樣做(如@tab和@Zohar在各種評論中所指出)而言更好的運氣。

。NET/C#代碼:

using System; 
using System.Data.SqlTypes; 
using System.Globalization; 
using Microsoft.SqlServer.Server; 

public class TryConvertStuff 
{ 
    [SqlFunction(IsDeterministic = true, IsPrecise = true)] 
    public static SqlDateTime TryConvertDateTime([SqlFacet(MaxSize = 50)] SqlString 
     StringDate, [SqlFacet(MaxSize = 10)] SqlString Culture) 
    { 
     CultureInfo _Culture = CultureInfo.CurrentCulture; 
     if (!Culture.IsNull && Culture.Value.Trim() != String.Empty) 
     { 
      _Culture = CultureInfo.GetCultureInfo(Culture.Value); 
     } 

     DateTime _RealDate; 
     if (DateTime.TryParse(StringDate.Value, _Culture, 
           DateTimeStyles.None, out _RealDate)) 
     { 
      return _RealDate; 
     }; 

     return SqlDateTime.Null; 
    } 
} 

測試:

SELECT dbo.TryConvertDateTime(N'2019-04-20', N'en'); -- 2019-04-20 00:00:00.000 
SELECT dbo.TryConvertDateTime(N'2019-04-20f', N'en'); -- NULL 
SELECT dbo.TryConvertDateTime(N'2019.04.20', N'en'); -- 2019-04-20 00:00:00.000 

SELECT dbo.TryConvertDateTime(N'20.04.2019', N'en'); -- NULL 
SELECT dbo.TryConvertDateTime(N'20.04.2019', N'de'); -- 2019-04-20 00:00:00.000 
SELECT dbo.TryConvertDateTime(N'20.04.2019', NULL); -- NULL 
+0

你是對的......我只是忘了提及我沒有可能使用CLR來開發(oops)。儘管如此,非常感謝您的CLR答案,我會在可以使用的時候期待這一點。 – GoToXY

+0

@GoToXY只是好奇,爲什麼不能使用CLR? –

+0

,因爲我被告知不能使用我們的編程指南(我們可以爭辯),並且據我所知,需要某些權限(DBA這裏是一些懶惰的帽子:)。 – GoToXY