如何跟踪列更改其值的次数?

b3b*_*bel 3 sql t-sql sql-server sql-server-2008

我有一张名为crewWork的表如下:

CREATE TABLE crewWork( 
       FloorNumber int, AptNumber int, WorkType int, simTime int )
Run Code Online (Sandbox Code Playgroud)

在填充表格之后,我需要知道apt发生了多少次变化以及楼层发生变化的次数.通常我希望在每个apt上找到10行,在每层上找到40-50行.我可以为此编写一个标量函数,但我想知道是否有任何方法可以在t-SQL中执行此操作而无需编写标量函数.

谢谢

数据如下所示:

FloorNumber  AptNumber    WorkType      simTime  
1            1            12            10  
1            1            12            25  
1            1            13            35  
1            1            13            47  
1            2            12            52  
1            2            12            59  
1            2            13            68  
1            1            14            75  
1            4            12            79  
1            4            12            89  
1            4            13            92  
1            4            14            105  
1            3            12            115  
1            3            13            129  
1            3            14            138  
2            1            12            142  
2            1            12            150  
2            1            14            168  
2            1            14            171  
2            3            12            180  
2            3            13            190  
2            3            13            200  
2            3            14            205  
3            3            14            216  
3            4            12            228  
3            4            12            231  
3            4            14            249  
3            4            13            260  
3            1            12            280  
3            1            13            295  
2            1            14            315  
2            2            12            328  
2            2            14            346  
Run Code Online (Sandbox Code Playgroud)

我需要报告的信息,我不需要将它存储在任何地方.

The*_*Pea 12

如果您使用现在所写的已接受答案(2023 年 1 月 6 日),您可以使用 OP 数据集得到正确的结果,但我认为您可能会使用其他数据得到错误的结果。

已确认:接受的答案有错误(截至 2023 年 1 月 6 日)

我在对已接受答案的评论中解释了可能出现错误结果的可能性。

此 db<>fiddle中,我演示了错误的结果。我使用了稍微修改过的已接受答案的形式(我的语法适用于 SQL ServerPostgreSQL)。我使用稍微修改过的OP数据形式(我更改了两行)。我演示了如何稍微改变已接受的答案,以产生正确的结果。

