TL;DR: tak, można to zrobić bez wcześniejszej znajomości nazw kluczy, a żaden z alternatywnych formatów danych nie ma żadnej przewagi nad oryginałem.
Można to zrobić bez wcześniejszej znajomości nazw kluczy, ale jest to bolesne… w zasadzie musisz spojrzeć na każdą wartość w tabeli, aby określić zestaw odrębnych kluczy w tabeli, zanim będziesz mógł je zsumować. Ze względu na ten wymóg oraz fakt, że wszystkie alternatywne formaty danych mogą mieć wiele kluczy na wpis, korzystanie z żadnego z nich nie jest korzystne.
Ponieważ musisz szukać wszystkich odrębnych kluczy, równie łatwo jest obliczać sumy, gdy ich szukasz. Ta funkcja i procedura razem to zrobią. Funkcja json_merge_sum
, pobiera dwie wartości JSON i łączy je, sumując wartości, w których klucz występuje w obu wartościach, np.
SELECT json_sum_merge('{"key1": 1, "key2": 3}', '{"key3": 1, "key2": 2}')
Wyjście:
{"key1": 1, "key2": 5, "key3": 1}
Kod funkcji:
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(20);
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
Procedura, count_keys
, wykonuje odpowiednik GROUP BY
klauzula. Znajduje wszystkie odrębne wartości col1
w tabeli, a następnie wywołuje json_sum_merge
dla każdego wiersza, który ma wartość col1
. Zwróć uwagę, że zapytanie wybierające wiersz wykonuje SELECT ... INTO
zmienna fikcyjna, więc żadne dane wyjściowe nie są generowane, i używa MIN()
aby upewnić się, że jest tylko jeden wynik (aby można go było przypisać do zmiennej).
Procedura:
DELIMITER //
DROP PROCEDURE IF EXISTS count_keys //
CREATE PROCEDURE count_keys()
BEGIN
DECLARE finished INT DEFAULT 0;
DECLARE col1val VARCHAR(20);
DECLARE col1_cursor CURSOR FOR SELECT DISTINCT col1 FROM table2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished=1;
OPEN col1_cursor;
col1_loop: LOOP
FETCH col1_cursor INTO col1val;
IF finished=1 THEN
LEAVE col1_loop;
END IF;
SET @total = '{}';
SET @query = CONCAT("SELECT MIN(@total:=json_sum_merge(col2, @total)) INTO @json FROM table2 WHERE col1='", col1val, "'");
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT col1val AS col1, @total AS col2;
END LOOP col1_loop;
END
Dla nieco większego przykładu:
col1 col2
aaa {"key1": 1, "key2": 3}
bbb {"key1": 4, "key2": 2}
aaa {"key1": 50, "key3": 0}
ccc {"key2": 5, "key3": 1, "key4": 3}
bbb {"key1": 5, "key2": 1, "key5": 3}
CALL count_keys()
produkuje:
col1 col2
aaa {"key1": 51, "key2": 3, "key3": 0}
bbb {"key1": 9, "key2": 3, "key5": 3}
ccc {"key2": 5, "key3": 1, "key4": 3}
Uwaga Nazwałem tabelę table2
w procedurze będziesz musiał to zmienić (w obu zapytaniach), aby dopasować.