sql - 从具有差异的 varchar 列计算年龄

标签 sql sql-server database tsql

希望您度过了一个 Restful 复活节。如果您能在以下方面给我建议/帮助,我将不胜感激。 (使用功能/不使用功能)

下面是我的数据集,所需的输出(使用规则中的 DOB 规范派生年龄)

需要您的帮助(请注意,我在 MSSQL 环境中寻找解决方案):-
1. 提出年龄字段。(我尝试了以下脚本,但它没有工作,因为它不够动态,无法包含所有 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]

样本_数据集

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    

Rules:- As you can see in the above 5 scenarios in the comments

  1. Everything is known (use the stated DOB to calculate the age)
  2. Unknown Year (put age as null as the year is known)
  3. Unknown day and month (Use 01/07 for the unknown dd/mm and the stated yyyy)
  4. Unknown month but known day (Use 07 for the unknown mm and the stated dd/07/yyyy)
  5. Unknown day but known month (Use 15 for the unknown dd and the stated 15/mm/yyyy)

Solution in Oracle

Creating a function first (Tried replicating this logic in T-SQL but unsuccessful, hence i am here)

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; 

任何帮助将不胜感激。 非常感谢。

最佳答案

SQL服务器

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

现场演示

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

关于sql - 从具有差异的 varchar 列计算年龄,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49625725/

相关文章:

php - 从多个表中按计数排序的最佳方法是什么?

mysql GROUP BY 与 DISTINCT 不匹配

python - 复杂 SQL 优化与通用语言

sql-server - 将集成服务添加到以前的 SQL Server 安装

c# - 如何摆脱数据库中的多个列?

sql - Oracle SQL 语句动态模式变量

c# - EF Code First 数据库迁移中的 DbMigrator 需要访问主数据库

来自连接表的 SQL 计数

json - 如何为 Firestore 创建大量示例数据?

javascript - 按升序/降序排列数据 - 使用 js 或 db 查询?