Related
can anybody tell me query to remove entire json object from array in jsonb type column by given id stored in json object.
example : I have data column in t1 table. Type of data column is jsonb.
Record in data column is as follows
"type" : "users",
"info" : [
{
"id":1,
"name" : "user1"
},
{
"id":2,
"name" : "user2"
}
]
}
Now I want to delete entire json object which has id = 1 ( I want to identify the object by json object id)
The expected result is
{
"type" : "users",
"info" : [
{
"id":2,
"name" : "user2"
}
]
}
Please help me out with queries.
Thanks in advance 🙂
You will need to use a subquery for each row on jsonb_array_elements that are then aggregated back to an array:
UPDATE t1
SET data = jsonb_set(data, '{info}', (
SELECT COALESCE(jsonb_agg(element), '[]'::jsonb)
FROM jsonb_array_elements(data -> 'info') element
WHERE element ->> 'id' <> '1'
))
##Here is a simple query to solve above problem ##
update t1 set data = jsonb_set(data,'{info}',jsonb_path_query_array(data,'$.info[*]?(#."id" != 1)'));
tl;dr -- is there some way to get values as a jsonb_array from a jsonb object in postgres?
Trying to use recursive cte in postgres to flatten an arbitrarily deep tree structure like this:
{
"type": "foo",
"properties": {...},
"children": [
"type": "bar",
"properties": {...},
"children": [
"type": "multivariate",
"variants": {
"arbitrary-name": {
properties: {...},
children: [...],
},
"some-other-name": {
properties: {...},
children: [...],
},
"another": {
properties: {...},
children: [...],
}
}
]
]
}
Generally following this post, however I'm stuck at processing the type: "multivariate" node where what I really want is essentially jsonb_agg(jsonb_object_values(json_object -> 'variants'))
Update:
Sorry, I obviously should have included the query I tried:
WITH RECURSIVE tree_nodes (id, json_element) AS (
-- non recursive term
SELECT
id, node
FROM trees
UNION
-- recursive term
SELECT
id,
CASE
WHEN jsonb_typeof(json_element) = 'array'
THEN jsonb_array_elements(json_element)
WHEN jsonb_exists(json_element, 'children')
THEN jsonb_array_elements(json_element -> 'children')
WHEN jsonb_exists(json_element, 'variants')
THEN (select jsonb_agg(element.value) from jsonb_each(json_element -> 'variants') as element)
END AS json_element
FROM
tree_nodes
WHERE
jsonb_typeof(json_element) = 'array' OR jsonb_typeof(json_element) = 'object'
)
select * from tree_nodes;
The schema is just an id & a jsonb node column
This query gives an error:
ERROR: set-returning functions are not allowed in CASE
LINE 16: THEN jsonb_array_elements(json_element -> 'children')
^
HINT: You might be able to move the set-returning function into a LATERAL FROM item.
I just want Object.values(json_element -> 'variants') 😫
Update 2:
After reading this all again, I realized this is a problem due to me using a recent version of PostgreSQL (10.3), which apparently no longer allows returning a set from a CASE statement, which was kind of the crux of getting this tree-flattening approach to work afaict. There's probably some way to achieve the same thing in recent versions of PostgreSQL but I have no idea how I'd go about doing it.
Use jsonb_each() in the FROM clause together with jsonb_agg(<jsonb_each_alias>.value) in the SELECT, for example:
select
id,
jsonb_agg(child.value)
from
(values
(101, '{"child":{"a":1,"b":2}}'::jsonb),
(102, '{"child":{"c":3,"d":4,"e":5}}'::jsonb
)) as t(id, json_object), -- example table, replace values block with actual tablespec
jsonb_each(t.json_object->'child') as child
group by t.id
You can always chain other jsonb functions which return setof jsonb (e.g. jsonb_array_elements) in the FROM if you need to iterate the higher level arrays before the jsonb_each; for example:
select
id,
jsonb_agg(sets.value)
from
(values
(101, '{"children":[{"a_key":{"a":1}},{"a_key":{"b":2}}]}'::jsonb),
(102, '{"children":[{"a_key":{"c":3,"d":4,"e":5}},{"a_key":{"f":6}}]}'::jsonb
)) as t(id, json_object), -- example table, replace values block with actual tablespec
jsonb_array_elements(t.json_object->'children') elem,
jsonb_each(elem->'a_key') as sets
group by t.id;
Update Answer
In answer to your comment and question edit about needing to walk the 'children' of each tree node and extract the 'variants'; I would achieve this by splitting the CTE into multiple stages:
with recursive
-- Constant table for demonstration purposes only; remove this and replace below references to "objects" with table name
objects(id, object) as (values
(101, '{"children":[{"children":[{"variants":{"aa":11}},{"variants":{"ab":12}}],"variants":{"a":1}},{"variants":{"b":2}}]}'::jsonb),
(102, '{"children":[{"children":[{"variants":{"cc":33,"cd":34,"ce":35}},{"variants":{"f":36}}],"variants":{"c":3,"d":4,"e":5}},{"variants":{"f":6}}]}'::jsonb)
),
tree_nodes as ( -- Flatten the tree by walking all 'children' and creating a separate record for each root
-- non-recursive term: get root element
select
o.id, o.object as value
from
objects o
union all
-- recursive term - get JSON object node for each child
select
n.id,
e.value
from
tree_nodes n,
jsonb_array_elements(n.value->'children') e
where
jsonb_typeof(n.value->'children') = 'array'
),
variants as (
select
n.id,
v.value
from
tree_nodes n,
jsonb_each(n.value->'variants') v -- expand variants
where
jsonb_typeof(n.value->'variants') = 'object'
)
select
id,
jsonb_agg(value)
from
variants
group by
id
;
This ability of breaking a query up into a "pipeline" of operations is one of my favourite things about CTEs - it makes the query much easier to understand, maintain and debug.
db<>fiddle
Expanded the test data with more children elements and deeper structure (more nested elements):
{
"type": "foo",
"children": [
{
"type" : "bar1",
"children" : [{
"type" : "blubb",
"children" : [{
"type" : "multivariate",
"variants" : {
"blubb_variant1": {
"properties" : {
"blubb_v1_a" : 100
},
"children" : ["z", "y"]
},
"blubb_variant2": {
"properties" : {
"blubb_v2_a" : 300,
"blubb_v2_b" : 4200
},
"children" : []
}
}
}]
}]
},
{
"type" : "bar2",
"children" : [{
"type" : "multivariate",
"variants" : {
"multivariate_variant1": {
"properties" : {
"multivariate_v1_a" : 1,
"multivariate_v1_b" : 2
},
"children" : [1,2,3]
},
"multivariate_variant2": {
"properties" : {
"multivariate_v2_a" : 3,
"multivariate_v2_b" : 42,
"multivariate_v2_d" : "fgh"
},
"children" : [4,5,6]
},
"multivariate_variant3": {
"properties" : {
"multivariate_v3_a" : "abc",
"multivariate_v3_b" : "def"
},
"children" : [7,8,9]
}
}
},
{
"type" : "blah",
"variants" : {
"blah_variant1": {
"properties" : {
"blah_v1_a" : 1,
"blah_v1_b" : 2
},
"children" : [{
"type" : "blah_sub1",
"variants" : {
"blah_sub1_variant1" : {
"properties" : {
"blah_s1_v1_a" : 12345,
"children" : ["a",1, "bn"]
}
}
}
}]
},
"blah_variant2": {
"properties" : {
"blah_v2_a" : 3,
"blah_v2_b" : 42,
"blah_v2_c" : "fgh"
},
"children" : [4,5,6]
}
}
}]
}
]
}
Result:
variants json
----------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
"multivariate_variant1" {"children": [1, 2, 3], "properties": {"multivariate_v1_a": 1, "multivariate_v1_b": 2}}
"multivariate_variant2" {"children": [4, 5, 6], "properties": {"multivariate_v2_a": 3, "multivariate_v2_b": 42, "multivariate_v2_d": "fgh"}}
"multivariate_variant3" {"children": [7, 8, 9], "properties": {"multivariate_v3_a": "abc", "multivariate_v3_b": "def"}}
"blah_variant1" {"children": [{"type": "blah_sub1", "variants": {"blah_sub1_variant1": {"properties": {"children": ["a", 1, "bn"], "blah_s1_v1_a": 12345}}}}], "properties": {"blah_v1_a": 1, "blah_v1_b": 2}}
"blah_variant2" {"children": [4, 5, 6], "properties": {"blah_v2_a": 3, "blah_v2_b": 42, "blah_v2_c": "fgh"}}
"blubb_variant1" {"children": ["z", "y"], "properties": {"blubb_v1_a": 100}}
"blubb_variant2" {"children": [], "properties": {"blubb_v2_a": 300, "blubb_v2_b": 4200}}
"blah_sub1_variant1" {"properties": {"children": ["a", 1, "bn"], "blah_s1_v1_a": 12345}}
The Query:
WITH RECURSIVE json_cte(variants, json) AS (
SELECT NULL::jsonb, json FROM (
SELECT '{/*FOR TEST DATA SEE ABOVE*/}'::jsonb as json
)s
UNION
SELECT
row_to_json(v)::jsonb -> 'key', -- D
CASE WHEN v IS NOT NULL THEN row_to_json(v)::jsonb -> 'value' ELSE c END -- C
FROM json_cte
LEFT JOIN LATERAL jsonb_array_elements(json -> 'children') as c ON TRUE -- A
LEFT JOIN LATERAL jsonb_each(json -> 'variants') as v ON TRUE -- B
)
SELECT * FROM json_cte WHERE variants IS NOT NULL
The WITH RECURSIVE structure checks elements in a recursive ways. The first UNION part is the starting point. The second part is the recursive part where the last calculation is taken for the next step.
A: if in the current JSON a children element exists all elements will be expanded into one row per child
B: if the current JSON has an element variants all elements will be expanded into a record. Note that in the example one JSON element can either contain a variants or a children element.
C: if there is a variants element then the expanded record will be converted back into a json. The resulting structure is {"key" : "name_of_variant", "value" : "json_of_variant"}. The values will be the JSON for the next recursion (the JSON of the variants can have own children elements. That's why it works). Otherwise the expanded children elements will be the next data
D: if there is a variants element then the key is printed
I am using MongoDB library https://github.com/jenssegers/laravel-mongodb version 3.1.0-alpha in Laravel 5.3.28 I have two collections in MongoDB and I want to make a hasMany relation b/w them. Means each Employee performs many tasks. I have used reference and added employee_ids in the task collection.
Below are my code:
MongoDB:
1st Collection: Employee
{
"_id" : ObjectId("586ca8c71a72cb07a681566d"),
"employee_name" : "John",
"employee_description" : "test description",
"employee_email" : "john#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20"
},
{
"_id" : ObjectId("586ca8d31a72cb07a6815671"),
"employee_name" : "Carlos",
"employee_description" : "test description",
"employee_email" : "carlos#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20"
}
2nd Collection: Task
{
"_id" : ObjectId("586ccbcf1a72cb07a6815b04"),
"task_name" : "New Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_id" : [
ObjectId("586ca8c71a72cb07a681566d"),
ObjectId("586ca8d31a72cb07a6815671")
]
},
{
"_id" : ObjectId("586cd3261a72cb07a6815c69"),
"task_name" : "2nd Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_id" : ObjectId("586ca8c71a72cb07a681566d")
}
Laravel:
Model:
Employee:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Employee extends Eloquent {
protected $collection = 'employee';
protected $primaryKey = '_id';
public function tasks()
{
return $this->hasMany('App\Models\Task');
}
}
Laravel:
Model:
Task:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Task extends Eloquent {
protected $collection = 'task';
protected $primaryKey = '_id';
public function employees()
{
return $this->belongsTo('App\Models\Employee');
}
}
I want to get tasks assigned to the specific employee.
Controller:
public function EmployeeData($data)
{
$employees = Employee::with('tasks')->where('_id', new \MongoDB\BSON\ObjectID('586ca8d31a72cb07a6815671'))->get();
echo "<pre>";
print_r($employees);exit;
}
Output:
Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
[0] => App\Models\Employee Object
(
[connection:protected] => mongodb
[collection:protected] => lt_employees
[primaryKey:protected] => _id
[employee_id:App\Models\Employee:private] =>
[employee_name:App\Models\Employee:private] =>
[employee_description:App\Models\Employee:private] =>
[employee_email:App\Models\Employee:private] =>
[employee_created_at:App\Models\Employee:private] =>
[employee_updated_at:App\Models\Employee:private] =>
[parentRelation:protected] =>
[table:protected] =>
[keyType:protected] => int
[perPage:protected] => 15
[incrementing] => 1
[timestamps] => 1
[attributes:protected] => Array
(
[_id] => MongoDB\BSON\ObjectID Object
(
[oid] => 586ca8d31a72cb07a6815671
)
[employee_name] => Carlos
[employee_description] => test description
[employee_email] => carlos#email.com
[updated_at] => 2017-01-04 11:45:20
[created_at] => 2017-01-04 11:45:20
)
[original:protected] => Array
(
[_id] => MongoDB\BSON\ObjectID Object
(
[oid] => 586ca8d31a72cb07a6815671
)
[employee_name] => Carlos
[employee_description] => test description
[employee_email] => carlos#email.com
[updated_at] => 2017-01-04 11:45:20
[created_at] => 2017-01-04 11:45:20
)
[relations:protected] => Array
(
[tasks] => Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
)
)
)
[hidden:protected] => Array
(
)
[visible:protected] => Array
(
)
[appends:protected] => Array
(
)
[fillable:protected] => Array
(
)
[guarded:protected] => Array
(
[0] => *
)
[dates:protected] => Array
(
)
[dateFormat:protected] =>
[casts:protected] => Array
(
)
[touches:protected] => Array
(
)
[observables:protected] => Array
(
)
[with:protected] => Array
(
)
[exists] => 1
[wasRecentlyCreated] =>
)
)
)
In the output, relation tasks items are empty.
Can anyone suggest me that the relation b/w collections are correct?
Update
I have used belongsToManyin the relation. Now my models are:
In the Employee Model:
public function tasks()
{
return $this->belongsToMany('App\Models\Task');
}
In the Task Model:
public function employees()
{
return $this->belongsToMany('App\Models\Employee');
}
These are the documents:
Employee collection
{
"_id" : ObjectId("586ca8c71a72cb07a681566d"),
"employee_name" : "Carlos",
"employee_description" : "test description",
"employee_email" : "carlos#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20",
"task_ids" : [
ObjectId("586ccbcf1a72cb07a6815b04"),
ObjectId("586cd3261a72cb07a6815c69")
]
},
{
"_id" : ObjectId("586ca8d31a72cb07a6815671"),
"employee_name" : "John",
"employee_description" : "test description",
"employee_email" : "john#email.com",
"updated_at" : "2017-01-04 11:45:20",
"created_at" : "2017-01-04 11:45:20"
}
Task collection
{
"_id" : ObjectId("586ccbcf1a72cb07a6815b04"),
"task_name" : "New Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_ids" : [
ObjectId("586ca8c71a72cb07a681566d"),
ObjectId("586ca8d31a72cb07a6815671")
]
},
{
"_id" : ObjectId("586cd3261a72cb07a6815c69"),
"task_name" : "2nd Task",
"task_description" : "test description",
"task_status" : 1,
"task_start" : "2017-04-01 12:00:00",
"task_end" : "2017-04-01 02:00:00",
"task_created_at" : "2017-04-01 02:17:00",
"task_updated_at" : "2017-04-01 02:17:00",
"employee_ids" : ObjectId("586ca8c71a72cb07a681566d")
}
I get the first employee with these documents:
$employee = Employee::with('tasks')->first();
dd($employee);
And I gotthe output with empty relation:
Employee {#176
#connection: "mongodb"
#collection: "employee"
#primaryKey: "_id"
-employee_id: null
-employee_name: null
-employee_description: null
-employee_email: null
-employee_created_at: null
-employee_updated_at: null
#parentRelation: null
#table: null
#keyType: "int"
#perPage: 15
+incrementing: true
+timestamps: true
#attributes: array:10 [
"_id" => ObjectID {#170}
"employee_name" => "Carlos"
"employee_description" => "test description"
"employee_email" => "carlos#email.com"
"updated_at" => "2017-01-04 11:45:20"
"created_at" => "2017-01-04 11:45:20"
"task_ids" => array:2 [
0 => ObjectID {#174}
1 => ObjectID {#175}
]
]
#original: array:10 [
"_id" => ObjectID {#170}
"employee_name" => "Carlos"
"employee_description" => "test description"
"employee_email" => "carlos#email.com"
"updated_at" => "2017-01-04 11:45:20"
"created_at" => "2017-01-04 11:45:20"
"task_ids" => array:2 [
0 => ObjectID {#174}
1 => ObjectID {#175}
]
]
#relations: array:1 [
"tasks" => Collection {#173
#items: []
}
]
#hidden: []
#visible: []
#appends: []
#fillable: []
#guarded: array:1 [
0 => "*"
]
#dates: []
#dateFormat: null
#casts: []
#touches: []
#observables: []
#with: []
+exists: true
+wasRecentlyCreated: false
}
I understood by your other question, that a task can belong to many employees, right? So you should be using belongsToMany relationship in your Task model. Also your example "task" collection shows that in one document employee_id is an array and in the other document it is an ObjectId, when both should be arrays.
Anyway, I've had a hard time trying to figure this out, but I've seen that you can't use hasMany as the inverse of belongsToMany, because belongsToMany creates an array of ids, and hasMany doesn't work well with arrays. I would say that we would need something like hasManyInArray, but when I associate a belongsToMany relationship, the "parent" document gets created an array of ids, which leads me to think that the parent should also use belongsToMany even though it doesn't "belong to" but actually "has". So when you would associate an employee to a task like this:
$task->employees()->save($employee);
The "employee" document will end up having a "task_ids" attribute with the only task id it should have. So that seems to be the way to go with Jenssegers: to use belongsToMany in both models:
Laravel: Model: Employee:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Employee extends Eloquent
{
protected $collection = 'employee';
public function tasks()
{
return $this->belongsToMany(Task::class);
}
}
Laravel: Model: Task:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
class Task extends Eloquent
{
protected $collection = 'task';
public function employees()
{
return $this->belongsToMany(Employee::class);
}
}
And you would use this like:
// Give a task a new employee
$task->employees()->save($employee);
// Or give an employee a new task
$employee->tasks()->save($task);
The only thing about this is that when you look at the database, you will see that your employee documents have an array called "task_ids", and inside it, the id of the only task each employee have. I hope this helped.
Just some side notes, you know that you don't have to define the name of the primary key on each model, right? You don't need this:
protected $primaryKey = '_id';
Also you don't have to define the name of the collection (i.e. protected $collection = 'employee';), unless you really want them to be in singular (by default they are in plural).
I got up in the middle of the night (it's 3:52AM here) and checked something on the computer and then checked SO an saw your question, I hope this time I answered soon enough for you, we seem to be in different timezones.
Update
These are the documents I created for testing:
employee collection
{
"_id" : ObjectId("5870ba1973b55b03d913ba54"),
"name" : "Jon",
"updated_at" : ISODate("2017-01-07T09:51:21.316Z"),
"created_at" : ISODate("2017-01-07T09:51:21.316Z"),
"task_ids" : [
"5870ba1973b55b03d913ba56"
]
},
{
"_id" : ObjectId("5870ba1973b55b03d913ba55"),
"name" : "Doe",
"updated_at" : ISODate("2017-01-07T09:51:21.317Z"),
"created_at" : ISODate("2017-01-07T09:51:21.317Z"),
"task_ids" : [
"5870ba1973b55b03d913ba56"
]
}
task collection
{
"_id" : ObjectId("5870ba1973b55b03d913ba56"),
"name" : "New Task",
"updated_at" : ISODate("2017-01-07T09:51:21.317Z"),
"created_at" : ISODate("2017-01-07T09:51:21.317Z"),
"employee_ids" : [
"5870ba1973b55b03d913ba54",
"5870ba1973b55b03d913ba55"
]
}
With these documents I get the first employee like this:
$employee = Employee::with('tasks')->first();
dd($employee);
And in the output we can see the relations attribute is an array:
Employee {#186 â–¼
#collection: "employee"
#primaryKey: "_id"
// Etc.....
#relations: array:1 [â–¼
"tasks" => Collection {#199 â–¼
#items: array:1 [â–¼
0 => Task {#198 â–¼
#collection: "task"
#primaryKey: "_id"
// Etc....
#attributes: array:5 [â–¼
"_id" => ObjectID {#193}
"name" => "New Task"
"updated_at" => UTCDateTime {#195}
"created_at" => UTCDateTime {#197}
"employee_ids" => array:2 [â–¶]
]
}
]
}
]
}
Update 2
The belongsToMany method isn't in the file you mention because that class (i.e. Jenssegers\Mongodb\Eloquent\Model) extends Laravel's Eloquent Model class, and that's where the belongsToMany method is.
Ok so that must be why it's not working for you, because the arrays have to be strings instead of ObjectIds. Why is this? Because that's how the Jenssegers library work, it saves the Ids as strings. I've also found this behaviour strange, but that's how it works. Remember that you are supposed to relate objects using the Jenssegers library, not by creating the data manually in the database.
How can you index the ids? Just create a normal index in MongoDB, like tasks.createIndex({task_ids: 1}). Here's the documentation on how to create indexes: https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/. You can also create indexes on migrations, here are the docs on migrations, make sure to read Jenssegers notes on migrations too.
You can access the tasks realtion like this: $employee->tasks;. You access relations by getting a property with the same name of the method you declared your relation with, so if you have:
class Post
{
public function owner()
{
return $this->belongsTo(User::class);
}
}
You get the relation as $post->owner;. Here's the documentation on relations: https://laravel.com/docs/5.3/eloquent-relationships
I have a DB like:
{
'a' : [
{ 'name' : 'john',
'phone' : 111111
},
{ 'name' : 'doe',
'phone' : 222222
},
],
'b' : [
{ 'name' : 'john',
'phone' : 111111
},
{ 'name' : 'doe',
'phone' : 222222
},
]
}
now I want to add a new field, "state : 1" to all the entries (like name, phone)
any suggestions on the update clause?
I tried using $set and $addToSet, but I'm not sure about the criteria too
thanks
You need to use the $ positional operator. See the documentation at http://www.mongodb.org/display/DOCS/Updating#Updating-The%24positionaloperator
I am using mongoDB database. in which I have collection in below format.
{ name : name1 , area: a , country : c}
{ name : name2 , area: b , country : c}
{ name : name3 , area: a1 , country : c1}
{ name : name4 , area: b1 , country : c1}
i want query like
select * from coll where (country =c and area =a) or (country = c1 and area = b1)
in mongodb query.
I have read many document but not found suitable answer.
So if any one knows please reply.
Thanks
By default, all elements in a mongodb query use the and operator. You can specify an or operator using the { "$or": [ ... ] } construct, as described in the documentation.
Your query will be :
{ "$or": [ { "country": "c", "area": "a" }, { "country": "c1", "area": "b1" } ] }