SQL - поиск пробелов в покрытии - PullRequest
0 голосов
/ 26 октября 2019

Я запускаю эту проблему на сервере SQL

Вот моя проблема.

есть что-то вроде этого

Dataset A
FK_ID  StartDate  EndDate     Type
1      10/1/2018  11/30/2018  M
1      12/1/2018  2/28/2019   N
1      3/1/2019   10/31/2019  M

У меня есть второй источник данных у меня нетуправление данными примерно таким образом:

Dataset B
FK_ID  SpanStart  SpanEnd    Type
1      10/1/2018  10/15/2018 M
1      10/1/2018  10/25/2018 M
1      2/15/2019  4/30/2019  M
1      5/1/2019   10/31/2019 M

Я пытаюсь проверить, чтобы каждая дата в каждой записи TYPE M в наборе данных A имела как минимум 1 запись в наборе данных B.

Например, запись 1 в наборе данных A НЕ имеет покрытия с 26.10.2016 по 30.11.2008. Меня действительно волнует только когда закончится покрытие, в этом случае я хочу вернуться 26.10.2008, потому что это первая дата, когда диапазон не имеет покрытия из набора данных B.

Я написал функциюэто делает это, но это довольно медленно, потому что он циклически перебирает каждую дату в каждой M-записи и подсчитывает количество записей в наборе данных B. Он выходит из цикла, когда находит первую, но я действительно хотел бы сделать это более эффективным. Я уверен, что не думаю об этом должным образом, поэтому любые предложения, которые кто-либо может предложить, были бы полезны.

Это раздел кода, который я сейчас выполняю

        else if @SpanType = 'M'
            begin
                    set @CurrDate = @SpanStart
                    set @UncovDays = 0
                    while @CurrDate <= @SpanEnd
                        Begin
                            if (SELECT count(*) 
                                FROM eligiblecoverage ec join eligibilityplan ep on ec.plandescription = ep.planname 
                                WHERE ec.masterindividualid = @IndID
                                    and ec.planbegindate <= @CurrDate and ec.planenddate >= @CurrDate
                                    and ec.sourcecreateddate = @MaxDate
                                    and ep.medicaidcoverage = 1) = 0 
                                begin
                                    SET @Result = concat('NON Starting ',format(@currdate, 'M/d/yyyy'))
                                    BREAK
                                end
                            set @CurrDate = @CurrDate + 1
                        end
                    end

Я не женатчтобы иметь функцию, он просто не мог найти способ сделать это в запросах, которые были не очень-очень медленными.

РЕДАКТИРОВАТЬ: Набор данных B будет никогда не иметь никаких типов, кроме M, так чтоне учитывается

РЕДАКТИРОВАТЬ 2: Код, предлагаемый DonPablo, де-перекрывает данные, но только в тех случаях, когда есть совпадение вообще. Он уменьшает набор данных B до:

FK_ID  SpanStart  SpanEnd  Type
1      10/1/2018  10/25/2018 M

вместо

FK_ID  SpanStart  SpanEnd   Type
1      10/1/2018  10/25/2018 M
1      2/15/2019  4/30/2019  M
1      5/1/2019   10/31/2019 M

Я все еще спорю с этим, но это начало.

Ответы [ 2 ]

0 голосов
/ 26 октября 2019

Я бы подошел к этому, сосредоточившись на B. Я предполагаю, что любая отсутствующая запись будет следовать span_end в таблице. Итак, вот идея:

  • Отключить даты в B (добавив «1» к конечным датам)
  • Добавить флаг, если они присутствуют с типом «M».
  • Проверьте, есть ли в промежутке какие-либо отсутствующие записи для A.
  • Проверьте также первую и последнюю даты.

Итакэто выглядит так:

