如何确定一段时间内对客户列表的更改

时间:2019-05-30 15:12:52

标签: tsql

我有一群定期变化的客户。每次同类群组更改时,都会将新客户的完整转储附加到表中并赋予新的List_Name。 Alpha,Beta,Gamma等。每个列表通常会添加一些客户,删除一些客户并保留一些客户。我正在尝试创建一个简单的瀑布图以显示列表之间的变化。通常,我只需要FULL OUTER JOIN Alpha <-> Beta,然后FULL OUTER JOIN Beta <-> Gamma,以此类推,就可以得到留下,离开和增加的客户。但是List_Versions增长了很多,我想知道是否有一种更简单的方法来执行此计算,而不是每次附加新列表时都必须对其进行编辑。我无法更改此过程,因为它支持旧系统并且由另一个部门控制。有想法吗?

编辑:Sql Server 2016 SP2。

CREATE TABLE #customers(cust_id int, list_name varchar(10), create_dt date)
INSERT INTO #customers values (1,'Alpha','2019-01-01')
    ,(2,'Alpha','2019-01-01')
    ,(3,'Alpha','2019-01-01')
    ,(4,'Alpha','2019-01-01')
    ,(5,'Alpha','2019-01-01')
    ,(2,'Beta','2019-03-01')
    ,(3,'Beta','2019-03-01')
    ,(4,'Beta','2019-03-01')
    ,(5,'Beta','2019-03-01')
    ,(6,'Beta','2019-03-01')
    ,(7,'Beta','2019-03-01')
    ,(1,'Gamma','2019-05-05')
    ,(6,'Gamma','2019-05-05')
    ,(7,'Gamma','2019-05-05')
    ,(9,'Gamma','2019-05-05')

--Desired Output (long way that needs to be edited every time there is a new list)
SELECT List_Name, 'Starting' Descrip, count(*) Custs FROM #customers WHERE list_name = 'alpha' group by list_name
UNION ALL
SELECT List_Name, 'Add', count(*) FROM #customers a WHERE list_name = 'Beta' AND not exists(SELECT * FROM #customers x WHERE List_Name = 'Alpha' AND a.cust_id = x.cust_id) GROUP BY List_Name
UNION ALL
SELECT 'Beta', 'Remove', -count(*) FROM #customers a WHERE list_name = 'Alpha' AND not exists(SELECT * FROM #customers x WHERE List_Name = 'Beta' AND a.cust_id = x.cust_id) GROUP BY List_Name
UNION ALL
SELECT List_Name, 'Add', count(*) FROM #customers a WHERE list_name = 'Gamma' AND not exists(SELECT * FROM #customers x WHERE List_Name = 'Beta' AND a.cust_id = x.cust_id) GROUP BY List_Name
UNION ALL
SELECT 'Gamma', 'Remove', -count(*) FROM #customers a WHERE list_name = 'Beta' AND not exists(SELECT * FROM #customers x WHERE List_Name = 'Gamma' AND a.cust_id = x.cust_id) GROUP BY List_Name

2 个答案:

答案 0 :(得分:0)

您可以创建一个临时表来生成ID,而不是对列表名称进行硬编码,然后使用ID作为聚集的连接条件。这样一来,您无需依赖已知名称和生成顺序即可将列表移至上一个列表。

CREATE TABLE #customers(cust_id int, list_name varchar(10), list_date date)
INSERT INTO #customers
(
    cust_id
    ,list_name
    ,list_date
)
values (1,'Alpha', '01-01-2019')
    ,(2,'Alpha', '01-01-2019')
    ,(3,'Alpha', '01-01-2019')
    ,(4,'Alpha', '01-01-2019')
    ,(5,'Alpha', '01-01-2019')
    ,(2,'Beta', '02-01-2019')
    ,(3,'Beta', '02-01-2019')
    ,(4,'Beta', '02-01-2019')
    ,(5,'Beta', '02-01-2019')
    ,(6,'Beta', '02-01-2019')
    ,(7,'Beta', '02-01-2019')
    ,(1,'Gamma', '03-01-2019')
    ,(6,'Gamma', '03-01-2019')
    ,(7,'Gamma', '03-01-2019')
    ,(9,'Gamma', '03-01-2019')


CREATE TABLE #lists
(
    list_id INT IDENTITY(1,1)
    ,list_name varchar(10)
    ,Starting INT
    ,Added INT
    ,Removed INT
    ,list_date date
)

