如何使 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 表?

如何使用sql语句和vba将数据从MS-Access导入excel power查询?

MS SQL 转置类似于 Excel 转置(动态 SQL)

如何使 MS SQL Server 可用于连接?