从具有差异的{var}列计算年龄

时间:2018-04-03 08:42:31

标签: sql sql-server database tsql

希望你有一个宁静的复活节。感谢您是否可以在以下建议/帮助我。 (使用功能/无功能)

下面是我的DataSet,Desired Output(使用规则中的DOB规范派生年龄)

需要你的帮助(请注意我在MSSQL环境中寻找解决方案): -
1. 提出Age字段。(我尝试了以下脚本,但它没有工作,因为它不够动态,不包括所有DOB规则,我还附加了一个有效的oracle脚本作为你们的参考)

SELECT 
[ID],
[DOB],
 'age' = DATEDIFF(HOUR,(CONVERT(date,(CASE WHEN ([DOB] like '99/%/%') THEN (REPLACE([DOB],'99','01'))
               ELSE [DOB] END),103)),GETDATE())/8766 
from [Sample]

Sample_Dataset

create table Sample (
  Id  Varchar (50),
  DOB Varchar (50))

  insert into Sample(Id, DOB)
  Values 
  ('38603', '24/02/1969'),
  ('38605', '22/09/1969'),
  ('36356', '17/03/1954'),
  ('36374', '17/05/1975'),
  ('36441', '17/08/1961'),
  ('1a', '10/05/9999'),
  ('1b', '10/99/9999'),
  ('1c', '99/99/9999'),
  ('2a', '--/--/1935'),
  ('2b', '00/00/1935'),
  ('2c', '88/88/1935'),
  ('2d', '99/99/1935'),
  ('3a', '10/--/1935'),
  ('3b', '10/00/1935'),
  ('3c', '10/88/1935'),
  ('3d', '10/99/1935'),
  ('4a', '--/09/1935'),
  ('4b', '00/09/1935'),
  ('4c', '88/09/1935'),
  ('4d', '99/09/1935')

所需的输出

ID     | DOB        | Age (As of 05-03-2018; dd-mm-yyyy)      
38603  | 24/02/1969 | 49  --Everything is known      
38605  | 22/09/1969 | 48        
36356  | 17/03/1954 | 63        
36374  | 17/05/1975 | 42        
36441  | 17/08/1961 | 56    
1a     | 10/05/9999 |null --unknown year
1b     | 10/99/9999 |null
1c     | 99/99/9999 |null
2a     | --/--/1935 |82   --unknown day and month 
2b     | 00/00/1935 |82
2c     | 88/88/1935 |82
2d     | 99/99/1935 |82
3a     | 10/--/1935 |82   --unknown month but known year 
3b     | 10/00/1935 |82
3c     | 10/88/1935 |82
3d     | 10/99/1935 |82
4a     | --/09/1935 |82   --unknown day but known month 
4b     | 00/09/1935 |82
4c     | 88/09/1935 |82
4d     | 99/09/1935 |82    

规则: - 正如您在评论

中的上述 5场景中所看到的那样
  1. 一切都是已知的(使用规定的DOB来计算年龄)
  2. 未知年度(将年龄定为 null ,因为年份已知)
  3. 未知日期和月份(使用 01/07 表示未知dd / mm和所述yyyy)
  4. 未知月份但知道日期(对于未知 mm 使用 07 以及所述dd / 07 / yyyy)
  5. 未知天但已知月份(使用 15 表示未知 dd 和所述15 / mm / yyyy)
  6. Oracle解决方案

    首先创建一个函数(尝试在T-SQL中复制此逻辑但不成功,因此我在这里)

    create or replace function check_dt(in_date in VARCHAR2, in_format in VARCHAR2 default 'DD/MM/YYYY')
    RETURN NUMBER
    IS
    V_DATE DATE;
    V_STATUS INTEGER;
    BEGIN
    
     SELECT TO_DATE(in_date,in_format)
     INTO V_DATECASE  
     FROM DUAL;
    
     V_STATUS := 0;
     RETURN V_STATUS;  
     EXCEPTION WHEN OTHERS THEN
     V_STATUS := SQLCODE; 
             RETURN V_STATUS;
            END;
    
            select check_dt('11/30/2017') from dual;
            select TO_DATE('15/--/9999','DD/MM/YYYY') from dual;
    
    select id, dob,
           case when check_dt(dob) = -1843 --not valid month, default it to July (07)
                   THEN substr(dob,1,2)||'/07'||substr(dob,7,4) 
                when check_dt(dob) = -01847 -- day of month must between 1 and last day of month
                   THEN '1/07/'||substr(dob,7,4)
                WHEN check_dt(dob) = 0 and to_date(dob,'dd/mm/yyyy') > sysdate
                   THEN NULL
                WHEN check_dt(dob) = -0183 -- date not valid for month
                   THEN '15/'||substr(dob,4)
                ELSE
                   THEN dob 
                END New_dob
    from SAMPLE; 
    

    非常感谢任何帮助。 非常感谢你。

