【问题标题】:Get a count based on the row order根据行顺序获取计数
【发布时间】:2017-07-26 20:45:30
【问题描述】:

我有一个这种结构的表

Create Table Example (
[order] INT,
[typeID] INT
)

有了这些数据:

order|type
1   7
2   11
3   11
4   18
5   5
6   19
7   5
8   5
9   3
10  11
11  11
12  3

我需要根据顺序获取每种类型的计数,例如:

type|count
7      1
11     **2**
18     1
5      1
19     1
5      **2**
3      1
11     **2**
3      1

上下文

假设这张表是关于房屋的,所以我有一个按顺序排列的房屋清单。所以我有

  • 订单 1:红房子
  • 2:白宫
  • 3:白宫
  • 4:红房子
  • 5:蓝色的房子
  • 6:蓝色的房子
  • 7:白宫

所以我需要展示这些信息的浓缩。我需要说:

  • 我有1个红房子
  • 那我有2个白色房子
  • 那我有1个红房子
  • 那我有2个蓝色房子
  • 那我有 1 个白宫

所以计数是基于顺序的。如果我能够在分区更改时重置 RANK,DENSE_RANK 函数将对我有所帮助。

【问题讨论】:

  • 到目前为止你尝试过什么?你得到计数的逻辑是什么?如果只是按类型,那么类型 5 不应该是 3 而不是 2 吗?
  • 我相信你需要更详细地解释你的逻辑。
  • 我完全不明白您对这里输出的期望是什么?您似乎对自己想要的东西有非常深刻的理解,但这种解释对我来说根本没有任何意义。我在想 scsimon 在他删除的答案中发布的确切内容。
  • 清如泥。看看你的解释为什么你有两个白色的房子而不是三个?
  • 在数字数据中输入 11。为什么类型 11 的计数为 2?为什么后来还是一样?当下一行不再是同一类型时,“组”是否会改变?

标签: sql-server sql-server-2016


【解决方案1】:

所以我有一个答案,但我必须警告你,由于它是如何完成的,它可能会引起一些人的注意。它使用称为“Quirky Update”的东西。如果您打算实施这个,请看在上帝的份上,阅读链接的文章并理解这是一个“未记录的黑客”,需要精确实施以避免意外后果。

如果你有一点点数据,为了简单明了,我会通过痛苦的行来做它。但是,如果您有大量数据并且仍需要高性能,则可能会这样做。

要求

  1. 表必须按照您希望的顺序有一个聚集索引
  2. 表必须没有其他索引(这可能会导致 SQL 从另一个顺序不正确的索引中读取数据,从而导致行顺序的量子叠加崩溃)。
  3. 在操作过程中必须完全锁定表 (tablockx)
  4. 更新必须以串行方式进行 (maxdop 1)

它的作用

您知道人们如何告诉您表中的数据没有隐含顺序吗?这在 99% 的情况下仍然是正确的。除了我们知道最终它必须以某种顺序存储在磁盘上。这就是我们在这里利用的顺序。通过强制更新聚集索引以及您可以在更新列的同一更新语句中分配变量这一事实,您可以有效地快速滚动数据。

让我们设置数据:

if object_id('tempdb.dbo.#t') is not null drop table #t
create table #t
(
    _order int primary key clustered,
    _type int,
    _grp int
)

insert into #t (_order, _type)
select 1,7
union all select 2,11
union all select 3,11
union all select 4,18
union all select 5,5
union all select 6,19
union all select 7,5
union all select 8,5
union all select 9,3
union all select 10,11
union all select 11,11
union all select 12,3

这是更新声明。我将逐一介绍下面的组件

declare @Order int, @Type int, @Grp int

update #t with (tablockx)
set @Order = _order,
    @Grp = case when _order = 1 then 1
                when _type != @Type then @grp + 1
                else @Grp
           end,
    @Type = _type,
    _grp = @Grp
