sql >> Base de Datos >  >> RDS >> Mysql

MySQL SUM json valores agrupados por claves json

TL;RD: sí, se puede hacer sin saber los nombres de las claves de antemano, y ninguno de los formatos de datos alternativos tiene ninguna ventaja sobre el original.

Esto se puede hacer sin conocer los nombres de las claves de antemano, pero es doloroso... básicamente, debe mirar cada valor en la tabla para determinar el conjunto de claves distintas en la tabla antes de poder sumarlas. Debido a este requisito, y al hecho de que todos los formatos de datos alternativos pueden tener varias claves por entrada, no hay ninguna ventaja en utilizar ninguno de ellos.

Como tienes que buscar todas las claves distintas, es igual de fácil hacer las sumas mientras las buscas. Esta función y procedimiento juntos harán eso. La función, json_merge_sum , toma dos valores JSON y los fusiona, sumando los valores donde aparece una clave en ambos valores, por ejemplo,

SELECT json_sum_merge('{"key1": 1, "key2": 3}', '{"key3": 1, "key2": 2}')

Salida:

{"key1": 1, "key2": 5, "key3": 1}

El código de función:

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

El procedimiento, count_keys , realiza el equivalente de GROUP BY cláusula. Encuentra todos los valores distintos de col1 en la tabla y luego llama a json_sum_merge para cada fila que tiene ese valor de col1 . Tenga en cuenta que la consulta de selección de fila realiza un SELECT ... INTO una variable ficticia para que no se genere ningún resultado y utiliza un MIN() para asegurarse de que solo haya un resultado (para que pueda asignarse a una variable).

El procedimiento:

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

Para un ejemplo un poco más 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}

Tenga en cuenta que he llamado a la tabla table2 en el procedimiento, deberá editar eso (en ambas consultas) para adaptarlo.