How can I build query by the lowest hierarchy level? - php

On the frontend there is a table that shows information about users and their position in the company.
There are 4 dropdowns for each level of company hierarchy:
groups, units, departments, teams.
These dropdowns are meant to filter out users to display in the table from the selected hierarchy.
Each dropdown has its unique identifier like group1, unit4, department10, team99.
Each hierarchy holds data about its ancestors. For example, team99 belongs to department10, which belongs to unit4 which belongs to group1.
Then if team99 is selected, it will be an array that contains all of its ancestors: unit4, department10 and group1.
From each of these selections I need to build a select query:
foreach ($selections as $selection) {
$fullQuery->orWhere(function($query) {
$query->where("group", "=", $selection->group) //"group1"
->where("unit", "=", $selection->unit) //"unit4"
->where("department", "=", $selection->department) //"department10"
->where("team", "=", $selection->team) //"team99"
})
}
The dropdowns in the frontend are free selections so a user can first choose the team and then choose group.
In this case the returned $selections would be an array containing group1 alone, and another array containing group1, unit4, deprtment10 and team99.
If I don't filter the results beforehand, the query builder would be both for where group = group1 or where all the others (group1, unit4, department10, team99).
But this is not what I want, I need to build the query with the lowest of selections.
How can I prepare the data such that it will ignore the unnecessary hierarchies?
Another example:
received input:
[
["group2"],
["group2", "unit11", "department50", "team10"],
["group2", "unit11", "department50", "team58"],
["group2", "unit10"],
["group5", "unit23"],
["group5", "unit23", "department101"]
]
Then I will need to build a query from the lowest hierarchies, in this case:
["group2", "unit11", "department50", "team10"],
["group2", "unit11", "department50", "team58"],
["group2", "unit10"],
["group5", "unit23", "department101"]

If you need to essentially remove all arrays that are subsets of some other array you can do:
$filteredSelections = collect($selections);
$filteredSelections = $filteredSelections->filter(
fn ($selection) => $filteredSelections->first(
fn ($item) => $selection !== $item && array_intersect($selection, $item) == $selection
) == null;
);

Related

Having a Laravel query with a 'join' and a 'where' in order to get a recipe based on allergen constraints?

I'm experimenting with Laravel 8.0x Eloquent and Query builder in order to generate a meal plan with recipes chosen by user input constraints. For example, the user inputs they are looking for Dinner (meal_type), Vegetarian (suitable_for), 6 people (feeds), for a meal plan of 2 days (days). The recipe ID is then pulled from the Database based on these constraints.
The input form is all drop down, except for allergens is which a checkbox (as users may have multiple allergens).
When it comes to allergens though, one recipe can have multiple allergens. There are two tables, Recipe and Recipe_Allergens. Recipe_Allergens has fields id, Recipe_ID, and Allergen_Description. The checkbox request requires an implode:
$request->merge([ 'allergens' => implode(',', (array) $request->get('allergens')) ]);
So if two allergens, 'Dairy' and 'Gluten' are checked, the result is saved as 'Dairy,Gluten'. However, I want it to loop through each individual allergen to check if the Recipe contains any of these allergens, and avoid selecting that Recipe_ID if it does.
The user inputs:
$meal = $request->meal_type;
$suited = $request->suitable_for;
$allerg = $request->allergens;
$feeds = $request->no_of_people;
$days = $request->no_of_days;
$allergenarray2 = explode(",", $allerg);
The Eloquent 'join' by the Recipe id and 'where' in order to get the Recipe_ID:
$recipenew = Recipe::join('recipe_allergens', 'recipe_allergens.recipe_id', '=', 'recipe.id')->where('recipe.suitable_for', $suited)->where('recipe_allergens.allergen_description', '!=', $allergenarray2)->where('recipe.feeds_total', $feeds)->get();
Inputting into DB table:
while ($x < $days){
$recidnew = $recipenew[$x]->id;
MealPlanDisplay::create([
'Recipe_ID' => $recipenew[$x],
'Day' => $recipeday,
'user_id' => $currentuserid,
]);
$x = $x + 1;
}
All of the constraints work except for the allergens. Does this sort of join work? It won't seem to accept the exploded array either, and only recognises the first value in it (for example Dairy,Gluten is only recognised as 'Dairy'). What can I do?
use whereNotIn() function instead of where condition and pass the array in the parameter along with the column name like this
$recipenew = Recipe::join('recipe_allergens', 'recipe_allergens.recipe_id', '=', 'recipe.id')->where('recipe.suitable_for', $suited)->whereNotIn('recipe_allergens.allergen_description', $allergenarray2)->where('recipe.feeds_total', $feeds)->get();

