如何在SQL Server中查询此输出

时间:2018-08-14 10:31:19

标签: sql sql-server sql-server-2008 tsql sql-server-2012

我有一张表,上面有这样的数据:

CREATE TABLE Test
    (CustName varchar(20), Country varchar(20), RecordedTime datetime, CurrNo tinyint);

INSERT INTO Test
    (CustName, Country, RecordedTime, CurrNo)
VALUES
    ('Alex', 'Australia', '2018-06-01 08:00:00', 1),
    ('Alex', 'China', '2018-06-01 10:00:00', 2),
    ('Alex', 'India', '2018-06-01 10:05:00', 3),
    ('Alex', 'Japan', '2018-06-01 11:00:00', 4),
    ('John', 'Australia', '2018-06-01 08:00:00', 1),
    ('John', 'China', '2018-06-02 08:00:00', 2),
    ('Bob', 'Australia', '2018-06-02 09:00:00', 1),
    ('Bob', 'Brazil', '2018-06-03 09:50:00', 2),
    ('Bob', 'Africa', '2018-06-03 11:50:00', 3),
    ('Bob', 'India', '2018-06-03 11:55:00', 4),
    ('Tim', 'Brazil', '2018-06-10 00:00:00', 2),
    ('Tim', 'Cuba', '2018-06-11 00:00:00', 3),
    ('Tim', 'India', '2018-06-11 00:05:00', 4),
    ('Jerry', 'Cuba', '2018-06-12 00:00:00', 4),
    ('Jerry', 'Brazil', '2018-06-12 00:05:00', 5),
    ('Jerry', 'India', '2018-06-12 00:10:00', 7),
    ('Jerry', 'USA', '2018-06-12 00:15:00', 9)

    ('Maulik', 'Aus', '2018-06-12 00:00:00',3),
    ('Maulik', 'Eng', '2018-06-13 00:00:00',4),
    ('Maulik', 'USA', '2018-06-14 00:00:00',5),
    ('Maulik', 'Ind', '2018-06-14 00:00:00',6);

表结果:

 CustName    Country    RecordedTime           CurrNo
 -----------------------------------------------------
  Alex        Australia  2018-Jun-01 08:00 AM    1
  Alex        China      2018-Jun-01 10:00 AM    2
  Alex        India      2018-Jun-01 10:05 AM    3
  Alex        Japan      2018-Jun-01 11:00 AM    4
  John        Australia  2018-Jun-01 08:00 AM    1
  John        China      2018-Jun-02 08:00 AM    2
  Bob         Australia  2018-Jun-02 09:00 AM    1
  Bob         Brazil     2018-Jun-03 09:50 AM    2
  Bob         Africa     2018-Jun-03 11:50 AM    3
  Bob         India      2018-Jun-03 11:55 AM    4
  Tim         Brazil     2018-Jun-10 12:00 AM    2
  Tim         Cuba       2018-Jun-11 12:00 AM    3
  Tim         India      2018-Jun-11 12:05 AM    4
  Jerry       Cuba       2018-Jun-12 12:00 AM    4
  Jerry       Brazil     2018-Jun-12 12:05 AM    5
  Jerry       India      2018-Jun-12 12:10 AM    7
  Jerry       USA        2018-Jun-12 12:15 AM    9
  Maulik      Aus        2018-Jun-12 00:00:AM    3
  Maulik      Eng        2018-Jun-13 00:00:AM    4
  Maulik      USA        2018-Jun-14 00:00:AM    5
  Maulik      Ind        2018-Jun-14 00:00:AM    6

我需要涵盖以下所有情况的输出。

