Эффективный способ выбора массивных строк и масштабирования выбора в хронологическом порядке?

76
7

У меня есть данные, вставленные в таблицу каждые 5 минут, столбцы содержат метку времени и данные. Я хочу выбрать данные, основанные на заданном временном интервале, с данными, которые должным образом опущены для производительности и хронологического масштабирования, так что запрос возвращает максимум около 32.


Например, у меня есть 2 недели данных, или 4032 записи из 5-минутных разделенных записей. Я хочу выбирать от начала до конца, уменьшая результирующий набор до 32 записей, но хронологически устанавливая шкалу записей, чтобы каждая запись в 32 записях была как можно более равноудаленной по времени, также оставляя записи о краях (начало и конечные записи в наборе) без изменений.


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


Спасибо.

спросил(а) 2021-01-19T16:34:18+03:00 3 месяца, 2 недели назад
1
Решение
87

Хорошо, не говорите, что я вас не предупреждал (в соответствии с разделом комментариев выше). Это написано для MSSQL; Я не знаком с MySQL, поэтому я попытался срубить фирменный материал uber. Возможно, все это можно было бы сделать в одном большом уродливом запросе, но тогда это было бы еще более непонятно, поэтому я разбил его на несколько шагов.


Сначала установите некоторые переменные:


DECLARE
@Items real = 32 -- How many items you wish to display
,@From int = 16000 -- Low range delimiter on your target data set
,@Thru int = 17500 -- High range delimiter on your target data set
,@Total real -- Used to store how many items are actually in the target range

Краткое тестирование показало, что вещи терпят неудачу, если @Items меньше 2 или больше некоторого большого кратного значения @Total. Требуется обработка ошибок или тестирование входов. Я использую реальный тип данных, так что деление производит десятичные значения, а не усеченные целые числа; не забудьте установить их с целыми значениями, или я не знаю, что произойдет.


Этот следующий бит создает таблицу "Tally" или "таблицу чисел". Это просто таблица с одним столбцом восходящих целых чисел, начиная с 1 и поднимаясь до любого верхнего предела. Здесь я закрыл его на 256, так как 32 кажется вашим максимальным. (Этот конкретный код довольно тупой, но он может генерировать миллионы строк в очень короткое время, поэтому я разрезаю-в-вставляю его всякий раз, когда мне нужны такие вещи.)


CREATE TABLE #Tally (Num  int  not null)

-- "Table of numbers" data generator, as per Itzik Ben-Gan (from multiple sources)
-- Modified to generate 1 through 256
;WITH
L0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
L1 AS (SELECT 1 AS C FROM L0 AS A, L0 AS B),--4 rows
L2 AS (SELECT 1 AS C FROM L1 AS A, L1 AS B),--16 rows
L3 AS (SELECT 1 AS C FROM L2 AS A, L2 AS B),--256 rows
num AS (SELECT ROW_NUMBER() OVER(ORDER BY C) AS N FROM L3)
insert #Tally (Num)
select N FROM num


Получить количество строк в целевом наборе данных:


SELECT @Total = count(*)
from Time
where TimeId between @From and @Thru

Просмотрите запрос, перечислите целевой диапазон в порядке с ранжированием (позиция, например, 1, 2, 3, 4 и т.д.) в пределах набора. Это будет обрабатывать повторяющиеся значения. (Я основывал свои тесты на нашей общей таблице "Время", которая выглядит как любая любая таблица измерений времени в любом хранилище данных.)


SELECT
row_number() over (order by TimeId) Ranking
,TimeId
from Time
where TimeId between @From and @Thru

Другой запрос. Это возвращает набор чисел, который идентифицирует "точки останова" вашего окончательного набора. Например, если у вас было 30 предметов и вам нужно 7, это создаст {5, 10, 15, 20, 25, 30}; в сочетании с 1, его семь вы хотите (если у меня проблема прямо).


SELECT distinct ceiling((Num - 1) * @Total / (@Items - 1)) from #Tally

