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

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

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

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

Спасибо.

Ответы [ 3 ]

0 голосов
/ 15 декабря 2011

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

Сначала настройте некоторые переменные:

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

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

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

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

0 голосов
/ 15 декабря 2011

Ну, я придумал эту процедуру, любая уборка приветствуется. Это работает так, как я хотел, после слепой отладки. Время сохраняется как метки времени 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 для всех, кто захочет его использовать.

0 голосов
/ 14 декабря 2011

Итак, что-то вроде этих строк, где вводятся две даты, 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...