对于如何在“审核”和“历史记录”字段中显示值,存在一条经验法则;

  1. 记录应仅对原始帐户具有Audit =“ ADD”或“ CHANGE”,并且History =“ NEW”,“ BEFORE”或“ CURRENT”(这意味着表中的条目必定是从CurrNo = 1开始

  2. 记录中不应包含Audit =“ ADD”和History =“ NEW” 对于迁移的帐户(这意味着表中的条目不是从CurrNo = 1开始,它可能从2或3或任何升序数字开始),对于这种类型的帐户,审计应具有“ CHANGE”和历史记录字段应具有“ BEFORE”或“ CURRENT”

场景1 : 如果输入日期为2018年6月1日,则输出应如下所示(即,当同一天多次添加和修改记录时)

CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
   Alex        Australia  2018-Jun-01 08:00 AM   ADD      NEW
   Alex        Australia  2018-Jun-01 08:00 AM   CHANGE   BEFORE
   Alex        Japan      2018-Jun-01 11:00 AM   CHANGE   CURRENT
   John        Australia  2018-Jun-01 08:00 AM   ADD      NEW

场景2 : 如果给定输入日期为2018年6月2日,则输出应如下所示(即,当前几天已经有一条记录并且今天编辑了同一条记录并且今天有任何新记录时)

   CustName    Country    RecordedTime           Audit    History
  -----------------------------------------------------------------
   John        Australia  2018-Jun-01 08:00 AM   CHANGE   BEFORE
   John        China      2018-Jun-02 08:00 AM   CHANGE   CURRENT
   Bob         Australia  2018-Jun-02 09:00 AM   ADD      NEW

场景3 : 如果给定输入日期为2018年6月3日,则输出应如下所示(即,当同一天对记录进行多次编辑时,它应列出最新日期的最后一条记录,然后列出当前给定日期的最后一条记录)

   CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
   Bob         Australia  2018-Jun-02 09:00 AM   CHANGE   BEFORE
   Bob         India      2018-Jun-03 12:55 AM   CHANGE   CURRENT

场景4 : 如果输入日期为2018年6月10日,则输出应为以下

   CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
   Tim         Brazil     2018-Jun-10 12:00 AM    CHANGE   CURRENT

场景5 : 如果输入日期为2018年6月11日,则输出应如下所示(即类似于方案2)

   CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
   Tim         Brazil     2018-Jun-10 12:00 AM    CHANGE   BEFORE
   Tim         India      2018-Jun-11 12:05 AM    CHANGE   CURRENT

场景6 : 如果输入日期为2018年6月12日,则输出应如下所示(即类似于方案3)

   CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
    Jerry       Cuba       2018-Jun-12 12:00 AM    CHANGE   BEFORE
    Jerry       USA        2018-Jun-12 12:15 AM    CHANGE   CURRENT
    Maulik      Aus        2018-Jun-12 00:00 AM    CHANGE   CURRENT

如果输入日期为2018年6月13日,则输出应如下所示

   CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
    Maulik      Aus        2018-Jun-12 00:00 AM    CHANGE   BEFORE
    Maulik      Eng        2018-Jun-13 00:00 AM    CHANGE   CURRENT

如果输入日期为2018年6月14日,则输出应如下所示

   CustName    Country    RecordedTime           Audit    History
  ----------------------------------------------------------------
    Maulik      Eng        2018-Jun-13 00:00 AM    CHANGE   BEFORE
    Maulik      Ind        2018-Jun-14 00:00 AM    CHANGE   CURRENT

下面是我正在使用的当前代码(满足方案2和3,但不满足其余部分);

declare @d date='2018-Jun-03'

; with Indexer as 
(
    select 
        *, 
        rn= row_number() over(partition by CustName order by RecordedTime),
        rn2=row_number() over(partition by CustName order by RecordedTime desc)
    from records
)
,GetValidCustomerRecords as
(
    select 
        CustName,
        Country,
        RecordedTime,
        Audit   = case when cast(RecordedTime as date)=@d and rn=1 then 'add' else 'change' end,
        History = case 
                    when cast(RecordedTime as date)=@d and rn=1 
                    then 'new' 
                    when cast(RecordedTime as date)<@d and rn=1 
                    then 'before'
                    else 'current' end
    from Indexer i 
    where CustName in
    (
    select 
        distinct CustName 
    from records
    where cast(RecordedTime as date)=@d
    ) 
    and (rn=1 or rn2=1) and cast(RecordedTime as date)<=@d
)

select * from GetValidCustomerRecords
order by CustName, RecordedTime

任何SQL专家都可以修改此查询以满足所有情况?非常感谢和感谢。

注意:这里有一个类似的问题仅供参考-How to retrieve data from SQL Server based on below example?

4 个答案:

答案 0 :(得分:1)

根据我的理解,以下是您所需输出的逻辑

第1步

  • 为每个客户名称获取给定日期的max(CurrNo)。
  • 这里是cte。

第2步

  • 获取cte中可用的所有CustName的提及日期的第一个输入记录。
  • 在上述记录的并集中,获取cte中可用的所有CustNames的当前日期少于当前日期而被编辑的记录的最后编辑记录。
  • 这里是cte2。

第3步

  • 从#test表中获取其中currNo = maximun或currno = 1的记录,您将获得最近更新的记录以及给定日期的任何新添加的记录。
  • 这是继cte和cte2之后的第一个查询。

第4步

  • 获取给定日期的第一个添加记录(如果有),其中“审计”为更改,“历史记录”为以前。
  • 这是针对自定义特殊情况的要求
  • 这是第二个查询。

第5步

  • 从cte2获取第一条记录,以获取给定日期的先前编辑记录的一条记录。
  • 这是第三个查询。

-

CREATE TABLE #Test
    (CustName varchar(20), Country varchar(20), RecordedTime datetime, CurrNo tinyint);


INSERT INTO #Test
    (CustName, Country, RecordedTime, CurrNo)
VALUES
    ('Alex', 'Australia', '2018-06-01 08:00:00', 1),
    ('Alex', 'China', '2018-06-01 10:00:00', 2),
    ('Alex', 'India', '2018-06-01 10:05:00', 3),
    ('Alex', 'Japan', '2018-06-01 11:00:00', 4),
    ('John', 'Australia', '2018-06-01 08:00:00', 1),
    ('John', 'China', '2018-06-02 08:00:00', 2),
    ('Bob', 'Australia', '2018-06-02 09:00:00', 1),
    ('Bob', 'Brazil', '2018-06-03 09:50:00', 2),
    ('Bob', 'Africa', '2018-06-03 11:50:00', 3),
    ('Bob', 'India', '2018-06-03 00:55:00', 4),
    ('Tim', 'Brazil', '2018-06-10 00:00:00', 2),
    ('Tim', 'Cuba', '2018-06-11 00:00:00', 3),
    ('Tim', 'India', '2018-06-11 00:05:00', 4),
    ('Jerry', 'Cuba', '2018-06-12 00:00:00', 4),
    ('Jerry', 'Brazil', '2018-06-12 00:05:00', 5),
    ('Jerry', 'India', '2018-06-12 00:10:00', 7),
    ('Jerry', 'USA', '2018-06-12 00:15:00', 9),
    ('Maulik', 'Aus', '2018-06-12 00:00:00',3),
    ('Maulik', 'Eng', '2018-06-13 00:00:00',4),
    ('Maulik', 'USA', '2018-06-14 00:00:00',5),
    ('Maulik', 'Ind', '2018-06-14 00:00:00',6);

select * from #Test

   declare @selectedDate date='2018-06-14';
with cte as
(
select CustName,max(CurrNo) maxno,count(1) cnt
from #Test where datediff(day,RecordedTime,@selectedDate)=0 
group by CustName
),cte2 as (
select top 1 t.*,cnt
 from #Test t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)>0 and t.CurrNo!=c.maxno
order by t.RecordedTime  desc
union select top 1 t.*,cnt
 from #Test t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)=0 and t.CurrNo!=c.maxno
order by t.RecordedTime 
)
select t.CustName,t.Country,t.RecordedTime,cnt
,case when t.CurrNo=1 then 'ADD' else 'CHANGE' End as Audit
,case when t.CurrNo=1 then 'NEW' when t.CurrNo=c.maxno then 'CURRENT' else 'BEFORE' end History
 from #Test t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)=0 and (t.CurrNo=c.maxno or t.CurrNo=1)
union 
select top 1 t.CustName,t.Country,t.RecordedTime,cnt
,'CHANGE' Audit
,'BEFORE' History
 from #Test t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)=0 and t.CurrNo=1 and c.cnt>1
union 
select top 1 t.CustName,t.Country,t.RecordedTime,cnt
,'CHANGE' Audit
,'BEFORE' History
 from cte2 t order by RecordedTime

drop table #Test

“ Ronen Ariely”的观点很不错。下面是更新的查询,它具有2次表扫描而不是8次表扫描。

with cte as
(
select CustName,max(CurrNo) maxno,count(1) cnt
from #Test where datediff(day,RecordedTime,@selectedDate)=0 
group by CustName
),cte2 as (
select * from #Test where CustName in 
(
select distinct custname from cte
)
and datediff(day,RecordedTime,@selectedDate)>=0
),cte3 as (
select top 1 t.*,cnt
 from cte2 t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)>0 and t.CurrNo!=c.maxno
order by t.RecordedTime  desc
union select top 1 t.*,cnt
 from cte2 t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)=0 and t.CurrNo!=c.maxno