Здесь рабочая лошадка, содержащая два вышеуказанных запроса. В принципе, возьмите все, начиная с первого запроса, где он ранг/позиция совпадает с идентифицированной "точкой прерывания" во втором запросе. Я бросил в первый элемент с OR, так проще, чем пытаться втиснуть его математически.


SELECT xx.Ranking, xx.TimeId
from (select
row_number() over (order by TimeId) Ranking
,TimeId
from Time
where TimeId between @From and @Thru) xx
where Ranking in (select distinct ceiling((Num - 1) * @Total / (@Items - 1)) from #Tally)
or Ranking = 1

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

ответил(а) 2021-01-19T16:34:18+03:00 3 месяца, 2 недели назад
44

Хорошо, я придумал эту процедуру, любая чистка оценена. Он работает так, как я хотел, после некоторой слепой отладки. Время хранится как отметки времени UTC.


    DELIMITER $$

CREATE PROCEDURE `SelectChronoRange`(IN timeBegin BIGINT,
IN timeEnd BIGINT)
BEGIN
DECLARE totalAvail, skip, insideResultMax INT;
SET @maxResults = 64;

SELECT count(*)
INTO totalAvail
FROM `dediwatcherstats`;

SET insideResultMax:= @maxResults - 2;
SET skip := CEIL(totalAvail / insideResultMax);
SET @firstpid = 0;
SET @lastpid = 0;

SELECT `pid` INTO @firstpid
FROM `dediwatcherstats`
WHERE
CASE
WHEN timeBegin IS NOT NULL AND timeEnd IS NOT NULL THEN
`Time`>=timeBegin AND `Time`<=timeEnd
WHEN timeEnd IS NOT NULL THEN
`Time`<=timeEnd
WHEN timeBegin IS NOT NULL THEN
`Time`>=timeBegin
ELSE
TRUE
END
ORDER BY `Time` ASC, `pid` ASC LIMIT 1;

SELECT `pid` INTO @lastpid
FROM `dediwatcherstats`
WHERE
CASE
WHEN timeBegin IS NOT NULL AND timeEnd IS NOT NULL THEN
`Time`>=timeBegin AND `Time`<=timeEnd
WHEN timeEnd IS NOT NULL THEN
`Time`<=timeEnd
WHEN timeBegin IS NOT NULL THEN
`Time`>=timeBegin
ELSE
TRUE
END
ORDER BY `Time` DESC, `pid` DESC LIMIT 1;

SELECT * FROM
(
(
SELECT * FROM `dediwatcherstats`
WHERE `pid`=@firstpid
)
UNION
(
SELECT * FROM `dediwatcherstats`
WHERE
CASE
WHEN timeBegin IS NOT NULL AND timeEnd IS NOT NULL THEN
`Time`>=timeBegin AND `Time`<=timeEnd
WHEN timeEnd IS NOT NULL THEN
`Time`<=timeEnd
WHEN timeBegin IS NOT NULL THEN
`Time`>=timeBegin
ELSE
TRUE
END
AND `pid` % skip=0
LIMIT 62
)
) AS notused
UNION
SELECT * FROM `dediwatcherstats`
WHERE `pid`=@lastpid;
END


Он работает на этой простой таблице:

CREATE TABLE `dediwatcherstats` (
`pid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`Time` bigint(20) unsigned NOT NULL,
`Data` text,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Я хочу, чтобы параметр LIMIT разрешал переменные аргумента. В опубликованном мной кодеке я использовал лимит 64 вместо 32 для всех, кто может захотеть его использовать.

ответил(а) 2021-01-19T16:34:18+03:00 3 месяца, 2 недели назад
44

Итак, что-то в этих строках, где две даты - это вход, 5-минутный диапазон и 32 выборки в диапазоне:


SELECT rownum
FROM (SELECT @row := @row +1 AS rownum
,@sampleRate AS sampleRate
FROM (SELECT @row := 0
,@sampleRate := TIMESTAMPDIFF(MINUTE,'2011-12-01 00:00:00','2011-12-15 00:00:00') / 5 / 32 ) r
,clientpc
) ranked
WHERE rownum % @sampleRate = 1

ответил(а) 2021-01-19T16:34:18+03:00 3 месяца, 2 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема