PostgreSql : Json 使用横向连接的数组到行

PostgreSql : Json Array to Rows using Lateral Join

我的 table 的详细信息字段中有两个以下 JSON 数组,需要评估我在另一个关系 table.

中使用的查询
{
    "city": "London",
    "name": "Sainburry",
    "quantities": [112, 145, 222, 122, 124],
    "prices": [4, 4, 4, 0, 3],
    "dates": ["13.05.2020", "14.05.2020", "15.05.2020", "16.05.2020", "17.05.2020"]
}

我想为此 JSON 数组评估以下查询:

select quantities,
       prices,
       AVG(quantities/prices::float) as ratio
from my_table
where city = 'London'
group by quantities, prices;

我使用了以下查询 和许多类似的查询,包括横向连接 :

select q.*
from my_table mt
  cross join json_array_elements_text(details -> 'quantities') as q

但是,当通过交叉连接将其他字段(价格和日期)添加到查询时,行数成倍增加。所以,我正在寻找一个新功能 Lateral Join 来使用,但无法正确应用。如何在 PostgreSQL 中使用 Lateral Join 获取我之前查询得到的结果?任何帮助将不胜感激。

更新:

这里是fiddle。如果我成功地将 json 数组值转换为行而不相乘(应返回 5 条记录),我就可以评估所需的结果。只需使用 lateral join 和 json_array_elements_text.

帮助我将 json 数组转换为行

这是你想要的吗?

    -- just simulate table:
with my_table(details) as(
values
('{
"city": "London",
"name": "Sainburry",
"quantities": [112, 145, 222, 122, 124],
"prices": [4, 4, 4, 0, 3],
"dates": ["13.05.2020", "14.05.2020", "15.05.2020", "16.05.2020", "17.05.2020"]
}'::json)
)


-- here is query:
select  
my_table.details->>'city',  u.quantities, u.prices  
from my_table
JOIN LATERAL UNNEST( 
    ARRAY(SELECT json_array_elements_text(details->'quantities')) ,
    ARRAY(SELECT json_array_elements_text(details->'prices')) 
) u(quantities, prices) ON TRUE
WHERE
my_table.details->>'city' = 'London'

demo

由于数组中的顺序,您似乎需要 WITH ORDINALITYLEFT JOIN LATERALs 来分别匹配数组的相应元素:

SELECT q.elm AS quantities, p.elm AS prices, 
       AVG(p.elm::float/q.elm::float) AS ratio
  FROM my_table t0
  LEFT JOIN LATERAL jsonb_array_elements(details -> 'quantities') 
    WITH ORDINALITY AS q(elm, i) ON TRUE
  LEFT JOIN LATERAL jsonb_array_elements(details -> 'prices') 
    WITH ORDINALITY AS p(elm, i) ON q.i = p.i
  LEFT JOIN LATERAL jsonb_array_elements(details -> 'dates') 
    WITH ORDINALITY AS d(elm, i) ON d.i = q.i
 WHERE t0.details ->> 'city' = 'London'   
 GROUP BY q.elm, p.elm;

Demo

select
  quantity, price,
  avg(quantity/price) as ratio
from my_table cross join lateral (
  select
    json_array_elements_text(details->'dates') as dates,
    (json_array_elements_text(details->'quantities'))::numeric as quantity,
    (json_array_elements_text(details->'prices'))::numeric as price) as data
where details->>'city' = 'London'
group by quantity, price;

基本上,。只是一些改进和一些解释:

SELECT to_date(d, 'DD.MM.YYYY') AS date -- ①
     , quantity, price
     , round(price / quantity, 4) AS ratio -- ③, ④
FROM   my_table
CROSS  JOIN LATERAL ( -- ②
   SELECT json_array_elements_text(details->'dates'     )          AS d -- ①
        , json_array_elements_text(details->'quantities')::int     AS quantity -- ③
        , json_array_elements_text(details->'prices'    )::numeric AS price -- ③
   ) AS data
WHERE  details->>'city' = 'London';

db<>fiddle here

① 日期 字符串 默认根据区域设置和会话变量进行解释。使用 to_date().

以干净的方式进行

SELECT 列表中的多个集合返回函数在 Postgres 10 之前的表现令人惊讶,如果结果行的数量不是全部相同的话。 (考虑升级。无论如何。)参见:

③ 在您的原始查询中 AVG(quantities/prices::float)group by quantities, prices 组合没有任何意义。 quantities/prices 本身也没有。我按我认为合适的方式进行了修复,并投入 round() 来格式化输出。

④ 如果数量可以为 0 防止除以 0 NULLIF:

     , round(price / NULLIF(quantity, 0), 4) AS ratio