order by t.RecordedTime 
)
select t.CustName,t.Country,t.RecordedTime
,case when t.CurrNo=1 then 'ADD' else 'CHANGE' End as Audit
,case when t.CurrNo=1 then 'NEW' when t.CurrNo=c.maxno then 'CURRENT' else 'BEFORE' end History
 from cte2 t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)=0 and (t.CurrNo=c.maxno or t.CurrNo=1)
union 
select top 1 t.CustName,t.Country,t.RecordedTime
,'CHANGE' Audit
,'BEFORE' History
 from cte2 t join cte c on c.CustName =t.CustName
where datediff(day,RecordedTime,@selectedDate)=0 and t.CurrNo=1 and c.cnt>1
union 
select top 1 t.CustName,t.Country,t.RecordedTime
,'CHANGE' Audit
,'BEFORE' History
 from cte3 t order by RecordedTime

答案 1 :(得分:1)

据我从不同场景所了解的,它们基本上只能通过2条规则来实现。

  • 规则1:同一天具有RecordedTime的CustName的记录。
  • 规则2:具有RecordedTime超过一天的CustName的记录。

有了一些窗口函数和外部查询中的OR,我们可以有两个规则来定位所有场景。

然后使用CASE WHEN计算“审计”和“历史”。

 SELECT
  CustName, Country, 
  FORMAT(RecordedTime, 'yyyy-MMM-dd hh:mm tt') as RecordedTime,
  (CASE CurrNo 
   WHEN 1 then 'ADD'
   ELSE 'CHANGE'
   END) as [Audit],
  (CASE  
   WHEN CurrNo = 1 then 'NEW'
   WHEN ReverseCurrRN = 1 then 'CURRENT'
   ELSE 'BEFORE'
   END) as [History]
   --, CurrNo, ReverseCurrRN, MinCurrNoPerCust, MaxCurrNoPerCustDate ,MinCurrNoPerCust, MinDatePerCust, MaxDatePerCust
 FROM
 (
    SELECT CustName, Country, RecordedTime, CurrNo
      ,ROW_NUMBER() OVER (PARTITION BY CustName ORDER BY CurrNo DESC) AS ReverseCurrRN
      ,MIN(CurrNo) OVER (PARTITION BY CustName) AS MinCurrNoPerCust
      ,MAX(CurrNo) OVER (PARTITION BY CustName, cast(RecordedTime AS DATE)) AS MaxCurrNoPerCustDate
      ,CAST(MIN(RecordedTime) OVER (PARTITION BY CustName) AS DATE) AS MinDatePerCust
      ,CAST(MAX(RecordedTime) OVER (PARTITION BY CustName) AS DATE) AS MaxDatePerCust
    FROM Test t
    WHERE CAST(RecordedTime AS DATE) between DATEADD(day,-1,@Date) and @Date
 ) q
 WHERE MaxDatePerCust = @Date
 AND (
    -- Scenario 1 & 2 & 4 & 6
       (MinDatePerCust = MaxDatePerCust AND (ReverseCurrRN = 1 OR CurrNo = MinCurrNoPerCust OR (MinCurrNoPerCust = 1 AND ReverseCurrRN = 2)))
    -- Scenario 2 & 3 & 5 & 7 & 8
    OR (MinDatePerCust != MaxDatePerCust AND (ReverseCurrRN = 1 OR CurrNo = 1 OR CurrNo = MaxCurrNoPerCustDate))
 )
 ORDER BY MinDatePerCust, CustName, CurrNo;