Laravel - how to group data by key and save to array?

I have table attribute_values(id, value, attr_group_id).
I need to return the collection grouped by key attr_group_id.
in clear php using ORM RedBean i made:
$data = \DB::table('attribute_values')->get();
$attrs = [];
foreach ($data as $k => $v){
$attrs [$v['attr_group_id']][$k] = $v['value'];
}
return $attrs;
I need same using Laravel, after this one:
$data = \DB::table('attribute_values')->get();
My table
id value attr_group_id
1 one 1
2 two 1
3 three 2
4 four 2
5 five 3
6 six 3
And i need result
Array(
[1] => Array
(
[1] => one
[2] => two
)
[2] => Array
(
[3] => three
[4] => four
)
[3] => Array
(
[5] => five
[6] => six
)
)
Fetch all data, and map it with attribute id of every row will work,
$data = \DB::table('attribute_values')->get();
$attrs = [];
foreach ($data as $key => $value) {
// -> as it return std object
$attrs[$value->attr_group_id][] = $value->value;
}
dd($attrs);
You can use the groupBy() function of collection as:
$data = \DB::table('attribute_values')->get()->groupBy('attr_group_id');
It merges records with same attr_group_id under this field's value as making key of the collection.
Doing all this in raw SQL will be more efficient, SQL database are quite good at these operations. SQL has a group by function, since you are overwriting value, i just get it out with max() (this seems weird, that you overwrite the value, do you actually just want unique results?).
DB::table('attribute_values')
->select('attr_group_id', DB::raw('max(value)'))
->groupBy('attr_group_id')
->get();
EDIT
Since the scope has changed, you can utilize Laravels Collection methods, that is opreations on a Collection.
DB::table('attribute_values')
->get()
->groupBy('attr_group_id')
->toArray();
Friends, this is a ready task that I needed !
I did it myself and you helped me. If anyone interested can read.
I'll explain to you why I needed this particular method. I am doing an online store with a clock and now there was a task to make filters and attributes for filters.
So there are three tables
attribute_groups table
attribute_products table
attribute_values
I need to display the Laravel widget on my .blade.php like as
{{ Widget::run('filter', 'tpl' => 'widgets.filter', 'filter' => null,]) }}
When i creating a new product in the admin panel.
I must to save the product id and attribute_id in attribute_products, but there can be as many attributes as possible for one product. so, if I'll use this option
$data = \DB::table('attribute_values')
->get()
->groupBy('attr_group_id')
->toArray();
I got result:
But! each new array starts with index 0. But I need an index that means its id. attr_group_id from table attribute_value for saving into attribute_products.
And after I see only one method for me.
$data = \DB::table('attribute_values')->get();
$attrs = [];
foreach ($data as $key => $value) {
$attrs[$value->attr_group_id][$value->id] = $value->value;
}
return $attrs;
and the result I was looking for
now you can see what's the difference and what was needed. Array index starts 1,2,3,4,5 and this index = attr_group_id. Unfortunately I could not initially ask the right question. thanks to all.
Laravel Version 5.8
So You need to Group the id
if You need in the Model Way I have created the Model as AttributeValue
$modelWay = \App\AttributeValue::get()
->groupBy('attr_group_id');
if You need in the DBWay I have created the table as attribute_values
$dbWay = \DB::table('attribute_values')
->get()
->groupBy('attr_group_id');
Both Will give the Same Result

laravel append wheres in a loop to perform AND filtering

