Helpers - компьютеры, интернет, программирование

SQL Найти последовательные числа в группах

У меня есть таблица, похожая на показанную. Он содержит список идентификаторов пользователей, значение часа для каждого часа дня и флаг доступности, чтобы определить, доступен ли этот пользователь в этот час.

Мне нужно перечислить все идентификаторы пользователей, которые доступны в течение нескольких последовательных часов, определенных как @n

#####################
# UID # Avail # Hour#
#####################
# 123 #   1   #  0  #
# 123 #   1   #  1  #
# 123 #   0   #  2  #
# 123 #   0   #  3  #
# 123 #   0   #  4  #
# 123 #   1   #  5  #
# 123 #   1   #  6  #
# 123 #   1   #  7  #
# 123 #   1   #  8  #
# 341 #   1   #  0  #
# 341 #   1   #  1  #
# 341 #   0   #  2  #
# 341 #   1   #  3  #
# 341 #   1   #  4  #
# 341 #   0   #  5  #
# 341 #   1   #  6  # 
# 341 #   1   #  7  #
# 341 #   0   #  8  #
######################

Это должно привести к следующему результату для @n=3

#######
# UID #
#######
# 123 #
#######

Я попытался использовать ROW_NUMBER() (раздел по UID, доступный ORDER BY UID, час), чтобы присвоить номер каждой строке, разделенной по UID, и независимо от того, помечены ли они как доступные. Однако это не работает, поскольку периоды доступности могут меняться несколько раз в день, а функция ROW_NUMBER() вела только два подсчета для каждого пользователя на основе флага доступности.

05.08.2015

  • Аналитика опережения/отставания глядя на ваш пример, почему в результате 123? 5,7,8 не являются последовательными ... или 0 означает, что они доступны, а 2,3,4 будет последовательной группой? 05.08.2015
  • Извините, ошибка с моей стороны, которую я отсортировал в примере 05.08.2015

Ответы:


1

Если вы используете SQL Server 2012+, вы можете использовать оконную SUM, но вам нужно заранее указать количество строк в оконном фрейме, поскольку он не будет принимать переменные, поэтому он не такой гибкий:

;with cte as 
(
    select distinct 
       UID, 
       SUM(avail) over (partition by uid 
                        order by hour 
                        rows between current row and 2 following
                       ) count 
    from table1
)
select uid from cte where count = 3;

Если вам нужна гибкость, вы можете сделать это хранимой процедурой и использовать динамический SQL для построения и выполнения инструкции, например:

