Laravel: For each where clause - php

I'm trying to return a query that has an arbitrary amount of where clauses based on the number of tags a user submits.
//unknown number of ids to query on
$tag_ids = array(1,5);
//multiple joins with closure
$items = DB::table('archives_items_metadata')->join('tags', 'archives_items_metadata.tag_id', '=', 'tags.id')->join('archives_items', 'archives_items_metadata.archive_item_id', '=', 'archives_items.id')->join('items', 'archives_items.item_id', '=', 'items.id')
->where(function ($query) use ($tag_ids) {
foreach ($tag_ids as $tag_id)
{
$query->where('archives_items_metadata.tag_id', $tag_id);
}
})->get();
The result I get is an empty array even though when I try array(1) or array(5) by themselves, they both return the same item. What am I missing?
EDIT::
I'm looking to return items that have each of the tag ids specified. The reference of items to tags is stored on the archives_items_metadata table. How can I get the result I'm expecting, and what's the most efficient way to accomplish this?

You are looking to do a WHERE tag_id IN (1, 2, 3) style clause laravel has the whereIn($col, $vals) builder function.
->whereIn('archives_items_metadata.tag_id', $tag_ids)
search for "whereIn" in the official docs

$tag_count = count($tag_ids);
$items = DB::table('archives_items')->join('archives_items_metadata', 'archives_items_metadata.archive_item_id', '=', 'archives_items.id')->join('tags', 'archives_items_metadata.tag_id', '=', 'tags.id')
->whereIn('archives_items_metadata.tag_id', $tag_ids)->whereNull('archives_items_metadata.deleted_at')
->groupBy('archives_items_metadata.archive_item_id')->havingRaw('count(*)='.$tag_count->get();

Related

Laravel change relationship where clause if not rows are returned

I have the following query in my controller.
$items = Item::with(['subitems' => function($query) {
$query->where('language_id', '=', 1);
}])->get();
This is correctly getting me all items including subitems that have a language id of 1.
There are two things I would like to do with this though
Firstly, I need to return all subitems that have a distinct 'ref_id' value.
Secondly, I would like to give preference to the language id 1 but if none exist, use any language id.
So for example, I know this code won't work, but the sort of thing I am looking for is:
$items = Item::with(['subitems' => function($query) {
$subItems = $query->where('language_id', '=', 1)
->where('ref_id', 'is', 'distinct');
if($subItems->count() <= 0) {
$subItems = $query->where('ref_id', 'is', 'distinct');
}
}])->get();
Is this possible or is it a bit too complicated for Query Builder? Even if one of the two requests was possible, that would be great.
Try this:
$items = Item::with(['subitems' => function($query) {
$join = Subitem::select('ref_id', DB::raw('MIN(language_id) language_id'))
->groupBy('ref_id');
$sql = '(' . $join->toSql() . ') subitems_distinct';
$query->join(DB::raw($sql), function($join) {
$join->on('subitems.ref_id', 'subitems_distinct.ref_id')
->on('subitems.language_id', 'subitems_distinct.language_id');
});
}])->get();
We can use the fact that your preferred language_id is the lowest value and select the minimum.

Laravel 5.4 Eloquent get x random records from collection where property is null

Learning eloquent/laravel. I have a collection:
$regions = Region::with('neighbors')
->join('cards', 'cards.id', '=', 'regions.risk_card_id')
->get();
I have a value or rows:
$regionsPerUser = 8;
I am doing this to pull random records:
$regions = $regions->random($regionsPerUser);
But I need to filter this selection where $regions->user_id is not null.
Is there a way to filter the random call as part of chaining?
I tried this which does not work:
$regions = $regions->whereNotNull('user_id')->random($regionsPerUser);
And I am wondering if there is a way to neatly do this in one chained statement as opposed to going down the path of filter / map.
Why not just add your conditions to a sql-query:
$regions = Region::with('neighbors')
->join('cards', 'cards.id', '=', 'regions.risk_card_id')
->whereNotNull('user_id')
->inRandomOrder()
->limit($regionsPerUser)
->get();
If you already have a collection then you can do something like:
$regions = $regions
->filter(function ($value) {
return !is_null($value['user_id']);
})
->random($regionsPerUser);

How to get an array result set from Laravel where condition

I am trying to get a result set from a laravel eloquent query whereby I match a column against a list of values in an array.
$authenticated_operation_ids = AccessControl::where('user_id', '=', $user_id)
->where('entity_type_id', '=', $operation_entity_id)
->pluck('entity_access_id')->toArray();
$authenticated_operations = Operation::whereIn('id', $authenticated_operation_ids);
return view('page.index')->withOperations($authenticated_operations);
You can try it as:
$authenticated_operation_ids = AccessControl::where('user_id', '=', $user_id)->where('entity_type_id', '=', $operation_entity_id)->pluck('entity_access_id')->toArray();
$authenticated_operations = Operation::whereIn('id', $authenticated_operation_ids)->get();
return view('page.index')->withOperations($authenticated_operations);
Add get() at the end of the query.
1) pluck returns a single value from a single row. You want lists to get a single column from multiple rows. toArray may not be needed, as it returns an array of values
$authenticated_operation_ids = AccessControl::where('user_id', '=', $user_id)
->where('entity_type_id', '=', $operation_entity_id)
->lists('entity_access_id');
2) You're forgetting to actually retrieve the rows in your second line:
$authenticated_operations = Operation::whereIn('id', $authenticated_operation_ids)
->get();
You have to call get() function on the result set to obtain results. The modified code will be like
$authenticated_operation_ids = AccessControl::where('user_id', '=', $user_id)->where('entity_type_id', '=', $operation_entity_id)->get()->pluck('entity_access_id');
$authenticated_operations = Operation::whereIn('id', $authenticated_operation_ids)->get();
return view('page.index')->withOperations($authenticated_operations);
or you can use a cursor to process it.