3 个答案:

答案 0 :(得分:2)

SQL SERVER

SELECT id,
       CASE WHEN YEAR(GETDATE())-REVERSE(LEFT(REVERSE(DOB), CHARINDEX('/', REVERSE(DOB)) - 1)) > = 0 
            THEN 
              YEAR(GETDATE())-REVERSE(LEFT(REVERSE(DOB), CHARINDEX('/', REVERSE(DOB)) - 1))
          ELSE 
             NULL
       END AS Age
FROM Sample

您的问题的解决方案

WITH CTE AS
(
 SELECT id,
       CASE WHEN ISNUMERIC(REVERSE(LEFT(REVERSE(DOB), CHARINDEX('/', REVERSE(DOB)) - 1))) = 1  THEN
                REVERSE(LEFT(REVERSE(DOB), CHARINDEX('/', REVERSE(DOB)) - 1))
            ELSE
                NULL
        END
       AS Year,
       CASE WHEN ISNUMERIC(LEFT(DOB, CHARINDEX('/', DOB) - 1)) = 1 THEN
                 LEFT(DOB, CHARINDEX('/', DOB) - 1)
            ELSE
                NULL
       END AS DAY,
       CASE WHEN ISNUMERIC(SUBSTRING(DOB,CHARINDEX('/',DOB)+1, CHARINDEX('/',DOB,CHARINDEX('/',DOB)+1) -CHARINDEX('/',DOB)-1)) = 1 THEN
            CASE WHEN SUBSTRING(DOB,CHARINDEX('/',DOB)+1, CHARINDEX('/',DOB,CHARINDEX('/',DOB)+1) -CHARINDEX('/',DOB)-1) >= 1 AND SUBSTRING(DOB,CHARINDEX('/',DOB)+1, CHARINDEX('/',DOB,CHARINDEX('/',DOB)+1) -CHARINDEX('/',DOB)-1)  <= 12 THEN
                SUBSTRING(DOB,CHARINDEX('/',DOB)+1, CHARINDEX('/',DOB,CHARINDEX('/',DOB)+1) -CHARINDEX('/',DOB)-1)
                ELSE 
                NULL
            END
            ELSE
                NULL
        END AS MONTH
FROM Sample),CTE1 AS
(
  SELECT id,
         year,
         month,
         CASE WHEN DAY IS NOT NULL THEN
              CASE WHEN DAY >= 1 AND DAY <= DAY(EOMONTH(year+'-'+month+'-01')) THEN
                DAY
              ELSE
                NULL
              END  
         ELSE NULL
         END AS Day
  FROM CTE
)
,CTE2 AS
(
SELECT id,
           CASE WHEN YEAR IS NULL
                       THEN NULL
                     ELSE
                       CASE WHEN DAY IS NULL AND MONTH IS NULL THEN '01/07'
                            WHEN MONTH IS NULL AND DAY IS NOT NULL THEN CAST(day AS VARCHAR)+'/07'
                            WHEN MONTH IS NOT NULL AND DAY IS NULL THEN '15/'+CAST(MONTH AS VARCHAR)
                            ELSE CAST(day AS VARCHAR)+'/'+CAST(MONTH AS VARCHAR)
                       END
                       + '/'+CAST(YEAR AS VARCHAR)
                END
        AS DOB
FROM CTE1
)
SELECT id,DOB,
   CASE WHEN DOB IS NOT NULL
        THEN 
          CASE WHEN DATEDIFF (day,  CONVERT(DATE, DOB, 103),CONVERT(DATE,GETDATE(),103)) >=0
           THEN FLOOR(DATEDIFF (day, CONVERT(DATE, DOB, 103), CONVERT(DATE,GETDATE(),103)) / 365.2425)
           ELSE
              NULL
          END
      ELSE 
         DOB
   END AS Age