with bdates as (
      select v.dte,
             (case when exists (select 1
                                from b b2
                                where v.dte between b2.spanstart and b2.spanend and
                                      b2.type = 'M'
                               )
                   then 1 else 0
              end) as in_b
      from b cross apply
           (values (spanstart), (dateadd(day, 1, spanend)
           ) v(dte)
      where b.type = 'M'  -- all we care about
      group by v.dte      -- no need for duplicates
     )
select a.*,
       (case when not exists (select 1
                              from b b2
                              where a.startdate between b2.spanstart and b2.spanend and
                                      b2.type = 'M'
                             )
             then 0
             when not exists (select 1
                              from b b2
                              where a.enddate between b2.spanstart and b2.spanend and
                                      b2.type = 'M'
                             )
             when exists (select 1
                          from bdates bd
                          where bd.dte between a.startdate and a.enddate and
                                bd.in_b = 0
                         )
             then 0
             when exists (select 1
                          from b b2
                          where a.startdate between b2.spanstart and b2.spanend and
                                b2.type = 'M'
                         )
             then 1
             else 0
       end)
from a;

Что это делает? Четыре проверки достоверности:

  • Действителен ли starttime?
  • Действителен ли endtime?
  • Недопустимы ли какие-либо промежуточные даты?
  • Есть ли хотя бы одна действительная запись?
0 голосов
/ 26 октября 2019

Начните с того, что сформулируйте проблему на более мелкие кусочки, выполняя последовательность действий, как я делал в комментарии. См. Джордж Поля "Как это решить" 1945

Тогда Google - ваш друг - посмотрите на ==> sql даты перекрытия перекрываются в одну запись (более миллиона результатов) ОБНОВЛЕНО - я выбрал Объедините перекрывающиеся даты в SQL Server и обновите его для наших имен таблиц и столбцов.

Также посмотрите на теорию из Алгебры Аллена за 1983 год https://www.ics.uci.edu/~alspaugh/cls/shr/allen.html Или из 2014 https://stewashton.wordpress.com/2014/03/11/sql-for-date-ranges-gaps-and-overlaps/Это учебник для начинающих о том, как настроить тестовые данные для этой проблемы.

Наконец, определите, что подсчитывается с помощью ранжирования различных пар A против B - обойдите их полностью внутри, затем поработайте с самыми ранними PartialOverlaps, наконец, выполните Precede/ Следовать пунктам.

- из Объединить перекрывающиеся даты в SQL Server

with SpanStarts as 
( 
  select distinct FK_ID, SpanStart 
  from Coverage_B as t1 
  where not exists 
    (select * from Coverage_B as t2 
     where t2.FK_ID = t1.FK_ID 
       and t2.SpanStart < t1.SpanStart 
       and t2.SpanEnd >= t1.SpanStart) 
), 
SpanEnds as 
( 
  select distinct FK_ID, SpanEnd 
  from Coverage_B as t1 
  where not exists 
    (select * from Coverage_B as t2 
     where t2.FK_ID = t1.FK_ID 
       and t2.SpanEnd > t1.SpanEnd 
       and t2.SpanStart <= t1.SpanEnd) 
),
DeOverlapped_B as
(
Select FK_ID, SpanStart, 
  (select min(SpanEnd) from SpanEnds as e 
   where e.FK_ID = s.FK_ID 
     and SpanEnd >= SpanStart) as SpanEnd 
from SpanStarts as s
)

Select * from DeOverlapped_B

Теперь у нас есть что подать на следующих шагах, и мы можем использоватьвыше как CTE

===========================================

with SpanStarts as 
( 
  select distinct FK_ID, SpanStart 
  from Coverage_B as t1 
  where not exists 
    (select * from Coverage_B as t2 
     where t2.FK_ID = t1.FK_ID 
       and t2.SpanStart < t1.SpanStart 
       and t2.SpanEnd >= t1.SpanStart) 
), 
SpanEnds as 
( 
  select distinct FK_ID, SpanEnd 
  from Coverage_B as t1 
  where not exists 
    (select * from Coverage_B as t2 
     where t2.FK_ID = t1.FK_ID 
       and t2.SpanEnd > t1.SpanEnd 
       and t2.SpanStart <= t1.SpanEnd) 
),
DeOverlapped_B as
(
Select FK_ID, SpanStart, 
  (select min(SpanEnd) from SpanEnds as e 
   where e.FK_ID = s.FK_ID 
     and SpanEnd >= SpanStart) as SpanEnd 
from SpanStarts as s
),

-- find A row's coverage
 ACoverage as (
Select
    a.*, b.SpanEnd, b.SpanStart,
    Case 
    When SpanStart <= StartDate And  StartDate <= SpanEnd
    And  SpanStart <= EndDate   And  EndDate   <= SpanEnd
    Then '1within' -- starts, equals, during, finishes

    When                             EndDate   < SpanStart
    Or   SpanEnd   < StartDate   
    Then '3beforeAfter' -- preceeds, meets, preceeded, met 

    Else '2overlap'  -- one or two ends hang over spanStart/End
    End as relation

    From Coverage_A a
    Left Join DeOverlapped_B b
    On a.FK_ID = b.FK_ID

    Where a.Type = 'M'
)

Select
    *
    ,Case
    When relation1 = '2' And StartDate < SpanStart Then StartDate
    When relation1 = '2' Then DateAdd(d, 1, SpanEnd)
    When relation1 = '3' Then StartDate
    End as UnCoveredBeginning 
    From (
    Select 
        *
        ,SUBSTRING(relation,1,1) as relation1
        ,ROW_NUMBER() Over (Partition by A_ID Order by relation, SpanStart) as Rownum 
        from ACoverage  
    ) aRNO
    Where Rownum = 1 
    And   relation1 <> '1'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...