I'm trying to build my own dynamic filtering for my Angular App and Laravel 5.1 API using $httpParamSerializer(params); and Laravel whereIns.
My goal is to pass a set of fields and values I want to filter on and drill down the records I need.
I loop through the fields in Laravel and perform whereIns, then group them into an array, removing duplicates. Unfortunately, this acts more as an OR than an AND, as each whereIn does a new search to match.
Frontend Query:
var filters = {
'review[]' : ['no', 'yes'],
'user_id[]' : [1]
};
HTTP URL: "http://dde-api.localhost/1.0/userquestions?review%5B%5D=no&review%5B%5D=yes&user_id%5B%5D=1"
DB:
Laravel:
$results = [];
// Loop through each field (`review`, `users`, etc..), then search thru array of params
foreach ($input as $filter_field => $filters_array) {
if ($this->validField($table, $filter_field)) {
$res = DB::table($table)->whereIn($filter_field, $filters_array)->get();
if (!in_array($res, $results)) {
array_push($results, $res);
}
I need the query to work as a multiple WHERE clause (AND, not OR) which loops through and appends where clauses to each field ($filter_field), then searches for matching field values.
So the result should be all yes and no records for user 1. Since user 1 doesn't have a record with review: yes, it uses the record from user_id: 4. This is bad.
How can I append multiple WHERE statements to one query as I loop through multiple fields?
Use Your loop like this
$dbTbOBJ = \DB::table("table_name")
// Loop through each field (`review`, `users`, etc..), then search thru array of params
foreach ($input as $filter_field => $filters_array) {
$dbTbOBJ->whereIn($filter_field, $filters_array);
}
$results = $dbTbOBJ->get()

How to KeyBy where multiple items have the same key

I am using Laravel Collections methods and am trying to key my query results (which are a collection) by the id. The problem is I have multiple entries with the same id, but point to different countries and I want to have all of the values, not just the last one.
Here is my code that i am using so far:
$allCountries = new Collection($allCountries);
$offerCountries = $allCountries->keyBy('id');
dd($offerCountries);
foreach ($offer as $o) {
$o->countries = $allCountries->get($o->id);
}
To explain, my query puts the results in $allCountries which contains ids and countries and those results looks something like this
id=>225, country=>US
id=>225, country=>IT
id=>3304, country=>NZ
Just to give you a quick idea. I want to key this by the id which results in $offerCountries. I then loop thru a previous Collection that contains offers which have a certain ID that relates to the country result by id. So for the offer 225, the countries it contains are US and IT. I loop thru each offer and set the countries object equal to all the $allCountries id that it equals. The problem I have here is keyBy overwrites the value and only takes the last one. I am hoping to get some results like this:
[
225 => countries: {'id' => 225, 'country' => 'US'}, {'id' =>
'225', 'country' => 'IT'}
3304 => ['id' => 3304, 'country' => 'NZ'],
]
Is there a laravel method to do this, or do I need to write my own keyBy so it does not overwrite. If so, how can I get started to write this method?
Thanks
Instead of using keyBy, use groupBy:
$countriesById = collect($allCountries)->groupBy('id');
You could use filter and create a custom filter
$filtered = $allCountries->filter(function ($item) use ($id) {
return $item->id == $id;
});
$filtered->all();

Get list of objects only when hasMany relations has element

I have 3 tables
Shops - id, place_id, name...
Products - id, shop_id, name ...
Product_Tag - product_id, tag_id ... (pivot table)
Tags - id....
And I would like to get results in array like this:
array [
0 => [
"id" => 1,
"name" => "Shop name",
"products" => [...]
]
]
but I would like to search it by place_id and tag name. Something like this:
$shops = Shop::where('place_id', 1)
->with(array('products' => function($query)
{
$query->whereHas('tags', function ($query) {
$query->where('slug', 'tagname1');
});
}))->get();
This is working okay. But if none of shop products has that tag, I would still get Shop object with empty products array. Everything is okay if in that shop, at least one product has that tag. I don't want to get shop if it has empty products list. Also I think it's overhead to foreach that array and search for empty arrays and then to remove shop object. Is there better way to don't fetch from database at all ?
You can nest your 'has' statements. See: http://laravel.com/docs/5.0/eloquent#querying-relations
So this should work:
$shops = Shop::where('place_id', 1)->whereHas('products.tags', function ($query) {
$query->where('slug', 'tagname1');
})->with(products.tags)->get();

Categories