Как найти значение с датой, ближайшей к другой дате - PullRequest
0 голосов
/ 04 февраля 2020

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

Предварительная пересадка:

1 год = 365 дней +/- 30 дней

3 месяца = 90 дней +/- 14 дней

1 месяц = ​​30 дней +/- 7 дней

После пересадки:

1 день = 24 часа +/- 12 часов

1 неделя = 7 дней +/- 1 день

1 месяц = ​​30 дней +/- 7 дней

3 месяца = 90 дней +/- 14 дней

6 месяцев = 180 дней + / - 30 дней

1 год = 365 дней +/- 30 дней

В моей модели данных много таблиц (результаты SQL запросов к серверу), но основная лабораторная таблица выглядит следующим образом следующее:

+-----------------------+-----------------+------------+-----------+
| Order ID | Episode ID | Transplant Date | Lab Date   | Lab Value |
+----------+------------+-----------------+------------+-----------+
| 111      | 222        | 5/2/2018        | 1/22/2018  | 23        |
| 112      | 222        | 5/2/2018        | 1/27/2018  | 15        |
| 113      | 222        | 5/2/2018        | 5/3/2018   | 14        |
| 114      | 222        | 5/2/2018        | 10/19/2018 | 12        |
| 115      | 223        | 1/23/2019       | 1/24/2019  | 20        |
| 116      | 223        | 1/23/2019       | 1/25/2019  | 25        |
| 117      | 223        | 1/23/2019       | 1/31/2019  | 29        |
| 118      | 223        | 1/23/2019       | 4/23/2019  | 30        |
| 119      | 223        | 1/23/2019       | 3/1/2019   | 35        |
| 120      | 224        | 7/19/2019       | 7/19/2018  | 5         |
| 121      | 224        | 7/19/2019       | 7/24/2018  | 13        |
+-----------------------+-----------------+------------+-----------+

Order ID - уникальный идентификатор лаборатории, Episode ID - уникальный идентификатор пациента, и мы ищем лаборатории относительно Transplant Date.

Есть еще одна таблица для данных пациента, которая выглядит примерно так:

+------------+----------------+-----------------+
| Episode ID | Patient Name   | Transplant Date |
+------------+----------------+-----------------+
| 222        | Alphers, Ralph | 5/2/2018        |
| 223        | Bethe, Hans    | 1/23/2019       |
| 224        | Gammow, George | 7/19/2019       |
+------------+----------------+-----------------+

Полученные данные должны выглядеть примерно так:

+------------+------------+--------------+-------------+------------+-------------+--------------+---------------+-------------+
| Episode ID | 1 year pre | 3 months pre | 1 month pre | 1 day post | 1 week post | 1 month post | 6 months post | 1 year post |
+------------+------------+--------------+-------------+------------+-------------+--------------+---------------+-------------+
| 222        |            | 15           |             | 14         |             |              | 12            |             |
| 223        |            |              |             | 20         | 29          | 35           |               |             |
| 224        | 5          |              |             |            |             |              |               |             |
+------------+------------+--------------+-------------+------------+-------------+--------------+---------------+-------------+

Есть ли лучший способ сделать это, принимая во внимание как про время перерыва (пользовательский опыт) и сложность разработки?

Прямо сейчас, вот как я это делаю.