您可以在 db <> fiddle here

上对其进行测试

答案 2 :(得分:1)

美好的一天,

请检查以下解决方案是否满足您的所有需求。我用您的数据和更多的行对其进行了测试,但是始终最好重新检查一次。乍一看,它似乎返回了请求的结果。稍后我会添加一些解释

我正在使用它的查询:

DECLARE @Date DATE = '2018-06-12';
with MyCTE as (
    SELECT 
        t.CustName,t.Country,t.RecordedTime,t.CurrNo, D = CONVERT(DATE, RecordedTime)
        ,RN_D = ROW_NUMBER() 
            OVER (partition by t.CustName order by t.CurrNo desc)
        ,RN = ROW_NUMBER() 
            OVER (partition by t.CustName order by t.CurrNo)
        ,RN_Old = ROW_NUMBER() 
            OVER (partition by t.CustName, (CASE WHEN CONVERT(DATE, RecordedTime) < @Date then 0 else 1 END) order by t.CurrNo desc)
        ,Cnt = COUNT(*) 
            OVER (partition by t.CustName)
        ,CntToday = COUNT(CASE WHEN CONVERT(DATE, RecordedTime) = @Date THEN 1 ELSE NULL END) 
            OVER (partition by t.CustName)
    FROM Test t
    where 
        -- returns rows untill current date
        CONVERT (DATE, RecordedTime) <= @Date 
        -- only if relevnat to current date
        and EXISTS (
            SELECT * FROM test t0 
            where CONVERT (DATE, RecordedTime) = @Date and t0.CustName = t.CustName
        )
)
,MyCTE2 as (
    select
        CustName, Country, RecordedTime, D, CurrNo, RN_D, RN, Cnt, t2.c, History, CntToday, RN_Old
    from MyCTE t1
    left JOIN (select * from (values(1, 'NEW'),(1, 'BEFORE')) t2(c, History) ) t2 
        on t1.CurrNo = t2.c
            and CntToday > 1
            and D = @Date
    where 
        RN_D = 1 
        or (RN = 1 and D = @Date) 
        or (RN_Old = 1 and D < @Date)
)
,MyCTE3 as (
    select CustName, Country, RecordedTime
        -- unmarke the bellow comment in order to get the accessories columns I used
        -- This is recommended to understand the line-of-thinking
        --, D, c, RN_D, RN, CurrNo, Cnt, CntToday, RN_Old
        , History = CASE
            WHEN CurrNo = 1 and Cnt = 1 then 'NEW'
            WHEN RN_D = 1 then 'CURRENT'
            else ISNULL(History,'BEFORE')
        END
    from MyCTE2
)
select CustName, Country, RecordedTime--, D, c, RN_D, RN, CurrNo, Cnt, CntToday, RN_Old
    ,Audit = CASE when History='New' then 'ADD' else 'CHANGE'  END
    , History
