Как я уже говорил в комментариях, я думаю, что отойти от 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}
С точки зрения конкретных вопросов в вашем ответе:
- Нет. Этот ответ показывает, что это можно сделать с вашим существующим форматом данных.
- Не применимо.
- Не применимо.
- Да, вы можете придерживаться существующего
{"key" : "value"}
формата
- Поскольку для получения списка тегов нам нужно пройти через каждую запись в
tag_counter
, индекс не подходит для этого раздела. Для временной таблицы я включил индексы в столбцы Time
и Tag_Name
, которые должны повысить скорость, поскольку они используются непосредственно в предложении GROUP BY
.
Если бы вы вели список ключей (например, в отдельной таблице, поддерживаемый триггером при вставке / обновлении / удалении до tag_counter
), этот код можно было бы сделать намного проще и эффективнее. Но это для другого вопроса.