У меня ниже таблица

create table #t (Id int, Name char)

insert into #t values
(1, 'A'),
(2, 'A'),
(3, 'B'),
(4, 'B'),
(5, 'B'),
(6, 'B'),
(7, 'C'),
(8, 'B'),
(9, 'B')

Я хочу подсчитать последовательные значения в столбце имени

+------+------------+
| Name | Repetition |
+------+------------+
| A    |          2 |
| B    |          4 |
| C    |          1 |
| B    |          2 |
+------+------------+

Лучшее, что я пробовал:

select Name
, COUNT(*) over (partition by Name order by Id) AS Repetition
from #t
order by Id

Но это не дает ожидаемого результата

38
FLICKER 29 Апр 2016 в 03:38

3 ответа

Лучший ответ

Один из подходов - это различие номеров строк:

select name, count(*) 
from (select t.*,
             (row_number() over (order by id) -
              row_number() over (partition by name order by id)
             ) as grp
      from t
     ) t
group by grp, name;

Логику легче всего понять, если вы запустите подзапрос и посмотрите значения каждого номера строки отдельно, а затем посмотрите на разницу.

30
Gordon Linoff 29 Апр 2016 в 00:42

Вы можете использовать оконные функции, такие как LAG и промежуточный итог:

WITH cte AS (
 SELECT Id, Name, grp = SUM(CASE WHEN Name = prev THEN 0 ELSE 1 END) OVER(ORDER BY id)
 FROM (SELECT *, prev = LAG(Name) OVER(ORDER BY id) FROM t) s
)
SELECT name, cnt = COUNT(*)
FROM cte
GROUP BY grp,name
ORDER BY grp;

db & lt; & gt; fiddle demo

Первый cte возвращает номер группы:

+-----+-------+-----+
| Id  | Name  | grp |
+-----+-------+-----+
|  1  | A     |   1 |
|  2  | A     |   1 |
|  3  | B     |   2 |
|  4  | B     |   2 |
|  5  | B     |   2 |
|  6  | B     |   2 |
|  7  | C     |   3 |
|  8  | B     |   4 |
|  9  | B     |   4 |
+-----+-------+-----+

И основной запрос группирует его на основе столбца grp, рассчитанного ранее:

+-------+-----+
| name  | cnt |
+-------+-----+
| A     |   2 |
| B     |   4 |
| C     |   1 |
| B     |   2 |
+-------+-----+
3
Lukasz Szozda 28 Мар 2020 в 11:26

Я использую рекурсивный CTE и минимизирую использование row_number, а также избегаю count (*).

Я думаю, что он будет работать лучше, но в реальном мире это зависит от того, какой еще фильтр вы используете, чтобы минимизировать количество затронутых строк.

Если ID имеет скрытые значения, то для генерации непрерывного идентификатора будет использоваться один дополнительный CTE.

 ;With CTE2 as
(
select ROW_NUMBER()over(order by id) id, name,1 Repetition ,1 Marker  from @t
)
, CTE as
(
select top 1 cast(id as int) id, name,1 Repetition ,1 Marker  from CTE2 order by id

union all

select a.id, a.name
, case when a.name=c.name then Repetition +1 else 1 end  
, case when a.name=c.name then c.Marker else  Marker+1 end
from @t a
inner join CTE c on a.id=c.id+1

)
,CTE1 as
(select *,ROW_NUMBER()over(partition by marker order by id desc)rn from cte c
)
select Name,Repetition from cte1 where rn=1
2
KumarHarsh 29 Апр 2016 в 08:46