Реализация трендовых тегов на основе MySQL - PullRequest
0 голосов
/ 11 мая 2018

Я пытаюсь идентифицировать тренды тренда (на основе максимальных хитов) по временным рядам с помощью функции mysql json.Ниже моя таблица

CREATE TABLE TAG_COUNTER (
    account       varchar(36) NOT NULL,
    time_id       INT NOT NULL,
    counters      JSON,
    PRIMARY KEY   (account, time_id)
)

В каждом веб-запросе API я буду получать несколько разных тегов для каждой учетной записи, и на основе количества тегов я подготовлю запрос INSERT ON DUPLICATE KEY UPDATE.Ниже приведен пример вставки с двумя тегами.

INSERT INTO `TAG_COUNTER`
  (`account`, `time_id`, `counters`)
VALUES
  ('google', '2018061023', '{"tag1": 1, "tag2": 1}')
ON DUPLICATE KEY UPDATE `counters` =
  JSON_SET(`counters`,
           '$."tag1"',
           IFNULL(JSON_EXTRACT(`counters`,
                        '$."tag1"'), 0) + 1,
           '$."tag2"',
           IFNULL(JSON_EXTRACT(`counters`,
                        '$."tag2"'), 0) + 1
  );

time_id равен yyyyMMddhh, и для каждой строки производится агрегация по часам.

Теперь моя проблема заключается в получении тегов treding.Приведенный ниже запрос даст мне агрегацию для tag1, но мы не будем знать теги перед выполнением этого запроса.

SELECT
SUBSTRING(time_id, 1, 6) AS month,
SUM(counters->>'$.tag1')
FROM TAG_COUNTER
WHERE counters->>'$.tag1' > 0
GROUP BY month;

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

Пример ожидаемой выработки:

Time(hour/day/month)  Tag_name  Tag_count_value(total hits)

Когда я искал в Интернете, каждый раз, когда он упоминается, как показано ниже {"tag_name": "tag1", "tag_count": 1} вместо прямого {"tag1" : 1}и они использовали tag_name в группе по.

Q1) Так всегда ли необходимо иметь общеизвестный ключ json для выполнения группы по ...?

Q2) Если мне нужнопойти по этому пути, то каково изменение в моем запросе INSERT ON DUPLICATE KEY UPDATE для этого нового json label / value struture?Поскольку счетчик должен быть создан, когда он не существует, и должен увеличиваться на единицу, когда он существует.

Q3) нужно ли поддерживать массив объектов

[
 {"tag_name": "tag1", "tag_count": 2},
 {"tag_name": "tag2", "tag_count": 3}
]

ИЛИ объект объектов, как показано ниже?

{
 {"tag_name": "tag1", "tag_count": 2},
 {"tag_name": "tag2", "tag_count": 3}
}

Так что же лучше, чем промежуточные структуры json INSERT и RETRIEVAL количества трендов?

Q4) Можетя иду с существующим форматом {"key" : "value"} вместо {"key_label" : key, "value_lable" : "value"} и возможно извлечь тренды ..?так как я думаю, что {"key" : "value"} очень прямолинеен и хорош в производительности.

Q5) При получении я использую SUBSTRING(time_id, 1, 6) AS month.Сможет ли он использовать индекс?

ИЛИ мне нужно создать несколько столбцов, таких как time_hour(2018061023), time_day(20180610), time_month(201806) и использовать запрос для определенных столбцов?

ИЛИ я могу использовать функции даты и времени mysql ?будет ли этот индекс использовать для более быстрого поиска?

Пожалуйста, помогите.

Ответы [ 2 ]

0 голосов
/ 20 мая 2018

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

DELIMITER //
DROP FUNCTION IF EXISTS json_merge_sum //
CREATE FUNCTION json_sum_merge(IN j1 JSON, IN total JSON) RETURNS JSON
BEGIN
  DECLARE knum INT DEFAULT 0;
  DECLARE jkeys JSON DEFAULT JSON_KEYS(j1);
  DECLARE kpath VARCHAR(30);
  DECLARE v INT;
  DECLARE l INT DEFAULT JSON_LENGTH(jkeys);
  kloop: LOOP
    IF knum >= l THEN
      LEAVE kloop;
    END IF;
    SET kpath = CONCAT('$.', JSON_EXTRACT(jkeys, CONCAT('$[', knum, ']')));
    SET v = JSON_EXTRACT(j1, kpath);
    IF JSON_CONTAINS_PATH(total, 'one', kpath) THEN
      SET total = JSON_REPLACE(total, kpath, JSON_EXTRACT(total, kpath) + v);
    ELSE
      SET total = JSON_SET(total, kpath, v);
    END IF;
    SET knum = knum + 1;
  END LOOP kloop;
  RETURN total;
END //

Процедура аналогична той, что была в моем другом ответе, в том, что она находит все различные теги, связанные с данной подстрокой time_id (заданной в качестве параметра), и суммирует значения, связанные с каждым тегом. Затем отдельные теги и счетчики записываются во временную таблицу, из которой затем производится выборка, группирующаяся по периоду времени и имени тега.

