How can I achieve mongo "unwind" in postgres JSONB? (Flatten nested arrays) - mongodb

I recently looked into migrating our product database from mongo to postgres. Coming from mongoDb, I am used of "unwinding" objects and arrays.
Suppose you have the following object:
{
"styleGroupId": "2",
"brand": "MOP",
"colorVariants": [
{
"color": "red",
"colorCode": "222",
"sizeVariants": [
{"gtin": "444",
"size": "M"},
{"gtin": "555",
"size": "L"}
]
},
{
"color": "blue",
"colorCode": "111",
"sizeVariants": [
{"gtin": "66",
"size": "M"},
{"gtin": "77",
"size": "L"}
]
}
]
}
If you want to flatten it, in mongo you use the following:
db.test.aggregate([
{
$unwind: "$colorVariants"
},
{
$unwind: "$colorVariants.sizeVariants"
}
])
which will result in objects like this:
{
"_id" : ObjectId("5a7dc59dafc86d25964b873c"),
"styleGroupId" : "2",
"brand" : "MOP",
"colorVariants" : {
"color" : "red",
"colorCode" : "222",
"sizeVariants" : {
"gtin" : "444",
"size" : "M"
}
}
}
I have spent hours searching for "mongo unwind in postgres" but could hardly find a satisfying answer. Also a lot of the resources on querying JSONB data in postgres barely touch nested arrays. Hopefully this post will help other poor souls searching for a migration from mongoDb to postgres.