option (maxdop 1)
  1. 使用(tablockx) 执行更新。如果您正在使用临时表,您知道该表没有争用,但仍然是一个好习惯(如果使用这种方法甚至可以被认为是一个好习惯的话)。
  2. 设置@Order = _order。这看起来像是一个毫无意义的陈述,而且确实如此。但是,由于 _order 是表的主键,因此将其分配给变量是强制 SQL 执行聚集索引更新的原因,这对于这项工作至关重要
  3. 填充一个整数来表示您想要的顺序组。这就是魔法发生的地方,你必须从它在表格中滚动的角度来考虑它。当_order 为1(第一行)时,只需将@Grp 变量设置为1。如果在任何给定行上,_type 的列值与@type 的变量值不同,我们增加分组多变的。如果值相同,我们只需使用上一行中的@Grp
  4. 使用列_type 的值更新@Type 变量。请注意,这必须在分配 @Grp 之后才能具有正确的值。
  5. 最后,设置_grp = @Grp。这是使用第 3 步的结果更新实际列值的地方。
  6. 所有这些都必须通过option (maxdop 1) 完成。这意味着最大并行度设置为 1。换句话说,SQL 无法执行任何可能导致排序关闭的任务并行化。

现在只需按_grp 字段进行分组。对于每批连续的_type,您将拥有一个唯一的_grp 值。

结论

如果这看起来像香蕉和 hacky,它就是。与所有事情一样,您需要对此持保留态度,如果您打算实施它,我建议您真正玩弄这个概念以完全理解它,因为我保证没有其他人会知道如何解决它如果你在半夜接到电话说它坏了。

【讨论】:

  • 这个古怪的更新是我非常熟悉的。您发布了 Jeff Moden 文章的链接,做得很好。
  • 哇,尊敬的人,我会试一试,但你的回答对社区绝对有帮助。
  • 虽然我认为,这是用链锯切面包 :-D 答案很好,显示出非常深刻的洞察力。从我这边 +1!
【解决方案2】:

此解决方案使用递归 CTE,并依赖于无间隙的 order 值。如果你没有这个,你可以用ROW_NUMBER()创建它on the fly

DECLARE @mockup TABLE([order] INT,[type] INT);
INSERT INTO @mockup VALUES
 (1,7)
,(2,11)
,(3,11)
,(4,18)
,(5,5)
,(6,19)
,(7,5)
,(8,5)
,(9,3)
,(10,11)
,(11,11)
,(12,3);

WITH recCTE AS
(
    SELECT m.[order]
          ,m.[type] 
          ,1 AS IncCounter
          ,1 AS [Rank]
    FROM @mockup AS m
    WHERE m.[order]=1

    UNION ALL

    SELECT m.[order]
          ,m.[type]
          ,CASE WHEN m.[type]=r.[type] THEN r.IncCounter+1 ELSE 1 END
          ,CASE WHEN m.[type]<>r.[type] THEN r.[Rank]+1 ELSE r.[Rank] END
    FROM @mockup AS m
    INNER JOIN recCTE AS r ON m.[order]=r.[order]+1
)
SELECT recCTE.[type]
      ,MAX(recCTE.[IncCounter])
      ,recCTE.[Rank]
FROM recCTE
GROUP BY recCTE.[type], recCTE.[Rank];

递归是向下遍历行,如果类型不变则增加计数器,如果类型不同则增加排名。

剩下的就是一个简单的GROUP BY

