Elasticsearch partial update of Object(multi=True) - elasticsearch

How to update document with field mapping Object(multi=True),
when a document can have both single (dictionary) and multiple values (list of dictionaries).
Example of documents in the same index:
A single value in items:
{
"title": "Some title",
"items": {
"id": 123,
"key": "foo"
}
}
Multiple values in items:
{
"title": "Some title",
"items": [{
"id": 456,
"key": "foo"
}, {
"id": 789,
"key": "bar"
}]
}

You can try to use the following script.
I intentionally formatted inline attribute to show what's inside.
POST index_name/_update_by_query
{
"search": {
"term": {
"items.key": "foo"
}
},
"script": {
"inline": "
if (ctx._source.items instanceof List) {
for (item in ctx.source.items) {
if (item.key == params.old_value) {
item.key = params.new_value;
break;
}
}
} else {
ctx._source.items.key = params.new_value;
}
",
"params": {"old_value": "foo", "new_value": "bar"},
"lang": "painless'
}
}
And to make it work, replace inline attribute with a single line value.
"inline": "if (ctx._source.items instanceof List) {for (item in ctx.source.items) {if (item.key == params.old_value) {item.key = params.new_value;break;}}} else {ctx._source.items.key = params.new_value;}"

Related

Get GraphQL output without second curly bracket