接受的答案很聪明,但需要进行一些小的更改才能产生正确的结果(如上面的 db<>fiddle 所示并在此处描述:

  • 而不是按照接受的答案中的方式执行此操作COUNT(DISTINCT AptGroup)...
  • 你应该做这个COUNT(DISTINCT CONCAT(AptGroup, '_', AptNumber))...

数据定义语言:


SELECT * INTO crewWork  FROM (VALUES
-- data from question, with a couple changes to demonstrate problems with the accepted answer
-- /sf/ask/606640681/
--FloorNumber  AptNumber    WorkType      simTime  
(1,            1,            12,            10 ),
-- (1,            1,            12,            25 ), -- original
(2,            1,            12,            25 ), -- new, changing FloorNumber 1->2->1 
(1,            1,            13,            35 ),
(1,            1,            13,            47 ),
(1,            2,            12,            52 ),
(1,            2,            12,            59 ),
(1,            2,            13,            68 ),
(1,            1,            14,            75 ),
(1,            4,            12,            79 ),
-- (1,            4,            12,            89 ), -- original
(1,            1,            12,            89 ), -- new , changing  AptNumber 4->1->4)
(1,            4,            13,            92 ),
(1,            4,            14,            105 ),
(1,            3,            12,            115 ),
...
Run Code Online (Sandbox Code Playgroud)

数据标记语言:

;
WITH groupedWithConcats as (SELECT
 *,
 CONCAT(AptGroup,'_', AptNumber) as AptCombo,
 CONCAT(FloorGroup,'_',FloorNumber) as FloorCombo
 -- SQL SERVER doesnt have TEMPORARY keyword; Postgres doesn't understand # for temp tables
 -- INTO TEMPORARY groupedWithConcats
 FROM
 ( 
   SELECT 
     -- the columns shown in Andriy's answer:
     -- /sf/answers/606723421/
     ROW_NUMBER() OVER (                            ORDER BY simTime)   as RN,
     -- AptNumber   
     AptNumber,
     ROW_NUMBER() OVER (PARTITION BY AptNumber      ORDER BY simTime)   as RN_Apt,
     ROW_NUMBER() OVER (                            ORDER BY simTime)
   - ROW_NUMBER() OVER (PARTITION BY AptNumber      ORDER BY simTime)   as AptGroup,

     -- FloorNumber   
     FloorNumber,
     ROW_NUMBER() OVER (PARTITION BY FloorNumber    ORDER BY simTime)   as RN_Floor,
     ROW_NUMBER() OVER (                            ORDER BY simTime)
   - ROW_NUMBER() OVER (PARTITION BY FloorNumber    ORDER BY simTime)   as FloorGroup
  FROM crewWork
 )  grouped
)
-- if you want to see how the groupings work:
-- SELECT * FROM groupedWithConcats
-- otherwise just run this query to see the counts of "changes":
SELECT 
 COUNT(DISTINCT AptCombo)-1     as CountAptChangesWithConcat_Correct,
 COUNT(DISTINCT AptGroup)-1     as CountAptChangesWithoutConcat_Wrong,
 COUNT(DISTINCT FloorCombo)-1   as CountFloorChangesWithConcat_Correct,
 COUNT(DISTINCT FloorGroup)-1   as CountFloorChangesWithoutConcat_Wrong
FROM groupedWithConcats;  
Run Code Online (Sandbox Code Playgroud)

替代答案

接受的答案最终可能会被更新以消除错误。如果发生这种情况,我可以删除我的警告,但我仍然想给您留下这种替代方法来产生答案

我的方法是这样的:“检查前一行,如果前一行与当前行的值不同,则存在更改”。SQL 本身没有想法或行顺序函数(至少不像 Excel 那样;)

相反,SQL 有窗口函数。通过 SQL 的窗口函数,您可以使用窗口函数加上此处所示的RANK自我JOIN技术来组合当前行值和前一行值,以便可以比较它们。这是一个 db<>fiddle显示我的方法,我将其粘贴在下面。

中间表显示了如果有更改则值为 1 的列,否则为 0(即FloorChange, AptChange),显示在帖子的底部...

数据定义语言:

...same as above...

数据标记语言:

;
WITH rowNumbered AS (
  SELECT
     *,
     ROW_NUMBER() OVER ( 
         ORDER BY simTime)  as RN
  FROM crewWork
)
,joinedOnItself AS (
  SELECT 
     rowNumbered.*,
     rowNumberedRowShift.FloorNumber as FloorShift,
     rowNumberedRowShift.AptNumber as AptShift,
     CASE WHEN rowNumbered.FloorNumber <> rowNumberedRowShift.FloorNumber THEN 1 ELSE 0 END     as FloorChange,
     CASE WHEN rowNumbered.AptNumber <> rowNumberedRowShift.AptNumber THEN 1 ELSE 0 END         as AptChange
  
  FROM  rowNumbered
  LEFT OUTER JOIN rowNumbered as rowNumberedRowShift
  ON rowNumbered.RN = (rowNumberedRowShift.RN+1)
)
-- if you want to see:
-- SELECT * FROM joinedOnItself;
SELECT 
  SUM(FloorChange) as FloorChanges, 
  SUM(AptChange) as AptChanges
FROM joinedOnItself;
Run Code Online (Sandbox Code Playgroud)

下面看到中间表 ( ) 的前几行joinedOnItself。这显示了我的方法是如何运作的。请注意最后两列,当与 相比发生变化(在 中注明)或与发生变化(在 中注明)时,其值为 1 。FloorNumberFloorShiftFloorChangeAptNumberAptShiftAptChange

楼层号 设备号 工作类型 模拟时间 rn 地板移位 aptshift 换楼 aptchange
1 1 12 10 1 0 0
2 1 12 25 2 1 1 1 0
1 1 13 35 3 2 1 1 0
1 1 13 47 4 1 1 0 0
1 2 12 52 5 1 1 0 1
1 2 12 59 6 1 2 0 0
1 2 13 68 7 1 2 0 0

请注意,您可以使用窗口函数直接将当前行与上一行中的值进行比较(无需),而不是使用窗口函数RANK和。我这里没有这个解决方案,但维基百科文章示例中对此进行了描述:JOINLAGJOIN

窗口函数允许访问当前记录之前和之后的记录中的数据。

  • **你是对的。** 行数差异应该是一个*附加*分组/分区标准,而不是唯一的标准,以便产生正确数量的“岛”。当我发布答案时,我一直忘记这一点。不管怎样,我很高兴您不仅发现了这个问题,而且还发布了正确的解决方案。现在尝试找到一种方法来推广它,以便它获得应有的选票...... (3认同)
  • 谢谢@AndriyM,首先感谢你的聪明解决方案“让我思考” (2认同)

And*_*y M 5

如果我没有遗漏任何内容,您可以使用以下方法查找更改的数量:

  • 确定具有相同值的后续行组;

  • 算这些群体;

  • 减去1.

单独应用该方法for AptNumber和for FloorNumber.

可以像在这个答案中那样确定组,只有Seq你的案例中没有列.相反,ROW_NUMBER()可以使用另一个表达式.这是一个近似的解决方案:

;
WITH marked AS (
  SELECT
    FloorGroup = ROW_NUMBER() OVER (                         ORDER BY simTime)
               - ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime),

    AptGroup   = ROW_NUMBER() OVER (                         ORDER BY simTime)
               - ROW_NUMBER() OVER (PARTITION BY AptNumber   ORDER BY simTime)
  FROM crewWork
)
SELECT
  FloorChanges = COUNT(DISTINCT FloorGroup) - 1,
  AptChanges   = COUNT(DISTINCT AprGroup)   - 1
FROM marked
Run Code Online (Sandbox Code Playgroud)

(我在这里假设该simTime列定义了更改的时间轴.)


UPDATE

下面的表格显示了如何获取不同的组AptNumber.

AptNumber  RN  RN_Apt  Apt_Group (= RN - RN_Apt)
---------  --  ------  ---------
1          1   1       0
1          2   2       0
1          3   3       0
1          4   4       0
2          5   1       4
2          6   2       4
2          7   3       4
1          8   5   =>  3
4          9   1       8
4          10  2       8
4          11  3       8
4          12  4       8
3          13  1       12
3          14  2       12
3          15  3       12
1          16  6       10
…          …   …       …
Run Code Online (Sandbox Code Playgroud)

RN是一个伪列代表ROW_NUMBER() OVER (ORDER BY simTime).你可以看到这只是从1开始的一系列排名.

另一个伪列,RN_Apt包含由另一个产生的值ROW_NUMBER,即ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime).它包含具有相同AptNumber值的各个组内的排名.您可以看到,对于新遇到的值,序列重新开始,对于重复的序列,它会在上次停止的位置继续.

您也可以从表中可以看到,如果我们减去RNRN_Apt(可能是倒过来,在这种情况下并不重要),我们得到的唯一标识相同的每个不同组的值AptNumber值.您也可以将该值称为组ID.

所以,既然我们已经拥有了这些ID,那么我们只能计算它们(当然,计算不同的值).这将是组的数量,并且更改的数量减少一个(假设第一组不计入更改).