Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Valores json do MySQL SUM agrupados por chaves json


TL;DR: sim, isso pode ser feito sem conhecer os nomes das chaves antecipadamente, e nenhum dos formatos de dados alternativos tem qualquer vantagem sobre o original.

Isso pode ser feito sem conhecer os nomes das chaves com antecedência, mas é doloroso... basicamente você tem que olhar para cada valor na tabela para determinar o conjunto de chaves distintas na tabela antes de poder somar. Devido a esse requisito e ao fato de que os formatos de dados alternativos podem ter várias chaves por entrada, não há vantagem em usar nenhum deles.

Como você precisa procurar todas as chaves distintas, é tão fácil fazer as somas enquanto as procura. Esta função e procedimento juntos farão isso. A função, json_merge_sum , pega dois valores JSON e os mescla, somando os valores em que uma chave aparece em ambos os valores, por exemplo.
SELECT json_sum_merge('{"key1": 1, "key2": 3}', '{"key3": 1, "key2": 2}')

Saída:
{"key1": 1, "key2": 5, "key3": 1}

O código da função:
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

O procedimento, count_keys , executa o equivalente do GROUP BY cláusula. Ele encontra todos os valores distintos de col1 na tabela e então chama json_sum_merge para cada linha que tenha esse valor de col1 . Observe que a consulta de seleção de linha executa um SELECT ... INTO uma variável fictícia para que nenhuma saída seja gerada e usa um MIN() para garantir que haja apenas um resultado (para que possa ser atribuído a uma variável).

O procedimento:
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 um exemplo um pouco maior:
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() produz:
col1    col2    
aaa     {"key1": 51, "key2": 3, "key3": 0}
bbb     {"key1": 9, "key2": 3, "key5": 3}
ccc     {"key2": 5, "key3": 1, "key4": 3}

Observe que chamei a tabela table2 no procedimento, você precisará editar isso (em ambas as consultas) para se adequar.