PostgreSql : Json Array to Rows using Lateral Join

坚强是说给别人听的谎言 提交于 2020-05-21 07:34:48

问题


I have two following JSON Array in details field of my table and need to evaluate the query as I use in another relational 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"]
}

I want to evaluate the following query for this JSON Array:

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

I used the following query and many similar queries including lateral join:

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

But, when adding the other fields (prices and dates) to the query by cross join, the rows multiplied. So, I am looking for a new feature Lateral Join to use, but not able to apply properly. How can I obtain the result I obtained previous query by using Lateral Join in PostgreSQL? Any help would be appreciated.

Update:

Here is the fiddle. I can evaluate the desired result if I succeed to convert the json array values to rows without multiplying (5 records should be returned). Just help me to convert json array to row using lateral join and json_array_elements_text.


回答1:


Seems you need WITH ORDINALITY along with LEFT JOIN LATERALs to match the corresponding elements of the arrays due to the order in the arrays, respectively :

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




回答2:


Is this what you want ?

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

See demo




回答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;



回答4:


Basically, what Abelisto said. Just a couple of improvements and some explanation:

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

① Date strings are interpreted depending on locale settings and session variables by default. Do it the clean way with to_date().

② Multiple set-returning functions in the SELECT list behave in surprising ways up until Postgres 10 if the number of resulting rows is not exactly the same for all. (Consider upgrading. In any case.) See:

  • What is the expected behaviour for multiple set-returning functions in SELECT clause?

③ In your original query AVG(quantities/prices::float) makes no sense in combination with group by quantities, prices. Neither does quantities/prices on its own. I fixed as I saw fit, and threw in round() to format output.

④ If quantity can be 0 defend against division by 0 with NULLIF:

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


来源:https://stackoverflow.com/questions/61709474/postgresql-json-array-to-rows-using-lateral-join

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!