基于多个条件加入2个表的问题

时间:2016-07-24 22:12:27

标签: sql oracle join

在使用连接时,我正在努力解决逻辑难题。所以我有两个表显示在EmpStatus和EmpEIN下面。所以我想要做的是加入这两个表并产生如下输出。如果消费者拥有一个完整月份的EmpNumber而不是应该获得另一行中的状态,那么状态应该带有空条目 在EmpNumber。

我不知道如何加入这两个表来产生输出。如果一些sql专业版可以帮助我,那将是非常棒的。

 EmpStatus:
 ConsumerID JanStatus   FebStatus   MarStatus   AprStatus   MayStatus
1001            P           P           P           P           P
1002            P           P           P           P           P
1003            P           P           P           P           P
1004            P           P           P           P           P
1005            P           P           P           P           P
EmpEIN:
ConsumerID  EmpNumber   EmpNumberStartDate  EmpNumberEndDate
1001        102020202       1/1/2015            31/1/2015   --  dates in dd/mm/yyyy format
1001        210201021       1/2/2015            31/3/2015
1002            NULL        NULL                NULL
1003            NULL        NULL                NULL
1004            NULL        NULL                NULL
1005            NULL        NULL                NULL

OUTPUT:
ConsumerID  EmpNumber   JanStaus    FebStatus   MarStatus   AprStatus   MayStatus
1001        102020202       P       NULL        NULL        NULL        NULL
1001        210201021       NULL    P           P           NULL        NULL
1001        NULL            NULL    NULL        NULL        P           P
1002        NULL            P       P           P           P           P
1003        NULL            P       P           P           P           P
1004        NULL            P       P           P           P           P
1005        NULL            P       P           P           P           P

3 个答案:

答案 0 :(得分:0)

我已经解决了解决方案的难点,即将日期范围分解为整个月(以及任何溢出的任何一方),并检查是否有任何跨度不能覆盖整个月。然后我在MAX(CASE ... WHEN ... END)stmt中标记每个月的状态,并使用固定状态' P'或NULL。所以实际上我完全忽略了EmpStatus表。加入以下解决方案是您需要做的事情,还有更多的测试。

-- create test data . This will be your EmpEIN table
create table DateTable ( ConsumerID  int, EMP_NUM int, from_date date , to_date date)
insert into DateTable VALUES ( 1001, 1, '2/1/2015', '3/31/2015' )

CREATE FUNCTION [dbo].[DaysInMonth](@date datetime)
RETURNS int
AS
BEGIN
    SET @date = DATEADD(MONTH, 1, @date)
    DECLARE @result int = (select DAY(DATEADD(DAY, -DAY(@date), @date)))
    RETURN @result
END
Go


CREATE FUNCTION SplitDates
(
    @from_date Datetime ,
    @to_date   Datetime 
)
        RETURNS 
        @Table_Var TABLE 
        (
            mo_from_date  Datetime, 
            mo_to_date    Datetime,
            Days_In_Month int
        )
AS
BEGIN

;WITH cte_SplitDates AS
        (
        SELECT  @from_date as from_date
              , @to_date   as to_date
              , @from_date AS mo_from_date
              , DATEADD(day, day(@from_date)* -1 + 1, @from_date) AS bom_date
        --   FROM DateTable
        UNION ALL
        SELECT from_date
             , to_date
             , DATEADD(month,1,bom_date)
             , DATEADD(month,1,bom_date)
          FROM cte_SplitDates
         where DATEADD(month,1,mo_from_date) < to_date
        )

    INSERT INTO @Table_Var
    SELECT 
      mo_from_date ,
      CASE when to_date < DATEADD(month,1,bom_date) 
       THEN
           to_date
       ELSE
           DATEADD(day, -1, DATEADD(month,1,bom_date))
       END AS mo_to_date ,
      [dbo].[DaysInMonth](mo_from_date) as Days_In_Month
   FROM cte_SplitDates

  RETURN 

END 

现在使用上述帮助函数的查询中最重要的部分是:

SELECT A.ConsumerID,  A.EMP_NUM , 
        MAX( CASE WHEN A.Mnth =1 AND Days_In_Month = Diff_Start_End_Dates THEN 'P' ELSE NULL  END ) as JanStaus    ,
        MAX( CASE WHEN A.Mnth =2 AND Days_In_Month = Diff_Start_End_Dates THEN 'P' ELSE NULL  END ) as Febtaus     ,
        MAX( CASE WHEN A.Mnth =3 AND Days_In_Month = Diff_Start_End_Dates THEN 'P' ELSE NULL  END ) as MarStaus     ,
        MAX( CASE WHEN A.Mnth =4 AND Days_In_Month = Diff_Start_End_Dates THEN 'P' ELSE NULL  END ) as AprStaus     ,
        MAX( CASE WHEN A.Mnth =5 AND Days_In_Month = Diff_Start_End_Dates THEN 'P' ELSE NULL  END ) as MayStaus     
        -- Other Months can be added just as above 
FROM
(
        SELECT D.ConsumerID, D.EMP_NUM , month( S.mo_from_date) as Mnth,
         S.mo_from_date , S.mo_to_date,  S.Days_In_Month, DATEDIFF( Day, S.mo_from_date , S.mo_to_date) + 1 As Diff_Start_End_Dates 
        FROM  -- DateTable D
        (
            SELECT * FROM DateTable D
            UNION ALL
            Select ConsumerID , NULL As EMP_NUM , DATEADD(D, 1, MAX(to_date) ) , STR(YEAR(MAX(to_date))) + '-12-31' -- This is how the NULL EmpNumber is added.
            FROM DateTable D 
            Group by ConsumerID 
        ) D
        CROSS APPLY dbo.SplitDates(D.from_date, D.to_date) S
) A
Group by A.ConsumerID,  A.EMP_NUM

说明: 该查询使用两个辅助函数

  1. DaysInMonth - 获取给定日期的一个月中的天数
  2. SplitDates - 将日期范围分成整个月的块。如果日期范围不跨越整个月(7/10 - 7/20)那么它就是 返回相同的。
  3. 我建议您使用示例DateTable来使用Query和辅助函数。此表与EmpEIN表具有相同的模式。 (而且我没有使用其他状态表,因为它的数据本质上是非常静态的)

    注意:我从这里借用了CTE代码:https://stackoverflow.com/a/20272758/2628302

答案 1 :(得分:0)

Oracle替代方案:

WITH base AS
  ( SELECT
      ee.ConsumerID, ee.EmpNumber,
      CASE 
        WHEN EmpNumber IS NULL THEN JanStatus
        WHEN
          EmpNumberStartDate<=           TO_DATE('01-Jan-'||TO_CHAR(EmpNumberStartDate,'YYYY')) AND 
          EmpNumberEndDate  >=ADD_MONTHS(TO_DATE('01-Jan-'||TO_CHAR(EmpNumberStartDate,'YYYY')),1)-1 
          THEN JanStatus 
        ELSE NULL 
      END JanStatus,
      CASE 
        WHEN EmpNumber IS NULL THEN FebStatus
        WHEN
          EmpNumberStartDate<=           TO_DATE('01-Feb-'||TO_CHAR(EmpNumberStartDate,'YYYY')) AND 
          EmpNumberEndDate  >=ADD_MONTHS(TO_DATE('01-Feb-'||TO_CHAR(EmpNumberStartDate,'YYYY')),1)-1 
          THEN FebStatus 
        ELSE NULL 
      END FebStatus,
      CASE 
        WHEN EmpNumber IS NULL THEN MarStatus
        WHEN
          EmpNumberStartDate<=           TO_DATE('01-Mar-'||TO_CHAR(EmpNumberStartDate,'YYYY')) AND 
          EmpNumberEndDate  >=ADD_MONTHS(TO_DATE('01-Mar-'||TO_CHAR(EmpNumberStartDate,'YYYY')),1)-1 
          THEN MarStatus 
        ELSE NULL 
      END MarStatus,
      CASE 
        WHEN EmpNumber IS NULL THEN AprStatus
        WHEN
          EmpNumberStartDate<=           TO_DATE('01-Apr-'||TO_CHAR(EmpNumberStartDate,'YYYY')) AND 
          EmpNumberEndDate  >=ADD_MONTHS(TO_DATE('01-Apr-'||TO_CHAR(EmpNumberStartDate,'YYYY')),1)-1 
          THEN AprStatus 
        ELSE NULL 
      END AprStatus,
      CASE 
        WHEN EmpNumber IS NULL THEN MayStatus
        WHEN
          EmpNumberStartDate<=           TO_DATE('01-May-'||TO_CHAR(EmpNumberStartDate,'YYYY')) AND 
          EmpNumberEndDate  >=ADD_MONTHS(TO_DATE('01-May-'||TO_CHAR(EmpNumberStartDate,'YYYY')),1)-1 
          THEN MayStatus 
        ELSE NULL 
      END MayStatus
    FROM
      EmpStatus es
        JOIN
      EmpEIN ee ON ee.ConsumerID = es.ConsumerID
  )
