How to query this output in SQL server

后端 未结 4 1865
不思量自难忘°
不思量自难忘° 2021-02-06 18:33

I have a table with data like this:

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

INSERT INTO Test
          


        
4条回答
  •  迷失自我
    2021-02-06 19:12

    Good day,

    Please check if the below solution solves all your needs. I tested it with your data and with some more rows, but it is always best to re-check. In first glance it seems like it returns the requested result. I will add some explanation later on

    The query I am using it this:

    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
    

    To make it simpler to test I insert the entire query into table function

    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
    

    Using the function it simpler to make multiple test, but probably in the production you will want to use the direct query

    -- 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
    

    /**************** Update at 2018-08-19 14:05 Israel Time ****************/

    I notice that some more information is important to add for the sake of the participate in the thread. I hope this will be useful

    NOTE! tested on Microsoft SQL Server 2017 Developer Edition

    First, let's compare percentage of resources uses according to the Execution Plans of three queries: (1) My solution, (2) maulik kansara seconds solution after update the first solution, and (3) maulik kansara first solution

    Now lets check the image of the EP of maulik kansara seconds solution:

    This query scans the table 11 times!

    ** Important! EP is not the only parameter that suggestes which query we should choose, but this is probably the first information we should check. In addition we should check IO statistics and Time statistics, and more...

    Credit: The image was taken using sentryone tool. There is a free version which can gives most of what DBAs need. I am using the full version which I got for free as Microsoft MVP, so Thanks ;-)

提交回复
热议问题