from MyCTE3

为了简化测试,我将整个查询插入表函数

DROP FUNCTION IF EXISTS dbo.F
GO
CREATE FUNCTION dbo.F(@Date DATE)
RETURNS TABLE AS RETURN (

--DECLARE @Date DATE = '2018-06-12';
with MyCTE as (
    SELECT 
        t.CustName,t.Country,t.RecordedTime,t.CurrNo, D = CONVERT(DATE, RecordedTime)
        ,RN_D = ROW_NUMBER() 
            OVER (partition by t.CustName order by t.CurrNo desc)
        ,RN = ROW_NUMBER() 
            OVER (partition by t.CustName order by t.CurrNo)
        ,RN_Old = ROW_NUMBER() 
            OVER (partition by t.CustName, (CASE WHEN CONVERT(DATE, RecordedTime) < @Date then 0 else 1 END) order by t.CurrNo desc)
        ,Cnt = COUNT(*) 
            OVER (partition by t.CustName)
        ,CntToday = COUNT(CASE WHEN CONVERT(DATE, RecordedTime) = @Date THEN 1 ELSE NULL END) 
            OVER (partition by t.CustName)
    FROM Test t
    where 
        -- returns rows untill current date
        CONVERT (DATE, RecordedTime) <= @Date 
        -- only if relevnat to current date
        and EXISTS (
            SELECT * FROM test t0 
            where CONVERT (DATE, RecordedTime) = @Date and t0.CustName = t.CustName
        )
)
,MyCTE2 as (
    select
        CustName, Country, RecordedTime, D, CurrNo, RN_D, RN, Cnt, t2.c, History, CntToday, RN_Old
    from MyCTE t1
    left JOIN (select * from (values(1, 'NEW'),(1, 'BEFORE')) t2(c, History) ) t2 
        on t1.CurrNo = t2.c
            and CntToday > 1
            and D = @Date
    where 
        RN_D = 1 
        or (RN = 1 and D = @Date) 
        or (RN_Old = 1 and D < @Date)
)
,MyCTE3 as (
    select CustName, Country, RecordedTime
        -- unmarke the bellow comment in order to get the accessories columns I used
        -- This is recommended to understand the line-of-thinking
        --, D, c, RN_D, RN, CurrNo, Cnt, CntToday, RN_Old
        , History = CASE
            WHEN CurrNo = 1 and Cnt = 1 then 'NEW'
            WHEN RN_D = 1 then 'CURRENT'
            else ISNULL(History,'BEFORE')
        END
    from MyCTE2
)
select CustName, Country, RecordedTime--, D, c, RN_D, RN, CurrNo, Cnt, CntToday, RN_Old
    ,Audit = CASE when History='New' then 'ADD' else 'CHANGE'  END
    , History
from MyCTE3
--order by CustName, RecordedTime
)
GO

使用该函数可以更轻松地进行多次测试,但是可能在生产中您将需要使用直接查询

-- Test
select * from F('2018-06-01') order by CustName , RecordedTime
select * from F('2018-06-02') order by CustName , RecordedTime
select * from F('2018-06-03') order by CustName , RecordedTime
select * from F('2018-06-10') order by CustName , RecordedTime
select * from F('2018-06-11') order by CustName , RecordedTime
select * from F('2018-06-12') order by CustName , RecordedTime
select * from F('2018-06-13') order by CustName , RecordedTime
select * from F('2018-06-14') order by CustName , RecordedTime

/ ****************更新于2018-08-19 14:05以色列时间**************** / < / p>

我注意到,为了参与线程,需要添加一些其他信息很重要。我希望这会有用

  

注意!在Microsoft SQL Server 2017开发人员版上测试

首先,让我们根据三个查询的执行计划来比较资源使用的百分比:(1)我的解决方案,(2)更新第一个解决方案后的(maulik kansara秒解决方案),和(3)maulik kansara第一个解决方案

enter image description here

现在让我们检查maulik kansara秒解决方案的EP图像:

enter image description here

此查询扫描表11次!

  

** 重要! EP不是建议我们选择哪个查询的唯一参数,但这可能是我们应该检查的第一个信息。此外,我们应该检查IO统计信息和时间统计信息,以及更多...

     

信用:该图像是使用sendryone工具拍摄的。有一个免费版本可以提供大多数DBA所需的内容。我正在使用作为Microsoft MVP免费获得的完整版本,所以谢谢;-)

答案 3 :(得分:1)

您在最初的请求中有很多if / and / buts,但也许有一个ALTERNATE VIEW数据可能是一个更好的解决方案,您只是没有想到或已经提供过,所以我会的。

为什么有单独的行同时显示前后,为什么不在返回的一行上显示。如果没有变化,为什么要显示?也许这是以后的问题。

要获得解决方案,我从测试数据中获取了一个简单的WITH,该数据用作查询的基准。您有自己的CurrNo,该CurrNo可能不会始终以1开头,甚至可能由于偶然而跳过序列号。因此,我的WITH声明由客户获取并由CurrNo排序的连续行号。即使CurrNo值可能是5、8、9、11(夸大的起始号和意外跳过的值),这也将始终按客户第1、2、3、4行返回。您甚至可以将订单更改为RecordedTime,以确保基于每个客户活动的DATE进行记录。

;with baseData as
(
   select 
         T.*,
         ROW_NUMBER() OVER (PARTITION BY CustName ORDER BY CurrNo) AS CustOrder
      from
         Test T
)

