如何使 Excel 在 MS SQL 2017 中任意旋转(交叉连接 + 循环)
Posted
技术标签:
【中文标题】如何使 Excel 在 MS SQL 2017 中任意旋转(交叉连接 + 循环)【英文标题】:How make Excel arbitrary pivoting in MS SQL 2017 (cross join + loops) 【发布时间】:2020-08-19 08:42:50 【问题描述】:请您帮我解决一下 SQL (MS SQL Server 2017) 中的任务。在 Excel 中很简单,但在 SQL 中似乎很复杂。
有一张表格,其中包含客户及其按天划分的活动:
client 1may 2may 3may 4may 5may other days
client1 0 0 0 0 0 ...
client2 0 0 0 0 0 ...
client3 0 0 0 0 0 ...
client4 1 1 1 1 1 ...
client5 1 1 1 0 0 ...
需要创建同一张表(行数和列数相同),但按规则将值转为新表: 当日值 =
A) 如果前一周的所有日常值(包括当前值)=1,则为 1
B) 如果前一周的所有日常值,包括当前值 = 0,则为 0
C) 如果值不同,那么我们就留下前一天的状态(如果前一天的状态不知道,比如Client是新的,那么就为0)
在 Excel 中,我使用以下公式:= IF (AND (AF2 = AE2; AE2 = AD2; AD2 = AC2; AC2 = AB2; AB2 = AA2; AA2 = Z2); current_day_value; IF (previous_day_value = " "; 0; previous_day_value ))。
带有excel文件is attached.的例子
非常感谢。
【问题讨论】:
你能补充一下你用的是什么牌子的sql吗?加上您必须使用的表和架构? 理查德,感谢您的回复。我使用 MS SQL Server 2017(SQL Server Management Studio)。你在我使用的表和架构下是什么意思? 【参考方案1】:首先,将日期作为列绝不是一个好主意。
因此,第 1 步将您的列转置为行。在其他世界建立一个三列的表
```
client date Value
client1 May1 0
client1 May2 0
client1 May3 0
.... ... ..
client4 May1 1
client4 May2 1
client4 May3 1
.... ... ..
```
第 2 步使用日期字段执行您需要的所有计算。
【讨论】:
大卫,非常感谢您的建议。你展示的数据结构是我的初始数据。但我将其转换为数据透视表以突出显示客户不活跃的日子。因为分析每天的活动对我来说很重要。例如。客户仅在 5 月 1 日和 4 日活跃。我只需要在表中记录 1may 和 4may 的行。但我需要每天客户活动的记录。因此我使用:1may = 1、2may(在旋转表格后发生)= 0、3may = 0、4may = 1、5may = 0、6may = 1 等。还有其他方法可以突出显示客户不活跃的日子吗? 大卫,我已经按照您通过交叉连接建议的方式重组了表格。你会提示我接下来的步骤吗?我应该使用循环来解决问题吗?【参考方案2】:基本上你把前一天的状态放在任何情况下(null除外)。
所以,我会做这样的事情(oracle 语法,也在 sql server 中工作),假设第一列是 1may
Insert into newTable (client, 1may,2may,....) select (client, 0, coalesce(1may,0), coalesce (2may,0), .... from oldTable;
无论如何,我也认为将日期作为关系表的列不是一个好习惯。
【讨论】:
Massimo,感谢您重新分组数据的答案。我并不总是把前一天的状态放在任何情况下,但都是空的。考虑 Excel 示例(单元格 FE3:当天的计算值!= 单元格 BO3:前一天的初始值)。 所以您在帖子中的 A、B、C 点写的规则是错误的。 C点应该是如果值不同,那么我们就离开当前天的状态。您能否澄清确切的规则,请在使用旧表值或已经计算的值时特别说明 Massimo,我在条件 C 中使用 - 前一天而不是当天,因为如果客户行为不稳定,我想保存现状。我想让客户行为更顺畅。如果使用当天,这:may=1, may2 = 0, may3 = 1, may4 =0, may5 = 1 将产生相同的值。但是使用我的逻辑,它会保留最后一个值,当这些值在一周内稳定时。 我的意思是,如果 current_day_value 和 previous_day_value 您的意思是来自“旧”表的值,或者您的意思是已经在新表中计算的值。从 excel 看起来你正在使用计算值,但我有点不清楚 在 Excel 中有 2 个表格:初始数据 - 在左侧,计算日期在右侧。如果数据不稳定,我们会从初始表中获取值。新表应该从零开始计算。【参考方案3】:您将很难解决这个问题,因为大多数品牌的 SQL 不允许“任意旋转”,也就是说,您需要指定要在轴上显示的列 - 而 Excel 只会这样做你。 SQL 可以做到这一点,但它需要动态 SQL,这会变得非常复杂且非常烦人。
我建议您仅使用 sql 来构建数据,然后使用 excel 或 s-s-rS(就像您在 TSQL 中一样)来实际进行可视化。
无论如何。我认为这可以满足您的要求:
WITH Data AS (
SELECT * FROM (VALUES
('Client 1',CONVERT(DATE, '2020-05-04'),1)
, ('Client 1',CONVERT(DATE, '2020-05-05'),1)
, ('Client 1',CONVERT(DATE, '2020-05-06'),1)
, ('Client 1',CONVERT(DATE, '2020-05-07'),0)
, ('Client 1',CONVERT(DATE, '2020-05-08'),0)
, ('Client 1',CONVERT(DATE, '2020-05-09'),0)
, ('Client 1',CONVERT(DATE, '2020-05-10'),1)
, ('Client 1',CONVERT(DATE, '2020-05-11'),1)
, ('Client 1',CONVERT(DATE, '2020-05-12'),1)
, ('Client 2',CONVERT(DATE, '2020-05-04'),1)
, ('Client 2',CONVERT(DATE, '2020-05-05'),0)
, ('Client 2',CONVERT(DATE, '2020-05-06'),0)
, ('Client 2',CONVERT(DATE, '2020-05-07'),1)
, ('Client 2',CONVERT(DATE, '2020-05-08'),0)
, ('Client 2',CONVERT(DATE, '2020-05-09'),1)
, ('Client 2',CONVERT(DATE, '2020-05-10'),0)
, ('Client 2',CONVERT(DATE, '2020-05-11'),1)
) x (Client, RowDate, Value)
)
SELECT
Client
, RowDate
, Value
, CASE
WHEN OnesBefore = DaysInWeek THEN 1
WHEN ZerosBefore = DaysInWeek THEN 0
ELSE PreviousDayValue
END As FinalCalculation
FROM (
-- This set uses windowing to calculate the intermediate values
SELECT
*
-- The count of the days present in the data, as part of the week may be missing we can't assume 7
-- We only count up to this day, so its in line with the other parts of the calculation
, COUNT(RowDate) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate) AS DaysInWeek
-- Count up the 1's for this client and week, in date order, up to (and including) this date
, COUNT(IIF(Value = 1, 1, NULL)) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate) AS OnesBefore
-- Count up the 0's for this client and week, in date order, up to (and including) this date
, COUNT(IIF(Value = 0, 1, NULL)) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate) AS ZerosBefore
-- get the previous days value, or 0 if there isnt one
, COALESCE(LAG(Value) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate), 0) AS PreviousDayValue
FROM (
-- This set adds a few simple values in that we can leverage later
SELECT
*
, DATEADD(DAY, -DATEPART(DW, RowDate) + 1, RowDate) As WeekCommencing
FROM Data
) AS DataWithExtras
) AS DataWithCalculations
由于您没有指定表格布局,我不知道在我的示例中使用什么表格和字段名称。希望如果这是正确的,您可以弄清楚如何使用您所拥有的内容单击它 - 如果不是,请发表评论
我也会注意到,我故意把它写得冗长。如果您不知道“OVER”子句是什么,您需要阅读:https://www.sqlshack.com/use-window-functions-sql-server/。要点是它们进行聚合而不实际将行合并在一起。
编辑:调整了计算,以便能够考虑一周中的任意天数
【讨论】:
理查德,非常感谢您提供如此详细的回答。我已经重读了你的答案好几次,从中得到了很多。但结果不是我想要的。客户 1 的理想结果:5 月 4 日 - 0; 5may - 0(没有相等值的 7 天,我们得到昨天计算的值) 6may - 0、7may - 0、8may - 0 等等,而一周内的初始值将等于 1。 啊,所以片场会缺几天?您能否以“客户、行日期、值”格式在问题的主体中发布一些示例数据,并在额外的一列中说明最终计算应该是什么?【参考方案4】:非常感谢大家,尤其是 David 和 Massimo,他们促使我重新构建数据。
--we join clients and dates each with each and label clients with 'active' or 'inactive'
with a as (
select client, dates
from (select distinct client from dbo.clients) a
cross join (select dates from dates) b
)
, b as (
select date
,1 end active
,client
from clients a
join dbo.dates b on a.id = b.id
)
select client
,a.dates
,isnull(b.active, 0) active
into #tmp2
from a
left join b on a.client= b.client and a.dates = b.dates
--declare variables - for date start and for loop
declare @min_date date = (select min(dates) from #tmp2);
declare @n int = 1
declare @row int = (select count(distinct dates) from #tmp2) --number of the loop iterations
--delete data from the final results
delete from final_results
--fill the table with final results
--run the loop (each iteration = analyse of each 1-week range)
while @n<=@row
begin
with a as (
--run the loop
select client
,max(dates) dates
,sum (case when active = 1 then 1 else null end) sum_active
,sum (case when active = 0 then 1 else null end) sum_inactive
from #tmp2
where dates between dateadd(day, -7 + @n, @min_date) and dateadd(day, -1 + @n, @min_date)
group by client
)
INSERT INTO [dbo].[final_results]
(client
,[dates]
,[final_result])
select client
,dates
,case when sum_active = 7 then 1 --rule A
when sum_inactive = 7 then 0 -- rule B
else
(case when isnull(sum_active, 0) + isnull(sum_inactive, 0) < 7 then 0
else
(select final_result
from final_results b
where b.dates = dateadd(day, -1, a.dates)
and a.client= b.client) end
) end
from a
set @n=@n+1
end
if object_id(N'tempdb..#tmp2', 'U') is not null drop table #tmp2
【讨论】:
以上是关于如何使 Excel 在 MS SQL 2017 中任意旋转(交叉连接 + 循环)的主要内容,如果未能解决你的问题,请参考以下文章
使用 Excel VBA 查询 MS Access,SQL BETWEEN 日期查询
如何使 MS Access 直通查询在 SQL Server 中正确运行
如何使用 SQL 重命名具有关系的 MS Access 表?