我有一群定期变化的客户。每次同类群组更改时,都会将新客户的完整转储附加到表中并赋予新的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
答案 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)