如何从T-SQL中的文本解析日期

时间:2018-04-04 17:19:55

标签: sql-server-2008 tsql substring

我已经有了从文本字段中提取格式化日期的代码,例如下面的示例:

    SUBSTRING(REPLACE(al.Comments + '.','.','{br}'), 
        PATINDEX('%Visit Date%',REPLACE(al.Comments+'.','.','{br}')) + 13, 
        PATINDEX('%{br}%',
            SUBSTRING(REPLACE(al.Comments+'.','.','{br}'), 
                PATINDEX('%Visit Date%', 
                    REPLACE(al.Comments+'.','.','{br}')) + 13, 14))-1))

将日期以编程方式插入内部软件的“注释”字段中。 我的问题是当评论和日期由最终用户手动输入时,如何从同一个表和列(al.Comments)中提取日期。

最终用户输入日期的可能方式是:

  • 米/ d / yy的
  • MM / DD / YY
  • 米/ d / yyyy的
  • 毫米/日/年
  • m / d(假设与当年年度相同)
  • mm / d(假设与当年年度相同)
  • m / dd(假设与当年年度相同)
  • mm / dd(假设与当年年度相同)

注意事项

  • 这是SQL 2008
  • 无法使用SQL CLR和Regex
  • 我无法请求为此过程更新软件
  • 我知道这很复杂,并且可能无法解释所有情况,因此任何可能接受尽可能多的最终用户输入的内容都是可以接受的

在这个日期的情况下(与我上面发布的示例不同),我将寻找一个关键字'proposed',后跟一个日期和任何其他数量的字符。下面是我在al.Comments的结果集中看到的一些示例:

Sample Results

我正在SSRS中创建一个跟踪器,在其他许多表格中我创建了一个结果集,可以让我跟踪建议日期一周或更短的任何建议日期。我的大多数其他结果集要么使用日期时间戳,要么从编程添加的日期中拉出,就像我发布的样本一样。我无法找到一种方法在SQL中拉出这个日期,这样我就可以为我的结果集/ SSRS跟踪器创建一列

修改

我已经松散地称之为“形式”,实际上它是一个内部软件,它拥有一个可以访问它的开发团队,但这是一个过程和数据库的一部分,它很复杂并且有很多历史。这个部门是其中之一,我们在标准Google和Microsoft软件之外的所有软件都是在内部创建的。我们有数十个软件,它们都是相互关联的,并且在一个非常大的,非常复杂的数据库中相互联系。我没有编写软件,而且这个软件上的团队有更大的鱼可以为我的跟踪器添加一个字段来进行操作。

这个部门正在经历成长的痛苦(一件好事),上个月刚刚重组了所有工作职能。我为报告团队工作,我被分配了一份报告,用于跟踪分配给现场技术人员的工作。

如果我必须写一个复杂的,长的SUBSTRING我会。部门标准确实要求我的查询得到优化且速度非常快。所以我一直在寻找一种查询数据库并尽可能简单地解析文本的方法。

编辑2(测试下面的Alan Burstein答案)

我一直在测试艾伦的答案,而且很接近。我可以使用一点帮助到达终点线。下面是一些测试代码片段和我得到的结果。

SELECT
    SUBSTRING(al.Comments,PATINDEX('%proposed%',al.Comments)+9,17)  [col1]
INTO
    #test1

遵循此代码:

SELECT
    CASE
    WHEN col1 LIKE '%/%-%/%' THEN SUBSTRING(#test1.col1,PATINDEX('%/%',#test1.col1)-2,5)
    WHEN col1 LIKE '%/%,%/%' THEN SUBSTRING(#test1.col1,PATINDEX('%/%',#test1.col1)-2,5)
    WHEN col1 LIKE '%/%/%' THEN SUBSTRING(#test1.col1,PATINDEX('%/%/%',#test1.col1)-2,12)
    END
FROM
    #test1

我得到的结果样本低于此 - 我还没有完成CAST因为我有一些前导空格和尾随文本我需要弄清楚如何排除 - 想法?

sample 2

这将在临时表和LEFT JOIN中,因此NULL很好。

编辑3 - 我如何使用某项功能

我将Alan的答案标记为正确,因为它在测试时起作用,但我的数据团队负责人告诉我没有任何功能。我在StackExchange上找到了另一个解决方案。阅读G Mastros的第二个答案。我将下一个查询应用到我之前的查询中,以删除有问题的字符:

SELECT
    LEFT(SUBSTRING(#test2.col1, PATINDEX('%[0-9/]%', #test2.col1), 10),
            PATINDEX('%[^0-9/]%', SUBSTRING(#test2.col1, PATINDEX('%[0-9/]%', #test2.col1), 10) + 'X')-1)
FROM #test2

新结果:

enter image description here

1 个答案:

答案 0 :(得分:2)

我在想:

DECLARE @table TABLE (col1 varchar(20));
INSERT @table VALUES 
('1/2/01'),('11/12/99'),('5/5/2013'),('09/13/2003'),
('2/4'),('12/4'),('8/11'),('12/12');

SELECT *, newvalue = 
  CASE 
    WHEN col1 LIKE '%/%/%' THEN CAST(col1 AS date) 
    ELSE col1+'/'+this.yr
  END

FROM @table
CROSS JOIN (VALUES (CAST(YEAR(getdate()) AS varchar(4)))) this(yr);

返回:

col1                 yr   newvalue
-------------------- ---- ----------
1/2/01               2018 2001-01-02
11/12/99             2018 1999-11-12
5/5/2013             2018 2013-05-05
09/13/2003           2018 2003-09-13
2/4                  2018 2018-02-04
12/4                 2018 2018-12-04
8/11                 2018 2018-08-11
12/12                2018 2018-12-12

易腻的柠檬挤压。

基于OP更新的更新

提取日期的一种简单方法是使用PatternSplitCM;代码如下:

-- Function by Chris Morris, read more here: http://www.sqlservercentral.com/articles/String+Manipulation/94365/
    CREATE FUNCTION dbo.PatternSplitCM
(
       @List                VARCHAR(8000) = NULL
       ,@Pattern            VARCHAR(50)
) RETURNS TABLE WITH SCHEMABINDING 
AS RETURN
    WITH numbers AS (
      SELECT TOP(ISNULL(DATALENGTH(@List), 0))
       n = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
      FROM
      (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n),
      (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n),
      (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n),
      (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) g (n))

    SELECT
      ItemNumber = ROW_NUMBER() OVER(ORDER BY MIN(n)),
      Item = SUBSTRING(@List,MIN(n),1+MAX(n)-MIN(n)),
      [Matched]
     FROM (
      SELECT n, y.[Matched], Grouper = n - ROW_NUMBER() OVER(ORDER BY y.[Matched],n)
      FROM numbers
      CROSS APPLY (
          SELECT [Matched] = CASE WHEN SUBSTRING(@List,n,1) LIKE @Pattern THEN 1 ELSE 0 END
      ) y
     ) d
     GROUP BY [Matched], Grouper;

这里有一些更新的示例数据,其中包含日期中隐藏的日期以及代码,以演示如何提取"日期":

DECLARE @table TABLE (col1 varchar(100));
INSERT @table VALUES 
('blah blah 1/2/01 xxxx'),('my name is fred and today is: 11/12/99'),
('5/5/2013 is the day I met Fred'),('The due date is 09/13/2003!!!'),
('This little piggy... ((2/4))'),('Call me on 12/4 at 10:30PM'),
('8/11 is the day after August 10th'),('Really?!?! 12/12 is the best?');

SELECT
  t.col1,
  ps.Item
FROM @table t
CROSS APPLY dbo.PatternSplitCM(t.col1,'[0-9/]') ps
WHERE [Matched] = 1 AND Item LIKE '%[0-9]/[0-9]%';

<强>返回:

col1                                        Item
------------------------------------------- -----------
blah blah 1/2/01 xxxx                       1/2/01
my name is fred and today is: 11/12/99      11/12/99
5/5/2013 is the day I met Fred              5/5/2013
The due date is 09/13/2003!!!               09/13/2003
This little piggy... ((2/4))                2/4
Call me on 12/4 at 10:30PM                  12/4
8/11 is the day after August 10th           8/11
Really?!?! 12/12 is the best?               12/12

接下来,将其转换为子查询并应用我原来的逻辑。

更新解决方案:

DECLARE @table TABLE (col1 varchar(100));
INSERT @table VALUES 
('blah blah 1/2/01 xxxx'),('my name is fred and today is: 11/12/99'),
('5/5/2013 is the day I met Fred'),('The due date is 09/13/2003!!!'),
('This little piggy... ((2/4))'),('Call me on 12/4 at 10:30PM'),
('8/11 is the day after August 10th'),('Really?!?! 12/12 is the best?');

SELECT original = col1, newvalue =
  CASE 
    WHEN dt.item LIKE '%/%/%' THEN CAST(dt.item AS date) 
    ELSE dt.item+'/'+this.yr
  END
FROM
    (
  SELECT
    t.col1,
    ps.Item
  FROM @table t
  CROSS APPLY dbo.PatternSplitCM(t.col1,'[0-9/]') ps
  WHERE [Matched] = 1 AND Item LIKE '%[0-9]/[0-9]%'
) dt
CROSS JOIN (VALUES (CAST(YEAR(getdate()) AS varchar(4)))) this(yr);

<强>返回:

original                                    newvalue
------------------------------------------- ----------
blah blah 1/2/01 xxxx                       2001-01-02
my name is fred and today is: 11/12/99      1999-11-12
5/5/2013 is the day I met Fred              2013-05-05
The due date is 09/13/2003!!!               2003-09-13
This little piggy... ((2/4))                2018-02-04
Call me on 12/4 at 10:30PM                  2018-12-04
8/11 is the day after August 10th           2018-08-11
Really?!?! 12/12 is the best?               2018-12-12