Во-первых, я использую Power Query (M) для создания моменты времени (например, Table.AddColumn(#"Changed Type", "Minutes to One Year Before Transplant", each Number.Abs(Duration.TotalMinutes(([Lab Date] - DateTime.From(Date.AddYears([Transplant Date], -1))))))). Затем я использую DAX, чтобы найти количество дней для записи, ближайшей к правильной целевой дате:

Labs shortest minutes to one year before transplant = 
VAR EpisodeID = Patients[Episode ID]
VAR TargetDate = DATEADD(Patients[Transplant Date], 1, MONTH)
VAR WindowDays = 30
RETURN
CALCULATE(
    MIN(Labs[Minutes to One Month After Transplant]),
    FILTER(Labs, Labs[Episode ID] = EpisodeID),
    FILTER(Labs, Labs[Lab Date] >= DATEADD(TargetDate, -WindowDays, DAY)),
    FILTER(Labs, Labs[Lab Date] <= DATEADD(TargetDate, WindowDays, DAY))
)

Затем я использую это количество минут в качестве идентификатора, чтобы получить Order ID

Lab Order ID closest to one year before transplant = 
VAR EpisodeID = Patients[Episode ID]
VAR TargetDate = DATEADD(Patients[Transplant Date], 1, MONTH)
VAR WindowDays = 30
VAR DaysFrom = Patients[Labs shortest minutes to one year before transplant]
RETURN
CALCULATE(
    MIN(Labs[Order ID]),
    FILTER(Labs, Labs[Episode ID] = EpisodeID),
    FILTER(Labs, Labs[Lab Date] >= DATEADD(TargetDate, -WindowDays, DAY)),
    FILTER(Labs, Labs[Lab Date] <= DATEADD(TargetDate, WindowDays, DAY))
)

Наконец, я могу использовать это Order ID, чтобы получить все, что я хочу из этой лаборатории, например значение:

Lab Value closest to one year before transplant = 
VAR EpisodeID = Patients[Episode ID]
VAR OrderID = Patients[Lab Order ID closest to one year before transplant]
RETURN
CALCULATE(
    MIN(Labs[Value]),
    FILTER(Labs, Labs[Episode ID] = EpisodeID),
    FILTER(Labs, Labs[Order ID] = OrderID)
)

И мне нужно сделать это для 3 различных лабораторий, что означает повторение этого процесса, как 30 раз. И итоговый отчет требует времени для выполнения расчетов. Я могу сделать sh кучу работы на SQL сервере, но, может быть, это не лучшая идея?

Ответы [ 4 ]

1 голос
/ 05 февраля 2020

Я добавляю другой ответ из-за ответа на предыдущий ответ о данных.

Я бы сделал таблицу с сегментами, в которые попадают даты. Таким образом, если кто-то запрашивает разные сегменты, его легко добавить.

CREATE TABLE [dbo].[table_Buckets](
    [Bucket] [varchar](50) NULL,
    [NumDaysLow] [int] NULL,
    [NumDaysHigh] [int] NULL
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Pre-1Yr', -395, -335)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Pre-3Mth', -105, -75)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Pre-1Mth', -37, -21)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Post-1Day', 0, 2)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Post-1Wk', 6, 8)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Post-1Mth', 21, 37)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Post-3Mth', 76, 104)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Post-6Mth', 150, 210)
GO
INSERT [dbo].[table_Buckets] ([Bucket], [NumDaysLow], [NumDaysHigh]) VALUES (N'Post-1Yr', 335, 395)
GO

Теперь вы можете выполнить следующий запрос sql он возьмет данные, поместит дату эпизода в эпизод, возьмет наименьшее число для каждого сегмента и затем повернет таблицу к желаемому виду. Вы должны будете спроектировать свои данные вокруг этой структуры.

select
   EpisodeID
  ,[Pre-1Yr]
  ,[Pre-3Mth]
  ,[Pre-1Mth]
  ,[Post-1Day]
  ,[Post-1Wk]
  ,[Post-1Mth]
  ,[Post-3Mth]
  ,[Post-6Mth]
  ,[Post-1Yr]

from 
(
  --this select statement takes the lowest value if there are more than one value per bucket
  select main.EpisodeID, main.Bucket, min(main.LabValue) as LabValue from
    (--this select statement assigns the episode to a buckets 
     select
        ml.EpisodeID
        , (select Bucket from
                table_Buckets
            where 
                    NumDaysLow  <= datediff(d,pd.TransplantDate, ml.LabDate)
                and NumDaysHigh >= datediff(d,pd.TransplantDate, ml.LabDate)
            ) AS Bucket
        , ml.LabValue as LabValue

    from 
        table_MainLab ML, 
        table_PatientData PD where ml.EpisodeID = pd.EpisodeID
    ) main
group by EpisodeID, Bucket) s