INSERT INTO #lists
(
    list_name
    ,Starting
    ,Added
    ,Removed
    ,list_date
)
SELECT DISTINCT 
    a.list_name 
    ,Starting = (SELECT COUNT(*) FROM #customers b WHERE b.list_name = a.list_name)
    ,Added = 0
    ,Removed = 0
    ,a.list_date
FROM #customers a
ORDER BY a.list_date ASC

现在使用此临时表创建具有客户ID和列表ID的另一个临时表。我这样做是因为我不必继续编写联接列表名称和列表ID的联接。

SELECT c.cust_id
      ,l.list_id
INTO #ListCus
FROM #customers c
INNER JOIN #lists l ON l.list_name = c.list_name

接下来,您可以计算已添加和已删除的数字。

UPDATE l 
SET l.Added = (SELECT COUNT(*) FROM #ListCus c1 WHERE c1.list_id = l.list_id AND NOT EXISTS (SELECT * FROM #ListCus x WHERE x.list_id = l.list_id-1 AND c1.cust_id = x.cust_id))
    ,l.Removed = (SELECT -COUNT(*) FROM #ListCus c1 WHERE c1.list_id = l.list_id-1 AND NOT EXISTS (SELECT * FROM #ListCus x WHERE x.list_id = l.list_id AND c1.cust_id = x.cust_id)) 
FROM #lists l
WHERE l.list_id > 1 --the first list won't have Added or Removed records

最后,我们通过取消透视和过滤不需要的描述来格式化数据。

;WITH unpivoted AS
(
    SELECT
        u.list_id
        ,u.Descrip
        ,u.custs
    FROM #lists l 
    UNPIVOT
    (
        custs 
        FOR Descrip IN (Starting, Added, Removed)
    )u
)
,SubResults AS
(
    SELECT u.list_id
          ,u.Descrip
          ,u.custs
    FROM unpivoted u 
    WHERE u.list_id = 1
        AND u.Descrip = 'Starting'

    UNION ALL

    SELECT u.list_id
          ,u.Descrip
          ,u.custs
    FROM unpivoted u 
    WHERE u.list_id <> 1
        AND u.Descrip <> 'Starting'
)
SELECT 
    l.list_name
    ,s.Descrip
    ,s.custs
FROM SubResults s
INNER JOIN #lists l ON l.list_id = s.list_id
ORDER BY s.list_id ASC, s.Descrip

答案 1 :(得分:0)

好的,因此您可以将“列表”存储在Common Table Expression中,然后执行以下操作。它不是性感的,但是它将为您提供以下输出。您显然可以#include <memory> #include <vector> #include <string> using namespace std;//Not a good practice and definitely a big no in header files. class Base { public: /* Constructors */ Base() { }; /* Pure Virtual Functions */ virtual double evaluate() = 0; virtual std::string stringify() = 0; }; class op : public Base { public: op() { }; op(double op1) { operand = op1; } double evaluate() { return operand; } string stringify() { string value = to_string(operand); return value; } private: double operand; }; class Command { protected: std::unique_ptr<Base> root; public: Command(std::unique_ptr<Base>&& root):root(std::move(root)) { } //Be const-correct double execute() const { return root->evaluate(); } std::string stringify() const { return root->stringify(); } Base* get_root() const { return root.get(); } }; class Menu { private: int history_index; // Indexes which command was last executed, accounting for undo and redo functions std::vector<std::unique_ptr<Command>> history; // Holds all the commands that have been executed until now public: Menu() { // Constructor which initializes the internal members history_index = -1; } std::string execute() const{ // Returns the string converted evaluation of the current command return to_string(history[history_index - 1]->execute()); } std::string stringify() const{ // Returns the stringified version of the current command return history[history_index]->stringify(); } bool initialized() const{ // Returns if the history has an InitialCommand, which is necessary to start the calculation if (history[history_index] != nullptr) return true; else return false; } void add_command(std::unique_ptr<Command>&& cmd) { // Adds a command to the history (does not execute it), this may require removal of some other commands depending on where history_index is history.emplace_back(std::move(cmd)); history_index++; } Command* get_command() const { // Returns the command that the history_index is currently referring to return history[history_index].get(); } void undo() { // Move back one command (does not execute it) if there is a command to undo history_index--; } void redo() { // Moves forward one command (does not execute it) if there is a command to redo history_index++; } }; class InitialCommand : public Command { protected: public: InitialCommand(std::unique_ptr<Base>&& b): Command(std::move(b)){} }; // There's no such thing as void main int main() { Menu menu; auto temp = std::make_unique<InitialCommand>(std::make_unique<op>(7)); menu.add_command(std::move(temp)); //EXPECT_EQ(menu.get_command()->execute(), 7); system("PAUSE"); } 认为合适。

*-1

结果:

WITH lists (ListName, ListDate, PreceedingDate)
AS
(
    select distinct list_name, create_dt, PreceedingDate
    from #customers C1
        outer apply (select top 1 PreceedingDate = C3.create_dt
                     from #customers C3
                     where C3.create_dt < C1.create_dt
                     order by C3.create_dt desc) C3
)
select ListID = coalesce(lists.ListDate, CTE2.ListDate)
    , ListName = max(lists.ListName)
    , Added = SUM(IIF(C2.cust_id is null, 1, 0))
    , Removed = SUM(IIF(C1.cust_id is null and C2.cust_id is not null, 1, 0))
    , Remained = SUM(IIF(C2.cust_id = C1.cust_id, 1, 0))
from lists
    inner join #customers C1 on C1.create_dt = lists.ListDate
    full outer join #customers C2 on C1.cust_id = C2.cust_id
                                    and C2.create_dt = lists.PreceedingDate
    --since for removed customers the current List will be NULL
    --we join it back on, which leads to all those COALESCEs
    left join lists CTE2 on CTE2.PreceedingDate = C2.create_dt
where coalesce(lists.ListDate, CTE2.ListDate) is not null
group by coalesce(lists.ListDate, CTE2.ListDate)