I have a mongoDB document comprising the following structure:
{
"_id" : ObjectId("537b9731fa4634134c8b45aa"),
"kpis" : [
{
"id" : 4,
"value" : 3.78,
"entered" : Timestamp(1401377656, 9)
}
]
}
I want to remove ALL kpi documents where the id is x. This is quite simple to do on the database directly using the pull command:
db.lead.update({}, {$pull:{"kpis":{id:5}}}, {multi:true});
However my (several) attempts to match this syntax using the doctrine ODM have failed:
$qb->update()
->field('kpis')
->pull($qb->expr()->field('kpis.id')->equals($kpi->getId()))
->multiple(true)
->getQuery()
->execute();
// I've also tried..
$qb->update()
->field('kpis')
->pull($qb->expr()->field('kpis')->elemMatch(
$qb->expr()->field('kpis.id')->equals($kpi->getId())
))
->multiple(true)
->getQuery()
->execute();
Nothing is removed. Am I using the query builder correctly?
I believe you want to do the following:
$qb->update()
->field('kpis')->pull(array('id' => $kpi->getId()))
->multiple(true)
->getQuery()
->execute();
You can use the Query::debug() method to dump the actual query generated by this. You should expect the following in the dumped newObj field:
{ "$pull": { "kpis": { "id": <number> }}}
To explain why your previous examples didn't work:
->field('kpis')
->pull($qb->expr()->field('kpis.id')->equals($kpi->getId()))
Here, you're creating the following query:
{ "$pull": { "kpis": { "kpis.id": <number> }}}
This would only match:
If each embedded object in the top-level kpis array had an embedded kpis object within it, which in turn had an id field that matched the number.
If the embedded objects in the top-level kpis array had their own kpis arrays consisting of embedded objects with an id field that matched. This would be MongoDB's array element matching coming into play.
In your second example, you have:
->field('kpis')
->pull($qb->expr()->field('kpis')->elemMatch(
$qb->expr()->field('kpis.id')->equals($kpi->getId())
))
This would generate the following query:
{ "$pull": { "kpis": { "$elemMatch": { "kpis.id": <number> }}}}
I've never seen such syntax before, but I think the $elemMatch is superflous, since $pull already takes criteria to match an array element as its argument. $elemMatch is useful in a query when you're directly matching on an array field and don't mean to do a full equality match against the array elements (i.e. you want to provide criteria instead of an exact match).
Related
I have a structure of MongoDB that looks like this:
Object_id {
workflow {
tree_id {
other_ids {...}
other_ids {...}
other_ids {...}
subscribers {
subscriber_id {
email : value
}
}
}
}
}
}
It can be clearly seen on this screen:
My MongoDB structure
I want to add an another field, for example name : last_name, under the email : value. I tried it by using this code:
Model::where('_id', '5adde78993def907b71ce503')->push(array('workflow.5ace115a93def953d254b502.subscribers.5ad09c2993def90a2c59fa59' => ['test' => 'testvalue']));
However, this code doesn't work. It shows me an error saying:
The field 'workflow.5ace115a93def953d254b502.subscribers.5ad09c2993def90a2c59fa59' must be an array but is of type object in document {_id: ObjectId('5adde78993def907b71ce503')}
Updating with ['upsert' => true] also doesn't work, because this method removes my collection and adds data. How can I add something to this array?
Structure shown by you does not have any array in it. All are document or sub documents. you can not perform array operation on non array objects. To treat Subscriber_id as an array , it should be something like this
subscriber_id [{
email : value
}]
Model::where('_id', '5adde78993def907b71ce503')->push('workflow.5ace115a93def953d254b502.subscribers.5ad09c2993def90a2c59fa59' => ['test' => 'testvalue']);
please try this.
I am having trouble in returning only the matched embedded document using MongoDB ODM query builder in PHP. Each embedded document has a MongoID generated at the time of creation. Following is my document structure of collection Project:
{
"_id" : ObjectId("59f889e46803fa3713454b5d"),
"projectName" : "usecase-updated",
"classes" : [
{
"_id" : ObjectId("59f9d7776803faea30b895dd"),
"className" : "OLA"
},
{
"_id" : ObjectId("59f9d8ad6803fa4012b895df"),
"className" : "HELP"
},
{
"_id" : ObjectId("59f9d9086803fa4112b895de"),
"className" : "DOC"
},
{
"_id" : ObjectId("59f9d9186803fa4212b895de"),
"className" : "INVOC"
}
]
}
Now i want to retrieve from the database only the class from the classes embedded documents which meets my criteria (i.e. class with a specific id).This is how i am building the query:
$qb = $dm->createQueryBuilder('Documents\Project');
$projectObj = $qb
->field('id')->equals("59f889e46803fa3713454b5d")
->field('classes')->elemMatch(
$qb->expr()->field("id")->equals(new \MongoId("59f9d7776803faea30b895dd"))
)
->hydrate(false)
->getQuery()
->getSingleResult();
First i match with the project id then i match with the embedded document class id. I was expecting it to return only the embedded document of OLA like this:
{
"_id" : ObjectId("59f889e46803fa3713454b5d"),
"projectName" : "usecase-updated",
"classes" : [
{
"_id" : ObjectId("59f9d7776803faea30b895dd"),
"className" : "OLA"
}
]
}
But doctrine is returning the whole Project record (shown in the start of question).I also tried with aggregation query building with the $match aggregation still the results are same the query i created with aggregation builder is as follows:
$qb = $dm->createAggregationBuilder('Documents\Project');
$projectObj = $qb
->match()
->field('id')->equals($projectId)
->field('classes._id')->equals(new \MongoId($classId))
->execute()
->getSingleResult();
Can someone help me with regards to this issue? How can i build query that i get the desired result as mentioned above.
So after some trouble i am able to retrieve only the matched embedded document with the aggregation builder. So thanks to #Alex Bles comment i was able to think more on using aggregation functions and i found $filter array aggregation function and tried building query using that and the final query was like this:
$qb = $dm->createAggregationBuilder('Documents\Project');
$classObject = $qb->match()
->field("id", new \MongoId($projectId))
->project()
->field("classes")
->filter('$classes', 'class', $qb->expr()->eq('$$class._id', new \MongoId($classId)))
->execute()->getSingleResult();
So in the query first i matched with the _id of the project. then within that project i projected the results for the classes using the $filter array aggregation method. In the end i was able to get the embedded document filtered by their _id. Hope this will help someone in the future with same problem.
I am trying to make a selection based on multiple ids in joins, each of the ids should match another condition of another join.
I need to get "Types" that have all the "Dichotomies" in "Position" 1 or 2. At the moment it gives me results that match one of the Dichotomies passed to the function, but not all of them.
$QB->select("Types","Types,ElementsPositions, Elements, Positions, Dichotomies, Quadras,TypesDescriptions,Relations")
->from($this->get_repository()[0], 'Types');
$QB->leftJoin("Types.ElementsPositions","ElementsPositions", \Doctrine\ORM\Query\Expr\Join::WITH, 'ElementsPositions.Positions = 1 OR ElementsPositions.Positions = 2');
$QB->leftJoin("ElementsPositions.Elements","Elements");
$QB->leftJoin("ElementsPositions.Positions","Positions");
$QB->leftJoin("Elements.Dichotomies","Dichotomies");
$QB->leftJoin("Types.Quadras","Quadras");
$QB->leftJoin("Types.TypesDescriptions","TypesDescriptions");
$QB->leftJoin("Types.Relations","Relations");
if(!empty($where['dichotomies'])){
foreach($where['dichotomies'] as $dichotomy){
$QB->andWhere('Dichotomies.id'.'=:dichotomy');
$QB->setParameter('dichotomy', $dichotomy['id']);
}
}
UPD.
Tables mapping - in JSON:
{
"table-name": "types",
"joins":[
{
"table-name":"elements_positions",
"type":"one-to-many"
},
{
"table-name":"quadras",
"type":"many-to-one"
},
{
"table-name":"types_descriptions",
"type":"one-to-one"
},
{
"table-name":"relations",
"type":"many-to-one"
}
]}
Elements Positions
{
"table-name": "elements_positions",
"joins":[
{
"table-name":"elements",
"type":"many-to-one"
},
{
"table-name":"positions",
"type":"many-to-one"
},
{
"table-name":"types",
"type":"many-to-one"
}
]
}
Elements
{
"table-name": "elements",
"joins":[
{
"table-name":"elements_positions",
"type":"one-to-many"
},
{
"table-name":"quadras",
"type":"many-to-many"
},
{
"table-name":"dichotomies",
"type":"many-to-many"
}
]
}
Positions
"table-name": "positions",
"joins":[
{
"table-name":"elements_positions",
"type":"one-to-many"
}
]
}
Dichotomies:
{
"table-name": "dichotomies",
"joins":[
{
"table-name":"elements",
"type":"many-to-many-inversed"
}
]
}
Your query has a two different problems.
First, multiple parameter values are bound with single parameter. Every next element of $where['dichotomies'] replaces previous value of parameter :dichotomy in the query. The method setParameters() don't really binds values to the prepared statement: it just stores them in QueryBuilder object. So, after the end of foreach-loop all conditions will be use the same value (the last of $where['dichotomies']). To avoid that you need to use different parameter names or numeric indexes.
Second, you add conditions that are contradictory: $QB->andWhere() will produce something like that:
Dichotomies.id = :dichotomy
AND Dichotomies.id = :dichotomy
AND Dichotomies.id = :dichotomy
...
One entity ID obviously cannot be equal to different values simultaneously. So, you need to replace AND by the OR operator.
But better way is to use IN clause:
Calling setParameter() automatically infers which type you are setting as value. This works for integers, arrays of strings/integers, DateTime instances and for managed entities.
Just replace the foreach-loop by the following lines:
$QB->andWhere('Dichotomies.id IN (:dichotomies)');
$QB->setParameters('dichotomies', array_column($where['dichotomies'], 'id'));
The array_column() function returns a list of IDs of all dichotomies. Doctrine generates IN expression and uses that list to generate query placeholders and bind the values.
I have the following mongo document structure
"search": [
[
"keyword",
"match"
],
[
"testing",
"something",
"serious"
]
]
I want to find documents where the array of keywords inside of the array match an $all query.
E.g if search had only 1 level I would do
{'search': {'$all': ['keyword','match']}}
I've tried using:
{'search': {'$elemMatch': {'$all': ['keyword','match']}}}
But I get no results.
If you know the array of keywords in advance and you want to match a document that contains that array inside the search array, you can just use a simple query as follows.
db.collection.find({"search": ["keyword", "match"]});
That should return your sample document. On the other hand, if the array is not completely contained by an element inside search, it will not return anything. For example the following query will not return your sample document.
db.collection.find({"search": ["keyword", "match", "testing"]});
in mongo shell or PHP
how do I retreive the "list" attribute of this document ?
{
"_id" : ObjectId("51b972ebe4b075a9690bbc5b"),
"list" : [
"Environnement",
"Toutes thématiques",
"Transports"]
}
I'm looking how to do
db.tags.list would return all the 'list' attributes of documents in mycollection
The answer was :
db.tags.findOne().list
If you need all of the list attributes across all documents in the tags collection, you may want to use the db.collection.find() method (MongoCollection::find() in PHP).
$tags = array();
foreach ($collection->find() as $document) {
if (empty($document['list'] || ! is_array($document['list'])) {
continue;
}
foreach ($document['list'] as $tag) {
$tags[] = $tag;
}
}
Offhand, if these documents had other fields that you didn't need, you could use the second argument to provide a projection to find() and limit the fields returned. In this case, { list: 1 } would do it.
Taking this a step further, if you wanted to quickly retrieve all of the unique tags across the list fields in the collection's documents, you could use the distinct database command (MongoCollection::distinct() in PHP.
Assuming the collection had the following documents:
> db.foo.insert({x:['a','b','c']})
> db.foo.insert({x:['a','c','d']})
The following method would execute the distinct database command and return the array [ "a", "b", "c", "d" ]:
$uniqueTags = $collection->distinct('list');
If you want to access your document directly:
document['list']
If you are accessing a collection:
db.collection.findOne({_id: ObjectId("51b972ebe4b075a9690bbc5b")}).list