确定子组中的开始日期

时间:2019-01-17 18:42:19

标签: sql-server tsql

我有一个需要在其中应用分组的结果集。但是,分组不仅是简单的group by子句(至少我可以弄清楚)。

我们的某些动物(我在一个非常大的动物庇护所里工作)需要兽医检查它们。有人会在动物的记录上输入“ VET CHECK”。历史记录被存储,因此当我目视查看历史记录时,我可以确定何时将动物首次放置在VET CHECK上以及何时将其移除。我遇到的问题是我需要在代码中确定这一点,似乎无法提出解决方案。下面的代码将创建表格,样本数据很好地表示了需要看兽医的大多数动物的样子。

创建表并插入数据后,选择*并按图章描述排序。您会看到在1/15的7:18 AM输入了VET CHECK(在result_request字段中查看)。如果我在上午7:18之前运行报告,则根本不应该显示该动物处于VET CHECK状态(请注意上一条记录中的NULL)。上午7:18之后(直到下午2:07的1/16为止),它应该显示动物处于VET CHECK状态。如果在动物停留期间仅进行一次兽医检查,我可以简单地使用MIN(stamp)来获取动物进入状态的日期。但是,您会注意到这只动物的状态有几次不同。

再往下看结果集,您将其置于下午12:07的1/10状态,并于下午4:02从1/14的状态中删除。如果我在下午12:07的1/10之前运行报告,则该动物不应显示状态。在12:07 pm的1/10和4:02 pm的1/14之间的任何时间运行报告,动物应显示状态,而状态的第一个日期应显示为1/10。同样,由于存在多个VET CHECK状态,因此我不能只使用MIN(stamp)。

CREATE TABLE kennel_history([kh_identity] [int] NOT NULL,
                            [kennel_identity] [int] NOT NULL,
                            [stamp] [datetime] NULL,
                            [userid] [varchar](8) NULL,
                            [impound_no] [varchar](10) NOT NULL,
                            [kennel_no] [varchar](10) NULL,
                            [kennel_stat] [varchar](10) NULL,
                            [hold_notify] [varchar](1) NULL,
                            [outcome_request] [varchar](10) NULL
);

