从历史更改表中选择实体的SQL查询

时间:2018-06-19 13:12:45

标签: sql sql-server sql-server-2008 union

我不得不创建一个我不确定该如何处理的查询,因为无法涵盖所有​​情况。

我有一个名为company的表,具有以下(相对)列

餐桌公司

列:

Id | Name | Status | Status_Effective_Date

让我们说Status可以从1 to 12中取值。逻辑是该公司的状态为2,例如自01/01/2018(status_effective_date)起

表公司状态记录

Id | Company_Id | Status | Status_Effective_Date

此表保存状态发生的更改的历史记录。例如,如果我有两个Id = 10这样的公司条目,例如

Row_1 : 1 | 10 | 1 | 02/03/2011
Row_2 : 2 | 10 | 5 | 06/08/2013

然后,具有Id = 10的公司从102/03/2011的状态为06/08/2013。之后就是状态5

我要做的是创建一个报告,向我显示在选定日期范围内处于选定状态的所有公司。

例如,现在假设我要使用status = 1和日期范围在01/01/2017 - 31/12/2017之间的查询

我必须理解的案例是:(是我想要的案例,是我不想要的案例)

  1. 一家公司始终处于状态1,从未改变(是)

    1.1 company_table条目的状态为1,生效日期早于开始日期

    1.2 company_status_history表没有任何行,因为未应用状态更改

  2. 一家公司的状态为1,并且在日期范围(否)之前更改为其他状态

    2.1 company_table条目的状态为<> 1,生效日期早于开始日期

    2.2 company_status_history_table的公司状态为1的公司,其生效日期为初始生效日期(初始状态),公司状态为1的新公司的状态(<> 1),其生效日期为变更日期。 (在日期范围之前)

  3. 一家公司的状态为1,并且在日期范围内(是)更改为其他状态

    3.1 company_table条目的状态为<> 1,生效日期在开始日期之后和结束日期之前

    3.2 company_status_history_table的状态为1的公司的一个条目,其有效日期为初始生效日期(初始状态),而状态为新状态的公司的一个条目(<> 1),其有效日期为变更日期。 (在日期范围内)

  4. 一家公司的状态为1,并且在日期范围(是)之后更改为其他状态

    4.1 company_table条目的状态为<> 1,且生效日期在结束日期之后

    4.2 company_status_history_table的公司状态为1的公司,其生效日期为初始生效日期(初始状态),公司状态为1的新公司的状态(<> 1),其生效日期为变更日期。 (日期范围之后)

  5. 一家公司的状态为<> 1,并且在日期范围(是)之前更改为状态= 1

    5.1 company_table条目的状态为1或<> 1(因为可能会再次更改),并且生效日期如果仍处于状态= 1,则可能在日期范围之前;如果再次更改,则生效日期可能在以后的某个日期。

    5.2 company_status_history_table的前一个状态为公司的一个条目,其生效日期为初始生效日期(初始状态),而新状态中的至少一个公司的条目(= 1)(生效日期为该公司的生效日期)。更改(在日期范围之前)

  6. 一家公司的状态为<> 1,并且在日期范围内(是)更改为状态1。

    6.1 company_table条目的状态为1或<> 1(如果已再次更改),并且生效日期为date_range内的日期,或者如果以后再次更改则为后一个日期

    6.2 company_status_history_table在初始状态下有一个公司的条目,生效日期是初始生效日期(初始状态),在状态1下有至少一个公司的条目,生效日期是变更日期(更改日期内)。日期范围)

  7. 一家公司的状态为<> 1,并且在日期范围(否)之后更改为状态1。

    7.1 company_table条目的状态为1或<> 1(如果有其他更改),并且生效日期在结束日期之后

    7.2 company_status_history_table的公司初始状态为<> 1,生效日期为初始生效日期(初始状态),至少一个新状态的公司(1),生效日期为日期更改的日期(在日期范围之后)

到目前为止,我尝试过的是:

-- Case 6
select *
from company com, company_status_history csh 
where csh.company_status_id = 1
    and com.company_id = csh.company_id 
    and csh.company_status_eff_date > '20170101'
    and csh.company_status_eff_date < '20171231'