DELIMITER //
DROP PROCEDURE IF EXISTS count_tags //
CREATE PROCEDURE count_tags(IN period VARCHAR(50))
BEGIN
  DECLARE finished INT DEFAULT 0;
  DECLARE timeval VARCHAR(20);
  DECLARE knum, l INT;
  DECLARE jkeys JSON;
  DECLARE time_cursor CURSOR FOR SELECT DISTINCT time_id FROM tag_counter;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished=1;
  CREATE TEMPORARY TABLE tag_counts (Time VARCHAR(20), Tag_Name VARCHAR(30), Tag_count_value INT, INDEX(Time, Tag_Name));
  OPEN time_cursor;
  time_loop: LOOP
    FETCH time_cursor INTO timeval;
    IF finished=1 THEN
      LEAVE time_loop;
    END IF;
    SET @total = '{}';
    SET @query = CONCAT("SELECT MIN(@total:=json_sum_merge(counters, @total)) INTO @json FROM TAG_COUNTER WHERE time_id='", timeval, "'");
    PREPARE stmt FROM @query;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
    SET @query = CONCAT('INSERT INTO tag_counts VALUES(', period, ', ?, ?)');
    PREPARE stmt FROM @query;
    SET @timeval = timeval;
    SET l = JSON_LENGTH(@total);
    SET jkeys = JSON_KEYS(@total);
    SET knum = 0;
    key_loop: LOOP
      IF knum >= l THEN
        LEAVE key_loop;
      END IF;
      SET @k = JSON_EXTRACT(jkeys, CONCAT('$[', knum, ']'));
      SET @t = JSON_EXTRACT(@total, CONCAT('$.', @k));
      EXECUTE stmt USING @k, @t;
      SET knum = knum + 1;
    END LOOP key_loop;
    DEALLOCATE PREPARE stmt;
  END LOOP time_loop;
  SELECT Time, Tag_Name, SUM(Tag_count_value) AS Tag_count_value FROM tag_counts GROUP BY Time, Tag_Name;
  DROP TABLE tag_counts;
END

Пара примеров, основанных на некоторых ограниченных выборочных данных из вашего предыдущего вопроса . В этих примерах @timeval эквивалентно столбцу time_id. Входные данные:

account     time_id     counters
google      20180510    {"gmail_page_viewed": 2, "search_page_viewed": 51}
google      20180511    {"gmail_page_viewed": 3, "search_page_viewed": 102}
apple       20180511    {"apple_page_viewed": 5, "search_page_viewed": 16}

ВЫЗОВ count_tags('@timeval'):

Time        Tag_Name                Tag_count_value
20180510    "gmail_page_viewed"     2
20180510    "search_page_viewed"    51
20180511    "apple_page_viewed"     5
20180511    "gmail_page_viewed"     3
20180511    "search_page_viewed"    118

CALL count_tags('SUBSTRING(@timeval, 1, 6)'):

Time    Tag_Name                Tag_count_value
201805  "apple_page_viewed"     5
201805  "gmail_page_viewed"     5
201805  "search_page_viewed"    169

Обратите внимание, что вы также можете использовать json_sum_merge, чтобы упростить ваш запрос INSERT, например,

INSERT INTO `TAG_COUNTER`
  (`account`, `time_id`, `counters`)
VALUES
  ('apple', '20180511', '{"apple_page_viewed": 9, "itunes_page_viewed": 4}')
ON DUPLICATE KEY UPDATE `counters` = json_sum_merge(VALUES(counters), counters)

Результат:

account     time_id     counters
apple       20180511    {"apple_page_viewed": 14, "itunes_page_viewed": 4, "search_page_viewed": 16}

С точки зрения конкретных вопросов в вашем ответе:

  1. Нет. Этот ответ показывает, что это можно сделать с вашим существующим форматом данных.
  2. Не применимо.
  3. Не применимо.
  4. Да, вы можете придерживаться существующего {"key" : "value"} формата
  5. Поскольку для получения списка тегов нам нужно пройти через каждую запись в tag_counter, индекс не подходит для этого раздела. Для временной таблицы я включил индексы в столбцы Time и Tag_Name, которые должны повысить скорость, поскольку они используются непосредственно в предложении GROUP BY.

Если бы вы вели список ключей (например, в отдельной таблице, поддерживаемый триггером при вставке / обновлении / удалении до tag_counter), этот код можно было бы сделать намного проще и эффективнее. Но это для другого вопроса.

0 голосов
/ 14 мая 2018

Не вижу веской причины, почему вы здесь используете JSON.Также неясно, почему вы считаете, что « nosql schema » в MySQL сделает что-то лучше.

Что вам, вероятно, понадобится, примерно так:

CREATE TABLE TAG_COUNTER (
    account       varchar(36) NOT NULL,
    time_id       INT NOT NULL,
    tag_name      varchar(50) NOT NULL,
    counter       INT UNSIGNED NOT NULL,
    PRIMARY KEY   (account, time_id, tag_name)
);

Это упростит ваши запросы.Оператор INSERT будет выглядеть следующим образом:

INSERT INTO TAG_COUNTER
  (account, time_id, tag_name, counter)
VALUES
  ('google', 2018061023, 'tag1', 1),
  ('google', 2018061023, 'tag2', 1)
ON DUPLICATE KEY UPDATE counter = counter + VALUES(counter);

Оператор SELECT может выглядеть примерно так:

SELECT
    SUBSTRING(time_id, 1, 6) AS month,
    tag_name,
    SUM(counter) AS counter_agg
FROM TAG_COUNTER
GROUP BY month, tag_name
ORDER BY month, counter_agg DESC;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...