pivot
(avg(LabValue)
for [Bucket] in
  ([Pre-1Yr]
  ,[Pre-3Mth]
  ,[Pre-1Mth]
  ,[Post-1Day]
  ,[Post-1Wk]
  ,[Post-1Mth]
  ,[Post-3Mth]
  ,[Post-6Mth]
  ,[Post-1Yr])
 ) as pivottable

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

0 голосов
/ 05 февраля 2020

Весь ваш код - М, поэтому я не уверен, почему вы отметили это SQL. Но вот [возможно, не самое элегантное] решение SQL:

create table labs (
    OrderID int not null,
    EpisodeID int not null,
    TransplantDate date not null,
    LabDate date not null,
    LabValue int not null)

insert labs
values 
(111, 222, cast('5/2/2018'  as date), cast('1/22/2018'  as date), 23),
(112, 222, cast('5/2/2018'  as date), cast('1/27/2018'  as date), 15),
(113, 222, cast('5/2/2018'  as date), cast('5/3/2018'   as date), 14),
(114, 222, cast('5/2/2018'  as date), cast('10/19/2018' as date), 12),
(115, 223, cast('1/23/2019' as date), cast('1/24/2019'  as date), 20),
(116, 223, cast('1/23/2019' as date), cast('1/25/2019'  as date), 25),
(117, 223, cast('1/23/2019' as date), cast('1/31/2019'  as date), 29),
(118, 223, cast('1/23/2019' as date), cast('4/23/2019'  as date), 30),
(119, 223, cast('1/23/2019' as date), cast('3/1/2019'   as date), 35),
(120, 224, cast('7/19/2019' as date), cast('7/19/2018'  as date),  5),
(121, 224, cast('7/19/2019' as date), cast('7/24/2018'  as date), 13)

create table patient (
    EpisodeID int not null,
    PatientName varchar(128) not null,
    TransplantDate date not null
)

insert patient
values
(222, 'Alphers, Ralph', cast('5/2/2018'  as date)),
(223, 'Bethe, Hans',    cast('1/23/2019' as date)),
(224, 'Gammow, George', cast('7/19/2019' as date))


select q.EpisodeID
, min(q.[1YrPre]  ) as '1YrPre'
, min(q.[3MoPre]  ) as '3MoPre'
, min(q.[1MoPre]  ) as '1MoPre'
, min(q.[1DayPost]) as '1DayPost'
, min(q.[1WkPost] ) as '1WkPost'
, min(q.[1MoPost] ) as '1MoPost'
, min(q.[3MoPost] ) as '3MoPost'
, min(q.[6MoPost] ) as '6MoPost'
, min(q.[1YrPost] ) as '1YrPost'

