преобразовать строку bigquery json в столбцы

Для данных json, которые поступают в виде строки, я хотел бы иметь что-то вроде JSON_EXTRACT_SCALAR, но для гибкого количества столбцов результатов.

Вот пример данных — разные строки могут иметь разные имена столбцов, а json может быть вложенным:

WITH `my_table` AS (
  SELECT '{"sku_types":"{\"id\":\"5433306\",\"product_code\":\"adfklj_ewkj\"}","additional_info":"Face 30 ml","stock_level":"20+"}' as json_string 
  union all 
  SELECT '{"additional_info":"Face 100 ml","offer_info":"30%"}' as json_string 
)
SELECT * 
from my_table;

Я хотел бы, чтобы эти данные были извлечены в отдельные столбцы: sku_types.id, sku_types.product_code, additional_info, stock_level, offer_info.

Можно ли это сделать в SQL или необходим javascript?

Я не знаю заранее названия полей json, поэтому не смог сделать это с помощью JSON_EXTRACT_SCALAR или JSON_EXTRACT.


person Giedrius Blazys    schedule 30.08.2019    source источник


Ответы (1)


Пример ниже для стандартного SQL BigQuery

#standardSQL
CREATE TEMPORARY FUNCTION parseJson(y STRING)
RETURNS ARRAY<STRING>
LANGUAGE js AS """
  var z = new Array();
  processKey(JSON.parse(y), '');
  function processKey(node, parent) {
    Object.keys(node).map(function(key) {
      value = node[key].toString();
      if (value !== '[object Object]') {
        if (parent !== '' && parent.substr(parent.length-1) !== '.') {
          z.push(parent + '.' + key + ':' + value)
        } else {
          z.push(key + ':' + value)
        }
      } else {
        if (parent !== '' && parent.substr(parent.length-1) !== '.') {parent += '.'};
        processKey(node[key], parent + key);
      };
    });         
  };
  return z
""";
WITH `my_table` AS (
  SELECT 1 id, '{"sku_types":{"id":"5433306","product_code":"adfklj_ewkj"},"additional_info":"Face 30 ml","stock_level":"20+"}' AS json_string UNION ALL 
  SELECT 2, '{"additional_info":"Face 100 ml","offer_info":"30%"}' AS json_string 
)
SELECT id, 
  ARRAY(
    SELECT AS STRUCT SPLIT(kv, ':')[OFFSET(0)] key, SPLIT(kv, ':')[SAFE_OFFSET(1)] value
    FROM UNNEST(parseJson(json_string)) kv
  ) params
FROM my_table

с результатом

Row id  params.key              params.value     
1   1   sku_types.id            5433306  
        sku_types.product_code  adfklj_ewkj  
        additional_info         Face 30 ml   
        stock_level             20+  
2   2   additional_info         Face 100 ml  
        offer_info              30%     

как вы можете видеть, вместо разбора всех возможных атрибутов в отдельные столбцы (что здесь совершенно невозможно - если вы не знаете их заранее) - вышеприведенный подход объединяет их в пары ключ: значение внутри массива параметров

Примечание. В приведенном выше примере я использую : для создания пар ключ: значение, а затем разделяю их. Если вы ожидаете, что значения имеют этот символ - вы можете изменить код и вместо : использовать что-то более уникальное, например :::::::

Быстрое обновление с учетом комментария:
... проблема в том, что некоторые значения json равны нулю, и в этом случае выдается ошибка

#standardSQL
CREATE TEMPORARY FUNCTION parseJson(y STRING)
RETURNS ARRAY<STRING>
LANGUAGE js AS """
  var z = new Array();
  processKey(JSON.parse(y), '');
  function processKey(node, parent) {
    Object.keys(node).map(function(key) {
      if (!node[key]) {
        value = 'n/a'
      } else {
        value = node[key].toString();
      }
      if (value !== '[object Object]') {
        if (parent !== '' && parent.substr(parent.length-1) !== '.') {
          z.push(parent + '.' + key + ':' + value)
        } else {
          z.push(key + ':' + value)
        }
      } else {
        if (parent !== '' && parent.substr(parent.length-1) !== '.') {parent += '.'};
        processKey(node[key], parent + key);
      };
    });         
  };
  return z
""";
WITH `my_table` AS (
  SELECT 1 id, '{"sku_types":{"id":"5433306","product_code":"adfklj_ewkj"},"additional_info":"Face 30 ml","stock_level":"20+"}' AS json_string UNION ALL 
  SELECT 2, '{"additional_info":"Face 100 ml","offer_info":"30%"}' AS json_string union all
  SELECT 3 as id , '{"offer_info":"30%", "price":null}' AS json_string  
)
SELECT id, 
  ARRAY(
    SELECT AS STRUCT SPLIT(kv, ':')[OFFSET(0)] key, SPLIT(kv, ':')[SAFE_OFFSET(1)] value
    FROM UNNEST(parseJson(json_string)) kv
  ) params
FROM my_table  

с результатом

Row id  params.key              params.value     
1   1   sku_types.id            5433306  
        sku_types.product_code  adfklj_ewkj  
        additional_info         Face 30 ml   
        stock_level             20+  
2   2   additional_info         Face 100 ml  
        offer_info              30%  
3   3   offer_info              30%  
        price                   n/a    

Как видите, я заменяю нули на 'n/a', но вы можете применить любую логику, какую захотите.

person Mikhail Berlyant    schedule 30.08.2019
comment
Это творит чудеса, спасибо! Моя единственная проблема заключается в том, что некоторые значения json равны нулю, и в этом случае он выдает ошибку: ... SELECT 3 as id , '{offer_info:30%, price:null}' AS json_string)... - person Giedrius Blazys; 30.08.2019
comment
Я проверю этот случай позже через день и обновлю свой ответ, чтобы решить нулевые вещи: o) - person Mikhail Berlyant; 30.08.2019
comment
Спасибо за вашу помощь! :) - person Giedrius Blazys; 30.08.2019
comment
конечно, как и ожидалось :о) - увидимся в ваших следующих сообщениях/вопросах! - person Mikhail Berlyant; 31.08.2019