【问题标题】:PostgreSql : Json Array to Rows using Lateral JoinPostgreSql:使用横向连接的 Json 数组到行
【发布时间】:2020-08-25 19:12:05
【问题描述】:

我的表的详细信息字段中有两个以下 JSON 数组,需要评估查询,因为我在另一个关系表中使用。

{
    "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 连接和 json_array_elements_text 将 json 数组转换为行。

【问题讨论】:

  • group by quantities, prices
  • 这里是fiddle。如果我成功地将 json 数组值转换为行而不进行乘法(应该返回 5 条记录),我可以评估所需的结果。只需帮助我使用 lateral 连接和 json_array_elements_text 将 json 数组转换为行。
  • 所以quantitiesprices 按索引相互评价对方?他们总是有相同数量的元素?
  • 数量、价格和日期。它们始终具有相同数量的元素,并且彼此对应。例如,对于第一个:112、4、“13.05.2020”。 ...
  • quantitiespricesdates 是否保证始终具有相同数量的数组元素?升级到当前版本的 Postgres 是一种选择吗?第 9.5 页即将发布:postgresql.org/support/versioning

标签: sql arrays json postgresql postgresql-9.5


【解决方案1】:

这是你想要的吗?

    -- 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

【讨论】:

  • 很好,结果正是我想要的。但是,1) 当我尝试通过order by my_table.details->>'quantities' asc; 订购时,它不会订购列表。 2) 我们应该使用 UNNEST 和 ARRAY 吗?非常感谢并投了赞成票。
  • 1) 使用:order by u.quantities, 2) 是的,我们可以使用它
  • 我使用u.quantities 修复了订单问题。第二个问题呢?有更短的版本吗?
【解决方案2】:

由于数组中的顺序,您似乎需要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

【讨论】:

  • 非常感谢您的帮助,投了赞成票。另一方面,是否还有更短的版本来解决这个问题,仍然使用 JOIN LATERAL 和 jsonb_array_elements?
【解决方案3】:
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;

【讨论】:

  • 正是我想要的。只是一个小问题:1) 当使用日期时,它会重复一些列,因为它应该在 group by 子句中。我认为这是正常的,因此我应该删除该字段,对吗? 2) 如果我使用 int 而不是 numeric 会怎样?在这种情况下,我还应该为比率线使用另一个演员吗?
  • @Jason 1) 在我的示例中,dates 列仅在内部子查询中使用,对于使用group by 的外部查询,这应该不是问题。 2) int/int 给你int (3/2 = 1) 所以你需要在公式中至少转换一个值:3::float/2 = 1.5;并使用float 类型可以导致一些calculation errors
  • 感谢您的回复。关于日期字段的问题是正常的,因为我在相关查询中也看到了相同的行重复。关于数字,如果我只是用浮点数替换数字怎么办?因为,在数字中,我在值的末尾看到了额外的 0,并且看到了很多 0 而不是单个 0。它是否也会导致计算错误(我不使用 int,只是将 numeric 替换为 float)?有什么帮助吗?
  • @Jason 这是另一个大讨论的主题。简而言之:对于与金钱相关的计算,始终使用 numeric 类型,可能具有特定的精度,例如 numeric(10,2)
  • 在下面的讨论中发表你的意见怎么样 :) Should I use numeric or float to avoid calculaton prolems in PostgreSQL
【解决方案4】:

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

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小提琴here

① 默认情况下,日期字符串根据区域设置和会话变量进行解释。使用to_date() 以干净的方式进行操作。

SELECT 列表中的多个 set-returning 函数在 Postgres 10 之前的行为方式令人惊讶,如果结果行数并不完全相同。 (考虑升级。无论如何。)请参阅:

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

④如果数量可以为0,用NULLIF防被0除:

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

【讨论】:

    猜你喜欢
    • 2020-11-30
    • 2021-02-21
    • 2020-05-16
    • 1970-01-01
    • 2020-01-25
    • 2020-10-01
    • 2020-08-09
    • 1970-01-01
    • 2019-04-29
    相关资源
    最近更新 更多