INSERT INTO kennel_history
VALUES (9471697,881929,'2019-01-17 08:05:41','CHEITMAN','K18-847522','QCB','UNAVAILABL',NULL,NULL),
(9471254,881929,'2019-01-16 14:07:18','THUTCHIN','K18-847522','QCE','UNAVAILABL',NULL,NULL),
(9469550,881929,'2019-01-15 07:18:36','BBUSCEMI','K18-847522','QCE','UNAVAILABL','Y','VET CHECK'),
(9469390,881929,'2019-01-14 16:56:02','LRAYNER','K18-847522','QCE','UNAVAILABL',NULL,NULL),
(9469302,881929,'2019-01-14 16:02:41','SHUNT','K18-847522','QRL03','UNAVAILABL',NULL,NULL),
(9467613,881929,'2019-01-13 08:30:46','DEADS','K18-847522','QRL03','UNAVAILABL','Y','VET CHECK'),
(9465923,881929,'2019-01-11 10:16:52','DEADS','K18-847522','QRL06','UNAVAILABL','Y','VET CHECK'),
(9465225,881929,'2019-01-10 12:07:17','KMORRIS','K18-847522','QRL03','UNAVAILABL','Y','VET CHECK'),
(9465224,881929,'2019-01-10 12:07:07','KMORRIS','K18-847522','QRL03','UNAVAILABL','Y','VET CHECK'),
(9463051,881929,'2019-01-08 06:43:19','CSILVEY','K18-847522','QRL03','AVAILABLE',NULL,NULL),
(9461197,881929,'2019-01-06 09:24:07','APENAZUR','K18-847522','QRL08','AVAILABLE',NULL,NULL),
(9460067,881929,'2019-01-05 08:57:46','APENAZUR','K18-847522','QRL07','AVAILABLE',NULL,NULL),
(9459250,881929,'2019-01-04 10:13:45','DEADS','K18-847522','QRL01','AVAILABLE',NULL,NULL),
(9458551,881929,'2019-01-03 12:30:42','ACLARK','K18-847522','QRL08','AVAILABLE',NULL,NULL),
(9458499,881929,'2019-01-03 11:51:48','AGARFIAS','K18-847522','DHC04','AVAILABLE',NULL,NULL),
(9458484,881929,'2019-01-03 11:48:32','AGARFIAS','K18-847522','DHC04','AVAILABLE',NULL,NULL),
(9454810,881929,'2018-12-29 12:20:01','ACLARK','K18-847522','DHC04','UNAVAILABL',NULL,NULL),
(9454683,881929,'2018-12-29 11:08:39','AGARFIAS','K18-847522','SXA24','UNAVAILABL',NULL,NULL),
(9454680,881929,'2018-12-29 11:06:32','AGARFIAS','K18-847522','SXA24','UNAVAILABL',NULL,NULL),
(9454511,881929,'2018-12-29 09:13:22','BBUSCEMI','K18-847522','SXA24','UNAVAILABL',NULL,NULL),
(9453649,881929,'2018-12-28 08:46:12','TSIMONS','K18-847522','SXA24','UNAVAILABL','Y','VET TECH'),
(9453648,881929,'2018-12-28 08:46:07','TSIMONS','K18-847522','SXA24','UNAVAILABL','Y','VET CHECK'),
(9453624,881929,'2018-12-28 08:03:19','BBUSCEMI','K18-847522','SXA24','UNAVAILABL','Y','VET TECH'),
(9453533,881929,'2018-12-27 17:45:22','DEADS','K18-847522','DO03','UNAVAILABL','Y','VET TECH'),
(9453405,881929,'2018-12-27 15:28:02','DEADS','K18-847522','DO02','UNAVAILABL','Y','VET TECH'),
(9452597,881929,'2018-12-26 15:27:48','SSUTTON','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9452426,881929,'2018-12-26 13:05:26','THUTCHIN','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9452121,881929,'2018-12-26 10:18:55','THUTCHIN','K18-847522','SXA04','UNAVAILABL',NULL,NULL),
(9451959,881929,'2018-12-26 08:09:21','BBUSCEMI','K18-847522','SXA04','UNAVAILABL',NULL,NULL),
(9451886,881929,'2018-12-25 14:12:49','SBUCKMAN','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9451884,881929,'2018-12-25 14:11:58','SBUCKMAN','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9451870,881929,'2018-12-25 13:28:15','SBUCKMAN','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9450863,881929,'2018-12-23 17:24:17','BLAEHLE','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9449482,881929,'2018-12-22 13:12:34','V-KTAYLO','K18-847522','DO02','UNAVAILABL','Y','VET CHECK'),
(9448808,881929,'2018-12-21 16:23:03','SBUCKMAN','K18-847522','DO02','UNAVAILABL','Y','VET CHECK'),
(9448111,881929,'2018-12-21 09:10:49','CHEITMAN','K18-847522','DO02','UNAVAILABL',NULL,NULL),
(9448069,881929,'2018-12-21 08:36:47','BMERMAN','K18-847522','DO07','UNAVAILABL',NULL,NULL),
(9447864,881929,'2018-12-20 17:32:53','DEADS','K18-847522','DO07','UNAVAILABL','Y','VET TECH'),
(9446090,881929,'2018-12-19 09:54:33','MGELTZ','K18-847522','DO07','UNAVAILABL',NULL,NULL),
(9445884,881929,'2018-12-19 07:25:51','ZKNOX','K18-847522','DO07','UNAVAILABL','Y','PRIORITY 1'),
(9444928,881929,'2018-12-18 08:22:30','EKNEPPER','K18-847522','DO07','UNAVAILABL','Y','PRIORITY 1'),
(9438860,881929,'2018-12-12 09:15:33','CMCCANN','K18-847522','DO07','UNAVAILABL',NULL,NULL),
(9438820,881929,'2018-12-12 08:21:33','JAUSEC','K18-847522','DO07','UNAVAILABL',NULL,NULL);

以某种方式,我必须考虑到result_request为NULL的状态两侧的记录,因此我知道状态何时开始和停止。这个让我难过。

在解决这个问题上的任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

没有理想的结果集,这实际上只是SWAG ...

我设置了一个带有日期说明的函数,该函数返回截至提供日期为止与当前状态有关的所有信息,以及 userid stamp 与该特定状态跨度开始的时间相关的值。

所以'2019-01-10 12:06:00.000'的结果看起来像这样:enter image description here

...并且'2019-01-12 15:00:00.000'的结果看起来像这样:

enter image description here

您可以在这里进行测试:https://rextester.com/XATW78525 在测试中,我为另外两个动物添加了一些信息-在 stamp userid outcome_request 的某些值之间切换。该功能应在当时为每只动物提供状态记录。

我采用的方法是使用CTE构建初始结果集,该结果集使用LAG来存储动物的先前result_request。有了这些信息后,我就使用CTE加入了两个子查询(一个用于获取最新状态,另一个用于标识状态跨度何时开始的初始实例)以获取每只动物的结果。可以将逻辑从函数中拉出并显式运行,但是以这种方式测试一系列日期更加容易。可能可以用更多的咖啡来清理...

