Essentially is it possible to find documents based on there sub document using $in. For example say I had:
array(
'item.1',
'item.2',
'item.3
)
would return document that have:
{
item {
1: {
}
}
}
{
item {
2: {
}
}
}
{
item {
3: {
}
}
}
I know if I had one I could use db.inventory.find( { qty: { $exists: true, $nin: [ 5, 15 ] } } ) but how would I do that with a $in?
You can use a query of this form:
db.collection.find ( { $or : [
{"item.1":{$exists:true}},
{"item.2":{$exists:true}},
{"item.3":{$exists:true}}
] } );
This will return any document which has one or more of "item.X" in this case 1, 2 or 3 set.
Related
take a look at this table first then i'll explain.
parent {
id:number;
name:string;
}
child {
id:number;
name:string;
parent_id:number; //foreignKey to parent table
}
and this is the API request to do an update to backend
Request = {
id:1; //parent id
name:'update the name';
childs:[
{
id:1,
name:'child 1'
},
{
id:null, //if null its mean i have to create this child
name:'child 2'
}
]
}
so if the Request.childs have id on each item then i have to update the child, but if its doesn't have an id i have to create a new child.
And if child doesn't exist in Request.childs i have to delete it.
my question is how do i delete the previous child that doesn't exist in Request.childs ?
what i am doing now is i am deleting all the child that belongs to the parent_id and create a new child based on Request, but since child have soft delete on all the deleted child will get stacked in the database.
what currently i am doing
{
child::where('parent_id',Request->id)->delete();
foreach($Request->childs as $item){
child::create['name'=>$item->name,'parent_id'=>$Request->id];
}
}
what i probably i have to do
foreach($Request->childs as $item){
if($item->id == null){
child::create['name'=>$item->name,'parent_id'=>$Request->id];
}
child::find($item->id)->update['name'=>$item->name];
}
i just don't know how to delete the previous child record without deleting everything ?
This is the way I would do it:
public function patch(Request $request)
{
Child::whereNotIn('id', array_column($request->childs, 'id'))->delete();
Child::upsert([
$request->childs
], ['id'], ['name']);
}
If you don't want to soft delete the child you can apply forceDelete()
as below:
child::where('parent_id',Request->id)->forceDelete();
foreach($Request->childs as $item) {
if($item->id == null){
child::create['name'=>$item->name,'parent_id'=>$Request->id];
}
child::find($item->id)->update['name'=>$item->name];
}
Oh, it's big problem. I solved it for my project, but not very happy with this solution.
What you get
$parent->childs()->sync($request['childs'])
if you pass true as second value - null childs will be removed
How you can get it
Add this code to AppServiceProvider::boot() - it's not my code, just copy pasted from somewhere and slightly improved
HasMany::macro('sync', function (array $data, $deleting = true) {
/** #var HasMany $this */
$changes = [
'created' => [], 'deleted' => [], 'updated' => [],
];
$relatedKeyName = $this->getRelated()->getKeyName();
$current = $this->newQuery()->pluck($relatedKeyName)->all();
$castKey = function ($value) {
if (is_null($value)) {
return $value;
}
return is_numeric($value) ? (int) $value : (string) $value;
};
$castKeys = function ($keys) use ($castKey) {
return (array) array_map(function ($key) use ($castKey) {
return $castKey($key);
}, $keys);
};
$deletedKeys = array_diff($current, $castKeys(
Arr::pluck($data, $relatedKeyName))
);
if ($deleting && count($deletedKeys) > 0) {
$this->getRelated()->destroy($deletedKeys);
$changes['deleted'] = $deletedKeys;
}
$newRows = Arr::where($data, function ($row) use ($relatedKeyName) {
return Arr::get($row, $relatedKeyName) === null;
});
$updatedRows = Arr::where($data, function ($row) use ($relatedKeyName) {
return Arr::get($row, $relatedKeyName) !== null;
});
if (count($newRows) > 0) {
$newRecords = $this->createMany($newRows);
$changes['created'] = $castKeys(
$newRecords->pluck($relatedKeyName)->toArray()
);
}
foreach ($updatedRows as $row) {
$this->getRelated()->where($relatedKeyName, $castKey(Arr::get($row, $relatedKeyName)))
->update($row);
}
$changes['updated'] = $castKeys(Arr::pluck($updatedRows, $relatedKeyName));
return $changes;
});
You just need to get the ids from the database and compare
{
$childIds = child::where('parent_id',Request->id)->pluck('id')->toArray();
$flippedChildIds = array_flip($childIds);// we flip the keys and values for easy unsetting.
$childsToBeInsertedOrUpdated = [];
foreach($Request->childs as $item){
//add deleted at in case a child was deleted and need to be re activated
// if you dont want the child to be reactivated, remove "deleted_at" from both arrays
$childsToBeInsertedOrUpdated[] = ['id' => $item->id, 'name' => $item->name, 'deleted_at' => null];
//if exists, remove from the to be deleted childs
unset($flippedChildIds[$item->id]));
}
//only two request for delete and update/create (faster)
if ($flippedChildIds) {
Child::whereIn('id', array_keys($flippedChildIds))->delete();
}
if ($childsToBeInsertedOrUpdated) {
Child::upsert($childsToBeInsertedOrUpdated, ['id'], ['name', 'deleted_at']);
}
}
I am returning JSON to my frontend like this:
public function newFlavorOrders()
{
$orders = request()->user()->ordersPaid;
return response()->json(['flavor_orders' => $orders]);
}
and right now, that returns this to the frontend:
{ orders: [
{
color: "Green"
size: "Large",
order_products: [ {'itemNum': 3, 'imgUrl': "zera.jpg"}, {'itemNum': 5, 'imgUrl': "murto.jpg"} ]
},
{
color: "Blue"
size: "Large",
order_products: [ {'itemNum': 3, 'imgUrl': "mcue.jpg"}, {'itemNum': 5, 'imgUrl': "cloa.jpg"} ]
}
]
}
But I want to alter the controller PHP function to add a field to each order_products item. I have the imgURL, but I want to add a processedImgUrl and stub it with true right now. How can I add the field to the above php function when returning the JSON?
Without the dataset this may not be exactly accurate but the way to do this is either to perform an array push or do a foreach loop and add create the index to be appended.
For example:
public function newFlavorOrders()
{
// CREATE A NEW ARRAY TO ADD THE MODIFIED DATA TO
$modified = array();
$orders = request()->user()->ordersPaid;
// LOOP THROUGH AND ADD THE VALUE TO THE ITERATION
foreach($orders as $row) {
foreach($row['order_products'] as $val){
$modified = $val;
if(!empty($val['imgUrl'])){
$modified['processedImgUrl'] = TRUE;
} else {
$modified['processedImgUrl'] = FALSE;
}
}
}
return response()->json(['flavor_orders' => $modified]);
}
Something like this should work. You need to loop through the first array, then get down to the next level array (order_products).
public function newFlavorOrders()
{
$orders = request()->user()->ordersPaid;
$orders = $orders->map(function ($order) {
$order->order_products = $order->order_products->map(function ($products) {
$products['processedImgUrl'] = true;
return $products;
});
return $order;
});
return response()->json(['flavor_orders' => $orders]);
}
Let's say I have 2 functions that can call each other
public static function goToAction($action,$sender_id)
{
$actions = array();
$logic = file_get_contents('../../logic/logic.json');
$logic_array = json_decode($logic, true);
unset($logic);
if (!isset($logic_array[$action])) {
return false;
} else {
foreach ($logic_array[$action] as $action) {
$actions[] = self::parseActionType($action,$sender_id);
}
}
return $actions;
}
public static function parseActionType($actions,$sender_id)
{
$data = array();
foreach ($actions as $key => $action) {
switch ($key) {
case 'goto': {
$goto_actions = self::goToAction($action,$sender_id);
foreach ($goto_actions as $goto_action){
$data[] = $goto_action;
} break;
...
}
}
return $data;
}
and here is my json file:
"no_return": [
{ "text": "Должно быть: 1, 2, 3"},
{ "text": "1" },
{ "goto": "2nr", "no_return": true},
{ "text": "5" }
],
"2nr": [
{ "text": "2" },
{ "goto": "3", "no_return": true},
{ "text": "4"}
],
"3nr": [
{ "text": "3" }
],
it returns 12345 , and its right, but how can I make it return 123 if no_return is setted to true? Maybe function must return something?
foreach ($logic_array[$action] as $action) {
$actions[] = self::parseActionType($action,$sender_id);
if (!empty($action['no_return'])) { break; }
}
Using break inside of a loop stops it even if there are more element left, there is also continue this will end the current run and proceed with the next element.
I need to parse JSON which looks like this:
{
"mdfId":"282088127",
"mdfConcept":"ME 3400EG-12CS-M Switch",
"children":[
{
"mdfId":"007",
"mdfConcept":"Another item",
"children": [
// many more here
]
},
{
"mdfId":"008",
"mdfConcept":"Another one",
"children": [
{
"mdfId":"010",
"mdfConcept":"What I'm looking for!",
"children": [] // no children
}
]
},
// many more here
]
},
This is a recursive structure in which every element has mdfId, mdfConcept and children keys.
Say I need to find node with ID=010 within this structure. I don't know at which level it lies (e.g. it can be on top level, or several children nodes below).
My current approach is:
$mdfId = '010'; // what I'm loking for
foreach ($jsonResponse as $category) {
while (true) {
if ($category['mdfId'] == $mdfId) {
// we found it!
$categoryDevices[$mdfId] = $category['children'];
break 2;
}
if (!empty($category['children'])) {
next_cat:
if (is_null($category['children'])) {
break;
}
$category = array_shift($category['children']);
continue;
}
if (empty($category['children'])) {
goto next_cat;
}
}
}
But current approach misses some cases. How can I optimize this recursive loop so it checks all nodes on same level and each one accesible through any number of children keys?
An embarrassing feature of your JSON object is that, while each children member is an array of the "child" structure, the top level one is the object itself, so it's an obstacle to a really recursive approach.
We might workaround by turning the source JSON object into the same structure as nested levels, i.e.:
having $jsonResponse as original object
use ['children' => $jsonResponse] instead
This way, it should work with something like this:
$mdfId = '010'; // what I'm loking for
if ($result = look4id(['children' => $jsonResponse], $mdfId) {
$categoryDevices[$mdfId] = $result;
}
function look4id($source, $id) {
foreach ($source as $child) {
if ($child['mdfId'] == $id) {
return $source['children'];
} else {
if ($source['children']) {
return look4id($source['children'], $id);
}
}
}
}
So basically I wrote a function that didn't return anything, but rather populated a variable from arguments.
function findRecursiveArrayNodeById($id, $array, &$node) {
foreach ($array as $child) {
if (isset($child['mdfId']) && $child['mdfId'] == $id) {
$node = $child;
return;
}
if (!empty($child['children'])) {
findRecursiveArrayNodeById($id, $child['children'], $node);
}
}
}
Usage as follows:
$result = false;
findRecursiveArrayNodeById($mdfId, $category_json, $result);
if (!$result) {
println("did not find {$mdfId}");
continue;
}
{
"$and":[ {
"mobile_nos":{
"$regex":{
"regex":"^((?!Nagpur).)*$",
"flags":"i"
}
}
},
{
"full_name":{
"$regex":{
"regex":"^((?!pune).)*$",
"flags":"i"
}
}
}
]
}
Above MongoDB query doesn't yield any result because the mobile_nos field does not exist in the document.
So how can I change the query so that it returns a result if field doesn't exist in document?
You could try the following query which uses the $or operator for the logical condition that use the regex search if the the field exists OR don't use it if the field does not exist (with the $exists operator).
No need to specify the $and operator in the case since MongoDB provides an implicit AND operation when specifying a comma separated list of expressions. Using an explicit AND with the $and operator is necessary when the same field or operator has to be specified in multiple expressions:
{
"$or": [
{
"mobile_nos": {
"$regex": {
"regex": "^((?!Nagpur).)*$",
"flags": "i"
}
}
},
{ "mobile_nos": { "$exists": false } }
],
"full_name": {
"$regex": {
"regex": "^((?!pune).)*$",
"flags": "i"
}
}
}
Try using $or with $exists:
{
"$and":[ {
$or:[
{
"mobile_nos":{
"$regex":{
"regex":"^((?!Nagpur).)*$",
"flags":"i"
}
},
{
"mobile_nos":{
$exists: false
}
}
]
}
},
{
"full_name":{
"$regex":{
"regex":"^((?!pune).)*$",
"flags":"i"
}
}
}
]
}