【问题标题】:How to query this output in SQL server如何在 SQL Server 中查询此输出
【发布时间】:2019-01-21 04:31:58
【问题描述】:

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

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" 仅适用于 Original Accts(这意味着表中的条目肯定从 CurrNo = 1) 开始

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

场景 1: 如果输入日期为 2018-Jun-01,则输出应如下所示(即在同一天多次添加和编辑记录时)

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-Jun-02,则输出应如下所示(即,前几天已经存在记录并且今天编辑了相同的记录并且今天存在任何新记录)

   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-Jun-03,则输出应如下所示(即,当记录在同一天被多次编辑时,它应列出最近一个日期的最后一条记录,然后列出当前给定日期的最后一条记录)

   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-Jun-10,则输出应如下所示

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

场景5: 如果输入日期为 2018-Jun-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-Jun-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-Jun-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-Jun-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?

【问题讨论】:

  • 对于场景 1,为什么国家“印度”没有一条记录?\
  • @maulikkansara 这是因为无论在同一天进行多少次编辑,结果都应该只显示最新记录。由于同样的原因,同样情况下中国也不会有记录。
  • 如果您提供包含 DDL 和示例数据的完整脚本 - 而不仅仅是您当前使用的查询,您将鼓励其他人查看您的问题。
  • 这篇文章有一些关于发布 DDL 和 DML 的指示。 How to post a T-SQL question on a public forum
  • 在场景 5 中,第一条记录的逻辑?

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


【解决方案1】:

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

第一步

  • 获取每个 custName 给定日期的 max(CurrNo)。
  • 这里是 cte。

第 2 步

  • 为 cte 中可用的所有客户名称(如果有)获取提到日期的第一个输入记录。
  • 在上述记录的联合中,为 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

【讨论】:

  • 感谢您的回答。除 Scenario1 外,所有 Scenario 均通过。在同一天添加和编辑的记录有例外或特殊要求,例如在 6 月 1 日,“Alex”在同一天被添加和编辑三次,因此我们需要为值的“CHANGE”和“BEFORE”提供相同的输出出现在“添加”和“新”中。换句话说,“ADD”和“NEW”条目应该有另一个具有相同值但带有“CHANGE”和“BEFORE”的条目。请查看场景 1 的结果。
  • 我的最后一个请求满足,但是当添加了连续三天数据的记录时,它不满足该场景,例如在 2014 年 6 月 12 日、13 日、14 日添加了新的 custName = Maulik (请参阅新附加的示例数据)。当 6 月 12 日是输入时,它显示 Maulik |澳大利亚 | 2018-06-12 |变化 | CURRENT - 当 6 月 13 日是它显示 Maulik 的输入时是正确的 |澳大利亚 | 2018-06-12 |变化 |之前和毛利克 |英文 | 2018-06-13 |变化 | CURRENT- 也是正确的
  • 当 6 月 14 日是输入时,它显示 Maulik |澳大利亚 | 2018-06-12 |变化 |之前和毛利克 |工业 | 2018-06-14 |变化 |当前-这里是变化| BEFORE 不正确,因为当给出 6 月 14 日时,它应该查找最新的先前记录,即它应该查找 2018-06-13 并且应该列为 Maulik |英文 | 2018-06-13 |变化 | BEFORE,但当前输出显示 CHANGE | 记录的第一个实例之前
  • 您能否对此有所了解!
  • Ok 也会调查并更新您的信息。一个问题。添加新记录后,我认为scenario6的结果也应该有所变化。
【解决方案2】:

根据我对不同场景的理解,它们基本上只需 2 条规则即可实现。

  • 规则 1:具有 RecordedTime 的 CustName 的记录都在同一天。
  • 规则 2:记录时间超过一天的 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;

您可以在 dbfiddle here

上对其进行测试

【讨论】:

  • 嗨@LukStorms,检查场景6。使用 OP 示例数据,您的查询返回 4 行而不是 3 行作为请求的结果。我没有阅读查询,所以我认为这可能是需要更改的非常小的东西。
  • @RonenAriely 很好发现!谢谢。曾经有一次,当试图修复一件事时,另一件事先是好的,然后因为修复而出错。不过现在应该没问题了。
【解决方案3】:

早安,

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

我正在使用它的查询:

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更新****************/

我注意到为了参与线程,添加更多信息很重要。我希望这会有用

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

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

