Mysql
 sql >> Database >  >> RDS >> Mysql

Valori json MySQL SUM raggruppati per chiavi json

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.