union
-- Case 1
select *
from company com
where com.company_status_id = 1
    and com.company_status_eff_date < '20181231'
    and com.company_id NOT IN (select company_id 
                        from company_status_history csh)

我猜想使用工会可能会有更有效的方法。

我所缺少的是案例3、4、5,这是我应该从以下查询的先前company_status_history条目(生效日期)中了解的部分,如果更改应将公司纳入我的最终清单中。 >

select * from company com, company_status_history csh 
where com.company_id = csh.company_id 

我们将非常感谢您的帮助。

3 个答案:

答案 0 :(得分:1)

根据我的阅读,我假设您希望所有在日期范围内状态为1的公司。如果您要这样做,那很简单。

以下语句可以完成此工作:

SELECT C.*
  FROM COMPANY C
  LEFT JOIN ( SELECT H.STATUS, H.COMPANY_ID
                FROM COMPANYSTATUS H
               WHERE H.STATUS_EFFECTIVE_DATE = (SELECT MAX(H1.STATUS_EFFECTIVE_DATE)
                                                  FROM COMPANYSTATUS H1
                                                 WHERE H1.COMPANY_ID = H.COMPANY_ID
                                                   AND H1.STATUS_EFFECTIVE_DATE <= '20171231'
             ) CH ON CH.COMPANY_ID = C.ID   
 WHERE ( C.STATUS = 1 AND CH.COMPANY_ID IS NULL ) -- CASE #1 : STATUS = 1 AND NEVER CHANGED (NO HISTORY)
    OR H.STATUS = 1

我所做的是创建一个嵌套视图,该嵌套视图的最后一个状态到您的日期范围结束为止,因此,如果公司状态的最后一次更改为1,则该公司应包括在您的结果中。我们不在乎您的日期范围后的更改,因此我将限制放入了嵌套视图中。

对于该请求,范围的开头并不重要。您可能出于其他目的需要连接其他表。

我是Oracle专家,所以我认为使用Oracle Analytics(分析)可以使此语句变得更好,但是我认为这对于SQL Server是有效的语句。

答案 1 :(得分:0)

我会开枪!

似乎您需要提供处于给定状态和日期范围内的所有公司的列表。当我们意识到您想了解在提供的日期范围之间具有任何地位的任何公司时,这似乎就不那么复杂了。

案例3,4和5是最重要的: 一家公司处于状态1,并在日期范围内更改为其他状态(是) 我们可以发现这没有问题。 一家公司处于状态1,并在日期范围后更改为其他状态(是) 只要它处于日期范围开头的状态,它就应该出现在结果中。

一家公司的状态为<> 1,并且在日期范围(是)之前更改为状态= 1,因此它进入了日期范围的开始和结束之间的状态。

这是我制作的一个工作样本。希望对您有所帮助。

   --TEST DATA
        create table #company
        (
        id int identity not null,
        name varchar(222) not null,
        status int not null,
        status_effective_date datetime not null
        )
        create table #companystatus
        (
        id int identity not null,
        company_id int not null,
        status int not null,
        status_effective_date datetime not null
        )

        insert into #company (name,status,status_effective_date) values('foo',1,'12/31/2017')
        insert into #company (name,status,status_effective_date) values('biz',2,'11/30/2017')
        insert into #company (name,status,status_effective_date) values('baz',1,'12/31/2017')
        insert into #company (name,status,status_effective_date) values('bloh',3,'11/30/2017')
        insert into #company (name,status,status_effective_date) values('blee',4,'12/31/2017')

        declare @fooid int
        set @fooid = (select id from #company where name = 'foo')
        declare @bizid int
        set @bizid =  (select id from #company where name = 'biz')
        declare @bazid int
        set @bazid = (select id from #company where name = 'baz')
        declare @blohid int
        set @blohid =   (select id from #company where name = 'bloh')

        insert into #companystatus (company_id,status,status_effective_date) values (@fooid,2,'1/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@bizid,5,'3/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@bazid,4,'2/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@blohid,1,'2/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@fooid,2,'4/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@fooid,4,'9/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@bizid,2,'5/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@bazid,1,'10/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@blohid,3,'7/1/2018')
        insert into #companystatus (company_id,status,status_effective_date) values (@fooid,1,'6/1/2018')

            --get only one distinct company id and name where the status date is between my two dates and the status is the value I have chosen (1)
        select distinct(cn.id), cn.name from #companystatus c
        join #company cn on
        cn.id = c.company_id
        where c.status_effective_date between '1/1/2017' and '5/1/2018'
        and c.status in (1) 
--replace the dates and status with what you want
        --clean up
        drop table #company
        drop table #companystatus

答案 2 :(得分:0)

由于简化了问题,this answer终于使我得到了有效的查询。 我正在发布它,以防其他人需要它。

-- View that gets the entity_history_status with start date and end date
-- At the moment if it is only one entry in the table (it went to current) the end_date is the same as the start date
;WITH changed_companies_with_ranges AS
(
    -- Get the final table for the selected status
    SELECT company_id,
           MIN(company_status_eff_date) AS start_date, 
           CASE 
              WHEN COUNT(*) > 1 THEN DATEADD(DAY, -1, MAX(company_status_eff_date))
              ELSE min(company_status_eff_date) -- Check why it changes the result when I set the today as end_date
           END AS end_date,
           grp

    FROM (
              -- Something with counting again in order to get the start and end date according to this grp
              SELECT company_status_hist_id, company_id, company_status_id, company_status_eff_date,
                     ROW_NUMBER() OVER (PARTITION BY company_id ORDER BY company_status_eff_date) - 
                     cnt AS grp
              FROM (
                        -- Get the entries from the history table that had at any point the selected status and add a new column saying how many entries do they have
                        SELECT esh2.company_status_hist_id, esh2.company_id, esh2.ecompany_status_id, esh2.company_status_eff_date, x.cnt
                        FROM company_status_history AS esh2
                        OUTER APPLY 
                        (
                           SELECT COUNT(*) AS cnt
                           FROM ecompany_status_history AS c
                           WHERE c.company_status_id = 1 -- The selected status
                                 AND c.company_id  = esh2.company_id 
                                 AND c.company_status_eff_date < esh2.company_status_eff_date
                        ) AS x
                   ) as CTE
            ) as CTE2 
    GROUP BY company_id, grp
    HAVING COUNT(CASE WHEN company_status_id = 1 THEN 1 END) > 0 -- The selected status
)
SELECT * FROM (
    SELECT 
     en.company_id
    ,en.company_name
    ,en.company_reg_num
    FROM company en
    where en.company_id in(
                            select company_id 
                            from changed_entitities_with_ranges 
                            where start_date = end_date
                        )
                        and en.company_status_eff_date > '2017-01-01 00:00:00.000' -- Start Date
    union
    select 
     en.company_id
    ,en.company_name
    ,en.company_reg_num
    FROM company en
    where en.company_id in(
                            select company_id 
                            from changed_entitities_with_ranges 
                            where (start_date between '2017-01-01 00:00:00.000' and '2017-12-31 00:00:00.000') -- Range
                               or (start_date < '2017-01-01 00:00:00.000' and end_date > '2017-01-01 00:00:00.000') -- Start date -- End date
                         )
    union
    -- 1. Without any history changes + 3. changed to 1 before start date + 5 Changes to the normal status from other statuses before the end date + 7 Changes to the normal status from other statuses in between the period
    -- Gets the entities that haven't changed at all and have been in status 1 before the end date
    SELECT 
     en.company_id
    ,en.company_name
    ,en.company_reg_num
    FROM company en
    WHERE en.company_status_id = 1
                       AND en.company_status_eff_date < '2017-12-31 00:00:00.000'
    UNION
    -- 2. Changes to the other statuses from the status of normal after the start date + 4. Changes to the other statuses from the status of normal before the end date  + 6. Changes to the other statuses from the status of normal in between the period
    -- Gets the entities that have been changed to any status but were created or altered ato some point inside the range
    SELECT 
     en.company_id
    ,en.company_name
    ,en.company_reg_num
    from company en
    where en.company_id IN (select company_id from company_status_history es 
                       where es.company_status_eff_date BETWEEN '2017-01-01 00:00:00.000' AND '2017-12-31 00:00:00.000' AND es.company_status_id = 1)
) as result ORDER BY company_id