I have a problem. I do not want a "break" down in my GraphQL output. I have a GraphQL schema with a person. That person can have one or more interests. But unfortunately I only get a breakdown
What I mean by breakdown is the second curly brackets.
{
...
{
...
}
}
Is there an option to get the id of the person plus the id of the interests and the status without the second curly bracket?
GraphQL schema
Person
└── Interest
Query
query {
model {
allPersons{
id
name
interest {
id
status
}
}
}
}
[OUT]
{
"data": {
"model": {
"allPersons": [
{
"id": "01",
"name": "Max",
"interest ": {
"id": 4488448
"status": "active"
}
},
{
"id": "02",
"name": "Sophie",
"interest ": {
"id": 15445
"status": "deactivated"
}
},
What I want
{
{
"id": "01",
"id-interest": 4488448
"status": "active"
},
{
"id": "02",
"name": "Sophie",
"id-interest": 15445
"status": "deactivated"
},
}
What I tried but that deliver me the same result
fragment InterestTask on Interest {
id
status
}
query {
model {
allPersons{
id
interest {
...InterestTask
}
}
}
}

How to mutate a list of objects in an array as an argument in GraphQL completely

I cannot mutate a list of objects completely, because only the last element of the array will be mutated.
What already works perfectly is, if I put each element ({play_positions_id: ...}) in the array manually like here:
mutation CreateProfile {
__typename
create_profiles_item(data: {status: "draft", play_positions: [{play_positions_id: {id: "1"}}, {play_positions_id: {id: "2"}}]}) {
id
status
play_positions {
play_positions_id {
abbreviation
name
}
}
}
}
Output:
{
"data": {
"__typename": "Mutation",
"create_profiles_item": {
"id": "1337",
"status": "draft",
"play_positions": [
{
"play_positions_id": {
"id": "1",
"abbreviation": "RWB",
"name": "Right Wingback"
}
},
{
"play_positions_id": {
"id": "2",
"abbreviation": "CAM",
"name": "Central Attacking Midfielder"
}
}
],
}
}
}
Since you can add many of those elements, I defined a variable/argument like here
mutation CreateProfile2($cpppi: [create_profiles_play_positions_input]) {
__typename
create_profiles_item(data: {status: "draft", play_positions: $cpppi}) {
id
status
play_positions {
play_positions_id {
id
abbreviation
name
}
}
}
}
Variable object for above:
"cpppi": {
"play_positions_id": {
"id": "1"
},
"play_positions_id": {
"id": "2
}
}
Output:
{
"data": {
"__typename": "Mutation",
"create_profiles_item": {
"id": "1338",
"play_positions": [
{
"play_positions_id": {
"id": "2",
"abbreviation": "CAM",
"name": "Central Attacking Midfielder"
}
}
],
}
}
}
Schema:
input create_profiles_input {
id: ID
status: String!
play_positions: [create_profiles_play_positions_input]
}
input create_profiles_play_positions_input {
id: ID
play_positions_id: create_play_positions_input
}
input create_play_positions_input {
id: ID
abbreviation: String
name: String
}
At the last both snippets, only the last object with the id "2" will be mutated. I need these to use the defined input type from my backend.
I figured it out. I got it wrong with the brackets in the variable. Here the solution:
"cpppi": [
{
"play_positions_id": {
"id": "1"
}
},
{
"play_positions_id": {
"id": "2"
}
}
]

Elasticsearch: Update/upsert an array field inside a document but ignore certain existing fields

GET _doc/1
"_source": {
"documents": [
{
"docid": "ID001",
"added_vals": [
{
"code": "123",
"label": "Abc"
},
{
"code": "113",
"label": "Xyz"
}
]
},
{
"docid": "ID002",
"added_vals": [
{
"code": "123",
"label": "Abc"
}
]
}
],
"id": "1"
}
POST /_bulk
{ "update": { "_id": "1"}}
{ "doc": { "documents": [ { "docid": "ID001", "status" : "cancelled" } ], "id": "1" }, "doc_as_upsert": true }
The problem above is when I run my bulk update script it replaces that document field, removing the added_vals list. Would I be able to achieve this using painless script? Thank you.
Using elasticsearch painless scripting
POST /_bulk
{ "update": { "_id": "1"} }
{ "scripted_upsert":true, "script" :{ "source": "if(ctx._version == null) { ctx._source = params; } else { def param = params; def src = ctx._source; for(s in src.documents) { boolean found = false; for(p in param.documents) { if (p.docid == s.docid) { found = true; if(s.added_vals != null) { p.added_vals = s.added_vals; } } } if(!found) param.documents.add(s); } ctx._source = param; }", "lang": "painless", "params" : { "documents": [ { "docid": "ID001", "status" : "cancelled" } ], "id": "1" } }, "upsert" : { } }
well, this one worked for me. I need to tweak a few more things that I require, but I will just leave it here for someone who may need it. Didnt know it was this simple. If there is any other answer that might be easier, please do submit so. Thanks.
"script" :
if(ctx._version == null)
{
ctx._source = params;
}
else
{
def param = params;
def src = ctx._source;
for(s in src.documents)
{
boolean found = false;
for(p in param.documents)
{
if (p.docid == s.docid)
{
found = true;
if(s.added_vals != null)
{
p.added_vals = s.added_vals;
}
}
}
if(!found) param.documents.add(s);
}
ctx._source = param;
}
I am not sure if I should modify the params directly so I used and pass the params to the param variable. I also used scripted_upsert: true with a ctx._version not null check.

Scripted field to count array length

I have the following document:
{
"likes": {
"data": [
{
"name": "a"
},
{
"name": "b"
},
{
"name": "c"
}
]
}
}
I'm trying to run an update_by_query that will add a field called 'like_count' with the number of array items inside likes.data
It's important to know that not all of my documents have the likes.data object.
I've tried this:
POST /facebook/post/_update_by_query
{
"script": {
"inline": "if (ctx._source.likes != '') { ctx._source.like_count = ctx._source.likes.data.length }",
"lang": "painless"
}
}
But getting this error message:
{
"type": "script_exception",
"reason": "runtime error",
"script_stack": [
"ctx._source.like_count = ctx._source.likes.data.length }",
" ^---- HERE"
],
"script": "if (ctx._source.likes != '') { ctx._source.like_count = ctx._source.likes.data.length }",
"lang": "painless"
}
Try ctx._source['likes.data.name'].length
According to https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html, the object array in ES is flattened to
{
"likes.data.name" :["a", "b", "c"]
}
The object array datatype we thought is Nest datatype.
Try this
ctx._source['likes']['data'].size()

Rethinkdb: Calculate tag occurrence per user

My table contains documents that look like this:
[{ user: {
key: '100'
},
product: {
name: 'Product 1',
tags: [ 'tag1', 'tag2' ],
}
}, { user: {
key: '100'
},
product: {
name: 'Product 1',
tags: [ 'tag1', 'tag3' ],
}
}, ...]
I would like to create a query which would
groupe documents by the user.key field (1 document per user on result),
the product.tags would be an object (instead of array) with tag occurrences count for each tag.
Result example:
[ { user: {
key: '100'
},
product: {
name: 'Product 1',
tags: {
tag1: 2, // tag1 found 2x for user.key=100
tag2: 1, // tag2 found 1x for user.key=100
tag3: 1
}
}
}, ...]
I think I could do this by mapping and reducing but I have problems - I'm using rethinkdb for the first time.
Here's a way to do it:
// Group by user key
r.table('30400911').group(r.row('user')('key'))
// Only get the product info inside the reduction
.map(r.row('product'))
.ungroup()
.map(function (row) {
return {
user: row('group'),
// Group by name
products: row('reduction').group('name').ungroup().map(function (row) {
return {
name: row('group'),
// Convert array of tags into key value pairs
tags: r.object(r.args(row('reduction').concatMap(function (row) {
return row('tags')
}).group(function (row) {
return row;
}).count().ungroup().concatMap(function (row) {
return [row('group'), row('reduction')]
})))
}
})
}
})
For the following data:
{
"id": "0565e91a-01ca-4ba3-b4d5-1043c918c79d" ,
"product": {
"name": "Product 2" ,
"tags": [
"tag1" ,
"tag3"
]
} ,
"user": {
"key": "100"
}
} {
"id": "39999c9f-bbef-4cb7-9311-2516ca8f9ba1" ,
"product": {
"name": "Product 1" ,
"tags": [
"tag1" ,
"tag3"
]
} ,
"user": {
"key": "100"
}
} {
"id": "566f3b79-01bf-4c29-8a9c-fd472431eeb6" ,
"product": {
"name": "Product 1" ,
"tags": [
"tag1" ,
"tag2"
]
} ,
"user": {
"key": "100"
}
} {
"id": "8e95c467-cedc-4734-ad4d-a1f7a371efd5" ,
"product": {
"name": "Product 1" ,
"tags": [
"tag1" ,
"tag2"
]
} ,
"user": {
"key": "200"
}
}
The results would be:
[
{
"products": [
{
"name": "Product 1" ,
"tags": {
"tag1": 2 ,
"tag2": 1 ,
"tag3": 1
}
}, {
"name": "Product 2" ,
"tags": {
"tag1": 1 ,
"tag3": 1
}
}
],
"user": "100"
} ,
{
"products": [
{
"name": "Product 1" ,
"tags": {
"tag1": 1 ,
"tag2": 1
}
}
] ,
"user": "200"
}
]

Resources