【讨论】:

    【解决方案3】:

    我想我会发布我制定的另一种方法,我认为更多的是类似于其他人正在考虑的 dense_rank() 工作。唯一的假设是 _order 是一个连续的整数(即没有间隙)。

    与之前相同的数据设置:

    if object_id('tempdb.dbo.#t') is not null drop table #t
    create table #t
    (
        _order int primary key clustered,
        _type int,
        _grp int
    )
    
    insert into #t (_order, _type)
    select 1,7
    union all select 2,11
    union all select 3,11
    union all select 4,18
    union all select 5,5
    union all select 6,19
    union all select 7,5
    union all select 8,5
    union all select 9,3
    union all select 10,11
    union all select 11,11
    union all select 12,3
    

    这种方法的作用是row_number 每个_type,因此无论_type 存在于何处,以及出现多少次,这些类型都将具有一个按_order 字段顺序唯一的row_number。通过从全局行号(即_order)中减去特定类型的行号,您将得到组。这是此代码的代码,然后我也将逐步介绍。

    ;with tr as
    (
        select 
            -- Create an incrementing integer row_number over each _type (regardless of it's position in the sequence)
            _type_rid = row_number() over (partition by _type order by _order),
            -- This shows that on rows 6-8 (the transition between type 19 and 5), naively they're all assigned the same group
            naive_type_rid = _order - row_number() over (partition by _type order by _order),
            -- By adding a value to the type_rid which is a function of _type, those two values are distinct. 
            -- Originally I just added the value, but I think squaring it ensures that there can't ever be another gap of 1
            true_type_rid = (_order - row_number() over (partition by _type order by _order)) + power(_type, 2),
            _type, 
            _order
        from #t
        -- order by _order -- uncomment this if you want to run the inner select separately
    )
    select 
        _grp = dense_rank() over (order by max(_order)),
        _type = max(_type)
    from tr
    group by true_type_rid
    order by max(_order)
    

    发生了什么

    首先要做的事;我不必在src cte 中创建单独的列来返回_type_rid。我这样做主要是为了排除故障和澄清问题。其次,我也不必再为_grp 列的最终选择做第二次dense_rank。我只是这样做了,所以它与我的其他方法的结果完全匹配。

    在每种类型中,type_rid 是唯一的,并且递增 1。_order 也递增 1。因此,只要给定的类型正在运行,仅间隔 1,_order - _type_rid 将是相同的值。让我们看几个例子(这是src cte 的结果,由_order 排序):

    _type_rid            naive_type_rid       true_type_rid        _type       _order
    -------------------- -------------------- -------------------- ----------- -----------
    1                    8                    17                   3           9
    2                    10                   19                   3           12
    1                    4                    29                   5           5
    2                    5                    30                   5           7
    3                    5                    30                   5           8
    1                    0                    49                   7           1
    1                    1                    122                  11          2
    2                    1                    122                  11          3
    3                    7                    128                  11          10
    4                    7                    128                  11          11
    1                    3                    327                  18          4
    1                    5                    366                  19          6
    

    第一行,_order - _type_rid = 1 - 1 = 0。这会将这一行(类型 7)分配给组 0 第二行,2 - 1 = 1。这将类型 11 分配给组 1 第三行,3 - 2 = 1。这也将第二个顺序类型 11 分配给组 1 第四行,4 - 1 = 3。这将类型 18 分配给组 3 ...等等。

    这些组不是连续的,但它们的顺序与_order 相同,这是重要的部分。您还会注意到我还将_type 的值添加到该值中。那是因为当我们点击后面的一些行时,组切换了,但序列仍然递增 1。通过添加 _type,我们可以区分这些非一值,并且仍然以正确的顺序执行。

    src 的最终外部选择按 max(_order) 排序(在我不必要的 dense_rank() _grp 修改中,以及只是一般结果顺序)。

    结论

    这仍然有点不稳定,但绝对在“支持的功能”范围内。鉴于我在那里遇到了一个问题(一件一件的事情),可能还有其他我没有考虑过的问题,所以再一次,用一粒盐,做一些测试。

    【讨论】:

    • 打我一拳...如果我看到使用排名函数的基于集合的方法,我正在徘徊。干得好!
    猜你喜欢
    • 2021-08-09
    • 2022-01-20
    • 2020-09-26
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    • 2017-03-03
    • 1970-01-01
    • 2020-02-16
    相关资源
    最近更新 更多