SELECT
  b.ConsumerID, TO_NUMBER(NULL) EmpNumber, 
  NVL2(b.JanStatus, NULL, es.JanStatus) JanStatus,
  NVL2(b.FebStatus, NULL, es.FebStatus) FebStatus,
  NVL2(b.MarStatus, NULL, es.MarStatus) MarStatus,
  NVL2(b.AprStatus, NULL, es.AprStatus) AprStatus,
  NVL2(b.MayStatus, NULL, es.MayStatus) MayStatus
FROM
  ( SELECT 
      ConsumerID, 
      MAX(JanStatus) JanStatus, 
      MAX(FebStatus) FebStatus, 
      MAX(MarStatus) MarStatus, 
      MAX(AprStatus) AprStatus, 
      MAX(MayStatus) MayStatus 
    FROM base
    WHERE empnumber IS NOT NULL
    GROUP BY ConsumerID 
  ) b
    JOIN
  EmpStatus es ON es.ConsumerID = b.ConsumerID
UNION ALL
SELECT *
FROM base
ORDER BY ConsumerID, EmpNumber NULLS LAST

答案 2 :(得分:0)

我提供此解决方案来说明您需要使用的技术。第一张表格不正常;在进行连接之前,你需要UNPIVOT它。第一个表中的列名包含实际数据,这是一个巨大的设计缺陷。您必须在UNPIVOT时恢复数据 - 您将在查询中看到我是如何做到这一点的。 (我对它进行了硬编码,您可以使用应用于列名称的字符串函数的日期函数,但在我看来这并不是一个很大的改进...你必须在最后的透视中“撤消”这个,无论如何,你无法避免硬编码列名。)

我将“P everywhere”更改为不同的字母,以便更容易理解正在发生的事情。加入是customerid,按日期 - 您将在查询中看到此内容。

with
     empstatus ( ConsumerID, JanStatus, FebStatus, MarStatus, AprStatus, MayStatus ) as (
       select 1001, 'A', 'B', 'C', 'D', 'E' from dual union all
       select 1002, 'F', 'G', 'H', 'I', 'J' from dual union all
       select 1003, 'K', 'L', 'M', 'N', 'O' from dual union all
       select 1004, 'P', 'Q', 'R', 'S', 'T' from dual union all
       select 1005, 'U', 'V', 'W', 'X', 'Y' from dual
     ),
     empein ( ConsumerID, EmpNumber, EmpNumberStartDate, EmpNumberEndDate ) as (
       select 1001, 102020202, date '2015-01-01', date '2015-01-31' from dual union all
       select 1001, 210201021, date '2015-02-01', date '2015-03-31' from dual union all
       select 1002,      NULL, NULL             , NULL              from dual union all
       select 1003,      NULL, NULL             , NULL              from dual union all
       select 1004,      NULL, NULL             , NULL              from dual union all
       select 1005,      NULL, NULL             , NULL              from dual
     ),
     a ( consumerid, mth, val ) as (                                           -- UNPIVOT
       select * from empstatus
       unpivot ( val for mth in (                    JanStatus as date '2015-01-01',
                     FebStatus as date '2015-02-01', MarStatus as date '2015-03-01',
                     AprStatus as date '2015-04-01', MayStatus as date '2015-05-01' ) )
     ),
     j ( consumerid, empnumber, mth, val ) as (                                --    JOIN
       select a.consumerid, e.empnumber, a.mth, a.val
       from a left outer join empein e
              on  a.consumerid = e.consumerid
              and a.mth between e.empnumberstartdate and empnumberenddate
     )
select * from j                                                                --   PIVOT
pivot ( min(val) for mth in (                    date '2015-01-01' as JanStatus,
                 date '2015-02-01' as FebStatus, date '2015-03-01' as MarStatus,
                 date '2015-04-01' as AprStatus, date '2015-05-01' as MayStatus ) )     
order by consumerid, empnumber;

<强>输出

CONSUMERID  EMPNUMBER JANSTATUS FEBSTATUS MARSTATUS APRSTATUS MAYSTATUS
---------- ---------- --------- --------- --------- --------- ---------
      1001  102020202 A
      1001  210201021           B         C
      1001                                          D         E
      1002            F         G         H         I         J
      1003            K         L         M         N         O
      1004            P         Q         R         S         T
      1005            U         V         W         X         Y