from (
    select r.OrderID
    , r.EpisodeID
    , case when r.[1YrPreCheck]   = m.[1YrPreCheck]   and m.[1YrPreCheck]   <= 30 then r.LabValue end as '1YrPre'
    , case when r.[3MoPreCheck]   = m.[3MoPreCheck]   and m.[3MoPreCheck]   <= 14 then r.LabValue end as '3MoPre'
    , case when r.[1MoPreCheck]   = m.[1MoPreCheck]   and m.[1MoPreCheck]   <=  7 then r.LabValue end as '1MoPre'
    , case when r.[1DayPostCheck] = m.[1DayPostCheck] and m.[1DayPostCheck] <=  1 then r.LabValue end as '1DayPost'
    , case when r.[1WkPostCheck]  = m.[1WkPostCheck]  and m.[1WkPostCheck]  <=  1 then r.LabValue end as '1WkPost'
    , case when r.[1MoPostCheck]  = m.[1MoPostCheck]  and m.[1MoPostCheck]  <=  7 then r.LabValue end as '1MoPost'
    , case when r.[6MoPostCheck]  = m.[3MoPostCheck]  and m.[3MoPostCheck]  <= 14 then r.LabValue end as '3MoPost'
    , case when r.[6MoPostCheck]  = m.[6MoPostCheck]  and m.[6MoPostCheck]  <= 30 then r.LabValue end as '6MoPost'
    , case when r.[1YrPostCheck]  = m.[1YrPostCheck]  and m.[1YrPostCheck]  <= 30 then r.LabValue end as '1YrPost'

    from (
        select p.EpisodeID
        , min(abs(datediff(day, l.LabDate, dateadd(year,  -1, p.TransplantDate)))) as '1YrPreCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(month, -3, p.TransplantDate)))) as '3MoPreCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(month, -1, p.TransplantDate)))) as '1MoPreCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(day,    1, p.TransplantDate)))) as '1DayPostCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(day,    7, p.TransplantDate)))) as '1WkPostCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(month,  1, p.TransplantDate)))) as '1MoPostCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(month,  3, p.TransplantDate)))) as '3MoPostCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(month,  6, p.TransplantDate)))) as '6MoPostCheck'
        , min(abs(datediff(day, l.LabDate, dateadd(year,   1, p.TransplantDate)))) as '1YrPostCheck'

        from labs l
          inner join patient p on p.EpisodeID = l.EpisodeID

        group by p.EpisodeID
    ) m
      inner join (
        select l.OrderID
        , p.EpisodeID
        , l.LabValue
        , abs(datediff(day, l.LabDate, dateadd(year,  -1, p.TransplantDate))) as '1YrPreCheck'
        , abs(datediff(day, l.LabDate, dateadd(month, -3, p.TransplantDate))) as '3MoPreCheck'
        , abs(datediff(day, l.LabDate, dateadd(month, -1, p.TransplantDate))) as '1MoPreCheck'
        , abs(datediff(day, l.LabDate, dateadd(day,    1, p.TransplantDate))) as '1DayPostCheck'
        , abs(datediff(day, l.LabDate, dateadd(day,    7, p.TransplantDate))) as '1WkPostCheck'
        , abs(datediff(day, l.LabDate, dateadd(month,  1, p.TransplantDate))) as '1MoPostCheck'
        , abs(datediff(day, l.LabDate, dateadd(month,  3, p.TransplantDate))) as '3MoPostCheck'
        , abs(datediff(day, l.LabDate, dateadd(month,  6, p.TransplantDate))) as '6MoPostCheck'
        , abs(datediff(day, l.LabDate, dateadd(year,   1, p.TransplantDate))) as '1YrPostCheck'

        from labs l
      inner join patient p on p.EpisodeID = l.EpisodeID
    ) r on r.EpisodeID = m.EpisodeID
)q 

group by q.EpisodeID
0 голосов
/ 05 февраля 2020

Я бы добавил это в комментарии, но мне нужно больше очков репутации, прежде чем я смогу комментировать. Может быть, модератор может переместить это для меня.

Для начала,

1 - Вам необходимо определить, что делать, если лаборатория не попадает ни в одну из групп, описанных выше. Например, что бы вы сделали, если бы дата лаборатории составляла 6 месяцев. Где бы вы хотели, чтобы о 6-месячной лаборатории сообщили? В приведенном выше примере вы потеряли некоторые данные из EpisodeID 222. По моему опыту, вы должны сообщать об этом где-то - даже если это ловушка, которую необходимо исследовать.

2 - Вам нужно будет определить, что вы хочу сделать, когда у вас есть 2 отчета за тот же период времени. С EpisodeID 222 вы увидите, что у вас есть 2 Лаборатории в период до 90 дней. 22 января и 27 января будут приходиться на этот период.

3 - у вас есть похожие данные в двух таблицах. TransplantDate должен быть только в вашей PatientTable

. Лучше всего, если это простой сводный (кросс-таблица) запрос. Если вы сможете лучше определить свои данные, ответив на вопросы 1 и 2 выше, вы продвинетесь дальше, чтобы это сделать.

0 голосов
/ 05 февраля 2020

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

1 Year Pre = IF('Table'[Lab Date]>='Table'[Transplant Date]-395 && 'Table'[Lab Date]<='Table'[Transplant Date]-335,'Table'[LabValue],BLANK())

для 3-месячного периода:

3 Months Pre = IF('Table'[Lab Date]>='Table'[Transplant Date]-104 && 'Table'[Lab Date]<='Table'[Transplant Date]-76,'Table'[LabValue],BLANK())

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

...