FROM CTE2

DEMO LIVE

  

http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=3714d33cacb02c3fce4f0868c9d0990b

答案 1 :(得分:1)

您可以使用以下内容。我使用不同的CTE来向您展示从varchar DOB获得出生日期的进展情况。我也把桌子换成了临时桌子。

 IF OBJECT_ID('tempdb..#Sample') IS NOT NULL
    DROP TABLE #Sample

create table #Sample (
  Id Int,
  DOB Varchar (50))

insert into #Sample(Id, DOB)
Values 
(38603, '24/02/1969'),
(38605, '22/09/1969'),
(36356, '17/03/1954'),
(36374, '17/05/1975'),
(36441, '17/08/1961'),
(119, '10/05/9999'),
(114, '10/99/9999'),
(132, '99/99/9999'),
(25125, '--/--/1935'),
(2323, '00/00/1935'),
(2512, '88/88/1935'),
(2156, '99/99/1935'),
(368, '10/--/1935'),
(34135, '10/00/1935'),
(3435, '10/88/1935'),
(3241, '10/99/1935'),
(4512, '--/09/1935'),
(4161, '00/09/1935'),
(4312, '88/09/1935'),
(456, '99/09/1935')

;WITH ParsedBirth AS
(
    SELECT
        S.Id,
        S.DOB,
        Year = SUBSTRING(S.DOB, 7, 4),
        Month = SUBSTRING(S.DOB, 4, 2),
        Day = SUBSTRING(S.DOB, 1, 2)
    FROM
        #Sample AS S
),
ParsedBirthInteger AS
(
    SELECT
        P.Id,
        P.DOB,
        Year = CASE WHEN ISNUMERIC(P.Year) = 1 AND P.Year <> '9999' THEN CONVERT(INT, P.Year) END,
        Month = CASE 
            WHEN ISNUMERIC(P.Month) = 1 AND CONVERT(INT, P.Month) BETWEEN 1 AND 12 THEN CONVERT(INT, P.Month) 
            ELSE 7 END,
        Day = CASE 
            WHEN ISNUMERIC(P.Day) = 1 AND CONVERT(INT, P.Day) BETWEEN 1 AND 31 THEN CONVERT(INT, P.Day) 
            ELSE 15 END
    FROM
        ParsedBirth AS P
),
InferredBirth AS
(
    SELECT
        P.Id,
        P.DOB,
        InferredBirth = CONVERT(DATE, CONVERT(VARCHAR(100), P.Year * 10000 + P.Month * 100 + P.Day))
    FROM
        ParsedBirthInteger AS P
)
SELECT
    T.Id,
    T.DOB,
    T.InferredBirth,
    Age = (CONVERT(INT,CONVERT(char(8), GETDATE(),112))-CONVERT(char(8),T.InferredBirth,112))/10000
FROM
    InferredBirth AS T

答案 2 :(得分:0)

首先:

  • 以文化相关的字符串格式存储日期是一个非常糟糕的主意。
  • 使用魔术值9999含义&#34;没有年份&#34;)是一个非常糟糕的主意。
  • 混合这个是最危险,最糟糕的主意!

以下代码会将您的值转换为您应该用来存储它的格式。你可以从这里建立你的年龄逻辑,但我真的建议你,使用这种方法来清理这些混乱并妥善存储你的数据!

DECLARE @sample TABLE(
  Id VARCHAR(10),
  DOB VARCHAR (50))

  INSERT INTO @sample(Id, DOB)
  VALUES 
  ('38603', '24/02/1969'),
  ('38605', '22/09/1969'),
  ('36356', '17/03/1954'),
  ('36374', '17/05/1975'),
  ('36441', '17/08/1961'),
  ('1a', '10/05/9999'),
  ('1b', '10/99/9999'),
  ('1c', '99/99/9999'),
  ('2a', '--/--/1935'),
  ('2b', '00/00/1935'),
  ('2c', '88/88/1935'),
  ('2d', '99/99/1935'),
  ('3a', '10/--/1935'),
  ('3b', '10/00/1935'),
  ('3c', '10/88/1935'),
  ('3d', '10/99/1935'),
  ('4a', '--/09/1935'),
  ('4b', '00/09/1935'),
  ('4c', '88/09/1935'),
  ('4d', '99/09/1935');

