How to query this output in SQL server

后端 未结 4 1863
不思量自难忘°
不思量自难忘° 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:09

    You have a bunch of if/and/buts going on in your original request, but maybe having an ALTERNATE VIEW of the data might be a better solution that you just did not think of or have been offered, so I will.

    Why have individual lines showing both the before and after, why not do on a single returned line. If there is no change, why show? Maybe that is a later question.

    To get my solution, I have a simple WITH from the test data that I am using as a baseline for my query. You have your own CurrNo which may not always start with 1, and could even possibly skip a sequence number by some accident. So my WITH declaration gets a sequential row number by customer and ordered by the CurrNo. This will ALWAYS return per customer rows 1, 2, 3, 4 even if the CurrNo values may be 5, 8, 9, 11 (exaggerated starting number and accidental skipped values). You could even change the order by to the RecordedTime to ensure logging based on the DATE per customer activity.

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

    So now, with the query. I have my first table which is the basis for finding any "changes" within the system. You will always start with these records. Apply a date restrictive filter as you need. The LEFT-JOIN will always be on the customer name PLUS whatever the row number is PLUS ONE joined to the NEXT of the same baseline data. So, if just looking at customer Alex and Jerry from your data, you would have the following

    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
    

    So you can see the natural row number normalization between each. So Now, my left-join is by customer AND the CustOrder column which will not skip gaps, so I will get records like

    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)
    

    Finally, I am getting the data from the first record which will always exist and IF A CORRESPONDING NEXT record exists, it will show that new changed TO value. That next record will be the next row and it's possible changed TO value and so on..

    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
    

    If you don't want the "last value" for a record because no changes after, just change the LEFT JOIN to INNER JOIN to guarantee something changed after. But that would fail if you wanted to see all new "ADD" records without changes. You could apply that as a where clause something like

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

    Additionally you could add a date filter such as

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

    As suggested in comment by Ronen Ariely, the above WHERE clause with the date would be applied to the WITH baseData as component, adding a

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

    to pre-filter the data before it gets to the rest of the join activity

提交回复
热议问题