根据行顺序获取计数

时间:2017-07-26 20:45:30

标签: sql-server sql-server-2016

我有一个具有这种结构的表

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个白宫
  • 然后我有一个红屋
  • 然后我有2个蓝房子
  • 然后我有1个白宫

因此,计数基于订单。如果我能够在分区更改时重置RANK,DENSE_RANK函数将对我有所帮助。

3 个答案:

答案 0 :(得分:5)

所以我有一个答案,但我必须警告你,由于它是如何完成的,它可能会引起一些焦虑。它使用称为“Quirky Update”的东西。如果您计划实施此项目,请通过链接文章阅读上帝的爱,并了解这是一个“无证件的黑客”,需要精确实施以避免意外后果。

如果你有一点点数据,我只是为了简单和清晰而痛苦地排着。但是,如果您有大量数据并且仍需要高性能,则可能会这样做。

<强>要求

  1. 表必须按照您要进行的顺序具有聚簇索引
  2. 表必须没有其他索引(这些索引可能会导致SQL从另一个索引中读取数据,该索引的顺序不正确,导致行顺序的量子叠加崩溃)。
  3. 表格必须在操作期间完全锁定(tablockx)
  4. 更新必须以串行方式进行(maxdop 1)
  5. 它的作用

    您知道人们如何告诉您表格中的数据没有隐含的顺序吗?这仍然是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的列值与{{1}的变量值不同1}},我们增加分组变量。如果值相同,我们只需坚持上一行中的@type
    4. 使用列@Grp的值更新@Type变量。请注意,在为_type指定正确的值后,我们需要这样做。
    5. 最后,设置@Grp。这是使用步骤3的结果更新实际列值的位置。
    6. 所有这一切都必须使用_grp = @Grp完成。这意味着最大并行度设置为1.换句话说,SQL无法执行任何可能导致排序关闭的任务并行化。
    7. 现在只需要按option (maxdop 1)字段进行分组。对于每个连续批_grp,您将拥有唯一的_grp值。

      <强>结论

      如果这看起来像香蕉和黑客,那就是。就像所有事情一样,你需要花一点时间,如果你计划实施它,我建议你真正使用这个概念来完全理解它,因为我保证没有其他人会知道如何解决它如果你在半夜接到一个电话,那它正在破碎。

答案 1 :(得分:3)

此解决方案使用递归CTE并依赖于无间隙<li id="id_name" class="class_names"> <a href="http://linktothepage.com/page" title="TITLE OF MY PAGE" aria-selected="true">TITLE OF MY PAGE</a> </li> 值。如果您没有这个,可以使用order 即时创建

ROW_NUMBER()

如果类型不变则递归遍历增加计数器的行,如果类型不同则递增等级。

其余的是一个简单的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];

答案 2 :(得分:2)

我认为我发布了另一种方法,我更多地考虑其他人正在考虑的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存在于何处,以及多少次,这些类型都会以{的顺序排列唯一的row_number {1}}字段。通过从全局行号中减去该类型特定的行号(即_order),您最终会得到组。这是这个代码的代码,然后我也将介绍这个代码。

_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) cte中创建单独的列以返回src。我这样做主要是为了排除故障和清晰。其次,我也没有必要在列_type_rid的最终选择上做第二次dense_rank。我只是这样做,所以它与我的其他方法的结果完全匹配。

在每种类型中,_grp是唯一的,增量为1. type_rid也增加1。因此,只要给定的类型只有1,但只有1,_order将是相同的值。让我们看几个例子(这是_order - _type_rid cte的结果,由src排序):

_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 = 1 - 1 = 0.这将此行(类型7)分配给组0 第二行,2 - 1 = 1.这将类型11分配给组1 第三行,3 - 2 = 1.这也将第二顺序类型11分配给组1 第四行,4 - 1 = 3.这将类型18分配给第3组 ......等等。

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

最终外部选择来自_type订单的max(_order)(在我不必要的src _grp修改中,只是一般结果顺序)。

结论

这仍然有点不稳定,但绝对是在支持功能的范围内#34;。鉴于我在那里碰到了一个问题(一个接一个的东西),可能还有其他我没有考虑过的问题,所以再一次,带着一点点盐,做一些测试。