- 查询会将您的字符串拆分为/并尝试将值转换为int

WITH Splitted AS
(
    SELECT Id
          ,DOB 
          ,CAST('<x>' + REPLACE(DOB,'/','</x><x>') + '</x>' AS XML).value('/x[1]','varchar(10)') AS DOB_Day
          ,CAST('<x>' + REPLACE(DOB,'/','</x><x>') + '</x>' AS XML).value('/x[2]','varchar(10)') AS DOB_Month
          ,CAST('<x>' + REPLACE(DOB,'/','</x><x>') + '</x>' AS XML).value('/x[3]','varchar(10)') AS DOB_Year
    FROM @sample
)
,Casted AS
(
    SELECT Id
          ,DOB
           --below SQL-Server 2012 you can use `CASE` with `ISNUMERIC` instead of TRY_CAST
          ,TRY_CAST(DOB_Day AS INT)  AS CastedDay 
          ,TRY_CAST(DOB_Month AS INT)  AS CastedMonth
          ,TRY_CAST(DOB_Year AS INT)  AS CastedYear 
    FROM Splitted
)
,Checked AS
(
    SELECT Id
          ,DOB
          --You can use further logic to get the month's days correctly (instead of the plain 31)  
          ,CASE WHEN CastedDay BETWEEN 1 AND 31 THEN CastedDay ELSE NULL END AS TheDay
          ,CASE WHEN CastedMonth BETWEEN 1 AND 12 THEN CastedMonth ELSE NULL END AS TheMonth
          ,CASE WHEN CastedYear BETWEEN 1900 AND 2100 THEN CastedYear ELSE NULL END AS TheYear
    FROM Casted
)
SELECT *
FROM Checked; 

结果

+-------+------------+--------+----------+---------+
| Id    | DOB        | TheDay | TheMonth | TheYear |
+-------+------------+--------+----------+---------+
| 38603 | 24/02/1969 | 24     | 2        | 1969    |
+-------+------------+--------+----------+---------+
| 38605 | 22/09/1969 | 22     | 9        | 1969    |
+-------+------------+--------+----------+---------+
| 36356 | 17/03/1954 | 17     | 3        | 1954    |
+-------+------------+--------+----------+---------+
| 36374 | 17/05/1975 | 17     | 5        | 1975    |
+-------+------------+--------+----------+---------+
| 36441 | 17/08/1961 | 17     | 8        | 1961    |
+-------+------------+--------+----------+---------+
| 1a    | 10/05/9999 | 10     | 5        | NULL    |
+-------+------------+--------+----------+---------+
| 1b    | 10/99/9999 | 10     | NULL     | NULL    |
+-------+------------+--------+----------+---------+
| 1c    | 99/99/9999 | NULL   | NULL     | NULL    |
+-------+------------+--------+----------+---------+
| 2a    | --/--/1935 | NULL   | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 2b    | 00/00/1935 | NULL   | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 2c    | 88/88/1935 | NULL   | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 2d    | 99/99/1935 | NULL   | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 3a    | 10/--/1935 | 10     | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 3b    | 10/00/1935 | 10     | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 3c    | 10/88/1935 | 10     | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 3d    | 10/99/1935 | 10     | NULL     | 1935    |
+-------+------------+--------+----------+---------+
| 4a    | --/09/1935 | NULL   | 9        | 1935    |
+-------+------------+--------+----------+---------+
| 4b    | 00/09/1935 | NULL   | 9        | 1935    |
+-------+------------+--------+----------+---------+
| 4c    | 88/09/1935 | NULL   | 9        | 1935    |
+-------+------------+--------+----------+---------+
| 4d    | 99/09/1935 | NULL   | 9        | 1935    |
+-------+------------+--------+----------+---------+