create procedure testproc (@n int) as
declare @sql nvarchar(max)
set @sql = concat('
    ;with cte as 
    (
       select distinct 
          UID, 
          SUM(avail) over (partition by uid 
                        order by hour 
                        rows between current row and ', @n - 1 , ' following
                        ) count 
       from table1
    )
    select uid from cte where count = ' , @n , ';')
exec sp_executesql @sql

и выполните его, используя execute testproc 3

Еще более негибким решением является использование коррелированных подзапросов, но тогда вам придется добавлять еще один подзапрос для каждого добавленного счетчика:

select distinct uid 
from Table1 t1
where Avail = 1
  and exists (select 1 from Table1 where Avail = 1 and UID = t1.UID and Hour = t1.Hour + 1)
  and exists (select 1 from Table1 where Avail = 1 and UID = t1.UID and Hour = t1.Hour + 2);

И еще один способ, используя row_number для поиска островов, а затем фильтруя по сумме avail для каждого острова:

;with c as (
    select 
       uid, avail, 
       row_number() over (partition by uid order by hour) 
       - row_number() over (partition by uid, avail order by hour) grp
from table1
)

select uid from c
group by uid, grp
having sum(avail) >= 3 
05.08.2015

2

Это работает... Он выполняет самообъединение по идентификатору пользователя и чему-либо во второй таблице с помощью @n (3 часа), а затем возвращает только те записи, которые имеют количество записей 3.

SELECT A.UID
FROM foo A
INNER JOIN foo B
 on A.UId = B.UID
 and A.Hour+3 <= B.Hour
 and A.Avail= 1 and B.Avail=1
GROUP BY A.UID
HAVING count(distinct B.hour) =3

http://sqlfiddle.com/#!6/f97ee

05.08.2015

3

Не было времени полировать это... но это один из вариантов.

  • Первый CTE (c) создает новый идентификатор столбца
  • Второй CTE (mx) получает максимальный номер строки, поскольку вы не можете использовать агрегаты в рекурсивных CTE.
  • Конечный CTE (rc) - это то, где мясо.

    ;WITH c AS (
        SELECT ROW_NUMBER() OVER (ORDER BY [UID],[Hour]) Id, 
            [UID],Avail,[Hour]
        FROM #tmp
    ), mx AS (
        SELECT MAX(Id) MaxRowCount FROM c
    ), rc AS (
    
        SELECT Id, [UID], Avail, [Hour], c.Avail AS CummulativeHour
        FROM c
        WHERE Id = 1
    
        UNION ALL
    
        SELECT c.Id, c.[UID], c.Avail, c.[Hour], CASE WHEN rc.Avail = 0 OR c.Avail = 0 OR rc.[UID] <> c.[UID] THEN c.Avail
                                                    WHEN rc. Avail = 1 AND c.Avail = 1 THEN rc.CummulativeHour + 1 END AS CummulativeHour
        FROM rc
        JOIN c
            ON rc.Id + 1 = c.Id
        WHERE c.Id <= (SELECT mx.MaxRowCount FROM mx)
    
    )
    SELECT * FROM rc
    

Вот пример создания данных...

CREATE TABLE #tmp ([UID] INT, Avail INT, [Hour] INT)

INSERT INTO #tmp
        ( UID, Avail, Hour )
VALUES  (123,1,0),
(123,1,1),
(123,0,2),
(123,0,3),
(123,0,4),
(123,1,5),
(123,1,7),
(123,1,8),
(341,1,0),
(341,0,2),
(341,1,3),
(341,1,4),
(341,0,5),
(341,1,6),
(341,1,7),
(341,0,8)
05.08.2015

4

Основной запрос с несколькими CTE ниже дает вам несколько возможностей, чтобы показать, что вам нужно (максимум на пользователя, пользователь с N часами и т. д.). Просто обновите последний запрос под CTE.

Создайте таблицу и данные:

declare @hours table(
uid int
, avail bit
, h tinyint
)
insert into @hours(uid, avail, h) values 
(123, 1, 0),
(123, 1, 1),
(123, 0, 2),
(123, 0, 3),
(123, 0, 4),
(123, 1, 5),
(123, 1, 6),
(123, 1, 7),
(123, 1, 8),
(341, 1, 0),
(341, 1, 1),
(341, 0, 2),
(341, 1, 3),
(341, 1, 4),
(341, 0, 5),
(341, 1, 6), 
(341, 1, 7),
(341, 0, 8),
(341, 1, 23) -- loop through midnight

Последняя строка была добавлена, чтобы показать, что она может обнаруживать непрерывные часы около полуночи (см. рисунок сзади). т. е. 23 => 2 часа ночи для uid 341

Запрос MAX непрерывных часов на пользователя:

-- remove duplicate, wrong hours and not available hours
;with hs as (
    Select distinct uid, h from @hours where avail = 1 and h < 24 
), loop(uid, first, last, diff) as (
    --loop through successive hours
    select uid, cast(h as tinyint), cast(h+1 as int), cast(1 as int) from hs
    union all
    select l.uid, l.first, l.last+1, l.diff+1 from loop as l
    inner join hs as h on l.uid = h.uid and l.last = h.h
), back(uid, first, last, diff) as (
    -- search for successive hours around midnight
    select uid, first, last, diff from loop
    union
    select l1.uid, l1.first, l2.last, l1.diff+l2.diff from loop as l1
    inner join loop as l2 on l1.uid = l2.uid and l1.last = 24 and l2.first = 0
), mx(uid, diff) as (
    -- get max continuous hours per user
    select uid, max(diff) from back group by uid
)
-- main query, change it based on what you need (see below)
select b.uid, b.first, b.last, b.diff from back as b
inner join mx as m on m.uid = b.uid and m.diff = b.diff
order by uid, diff desc

полученные результаты:

uid first   last    diff
123 5       9       4
341 23      2       3 <= present because I added 341/1/23. Would not be here otherwise

Получите пользователя как минимум с 3 непрерывными часами (замените последний выбор на этот):

select distinct uid from back where diff >= 3 -- @n goes here

Пожалуйста, обратите внимание, что я считал, что (123, 1, 5) дает 1 доступный час с 5 до 6. Следовательно, от 5 до 8 дает вам 4 доступных часа с 5 до 9.

05.08.2015
Новые материалы

Интуитивное понимание тензоров в машинном обучении
Тензор является важной концепцией во многих научных областях, таких как математика, физика, обработка сигналов и компьютерное зрение, и это лишь некоторые из них. В математике тензор — это..

Использование машинного обучения для диагностики болезни Альцгеймера, часть 4
Маркеры семантической согласованности для ранней диагностики болезни Альцгеймера (arXiv) Автор: Давиде Колла , Маттео Дельсанто , Марко Агосто , Бенедетто Витиелло , Даниэле Паоло Радичони..

Почему объяснимость так важна прямо сейчас?
По мере того, как системы искусственного интеллекта и инструменты на основе машинного обучения распространяются в нашей повседневной жизни, как практики, так и критики все чаще заявляют о..

Анимированный математический анализ
Использование Manim для создания математических анимированных визуализаций Визуализация данных помогает понять скрытые закономерности в данных, которые невозможно визуализировать..

Создание простого слайдера изображений с помощью JavaScript
Узнайте, как создать базовый слайдер изображений с помощью HTML, CSS и JavaScript. Введение В этом уроке мы создадим удобный слайдер изображений, используя JavaScript, HTML и CSS. Ползунок..

Создание базы данных с помощью супергероя «Python»
В этом посте мы узнаем, как создать «базу данных SQLite с помощью модуля python sqlite3, создав простую функцию входа и регистрации. Готовы ли вы к этому путешествию? Если да , давайте приступим..

ИИ для чайников: руководство для начинающих по пониманию будущего технологий
Вы чувствуете, что остались позади в мире ИИ? Не волнуйтесь, вы не одиноки! Со всей этой шумихой вокруг искусственного интеллекта может быть трудно понять, с чего начать. Но не позволяйте сленгу..