简体   繁体   中英

postgresql: filtering and calculating over jsonb object

I have a table with JSONb column which holds data like this:

create table car_stats (
    id int primary key,
    city varchar,
    date timestamp,
    info varchar
    stats jsonb
)

stats example:

[
  {
    "brand": "AUDI",
    "status": "NEW"
  },
  {
    "brand": "BMW",
    "status": "PRODUCTION"
  },
  {
    "brand": "BMW",
    "status": "NEW"
  },
  {
    "brand": "BMW",
    "status": "NEW"
  },
  {
    "brand": "BMW",
    "status": "DELIVERED"
  }
]

And I want to count % of new / production / delivered of car's brand grouped by city and month

 CITY   MONTH BRAND NEW  PRODUCTION DELIVERED
 LONDON  3    AUDI  100%   0          0
 PARIS   2    BMW   50%    25%        25%

tried: but no idea how to calculate elements in JSON (ex. all BMW in status NEW)

with cte as (
   select stats ->> 'brand',
          stats ->> 'status',
          city, 
          date
   from car_stats
   group by city
), 
 grouped as (
   select cte.brand,
          cte.country,
          cte.date,
          ARRAY_TO_JSON(ARRAY_AGG(base)) as statistics
   from cte
   group by cte.brand, cte.city, cte.date       
 ),
 stats as (
    count % statistics somehow here.. ? 
 )

)

You can associate each each element in stats with its corresponding city, and then use sum with group by :

with recursive cte(id, c, p, i, d) as (
   select c.id, c.city, (c.stats ->> 0)::jsonb, 1, c.stats from car_stats c
   union all
   select c.id, c.c, (c.d ->> c.i)::jsonb, c.i+1, c.d from cte c where c.i < jsonb_array_length(c.d)
)
select c.c, extract(month from c1.date), c.p -> 'brand', 
   round(sum(case when (c.p -> 'status')::text = '"NEW"' then 1 else 0 end)/count(*)::decimal,2), 
   round(sum(case when (c.p -> 'status')::text = '"PRODUCTION"' then 1 else 0 end)/count(*)::decimal,2), 
   round(sum(case when (c.p -> 'status')::text = '"DELIVERED"' then 1 else 0 end)/count(*)::decimal,2)
from cte c join car_stats c1 on c.id = c1.id 
group by c.c, extract(month from c1.date), c.p -> 'brand'

See demo .

First expand brand and status into separate rows and then use count filter conditional aggregation.

with t as 
(
 select city, date_trunc('month', "date") y_month, info, brand, status
 from car_stats 
 cross join lateral
 (
  select j ->> 'brand' brand,
         j ->> 'status' status
  from jsonb_array_elements(stats) j
 ) t
)
select city, y_month, brand,
       count(*) filter (where status = 'NEW') / count(*) * 100 "NEW",
       count(*) filter (where status = 'PRODUCTION') / count(*) * 100 "PRODUCTION",
       count(*) filter (where status = 'DELIVERED') / count(*) * 100 "DELIVERED"
from t
group by city, y_month, brand;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM