Arbitrary number of whereHas clauses - laravel

I have a product with several skus. Each sku has many attributes.
Each attribute has a type which could be something like "size" or "color", and a value, which would be something like "M", or "black".
Each product has an arbitrary number of attributes, which I've made selectable in a form.
Now, I have a list of each attribute and its value.
[
'size' => 'M',
'color' => 'black',
]
Great. Now I have to fetch the SKU(s) which correspond to these particular attributes.
For example, this works when I have just ONE attribute.
$product->skus->whereHas('attributes', function($query) {
return $query->where(['type' => 'size', 'value' => 'M']);
})->get();
But how do I build a query that returns only the skus that have attributes which match ALL of the qualifications?
And remember, these attributes are arbitrary in number. I can't hardcode it; I just have to pass the array of type-value pairs.
I figured out how to do this manually – but again it doesn't allow for the arbitrary number of attributes. Plus, it's clunky. Something like this:
$product->skus->whereHas('attributes', function($query) {
return $query->where(['type' => 'size', 'value' => 'M']);
})->whereHas('attributes', function($query) {
return $query->where(['type' => 'color', 'value' => 'white']);
})->get();
Edit: The following code appears to work but it's clunky as hell. There must be a better way to do it?
$list = $this->skus()->with('attributes');
foreach ($attrs as $type => $value) {
$list->whereHas('attributes', function ($query) use ($type, $value) {
return $query->where(['type' => $type, 'value' => $value]);
});
}
return $list->first();

The way that you have done it seems to be the cleanest way of doing what you asked for.
Remember that Eloquent is just an abstraction of SQL.
If your writing this in SQL, it would be something like:
select * from skus
where exists (select * from attributes where sku_id = skus.id and type = ? and value = ?)
and exists (select * from attributes where sku_id = skus.id and type = ? and value = ?)
and exists (select * from attributes where sku_id = skus.id and type = ? and value = ?)
...
This is essentially what you have already done with Eloquent in your question:
$list = $this->skus()->with('attributes');
foreach ($attrs as $type => $value) {
$list->whereHas('attributes', function ($query) use ($type, $value) {
return $query->where(['type' => $type, 'value' => $value]);
});
}
return $list->first();

Related

Best Way to Count Record Many Table - Laravel

I want to display the number of records from several tables at once in one view.
I've tried it using eloquent count.
public function index(){
$order = Order::count();
$owner = Owner::count();
$room = Room::count();
$member = Transaction::where([
['status', 'waiting'],
['type', 1]
])->count();
$highlight = Transaction::where([
['status', 'waiting'],
['type', 2]
])->count();
return view('admin.index', [
'order' => $order,
'owner' => $owner,
'room' => $room,
'member' => $member,
'highlight' => $highlight
]);
}
Is there a better way?
You could also use view-composers. But this is not better, just different. I mean you have to query anyway, so why do you think there should be a better way?

Laravel insert or update multiple rows