The function:
create or replace function jsonb_unwind(target jsonb, path text[])
returns jsonb language plpgsql as $$
begin
if jsonb_typeof(target #> path) = 'array' then
return jsonb_set(target, path, target #> path -> 0);
else
return target;
end if;
end $$;
Example usage:
select
jsonb_unwind(
jsonb_unwind(json_data, '{colorVariants}'),
'{colorVariants, sizeVariants}')
from my_table;
Test it in rextester.

The answer could implicitly found in this post: How to query nested arrays in a postgres json column?
edit
paired with this answer use - to remove a key from a JSONB
there is an easier way to get sth closer to mongo unwind starting from 9.6
You need to state each array level in the FROM clause of your query
exclude each nested object from your output so it doesn't appear multiple times
optional concatenate object and subobject
when concatenating objects with shared keys, only the key of the object right of the || operator will be preserved
-
SELECT
data::jsonb - 'colorVariants' ||
colorVariants.* - 'sizeVariants' ||
sizeVariants.*
FROM
test,
jsonb_array_elements(data -> 'colorVariants') colorVariants,
jsonb_array_elements(colorVariants -> 'sizeVariants') sizeVariants;
the result then is
?column?
-------------------------------------------------------------------------------------------------------
{"gtin": "11", "size": "M", "brand": "MOP", "color": "red", "colorCode": "222", "styleGroupId": "1"}
{"gtin": "22", "size": "L", "brand": "MOP", "color": "red", "colorCode": "222", "styleGroupId": "1"}
{"gtin": "33", "size": "M", "brand": "MOP", "color": "blue", "colorCode": "111", "styleGroupId": "1"}
{"gtin": "44", "size": "L", "brand": "MOP", "color": "blue", "colorCode": "111", "styleGroupId": "1"}
{"gtin": "444", "size": "M", "brand": "MOP", "color": "red", "colorCode": "222", "styleGroupId": "2"}
{"gtin": "555", "size": "L", "brand": "MOP", "color": "red", "colorCode": "222", "styleGroupId": "2"}
{"gtin": "66", "size": "M", "brand": "MOP", "color": "blue", "colorCode": "111", "styleGroupId": "2"}
{"gtin": "77", "size": "L", "brand": "MOP", "color": "blue", "colorCode": "111", "styleGroupId": "2"}
old post
You have to state each array level in the FROM clause of your query
You have to specifically list the attributes of each level. If you only state e.g. colorVariants the nested arrays will also be returned
So in the specific case the solution would be:
SELECT
data->'brand' as brand,
data->'styleGroupId' as styleGroupId,
colorVariants->'color' as color,
colorVariants->'colorCode' as colorCode,
sizeVariants->'gtin' as GTIN,
sizeVariants->'size' as size
FROM
test,
jsonb_array_elements(data->'colorVariants') colorVariants,
jsonb_array_elements(colorVariants->'sizeVariants') sizeVariants
which will result in
brand | stylegroupid | color | colorcode | gtin | size
-------+--------------+--------+-----------+-------+------
"MOP" | "1" | "red" | "222" | "11" | "M"
"MOP" | "1" | "red" | "222" | "22" | "L"
"MOP" | "1" | "blue" | "111" | "33" | "M"
"MOP" | "1" | "blue" | "111" | "44" | "L"
"MOP" | "2" | "red" | "222" | "444" | "M"
"MOP" | "2" | "red" | "222" | "555" | "L"
"MOP" | "2" | "blue" | "111" | "66" | "M"
"MOP" | "2" | "blue" | "111" | "77" | "L"

Very simple example.
Suppose you have the following data in userGroups table
id | userId | groups
1 | 21 | [{"name": "group_1" }, { "name": "group_2" }]
if you query for attributes with json_array_elements function for groups attribute
select id, userId, json_array_elements(groups) from userGroups;
you will get results similar to mongodb unwind operator results
id | userId | groups
1 | 21 | {"name": "group_1" }
1 | 21 | {"name": "group_2" }
Hope it helps someone!

Related

MongoDB distinct returns empty

I can't get collection.distinct to work for me on db.version '4.4.3'. I'm following the example at https://docs.mongodb.com/manual/reference/command/distinct/
{ "_id": 1, "dept": "A", "item": { "sku": "111", "color": "red" }, "sizes": [ "S", "M" ] }
{ "_id": 2, "dept": "A", "item": { "sku": "111", "color": "blue" }, "sizes": [ "M", "L" ] }
{ "_id": 3, "dept": "B", "item": { "sku": "222", "color": "blue" }, "sizes": "S" }
{ "_id": 4, "dept": "A", "item": { "sku": "333", "color": "black" }, "sizes": [ "S" ] }
Then run
db.runCommand ( { distinct: "inventory", key: "dept" } )
I get
{ values: [], ok: 1 }
This doesn't work either:
db.inventory.distinct( "dept" )
[]
I can't get it to work on a local server or a remote server. What gives?

Make a calculated field in mongo db

I have two collections one is Employee and the other one is a salary. I want to make a calculated field in employee collection call monthly salary. How can I access a SALARY from Salary collection and divided by 12?
Here are some info I included in MongoDB:
db.Salary.insertMany([
{
"POSITION": "1",
"BRANCH_SIZE": "HQ",
"SALARY": "150000"
},
{
"POSITION": "2",
"BRANCH_SIZE": "HQ",
"SALARY": "100000"
},
{
"POSITION": "3",
"BRANCH_SIZE": "HQ",
"SALARY": "70000"
},
{
"POSITION": "4",
"BRANCH_SIZE": "HQ",
"SALARY": "30000"
},
{
"POSITION": "5",
"BRANCH_SIZE": "HQ",
"SALARY": "56000"
}
])
db.Employee.insertMany([
{
"EMPLOYEE_NO": "1000",
"LNAME": "Wyatt",
"FNAME": " Stefan"
"CITY": "MANKATO",
"STATE": "MN",
"ZIP": "56001",
"STATUS": "1",
"POSITION": "1"
},
{
"EMPLOYEE_NO": "1029",
"LNAME": "Martin",
"FNAME": "Edward",
"STATUS": "1",
"START_DATE": "02-MAY-95",
"END_DATE": "",
"BRANCH_NO": "103",
"BRANCH_SIZE": "MD",
"POSITION": "3"
},
{
"EMPLOYEE_NO": "1089",
"LNAME": "Stewart",
"FNAME": "Macy",
"CITY": "SAINT PAUL",
"BRANCH_NO": "101",
"BRANCH_SIZE": "BG",
"POSITION": "4"
}
])
I want to make a monthly salary for each employee. How can I do that?
Thanks in advance.
Any help would be greatly appreciated.
You can use $lookup and $divide operator to grab salary from salary collection, and divide one of the fields by 12.
Example:
db.employee.aggregate([{
$lookup: {
from: 'salary',
localField: 'POSITION',
foreignField: 'POSITION',
as: 'salary',
},
},
{
{
$project: {
dividedSalary: {
$divide: ["$salary.SALARY", 12]
}
}
}
}
])

MongoDB - most efficient way to query and project matching nested array object

My doc structure in "test" collection looks like below:
{
"_id": "a1",
"t": [
{
"tId": "t1",
"do-not-project": "blah",
"b": [
{
"bId": "b1",
"name": "abc",
"do-not-project": "blah"
},
{
"bId": "b2",
"name": "def",
"do-not-project": "blah"
}
]
},
{
"tId": "t2",
"do-not-project": "blah",
"b": [
{
"bId": "b3",
"name": "ghi",
"do-not-project": "blah"
},
{
"bId": "b4",
"name": "jkl",
"do-not-project": "blah"
}
]
}
]
}
What's the most efficient way to query and project the below output if I know values for _id ("a1"), tId ("t2") and bId ("b3")? Aka only the matching nested array object and projected fields? Is this possible without aggregation at all?
{
"_id": "a1",
"t": [
{
"tId": "t2",
"b": [
{
"bId": "b3",
"name": "ghi"
}
]
}
]
}
According to MongoDB documentation, the find method has the following signature:
db.collection.find($query, $project)
So what you are looking for is probably something like:
db.collection.find(
{_id: "a1", "t"."tId": "t2", "t"."b"."bId": "b3" },
{"t"."tId": 1, "t"."b"."bId": 1, "t"."b"."name" :1}
)

Postgres JSONB datatype - How to extract data from JSON (of type JsonB) field of postgres database?

Hello Friends,
I need a help to solve the following issue,
I have set of record into my postgres db table, where table has JSONB type field.
JSONB type column contains following JSON,
Record#1 :-
{
"key1": "value1",
"key2": "value2",
"audience": [
{
"name": "Person1",
"email": "test1#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
},
{
"name": "Person2",
"email": "test2#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
}
]
}
Record#2:-
{
"key1": "value1",
"key2": "value2",
"audience": [
{
"name": "Person3",
"email": "test3#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
},
{
"name": "Person4",
"email": "test4#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
}
]
}
Expected Result (Get All Audience) :-
[
{
"name": "Person1",
"email": "test1#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
},
{
"name": "Person2",
"email": "test2#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
},
{
"name": "Person3",
"email": "test3#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
},
{
"name": "Person4",
"email": "test4#mail.com",
"country": "UK",
"primaryNumber": "+1234567890",
"secondaryNumber": "+1234567890"
}
]
Can Anyone help me to design a query either native query or through spring-data-jpa ?
I appreciate really if anyone who can help me to carry out from this situation!
You should extract 'audience' array elements of each row with jsonb_array_elements() and aggregate them to a single json object with jsonb_agg():
select jsonb_agg(value)
from my_table
cross join jsonb_array_elements(json_data->'audience')
Working example in rextester.

Why is Robomongo returning an object from db.collection.distinct() instead of an array?

Using the example from the MongoDB reference, I'd expect db.inventory.distinct("dept"); to return an array ["A", "B"], and that's exactly what happens when I run this from the shell. Using Robomongo (on OS X) I instead get an object with name-value pairs, like so: { "0" :"A", "1": "B" }.
This is the setup:
db.inventory.drop();
db.inventory.insert([
{ "_id": 1, "dept": "A", "item": { "sku": "111", "color": "red" },
{ "_id": 2, "dept": "A", "item": { "sku": "111", "color": "blue" },
{ "_id": 3, "dept": "B", "item": { "sku": "222", "color": "blue" },
{ "_id": 4, "dept": "A", "item": { "sku": "333", "color": "black" }
]);
Why is Robomongo behaving different? Can I do anything about it?