Map JSON to columns and rows in PostgreSQL - json

I'm trying to map JSON data to columns. Everything I need is contained in data array. Example:
{"data":
[
{"stamp":1348249585,"date":"2012-09-21 17:46","blur":"blurs/1.jpg","img":["imgs/1.jpg",[1600,1200]],"thumb":["thumbs/1.jpg",[150,113]]},
{"stamp":1375607177,"date":"2013-08-04 09:06","blur":"blurs/2.jpg","img":["imgs/2.jpg",[1600,1200]],"thumb":["thumbs/2.jpg",[150,113]]},
{"stamp":1376242046,"date":"2013-08-11 17:27","blur":"blurs/3.jpg","img":["imgs/3.jpg",[1600,1200]],"thumb":["thumbs/3.jpg",[150,113]]},
...
Currently, I am using #>> operator with dynamically generated condition:
1) Calculate number of elements in data array
2) Create varchar array condition to match every "row"
3) Process elements on individual rows.
My solution is:
select
json_row,
json_row#>>'{stamp}' as stamp,
json_row#>>'{date}' as date,
json_row#>>'{img,0}' as img,
json_row#>>'{img,1,0}' as img_width,
json_row#>>'{img,1,1}' as img_height,
json_row#>>'{thumb,0}' as thumb,
json_row#>>'{thumb,1,0}' as thumb_width,
json_row#>>'{thumb,1,1}' as thumb_height,
json_row#>>'{thumb,2,0}' as th_x1,
json_row#>>'{thumb,2,1}' as th_y1,
json_row#>>'{thumb,3,0}' as th_x2,
json_row#>>'{thumb,3,1}' as th_y2,
json_row#>>'{blur}'
from
(
select
(gjson#>>c.cond)::json json_row
from
gallery_json
cross join (
select ('{data,'|| generate_series(0,
(select json_array_length((gjson#>>'{data}')::json) from gallery_json) - 1) || '}')::varchar[] cond) c
) rd
This works and I can live with it. But, given that this is my first exercise with JSON in PostgreSQL I would like to ask if there is better way to map similar JSON structure to rows. I think that I am supposed to use json_populate_recordset, but did not succeed so far.
SQLFiddle does not work currently, sample data:
--drop table if exists gallery_json;
create table gallery_json(gjson json);
insert into gallery_json (gjson)
select '{"data":[
{"stamp":1348249585,"date":"2012-09-21 17:46","blur":"blurs/1.jpg","img":["imgs/1.jpg",[1600,1200]],"thumb":["thumbs/1.jpg",[150,113]]},
{"stamp":1376659268,"date":"2013-08-16 13:21","blur":"blurs/7.jpg","img":["imgs/7.jpg",[1600,539]],"thumb":["thumbs/7.jpg",[267,112],[332,112],[32,0]]},
{"stamp":1376666907,"date":"2013-08-16 15:28","blur":"blurs/8.jpg","img":["imgs/8.jpg",[1600,1200]],"thumb":["thumbs/8.jpg",[150,113]]},
{"stamp":1379016669,"date":"2013-09-12 20:11","blur":"blurs/11.jpg","img":["imgs/11.jpg",[1600,590]],"thumb":["thumbs/11.jpg",[267,112],[304,112],[18,0]]},
{"stamp":1383304027,"date":"2013-11-01 11:07","blur":"blurs/17.jpg","img":["imgs/17.jpg",[1600,1200]],"thumb":["thumbs/17.jpg",[150,113]]}]
,"blur":[600,336],"thumb":{"min":[150,112],"max":[267,200]}}'::json

SQL Fiddle
with data as (
select json_array_elements(gjson -> 'data') as data
from gallery_json
)
select
(data -> 'stamp')::text::bigint as stamp,
(data -> 'date')::text::timestamp as date,
(data -> 'blur')::text as blur,
(data -> 'img' -> 0)::text as img,
(data -> 'img' -> 1 -> 0)::text::int as img_width,
(data -> 'img' -> 1 -> 1)::text::int as img_height,
(data -> 'thumb' -> 0)::text as thumb,
(data -> 'thumb' -> 1 -> 0)::text::int as thumb_width,
(data -> 'thumb' -> 1 -> 1)::text::int as thumb_height,
(data -> 'thumb' -> 2 -> 0)::text::int as th_x1,
(data -> 'thumb' -> 2 -> 1)::text::int as th_y1,
(data -> 'thumb' -> 3 -> 0)::text::int as th_x2,
(data -> 'thumb' -> 3 -> 1)::text::int as th_y2
from data

Related

Postgresql - How to query nested array elements

I have JSON array data in PostgreSQL 13 table. I want to query this table to see all the nested array data in the output. I tried the below query, but it's not giving the expected output.
select
json_data::json -> 'Rows' -> 0 -> 'Values' ->> 0 as Lid
,json_data::json -> 'Rows' -> 0 -> 'Values' ->> 1 as L2LicenseId
,json_data::json -> 'Rows' -> 1 -> 'Values' ->> 0 as Lid
,json_data::json -> 'Rows' -> 1 -> 'Values' ->> 1 as L2LicenseId
from test;
Can someone please help me?
Sample Data
CREATE TABLE IF NOT EXISTS test
(
json_data text
);
INSERT INTO test (json_data) VALUES ('{"Origin":"api","Topic":"licenses","Timestamp":"2023-02-07T12:46:42.2568898+00:00","Columns":["LId","L2LicenseId","SfdcAccountId","SfdcLineItemId","SL","Quantity","StartDate","EndDate","DisplayName","ProductPrimaryKey"],"Schema":["string","string","string","string","string","int32","datetime","datetime","string","string"],"Rows":[{"Values":["1234","123456","ACC_","PurchaseT","SKU-0000","1","2023-01-09T00:00:00.0000000","2024-01-08T00:00:00.0000000","Automation with 5 users","lc11dev.my-dev.com"]},{"Values":["8967","8967-e567","fihikelo","Addon_00000490_2nd_GB","SKU-0490","3","2023-01-01T00:00:00.0000000","2023-01-22T00:00:00.0000000","Automation, Data 5GB","mygreattest01311433.my-dev.com"]}]}')
Expected Output
DB FIDDLE
You can do it using PostgreSQL function jsonb_array_elements (or json_array_elements). This function extracts all Json array elements like as rows view.
select
a2.value -> 'Values' ->> 0 as Lid,
a2.value -> 'Values' ->> 1 as L2LicenseId
from test a1
cross join jsonb_array_elements(a1.json_data::jsonb->'Rows') a2
-- Result:
lid | l2licenseid |
--- -+-------------+
1234 | 123456 |
8967 | 8967-e567 |

Postgres select value by key from json in a list

Given the following:
create table test (
id int,
status text
);
insert into test values
(1,'[]'),
(2,'[{"A":"d","B":"c"}]'),
(3,'[{"A":"g","B":"f"}]');
Is it possible to return?
id A B
1 null null
2 d c
3 g f
I am attempting something like this:
select id,
status::json ->> 0 #> "A" from test
Try this to address your specific example :
SELECT id, (status :: json)#>>'{0,A}' AS A, (status :: json)#>>'{0,B}' AS B
FROM test
see the result
see the manual :
jsonb #>> text[] → text
Extracts JSON sub-object at the specified path as text.
'{"a": {"b": ["foo","bar"]}}'::json #>> '{a,b,1}' → bar
This does it:
SELECT id,
(status::json->0)->"A" as A,
(status::json->0)->"B" as B
FROM test;

How do I declare an 'operator' that takes 2 arguments and uses letters rather than symbols?

I want to be able to make operators such as found in VB(.net) like shown below
Console.WriteLine ( 16 Mod 2 )
produces an output shown below
0
As you can see, this makes code slightly easier to read. How would I go about making functions that do this?
I have tried the following format
equal :: Integer -> Integer -> bool
x `equal` y = x == y
and I get the following error
*Main> 5 equal 5
<interactive>:1:1: error:
* Non type-variable argument
in the constraint: Num ((Integer -> Integer -> Bool) -> t -> t1)
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall t t1.
(Num ((Integer -> Integer -> Bool) -> t -> t1), Num t) =>
t1
*Main>
What is going wrong and how do I do it correctly?
You need to use backticks around equal when you call it, exactly as you did to define it.
5 `equal` 5
The way you wrote it, Haskell thinks you're trying to pass equal as an argument to 5, rather than the other way around.

Writing a function from definitions?

First off, I would like to make you all aware that I'm very new to Haskell, so to increase knowledge etc, I've been trying out questions and I'm pretty stuck on one. I think I'm nearly there but some more experienced advice would be appreciated. Here's the question:
A sporting team is represented by it's name and the amount of points they scored in their last games like so ("Newcastle",[3,3,3,0]). This data is modelled by the type definitions:
type TName = String
type Points = [Int]
type Team = (TName,Points)
From this I have to define the following function that orders one team higher than another if their points sum is greater:
sortPoints :: [Team] -> [Team]
This is what I've tried:
sortPoints :: [Team] -> [Team]
sortPoints [_,()] -> []
sortPoints [_,(x:xs)] = sum[x|x<-xs]
Once I get to here, I'm not too sure how to go about adding the conditions to check the points sum, any pointers would be greatly appreciated as I'm still coming to terms with a lot of Haskell features.
Note: This post is written in literate Haskell. You can save it as Team.lhs and try it. That being said, it's basically a longer version of Carsten's comment. If you still try to figure things out, use hoogle and look for the functions, although it's fine if you manage things with sortBy solely first.
First of all, we're going to work on lists, so you want to import Data.List.
> module Team where
> import Data.List
It contains a function called sortBy:
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
The first argument of sortBy should be a function that compares two elements of your list and returns
LT if the first is less than the second,
EQ if the both are equal,
GT if the first is greater than the second.
So we need something that that takes two teams and returns their ordering:
> -- Repeating your types for completeness
> type TName = String
> type Points = [Int]
> type Team = (TName, Points)
>
> compareTeams :: Team -> Team -> Ordering
Now, you want to compare teams based on their sum of their points. You don't need their name, so you can capture just the second part of the pair:
> compareTeams (_, s1) (_, s2) =
We need the sum of the points, so we define sum1 and sum2 to be the respective sums of the teams:
> let sum1 = sum s1
> sum2 = sum s2
Now we can compare those sums:
in if sum1 < sum2
then LT
else if sum1 == sum2
then EQ
else GT
However, that's rather verbose, and there's already a function that has type Ord a => a -> a -> Ordering. It's called compare and part of the Prelude:
> in sum1 `compare` sum2
That's a lot more concise. Now we can define sortTeams easily:
> sortTeams :: [Team] -> [Team]
> sortTeams = sortBy compareTeams
And that's it, we're done!
Fine, I lied, we're not 100% done. The module Data.Ord contains a function called comparing that's rather handy:
comparing :: Ord b => (a -> b) -> a -> a -> Ordering
comparing f x y = f x `compare` f y -- or similar
Together with snd and sum you can define sortTeams in a single line:
sortTeams = sortBy (comparing $ sum . snd)
The alternative on mentioned by Carsten is on from Data.Function:
sortTeams = sortBy (compare `on` sum . snd)

Declaration of this haskell method

Im trying to understand Haskell and I have a question: What is the type of this function and how do you call it.
two f(a,b) = f a b
If we take, for example, arguments of type Int, then the type of two is like this:
two :: (Int -> Int -> Int) -> (Int, Int) -> Int
two f (a,b) = f a b
example:
two (*) (3,4)
12
Explanation:
You are taking a function that takes 2 arguments (Int -> Int -> Int)and a tuple (Int, Int) and applying that function to a and b.
The actual type, when not constrained, is actually like this:
:t two
two :: (t1 -> t2 -> t) -> (t1, t2) -> t
So for example other things are possible:
two (++) ("he","llo")
"hello"
(etc etc.)