现在,使用查询。我有第一张表,这是查找系统中任何“更改”的基础。您将始终从这些记录开始。根据需要应用日期限制过滤器。无论行号是PLUS ONE还是相同基准数据的NEXT,LEFT-JOIN都将始终位于客户名PLUS上。因此,如果仅从您的数据中查看客户Alex和Jerry,您将获得以下

Customer  Country    Time                  CurrNo   CustOrder
Alex      Australia  2018-06-01 08:00:00   1        1
Alex      China      2018-06-01 10:00:00   2        2
Alex      India      2018-06-01 10:05:00   3        3
Alex      Japan      2018-06-01 11:00:00   4        4

Jerry     Cuba       2018-06-12 00:00:00   4        1
Jerry     Brazil     2018-06-12 00:05:00   5        2
Jerry     India      2018-06-12 00:10:00   7        3
Jerry     USA        2018-06-12 00:15:00   9        4

因此您可以看到每个之间的自然行号归一化。所以现在,我的左联接是由客户和CustOrder列组成的,它不会跳过空白,因此我将获得类似

的记录
Customer  CustOrder   NextCustOrder        CurrNo      NextCurrNo
Alex      1           2                    1           1
Alex      2           3                    2           2
Alex      3           4                    3           3
Alex      4           (no 5th record)      4           4

Jerry     1           2                    4           5
Jerry     2           3                    5           7
Jerry     3           4                    7           9
Jerry     4           (no 5th record)      9           (no next record)

最后,我将从始终存在的第一个记录中获取数据,如果存在对应的NEXT记录,它将显示新的已更改的TO值。该下一条记录将是下一行,并且可能会更改TO值,依此类推。

select 
        bd.CustName,
        case when bd.CurrNo = 1 then 'ADD' else 'CHANGE' end as Audit,
        bd.Country as CurrentValue,
        bd.RecordedTime,
        bdNext.Country as ChangedValue,
        bdNext.RecordedTime ChangedTime,
        bd.CurrNo,
        bd.CustOrder
    from 
        baseData bd
            LEFT JOIN baseData bdNext
                on bd.CustName = bdNext.CustName
                AND bd.CustOrder +1 = bdNext.CustOrder;


CustName  Audit    CurrentValue   RecordedTime              ChangedValue   ChangedTime               CurrNo   CustOrder
Alex      ADD      Australia      2018-06-01 08:00:00.000   China          2018-06-01 10:00:00.000   1        1
Alex      CHANGE   China          2018-06-01 10:00:00.000   India          2018-06-01 10:05:00.000   2        2
Alex      CHANGE   India          2018-06-01 10:05:00.000   Japan          2018-06-01 11:00:00.000   3        3
Alex      CHANGE   Japan          2018-06-01 11:00:00.000   NULL           NULL                      4        4

Jerry     CHANGE   Cuba           2018-06-12 00:00:00.000   Brazil         2018-06-12 00:05:00.000   4        1
Jerry     CHANGE   Brazil         2018-06-12 00:05:00.000   India          2018-06-12 00:10:00.000   5        2
Jerry     CHANGE   India          2018-06-12 00:10:00.000   USA            2018-06-12 00:15:00.000   7        3
Jerry     CHANGE   USA            2018-06-12 00:15:00.000   NULL           NULL                      9        4

如果您不希望记录的“最后一个值”是因为之后没有更改,只需将LEFT JOIN更改为INNER JOIN即可保证之后的内容有所更改。但是,如果您想查看所有新的“ ADD”记录而不进行更改,那将失败。您可以将其用作where子句,例如

where
      bd.CurrNo = 1
   OR bdNext.CustOrder IS NOT NULL

此外,您可以添加日期过滤器,例如

where
      bd.RecordedTime >= '2018-06-10'
  AND (    bd.CurrNo = 1
        OR bdNext.CustOrder IS NOT NULL )

Ronen Ariely在评论中建议,上述带有日期的WHERE子句将应用于WITH baseData作为组件,并添加

  where
     T.RecordedTime >= '2018-06-10'

在数据进入其余联接活动之前对其进行预过滤