TL;DR: sì, può essere fatto senza conoscere in anticipo i nomi delle chiavi e nessuno dei formati di dati alternativi ha alcun vantaggio rispetto all'originale.
Questo può essere fatto senza conoscere in anticipo i nomi delle chiavi, ma è doloroso... in pratica devi guardare ogni valore nella tabella per determinare l'insieme di chiavi distinte nella tabella prima di poterle sommare. A causa di questo requisito e del fatto che i formati di dati alternativi possono avere tutti più chiavi per voce, non vi è alcun vantaggio nell'usarne nessuno.
Dal momento che devi cercare tutte le chiavi distinte, è facile fare le somme mentre le stai cercando. Questa funzione e questa procedura insieme lo faranno. La funzione, json_merge_sum
, prende due valori JSON e li unisce, sommando i valori in cui compare una chiave in entrambi i valori, ad es.
SELECT json_sum_merge('{"key1": 1, "key2": 3}', '{"key3": 1, "key2": 2}')
Uscita:
{"key1": 1, "key2": 5, "key3": 1}
Il codice funzione:
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
La procedura, count_keys
, esegue l'equivalente di GROUP BY
clausola. Trova tutti i valori distinti di col1
nella tabella e quindi chiama json_sum_merge
per ogni riga che ha quel valore di col1
. Nota che la query di selezione riga esegue un SELECT ... INTO
una variabile fittizia in modo che non venga generato alcun output e utilizza un MIN()
per assicurarsi che vi sia un solo risultato (in modo che possa essere assegnato a una variabile).
La 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
Per un esempio leggermente più grande:
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()
produce:
col1 col2
aaa {"key1": 51, "key2": 3, "key3": 0}
bbb {"key1": 9, "key2": 3, "key5": 3}
ccc {"key2": 5, "key3": 1, "key4": 3}
Nota che ho chiamato la tabella table2
nella procedura, dovrai modificarlo (in entrambe le query) per adattarlo.