Selecting and ordering data from a query using Laravel Eloquent

I am trying to use Laravel and eloquent to return results based on the following query.
$blogPosts = BlogPosts::with('blog_user', 'blog_categories', 'blog_comments', 'tags')
->orderBy('created_at', 'desc')
->paginate(5);
Ok, so that is fine, it return exactly what it should, all the blog posts with associated relations to other tables.
What I now want to do is return only the $blogPosts where a tag is clicked by the user. So let's say there is a tag "PHP", so I pass in that value as $tag to the method. I then have something like this.
public function tag_search($tag)
{
$blogPosts = BlogPosts::with('blog_user', 'blog_categories', 'blog_comments', 'tags')
->where('tags', $tag)
->orderBy('created_at', 'desc')
->paginate(5);
$categories = BlogCategories::with('blog_posts')->get();
$data = array('blogPosts' => $blogPosts, 'categories' => $categories,
);
return view('blog.index')->with($data);
}
Now my issue is actually relatively simple I guess, if the where clause was a column in the BlogPosts table it would work, I know this because I tried that.
However the above won't work as is, I can only use;
->where('x', y)
Where x is a field in the BlogPosts table. I want to return a set of values where the submitted $tag is the same as one associated to the tags attached to the blog posts.
Make sense? I think I am over thinking it the point I am just not thinking now :)
Add the column field 'tags' to your table and then these queries:
$blogPosts = BlogPosts::with('blog_user', 'blog_categories', 'blog_comments', 'tags')
->where('tags', '=', $tag)
->orderBy('created_at', 'desc')
->paginate(5);
$categories = BlogCategories::with('blog_posts')->get();
return view('blog.index', compact ('categories', 'blogposts'));
Ok so the answer was to use whereHas like this.
$blogPosts = BlogPosts::whereHas('tags', function ($query) use ($tag) {
$query->where('name', $tag);
})->orderBy('created_at', 'desc')
->paginate(5);
That returns a search based on the associated elements.

Laravel whereIn with a where clause on each array item

Say I have a user object (which belongsToMany groups) and I'm doing a whereIn with an array of their respected ids like so:
whereIn('user_id', $group->users->modelKeys())
I need to, however, set a condition where I only pull data from each array item based on a condition of the group_user pivot table, "created_at" (which is basically a timestamp of when that user was added to the group).
So I need something like this:
whereIn('user_id', $group->users->modelKeys())->whereRaw('visits.created_at > group_user.created_at')
That doesn't work though because it's not doing the whereRaw for each array item but it's doing it once for the query as a whole. I might need to do something like a nested whereIn but not sure if that'll solve it either. Thoughts?
My full query as it is now:
$ids = $group->users->modelKeys();
return DB::table('visits')->whereIn('user_id', function($query) use ($ids) {
$query->select('user_id')->from('group_user')->whereIn('group_user.user_id', $ids)->whereRaw('visits.created_at > group_user.created_at');
})->sum("views");
Ok got it to work using nested loops instead:
$visits = DB::table('visits')->whereIn('user_id', $group->users->modelKeys())->get();
$sum = 0;
foreach($group->users as $user) {
foreach($visits as $visit) {
if($visit->user_id == $user->id) {
if($visit->created_at >= $user->pivot->created_at) {
$sum += $visit->views;
}
}
}
}
return $sum;
Would still like to see if it's possible to do it in a single query, no array looping.
Solved it! The foreach loop approach was making calls take waaaay too long. Some queries had over 100k records returning (that's a lot to loop through) causing the server to hang up. The answer is in part a big help from Dharmesh Patel with his 3rd edit approach. The only thing I had to do differently was add a where clause for the group_id.
Here's the final query (returns that 100k results query in milliseconds)
//Eager loading. Has overhead for large queries
//$ids = $group->users->modelKeys();
//No eager loading. More efficient
$ids = DB::table('group_user')->where('group_id', $group->id)->lists('user_id');
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.user_id')->on('visits.created_at', '>=', 'group_user.created_at');
})->whereIn('group_user.user_id', $ids)->where('group_id', $group->id)->sum('views');
Have you considered using a foreach?
$users = whereIn('user_id', $group->users->modelKeys());
foreach ($users as $user) {
// do your comparison here
}
I guess you need to use JOINS for this query, following code may take you in right direction:
$ids = $group->users->modelKeys();
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.user_id')
->whereIn('group_user.user_id', $ids)
->whereRaw('visits.created_at > group_user.created_at');
})->sum("views");
EDIT
$ids = $group->users->modelKeys();
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.user_id');
})->whereIn('group_user.user_id', $ids)
->whereRaw('visits.created_at > group_user.created_at')->sum("views");
EDIT
$ids = $group->users->modelKeys();
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.id') // group_user.id from group_user.user_id as per the loop
->on('visits.created_at', '>=', 'group_user.created_at');
})->whereIn('group_user.user_id', $ids)
->sum("views");

Categories