I need to export customer records from database of mongoDB. Exported customer records should not have duplicated values. "firstName+lastName+code" is the key to DE-duped the record and If there are two records present in database with same key then I need to give preference to source field with value other than email.
customer (id,firstName,lastName,code,source) collection is this.
If there are record 3 records with same unique key and 3 different sources then i need to choose only one record between 2 sources(TV,internet){or if there are n number of sources i need the one record only}not with the 'email'(as email will be choosen when only one record is present with the unique key and source is email)
query using:
db.customer.aggregate([
{
"$match": {
"active": true,
"dealerCode": { "$in": ["111391"] },
"source": { "$in": ["email", "TV", "internet"] }
}
},
{
$group: {
"_id": {
"firstName": "$personalInfo.firstName",
"lastName": "$personalInfo.lastName",
"code": "$vehicle.code"
},
"source": {
$addToSet: { "source": "$source" }
}
}
},
{
$redact:
{
$cond: [
{ $eq: [{ $ifNull: ["$source", "other"] }, "email"] },
"$$PRUNE",
"$$DESCEND"
]
}
},
{
$project:
{
"source":
{
$map:
{
"input": {
$cond: [
{ $eq: [{ $size: "$source" }, 0] },
[{ "source": "email" }],
"$source"
]
},
"as": "inp",
"in": "$$inp.source"
}
},
"record": { "_id": 1 }
}
}
])
sample output:
{ "_id" : { "firstName" : "sGI6YaJ36WRfI4xuJQzI7A==", "lastName" : "99eQ7i+uTOqO8X+IPW+NOA==", "code" : "1GTHK23688F113955" }, "source" : ["internet"] }
{ "_id" : { "firstName" : "WYDROTF/9vs9O7XhdIKd5Q==", "lastName" : "BM18Uq/ltcbdx0UJOXh7Sw==", "code" : "1G4GE5GV5AF180133" }, "source" : ["internet"] }
{ "_id" : { "firstName" : "id+U2gYNHQaNQRWXpe34MA==", "lastName" : "AIs1G33QnH9RB0nupJEvjw==", "code" : "1G4GE5EV0AF177966" }, "source" : ["internet"] }
{ "_id" : { "firstName" : "qhreJVuUA5l8lnBPVhMAdw==", "lastName" : "petb0Qx3YPfebSioY0wL9w==", "code" : "1G1AL55F277253143" }, "source" : ["TV"] }
{ "_id" : { "firstName" : "qhreJVuUA5l8lnBPVhMAdw==", "lastName" : "6LB/NmhbfqTagbOnHFGoog==", "code" : "1GCVKREC0EZ168134" }, "source" : ["TV", "internet"] }
This is a problem with this query please suggest :(
Your code doesn't work, because $cond is not an accumulator operator. Only these accumulator operators, can be used in a $group stage.
Assuming your records contain not more than two possible values of source as you mention in your question, you could add a conditional $project stage and modify the $group stage as,
Code:
db.customer.aggregate([
{
$group: {
"_id": {
"id": "$id",
"firstName": "$firstName",
"lastName": "$lastName",
"code": "$code"
},
"sourceA": { $first: "$source" },
"sourceB": { $last: "$source" }
}
},
{
$project: {
"source": {
$cond: [
{ $eq: ["$sourceA", "email"] },
"$sourceB",
"$sourceA"
]
}
}
}
])
In case there can be more that two possible values for source, then you could do the following:
Group by the id, firstName, lastName and code. Accumulate
the unique values of source, using the $addToSet operator.
Use $redact to keep only the values other than email.
Project the required fields, if the source array is empty(all the elements have been removed), add a
value email to it.
Unwind the source field to list it as a field and not an array.
(optional)
Code:
db.customer.aggregate([
{
$group: {
"_id": {
"id": "$id",
"firstName": "$firstName",
"lastName": "$lastName",
"code": "$code"
},
"sourceArr": { $addToSet: { "source": "$source" } }
}
},
{
$redact: {
$cond: [
{ $eq: [{ $ifNull: ["$source", "other"] }, "email"] },
"$$PRUNE",
"$$DESCEND"
]
}
},
{
$project: {
"source": {
$map: {
"input":
{
$cond: [
{ $eq: [{ $size: "$sourceArr" }, 0] },
[{ "source": "item" }],
"$sourceArr"]
},
"as": "inp",
"in": "$$inp.source"
}
}
}
}
])
Related
I am not able to transform the jolt properly using jolt spec. Thanks in photos array elements can be any like in above two are there, so it can be 3 or 4 or 5 any.
My jolt spec:
Input JSON :
{
"Entity": {
"card": {
"name": "RAM",
"lastName": "ABU"
},
"Photos": [
{
"Id": "327703",
"Caption": "TEST>> photo 1",
"Url": "http://bob.com/0001/327703/photo.jpg"
},
{
"Id": "327704",
"Caption": "TEST>> photo 2",
"Url": "http://bob.com/0001/327704/photo.jpg"
}
]
}
}
Jolt Spec :
[
{
"operation": "shift",
"spec": {
"Entity": {
"card": {
"name": "Photos[0].no",
"lastName": "Photos[0].caption2."
},
"Photos": {
"*": {
"Id": "Photos[&1].no",
"Caption": "Photos[&1].caption2"
}
}
}
}
}
]
Current output :
{
"Photos" : [ {
"no" : [ "RAM", "327703" ],
"caption2" : [ "ABU", "TEST>> photo 1" ]
}, {
"no" : "327704",
"caption2" : "TEST>> photo 2"
} ]
}
Expected output :
{
"Photos" : [
{
"no" : "Ram",
"caption2" : "ABU"
},
{
"no" : "327703" ,
"caption2" : "TEST>> photo 1"
},
{
"no" : "327704",
"caption2" : "TEST>> photo 2"
}
]
}
You can construct a shift transformation spec which will rearrange the desired elements to remain as two individual arrays nested within an object tagged Photos, and then put them into their respective (three) objects such as
[
{
"operation": "shift",
"spec": {
"Entity": {
"card": {
"name": "Photos.no",
"lastName": "Photos.caption2"
},
"Photos": {
"*": {
"Id": "Photos.no",
"Caption": "Photos.caption2"
}
}
}
}
},
{
"operation": "shift",
"spec": {
"*": {
"*": {
"*": {
"#": "&3[&1].&2" // grouped by "Photos" which is grabbed after going the tree 3 leves up(&3) and binded as array by their respective indexes([&1] which yields 0,1,2) and thier key names(&2)
}
}
}
}
}
]
the demo on the site http://jolt-demo.appspot.com/ is
I want to create a search for books with ElasticSearch and SpringData.
I index my books with ISBN/EAN without hyphens and save it in my database. This data I index with ElasticSearch.
Indexed data: 1113333444444
If I'm search for a ISBN/EAN with hyphen: 111-3333-444444
There is no result. If I'm searching without hyphen, my book will be found as expected.
My settings are like this:
{
"analysis": {
"filter": {
"clean_special": {
"type": "pattern_replace",
"pattern": "[^a-zA-Z0-9]",
"replacement": ""
}
},
"analyzer": {
"isbn_search_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"clean_special"
]
}
}
}
}
I index my fields like this:
#Field(type = FieldType.Keyword, searchAnalyzer = "isbn_search_analyzer")
private String isbn;
#Field(type = FieldType.Keyword, searchAnalyzer = "isbn_search_analyzer")
private String ean;
If I test my analyzer:
GET indexname/_analyze
{
"analyzer" : "isbn_search_analyzer",
"text" : "111-3333-444444"
}
I get following result:
{
"tokens" : [
{
"token" : "1113333444444",
"start_offset" : 0,
"end_offset" : 15,
"type" : "word",
"position" : 0
}
]
}
If I'm search like this:
GET indexname/_search
{
"query": {
"query_string": {
"fields": [ "isbn", "ean" ],
"query": "111-3333-444444"
}
}
}
I don't get any result. Have someone of you an idea?
As mentioned by #P.J.Meisch, you have done everything correct, but missed defining your field data type to text, when you define them as keyword, even though you are explicitly telling ElasticSearch to use your custom-analyzer isbn_search_analyzer, it will be ignored.
Working example on your sample data when field is defined as text.
Index mapping
{
"settings": {
"analysis": {
"filter": {
"clean_special": {
"type": "pattern_replace",
"pattern": "[^a-zA-Z0-9]",
"replacement": ""
}
},
"analyzer": {
"isbn_search_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"clean_special"
]
}
}
}
},
"mappings": {
"properties": {
"isbn": {
"type": "text",
"analyzer": "isbn_search_analyzer"
},
"ean": {
"type": "text",
"analyzer": "isbn_search_analyzer"
}
}
}
}
Index Sample records
{
"isbn" : "111-3333-444444"
}
{
"isbn" : "111-3333-2222"
}
Search query
{
"query": {
"query_string": {
"fields": [
"isbn",
"ean"
],
"query": "111-3333-444444"
}
}
}
And search response
"hits": [
{
"_index": "65780647",
"_type": "_doc",
"_id": "1",
"_score": 0.6931471,
"_source": {
"isbn": "111-3333-444444"
}
}
]
Elasticsearch does not analyze fields of type keyword. You need to set the type to text.
I have this collection of documents:
[
{
"name": "name1",
"data": [
{
"numbers": ["1","2","3"]
}
]
},
{
"name": "name2",
"data": [
{
"numbers": ["2","5","3"]
}
]
},
{
"name": "name3",
"data": [
{
"numbers": ["1","5","2"]
}
]
},
{
"name": "name4",
"data": [
{
"numbers": ["1","4","3"]
}
]
},
{
"name": "name5",
"data": [
{
"numbers": ["1","2"]
}
]
}
]
I want to get all documents of this collection when an array passed as a parameter is a subset of data.numbers.
This is the aggregation that I'm using.
db.testing.aggregate(
[
{ "$match" : { "data.numbers" : { "$exists" : true } } },
{ "$project" : { "is_subset" : { "$filter" : { "input" : "$data", "as" : "d", "cond" : { "$setIsSubset" :[ ["1"],"$$d.numbers"] } } } } },
{ "$match" : { "is_subset.0" : { "$exists" : true } } }]
);
I'm trying to reproduce the above aggregation in Spring Data MongoDB.
How to pass an array as parameter in $filter and $setIsSubset functions?
operations.aggregate(
newAggregation(Testing.class,
match(where("data.numbers").exists(true)),
project().and(
filter("data")
.as("d")
.by(???))
.as("is_subset"),
match(where("is_subset.0").exists(true))
), Testing.class);
I solve my issue.
operations.aggregate(
newAggregation(Testing.class,
match(where("data.numbers").exists(true)),
project("id", "name").and(
filter("data")
.as("d")
.by(context -> new Document("$setIsSubset", Arrays.asList(numbers, "$$d.numbers"))))
.as("is_subset"),
match(where("is_subset.0").exists(true))
), Testing.class);
I created a Document with the content that I needed in the $filter condition.
new Document("$setIsSubset", Arrays.asList(numbers, "$$d.numbers"))
I have the following data structure
[{
"id": "1c7bbebd-bc3d-4352-9ac0-98c01d13189d",
"version": 0,
"groups": [
{
"internalName": "Admin group",
"fields": [
{
"internalName": "Is verified",
"uiProperties": {
"isShow": true
}
},
{
"internalName": "Hide",
"uiProperties": {
"isHide": false
}
},
...
]
},
...
]
},
{
"id": "2b7bbebd-bc3d-4352-9ac0-98c01d13189d",
"version": 0,
"groups": [
{
"internalName": "User group",
"fields": [
{
"internalName": "Is verified",
"uiProperties": {
"isShow": true
}
},
{
"internalName": "Blocked",
"uiProperties": {
"isBlocked": true
}
},
...
]
},
...
]
},
...
]
Internal names of the fields can be repeated. I want to group by group.field.internalName and cut the array(for pagination) and get the output like:
{
"totalCount": 3,
"items": [
{
"internalName": "Blocked"
},
{
"internalName": "Hide"
},
{
"internalName": "Is verified"
}
]}
I wrote a query that works,
db.layouts.aggregate(
{
$unwind : "$groups"
},
{
$unwind : "$groups.fields"
},
{
$group: {
"_id" : {
"internalName" : "$groups.fields.internalName",
},
"internalName" : {
$first : "$groups.fields.internalName"
}
}
},
{
$group: {
"_id" : null,
"items" : {
$push : "$$ROOT"
},
"totalCount" : {
$sum : 1
}
}
},
{
$project: {
"items" : {
$slice : [ "$items", 0, 20 ]
},
"totalCount": 1
}
})
but I have the problem of translating it to java api. Notice that i need to use mongoTemplate approach. Here is what i have and where i'm struck
final List<AggregationOperation> aggregationOperations = new ArrayList<>();
aggregationOperations.add(unwind("groups"));
aggregationOperations.add(unwind("groups.fields"));
aggregationOperations.add(
group("groups.fields.internalName")
.first("groups.fields.internalName").as("internalName")
);
aggregationOperations.add(
group()
.push("$$ROOT").as("fields")
.sum("1").as("totalCount") // ERROR only string ref can be placed, but i need a number?
);
aggregationOperations.add(
project()
.andInclude("totalCount")
.and("fields").slice(size, page * size)
);
final Aggregation aggregation = newAggregation(aggregationOperations);
mongoTemplate.aggregate(aggregation, LAYOUTS, FieldLites.class).getMappedResults()
With this query i have the problem with sum(), because i can place only a String ref by api(but need a number) and with project operation - got an exception
java.lang.IllegalArgumentException: Invalid reference 'totalCount'!] with root cause
Can you help me with this query translation?
You can use count
group()
.push("$$ROOT").as("fields")
.count().as("totalCount")
This is my index mapping
"index":{
"mappings":{
"patient":{
"properties":{
"LastName":{
"type":"string"
},
"accountType":{
"type":"string"
},
"civilStatus":{
"type":"string"
},
"consultations":{
"type":"nested",
"properties":{
"deleted":{
"type":"boolean"
},
"diagnosis":{
"type":"string",
"index":"not_analyzed"
},
"documentDate":{
"type":"date",
"format":"dateOptionalTime"
},
"firstName":{
"type":"string"
},
"lastName":{
"type":"string"
},
"middleName":{
"type":"string"
},
"prescriptions":{
"type":"string"
}
}
},
"firstName":{
"type":"string"
},
"gender":{
"type":"string"
},
"id":{
"type":"string",
"index":"not_analyzed"
},
"lastName":{
"type":"string"
},
"middleName":{
"type":"string"
},
"occupation":{
"type":"string"
},
"owner":{
"type":"string",
"index":"not_analyzed"
},
"patientPin":{
"type":"string"
}
}
}
}
}
}
Here's the only saved data on ElasticSearch
{
"_index":"index",
"_type":"patient",
"_id":"TENANT1100066",
"_score":1.0,
"_source":{
"id":"100066",
"firstName":"Johnny",
"patientPin":"201408000001",
"middleName":"John ",
"consultations":[
{
"id":null,
"prescriptions":[
],
"diagnosis":[
"headache of unknown origin"
],
"documentDate":"2014-08-05T10:10:00.000+08:00",
"deleted":false,
"lastName":"David",
"firstName":"Johnny ",
"middleName":"John "
}
],
"owner":"TENANT1",
"gender":"MALE",
"occupation":"Unspecified",
"accountType":"INDIVIDUAL",
"civilStatus":"SINGLE",
"lastName":"David"
}
}
And here's the sample query I built to check how boolean query works.
{
"nested" : {
"query" : {
"bool" : {
"must" : [ {
"match" : {
"consultations.diagnosis" : {
"query" : "Kawasaki's Disease",
"type" : "phrase"
}
}
}, {
"match" : {
"consultations.diagnosis" : {
"query" : "Alcohol Intoxication",
"type" : "phrase"
}
}
} ],
"must_not" : {
"match" : {
"consultations.deleted" : {
"query" : "true",
"type" : "boolean"
}
}
},
"should" : {
"match" : {
"consultations.diagnosis" : {
"query" : "headache of unknown origin",
"type" : "phrase"
}
}
}
}
},
"path" : "consultations"
}
Apparently, Kawasaki's Disease and Fibriasis does not exist but headache of unknown origin exists but no
results are returned(Which is Johnny John David) what am I missing here? The operation I had in my mind was
(Kawasaki's Disease AND Fibriasis) OR headache of unknown origin.
What I had in mind was if there was no patients with Kawasakis Disease AND Fibriasis search for Patients with "headache of unknown origin". Which clearly we have, but my query is returning 0 results. what Am I missing here
In your query, you require that matching documents have both (Kawasaki's Disease AND Fibriasis) as you added these 2 conditions in the must clause.
Your document only match your should clause, so it doesn't appear in the search results.
To achieve what you want :
(Kawasaki's Disease AND Fibriasis) OR headache of unknown origin
You can embed the two diseases in another bool query and add this query in the should section of the root query, like this :
{
"query": {
"nested": {
"path": "consultations",
"query": {
"bool": {
"should": [
{
"bool": {
"must": [{
"match_phrase": {
"consultations.diagnosis": "Kawasaki's Disease"
}
},
{
"match_phrase": {
"consultations.diagnosis": "Alcohol Intoxication"
}
}
]
}
},
{
"match_phrase": {
"consultations.diagnosis": "headache of unknown origin"
}
}
],
"minimum_number_should_match": 1
}
}
}
}
}
Which outputs the previously indexed patient :
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.3007646,
"hits": [
{
"_index": "test",
"_type": "patient",
"_id": "TENANT1100066",
"_score": 0.3007646,
"_source": {
"id": "100066",
"firstName": "Johnny",
"patientPin": "201408000001",
"middleName": "John ",
"consultations": [
{
"id": null,
"prescriptions": [],
"diagnosis": [
"headache of unknown origin"
],
"documentDate": "2014-08-05T10:10:00.000+08:00",
"deleted": false,
"lastName": "David",
"firstName": "Johnny ",
"middleName": "John "
}
],
"owner": "TENANT1",
"gender": "MALE",
"occupation": "Unspecified",
"accountType": "INDIVIDUAL",
"civilStatus": "SINGLE",
"lastName": "David"
}
}
]
}
}