现在让我们来看看maulik kansara秒解的EP图片:

此查询扫描表 11 次!

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

学分:图片是使用 sentryone 工具拍摄的。有一个免费版本可以满足 DBA 的大部分需求。我正在使用作为 Microsoft MVP 免费获得的完整版本,谢谢 ;-)

【讨论】:

  • 特别感谢@Ronen Ariely 的回答,它也很有效,并且喜欢您提出的逻辑。我在生产环境中对此进行了测试,效果也很好。
  • 顺便说一句,我不知道您为什么选择我的解决方案作为答案,但 @maulik-kansara 得到了奖励积分。我真的希望你不要使用他的解决方案。根据执行情况,在线程中使用DDL+DML,他的新解决方案并不比第一个好很多。事实上,他的第二个解决方案仍然扫描同一张表 12 次!!似乎他认为,如果您不显式写入“from TableName”,那么它将不会扫描表,并且不会真正了解 SQL Server 的工作原理。
  • 我会尝试做一个简短的解释,因为这很重要,但我不能在 stackoverflow 糟糕的界面中做很多事情。我只能在这里添加短的可怜的 cmets ;-(
  • @maulikkansara 我希望您能看到这条消息,因为这是任何 DBA 都必须知道的关键基本信息:SQL Server 不会按原样执行您的查询,但他有一个查询引擎解析的前置步骤测试并选择执行计划 (EP)。它测试几个选项并选择一个。默认情况下,该 EP 将被存储以供将来使用。下次服务器需要执行相同的查询时,他可以跳过第一步并使用他已经拥有的 EP。
  • 构建所有 EP 并选择一个(在第一步中)可能会花费大量资源,有时甚至超过执行本身,这就是为什么下次使用 EP 会更快的原因。编写查询的最基本工具是 EP,它可以准确地告诉我们服务器如何在幕后执行您的查询。
【解决方案4】:

您的原始请求中有一堆 if/and/buts,但也许对数据有一个 ALTERNATE VIEW 可能是一个更好的解决方案,您只是没有想到或没有提供过,所以我会的。

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

为了获得我的解决方案,我从测试数据中获得了一个简单的 WITH,我将其用作查询的基线。你有自己的 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
)

现在,使用查询。我有我的第一个表,它是在系统中查找任何“更改”的基础。您将始终从这些记录开始。根据需要应用日期限制过滤器。 LEFT-JOIN 将始终位于客户名称 PLUS 上,无论行号是 PLUS ONE 加入到相同基线数据的 NEXT 中。因此,如果仅从您的数据中查看客户 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)

最后,我从始终存在的第一条记录中获取数据,如果存在对应的下一条记录,它将显示新更改的 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 以保证之后发生更改。但是,如果您想查看所有新的“添加”记录而不进行更改,那将失败。您可以将其用作 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'

在数据进入连接活动的其余部分之前对其进行预过滤

【讨论】:

  • 嗨@DRapp,(1)这不是确切的答案,而是重要的信息。不幸的是,这正是我讨厌stackoverflow界面的原因=我们无法进行讨论并发布详细解释,而只能提供“最终答案”或糟糕的cmets。 (2) 如果你想选择你的方法,那么你不应该使用 JOIN 而只是使用函数 LEAD。我很确定你会看到性能上的不同。 ChangedValue = LEAD (bd.Country, 1) OVER (partition by bd.CustName order by bd.CurrNo ),RecordedTime = LEAD (bd.RecordedTime, 1) OVER (partition by bd.CustName order by bd.CurrNo )
  • (3) 此外,您应该尽快过滤数据,这意味着在第一个 CTE 上,您可能应该只使用与输入日期相关的数据(用户的日期)正在寻找相关信息)。 (4) 我完全同意 OP 设计/要求有点问题 ;-)
  • @RonenAriely,1-一些线程可以进入聊天窗口进行更多讨论。 2-不熟悉 LEAD 命令,但会读到它。 3-同意放置的位置,将进行调整。 4. 是的,因此我的替代解决方案是他们的设计要求。我认为我的方法更直接,更易于阅读/遵循。
  • @DRapp 非常感谢提供的逻辑,它对我的​​其他任务也有很大帮助。
猜你喜欢
  • 1970-01-01
  • 2013-02-01
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 2019-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多