CREATE FUNCTION StatusAtTime (@RequestedTime DATETIME)
RETURNS TABLE
AS
RETURN
(
    WITH statusBuilder AS
    (
        SELECT
            kh1.*,
            lag(kh2.[outcome_request]) OVER (PARTITION BY kh2.[kennel_identity] ORDER BY kh2.[stamp]) AS [lagged_outcome_request]
        FROM dbo.kennel_history kh1
        JOIN dbo.kennel_history kh2
            ON kh2.[stamp] = kh1.[stamp]
            AND kh2.[kennel_identity] = kh1.[kennel_identity]
        WHERE kh1.[stamp] <= @RequestedTime 
    )

    SELECT 
        SB1.[kh_identity]
        ,SB1.[kennel_identity]
        ,SB1.[stamp]
        ,SB1.[userid]
        ,SB1.[impound_no]
        ,SB1.[kennel_no]
        ,SB1.[kennel_stat]
        ,SB1.[hold_notify]
        ,SB1.[outcome_request]
        ,SB2.[userid] AS [initial_outcome_request_userid]
        ,SB2.[stamp] AS [initial_outcome_request_stamp]
    FROM 
        (
            SELECT TOP 1  WITH ties *
            FROM statusBuilder 
            ORDER BY ROW_NUMBER() OVER (PARTITION BY statusBuilder.kennel_identity ORDER BY statusBuilder.stamp desc) 
        ) SB1
    JOIN 
        (
            SELECT TOP 1  WITH ties *
            FROM statusBuilder 
            WHERE ISNULL([outcome_request],'NullOutcome') <> ISNULL([lagged_outcome_request],'NullOutcome')
            ORDER BY ROW_NUMBER() OVER (PARTITION BY statusBuilder.kennel_identity ORDER BY statusBuilder.stamp desc)
        )SB2
    ON SB1.kennel_identity = SB2.kennel_identity
)

答案 1 :(得分:1)

我想分享自己的终极解决方案。使用上面从IsItGreyOrGray发布的答案(感谢您激发创意的力量,而您的答案就是让我选择此路径的原因)与另一个论坛的反馈以及我从供应商那里得到的反馈相结合,我的最终解决方案是创建一个函数来得到所需的日期:

CREATE FUNCTION StartDateOnOutcomeRequest (@KennelIdentity INT,
                                           @OutcomeRequest VARCHAR(10),
                                           @DBStamp        DATETIME)
RETURNS DATE
AS
BEGIN
    /* This section for testing. Leave commented out for normal execution.
    DECLARE @KennelIdentity INT = 881929;
    DECLARE @OutcomeRequest VARCHAR(10) = 'ADOPTIONS';
    DECLARE @DBStamp        DATETIME = '2019-01-21 12:56:17.457';
    */

    DECLARE @CurrentRowNum  INT;
    DECLARE @StartRowNum    INT;
    DECLARE @StartDate      DATETIME;
    DECLARE @RowNumTmp TABLE (RowNumber      INT IDENTITY(1, 1),
                              KHIdentity     INT,
                              OutcomeRequest VARCHAR(10),
                              Stamp          DATETIME);

    -- Load kennel history records into temp table with seqential row number.
    INSERT INTO @RowNumTmp (KHIdentity,
                            OutcomeRequest,
                            Stamp)
    SELECT kh_identity,
           ISNULL(outcome_request, 'None'),
           stamp
    FROM   SYSADM.kennel_history
    WHERE (kennel_identity = @KennelIdentity)
    ORDER BY kh_identity;

    -- Identify the history record matching the database timestamp and outcome request type. Store that record's row number in a variable.
    SELECT @CurrentRowNum = RowNumber
    FROM   @RowNumTmp
    WHERE (Stamp = @DBStamp)
      AND (OutcomeRequest = @OutcomeRequest);

    -- Identify the first row number on the current outcome request by looking at lesser rows where the outcome request does not match.
    SELECT @StartRowNum = MAX(RowNumber) + 1
    FROM   @RowNumTmp
    WHERE (RowNumber <= @CurrentRowNum)
      AND (OutcomeRequest <> @OutcomeRequest);

    -- Finally, get the date using the starting row number for the outcome request and return it below.
    SELECT @StartDate = Stamp
    FROM   @RowNumTmp
    WHERE (RowNumber = @StartRowNum);

    RETURN(@StartDate);
END;

用法:

SELECT k.KENNEL_NO AS KennelNumber,
       ISNULL(k.KENNEL_STAT, '') AS KennelStatus,
       ISNULL(k.kennel_substat, '') AS KennelSubstatus,
       a.ANIMAL_ID AS AnimalId,
       a.ANIMAL_TYPE AS AnimalType,
       a.age_long AS AgeLong,
       CASE
            WHEN a.SECONDARY_BREED IS NULL THEN a.PRIMARY_BREED
            ELSE a.PRIMARY_BREED + ' / ' + a.SECONDARY_BREED
       END + ' / ' + 
       CASE
            WHEN a.SECONDARY_COLOR IS NULL THEN a.PRIMARY_COLOR
            ELSE a.PRIMARY_COLOR + ' / ' + a.SECONDARY_COLOR
       END AS BreedColor,
       dbo.StartDateOnOutcomeRequest(k.kennel_identity, k.OUTCOME_REQUEST, k.STAMP) AS FirstHoldDate,
       DATEDIFF(DD, dbo.StartDateOnOutcomeRequest(k.kennel_identity, k.OUTCOME_REQUEST, k.STAMP), GETDATE()) AS DaysOnHold,
       k.extra3 AS [Level],
       ISNULL(k.OUTCOME_TYPE, '') AS OutcomeType
FROM   SYSADM.KENNEL AS k
       INNER JOIN SYSADM.ANIMAL AS a ON k.ANIMAL_ID = a.ANIMAL_ID