Im new in laravel, and im trying to update my navigation tree.
So i want to update my whole tree in one query without foreach.
array(
array('id'=>1, 'name'=>'some navigation point', 'parent'='0'),
array('id'=>2, 'name'=>'some navigation point', 'parent'='1'),
array('id'=>3, 'name'=>'some navigation point', 'parent'='1')
);
I just want to ask - is there posibility in laravel to insert(if new in array) or update my current rows in database?
I want to update all, because i have fields _lft, _right, parent_id in my tree and im using some dragable js plugin to set my navigation structure - and now i want to save it.
I tried to use
Navigation::updateOrCreate(array(array('id' => '3'), array('id'=>'4')), array(array('name' => 'test11'), array('name' => 'test22')));
But it works just for single row, not multiple like i tried to do.
Maybe there is another way to do it?
It's now available in Laravel >= 8.x
The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of columns that should be updated if a matching record already exists in the database:
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);
I wonder why this kind of feature is not yet available in Laravel core (till today). Check out this gist The result of the query string would look like this: here
I am putting the code here just in case the link breaks in the future, I am not the author:
/**
* Mass (bulk) insert or update on duplicate for Laravel 4/5
*
* insertOrUpdate([
* ['id'=>1,'value'=>10],
* ['id'=>2,'value'=>60]
* ]);
*
*
* #param array $rows
*/
function insertOrUpdate(array $rows){
$table = \DB::getTablePrefix().with(new self)->getTable();
$first = reset($rows);
$columns = implode( ',',
array_map( function( $value ) { return "$value"; } , array_keys($first) )
);
$values = implode( ',', array_map( function( $row ) {
return '('.implode( ',',
array_map( function( $value ) { return '"'.str_replace('"', '""', $value).'"'; } , $row )
).')';
} , $rows )
);
$updates = implode( ',',
array_map( function( $value ) { return "$value = VALUES($value)"; } , array_keys($first) )
);
$sql = "INSERT INTO {$table}({$columns}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}";
return \DB::statement( $sql );
}
So you can safely have your arrays inserted or updated as:
insertOrUpdate(
array(
array('id'=>1, 'name'=>'some navigation point', 'parent'='0'),
array('id'=>2, 'name'=>'some navigation point', 'parent'='1'),
array('id'=>3, 'name'=>'some navigation point', 'parent'='1')
)
);
Just in case any trouble with the first line in the function you can simply add a table name as a second argument, then comment out the line i.e:
function insertOrUpdate(array $rows, $table){
.....
}
insertOrUpdate(myarrays,'MyTableName');
NB: Be careful though to sanitise your input! and remember the timestamp fields are not touched. you can do that by adding manually to each arrays in the main array.
I've created an UPSERT package for all databases: https://github.com/staudenmeir/laravel-upsert
DB::table('navigation')->upsert(
[
['id' => 1, 'name' => 'some navigation point', 'parent' => '0'],
['id' => 2, 'name' => 'some navigation point', 'parent' => '1'],
['id' => 3, 'name' => 'some navigation point', 'parent' => '1'],
],
'id'
);
Eloquent Style
public function meta(){ // in parent models.
return $this->hasMany('App\Models\DB_CHILD', 'fk_id','local_fk_id');
}
.
.
.
$parent= PARENT_DB::findOrFail($id);
$metaData= [];
foreach ($meta['meta'] as $metaKey => $metaValue) {
if ($parent->meta()->where([['meta_key', '=',$metaKey]] )->exists()) {
$parent->meta()->where([['meta_key', '=',$metaKey]])->update(['meta_value' => $metaValue]);
}else{
$metaData[] = [
'FK_ID'=>$fkId,
'meta_key'=>$metaKey,
'meta_value'=> $metaValue
];
}
}
$Member->meta()->insert($metaData);
No, you can't do this. You can insert() multiple rows at once and you can update() multiple rows using same where() condition, but if you want to use updateOrCreate(), you'll need to use foreach() loop.
I didn't find a way to bulk insert or update in one query. But I have managed with only 3 queries. I have one table name shipping_costs. Here I want to update the shipping cost against the shipping area. I have only 5 columns in this table id, area_id, cost, created_at, updated_at.
// first get ids from table
$exist_ids = DB::table('shipping_costs')->pluck('area_id')->toArray();
// get requested ids
$requested_ids = $request->get('area_ids');
// get updatable ids
$updatable_ids = array_values(array_intersect($exist_ids, $requested_ids));
// get insertable ids
$insertable_ids = array_values(array_diff($requested_ids, $exist_ids));
// prepare data for insert
$data = collect();
foreach ($insertable_ids as $id) {
$data->push([
'area_id' => $id,
'cost' => $request->get('cost'),
'created_at' => now(),
'updated_at' => now()
]);
}
DB::table('shipping_costs')->insert($data->toArray());
// prepare for update
DB::table('shipping_costs')
->whereIn('area_id', $updatable_ids)
->update([
'cost' => $request->get('cost'),
'updated_at' => now()
]);
in your controller
use DB;
public function arrDta(){
$up_or_create_data=array(
array('id'=>2, 'name'=>'test11'),
array('id'=>4, 'name'=>'test22')
);
var_dump($up_or_create_data);
echo "fjsdhg";
foreach ($up_or_create_data as $key => $value) {
echo "key ".$key;
echo "<br>";
echo " id: ".$up_or_create_data[$key]["id"];
echo "<br>";
echo " Name: ".$up_or_create_data[$key]["name"];
if (Navigation::where('id', '=',$up_or_create_data[$key]["id"])->exists()) {
DB::table('your_table_ name')->where('id',$up_or_create_data[$key]["id"])->update(['name' => $up_or_create_data[$key]["name"]]);
}else{
DB::insert('insert into your_table_name (id, name) values (?, ?)', [$up_or_create_data[$key]["id"], $up_or_create_data[$key]["name"]]);
}
}

Yii2 hasMany without link fields

User model has follow relation:
public function getWorkload() : ActiveQuery
{
return $this->hasMany(ScheduleWorkload::className(), ['staff_id' => 'id']);
}
Find method:
$staffs = User::find()
->alias('u')
->joinWith(['workload as uw' => function($q) {
$q->select(['uw.staff_id', 'uw.date', 'uw.time_ranges']);
}], true)
->select([
'u.id',
'CONCAT(u.first_name, \' \', u.last_name) as name',
'u.first_name',
'u.last_name',
'u.undelivered_messages',
])
->where(['u.is_staff' => 1])
->asArray()
->all()
;
I need get data without uw.staff_id in result set? Is it possible without post-processing?
UPDATE:
Result set that I have
I need "workload" as array parameter but do not use post-processing and just exclude "staff_id" from result set.
Raw sql:
SELECT `u`.`id`, `u`.`undelivered_messages`
FROM `user` `u`
LEFT JOIN `schedule_workload` `uw` ON `u`.`id` = `uw`.`staff_id`
WHERE `u`.`is_staff`=1
You can't do in Yii without any post-processing because ActiveQuery will search for foreign key in nested query result to build nested arrays for joined relation.
The most convenient way for you is to use ArrayHelper:
$staffs = User::find()
->alias('u')
->joinWith(['workload')
->where(['u.is_staff' => 1])
->all();
return \yii\helpers\ArrayHelper::toArray($staffs, [
User::className() => [
'id',
'first_name',
'last_name',
'name' => function ($user) {
return $user->first_name . ' ' . $user->last_name
},
'undelivered_messages',
'workload',
],
ScheduleWorkload::className() => [
'date',
'time_ranges',
'comment'
]
], true);

how to build fluent query by array of criterias in Laravel

If I store in DB criterias what I want to use to build and filter queries - how to build query with Laravel Fluent Query Builder? Maybe you can give advice for refactoring this array for adding OR/AND to make complex filter by such conditions? Thanks!
For example if I read from database these criteria and made array:
$array = array(
'0' => array(
'table' => 'users',
'column' => 'name',
'criteria' => 'LIKE'
'value' => 'John'
),
'1' => array(
'table' => 'groups',
'column' => 'name',
'criteria' => 'NOT LIKE'
'value' => 'Administrator'
),
...
)
If you are really set on doing it your way, make a switch statement to set the flag for the index of the array
switch ($query)
{
case 0:
$this->get_data($x);
break;
case 1:
$this->get_data($x);
break;
case 2:
$this->get_data($x);
break;
default:
Return FALSE;
}
public function get_data($x){
$value = DB::table($array[$x]['table'])
->where($array[$x]['name'], $array[$x]['criteria'], $array[$x]['value'])
->get();
Return $value;
}
However, I would not advise this at all. Instead of storing the query filters in an array I feel you should just make them methods in your model. This will help with readability, modularity, and having reusable code.
public function get_user_by_name($name){
$user = DB::table('users')->where('name', 'LIKE', '%'.$name.'%')->get();
return $user;
}
public function get_group_by_name($name, $criteria = 'LIKE'){
$groups = DB::table('groups')->where('name', $criteria, '%'.$name.'%')->get();
return $groups;
}
http://laravel.com/docs/database/fluent

Codeigniter - Sending a query result_arry() to a View for form_dropdown

I am using form_dropdown() and have a a problem below:
The form code is:
echo form_dropdown($level,$level_options,'1');
It works when I use
$level_options = array(
'1' => 'Grade 6',
'2' => 'Grade 7'
);
but not when I send a $data['levels'] from controller to view
For reference, the model database retrieve code is:
public function getAllLevelNames() {
$query = $this->db->query("SELECT level_description from levels ORDER BY level_description");
return $query->result_array();
}
The Problem
The problem is I get a dropdown pick list with:
0
Grade 6
1
Grade 7
The indexes are greyed out.
How do I get rid of the indexes?
Thanks in advance!
P.S.
I seem to have the form working now with a data['levels'] sent to the view. Now, the following code in my view seems to return "null" to my controller. Any ideas why please?
$level = array(
'name' => 'level',
'id' => 'level',
'value' => '1',
'maxlength' => '50',
'size' => '50',
'style' => 'width:50%',
);
$level_options = $levels;
echo "<p>Level: ";
echo form_dropdown($level,$level_options,'1');
Thanks!
You'll need to loop through your results_array and create a new array which is formatted correctly.
$query = $this->db->query("SELECT level_description from levels ORDER BY level_description");
$for_dropdown = array();
foreach ($query->result_array() as $row) {
$for_dropdown[$row->level_description] = $row->level_description;
}
return $for_dropdown;
Also I'm not sure how your levels table is structured, but usually you'll have an ID of some sort, which will be the primary key. If you do have that, you can include it in your query and have something like this instead:
$query = $this->db->query("SELECT id, level_description from levels ORDER BY level_description");
... // other code
$for_dropdown[$row->id] = $row->level_description;

Resources