Eloquent delete all rows with where statement - laravel

I want to delete all the votes where the flag "isOnly" is true which means the article is voted before the plenary session.
I have this code written, which deletes ALL the votes.
foreach($commision->articles as $article) {
$article->votes()->delete();
$article->update([
'isVoted' => false
]);
}
What is the right way to delete all the votes with the flag 'isOnly' == true

You can stack where methods with delete call
$article->votes()->where('isOnly', true)->delete();
One better solution would be to avoid the foreach all together so you run only one query
$articleIds = $commision->pluck('articles.id'); //if the articles are already loaded calling a collection method pluck()
$articleIds = $commision->articles()->pluck('id'); // if articles are not loaded calling a query builder method pluck()
Votes::whereHas('article', function($articleQueryBuilder) use($articleIds) {
$articleQueryBuilder->whereIn('id', $articleIds);
})->where('isOnly', true)->delete();
Article::whereIn('id', $articleIds)->update([
'isVoted' => false
]);
This will result in a faster processing of your delete() & update().

Here is one way to delete by condition
$article->votes()->get()->filter(function($item){
return $item->isOnly == true;
})->each(function($vote){$vote->delete();});
This statement will get all votes and apply filter funtion on votes which will give us votes which has isOnly == true rows. Then each function will delete returned votes.
This will help. :)

Related

How to access the nth item in a Laravel collection?

I guess I am breaking all the rules by deliberately making a duplicate question...
The other question has an accepted answer. It obviously solved the askers problem, but it did not answer the title question.
Let's start from the beginning - the first() method is implemented approximately like this:
foreach ($collection as $item)
return $item;
It is obviously more robust than taking $collection[0] or using other suggested methods.
There might be no item with index 0 or index 15 even if there are 20 items in the collection. To illustrate the problem, let's take this collection out of the docs:
$collection = collect([
['product_id' => 'prod-100', 'name' => 'desk'],
['product_id' => 'prod-200', 'name' => 'chair'],
]);
$keyed = $collection->keyBy('product_id');
Now, do we have any reliable (and preferably concise) way to access nth item of $keyed?
My own suggestion would be to do:
$nth = $keyed->take($n)->last();
But this will give the wrong item ($keyed->last()) whenever $n > $keyed->count(). How can we get the nth item if it exists and null if it doesn't just like first() behaves?
Edit
To clarify, let's consider this collection:
$col = collect([
2 => 'a',
5 => 'b',
6 => 'c',
7 => 'd']);
First item is $col->first(). How to get the second?
$col->nth(3) should return 'c' (or 'c' if 0-based, but that would be inconsistent with first()). $col[3] wouldn't work, it would just return an error.
$col->nth(7) should return null because there is no seventh item, there are only four of them. $col[7] wouldn't work, it would just return 'd'.
You could rephrase the question as "How to get nth item in the foreach order?" if it's more clear for some.
I guess faster and more memory-efficient way is to use slice() method:
$collection->slice($n, 1);
You can try it using values() function as:
$collection->values()->get($n);
Based on Alexey's answer, you can create a macro in AppServiceProvider (add it inside register method):
use Illuminate\Support\Collection;
Collection::macro('getNth', function ($n) {
return $this->slice($n, 1)->first();
});
and then, you can use this throughout your application:
$collection = ['apple', 'orange'];
$collection->getNth(0) // returns 'apple'
$collection->getNth(1) // returns 'orange'
$collection->getNth(2) // returns null
$collection->getNth(3) // returns null
you may use offsetGet since Collection class implements ArrayAccess
$lines->offsetGet($nth);
Maybe not the best option, but, you can get item from array inside collection
$collection->all()[0]

Eloquent to count with different status

It's possible to make one query to get total, sold & unsold in laravel eloquent?
$total_apple = Item::whereName('Apple')->count();
$sold_apple = Item::whereName('Apple')->whereStatus(2)->count();
$unsold_apple = Item::whereName('Apple')->whereStatus(1)->count();
Yes you can totally do that. You can use filter method on collection object returned by your Eloquent query.
$apples = Item::whereName('Apple')->get();
$soldApples = $apples->filter(function ($apple){
return $apple->status == 2;
});
$unsoldApples = $apples->filter(function ($apple){
return $apple->status == 1;
});
$soldApples and $unsoldApples contains the object of the items. You can then just use count($soldApples) and count($unsoldApples) to get their count.
filter method is against the collection object so there is no sql overhead.
There is no need run multiple queries or even fetch the entire results and use collection methods to loop through. Just use raw queries.
$apples = Item::whereName('Apple')
->selectRaw('COUNT(*) as total_apples,
SUM(status=2) as sold_apples,
SUM(status=1) as unsold_apples')
->first();
echo $apples->total_apples; // Outputs total apples
echo $apples->unsold_apples; // Outputs the unsold apples
echo $apples->sold_apples; // Outputs the sold apples
Since you are only doing simple counts though, you can use the query builder as well.
I would get all the items in one collection, then run the where statement on that collection. This should trigger a single Query.
$apples = Item::whereName('Apple')->get(); // This goes against SQL
$total_apple = $apples->count(); //This runs on the Collection object not SQL
$sold_apple = $apples->whereStatus(2)->count();
$unsold_apple = $apples->whereStatus(1)->count();

removing redundant queries from laravel / eloquent

In a laravel code base I'm working with there's this line of code:
if ($limit === '-1') {
$items = $query->get();
if ($items->count() < 1) {
return Paginator::make([], 0, Config::get('api.result_limit'));
}
return $query->paginate($items->count());
}
$query is a \Illuminate\Database\Eloquent\Relations\HasManyThrough object.
Anyway, the problem is that a SELECT table_name.* is being performed twice. $query->get() does it and then $query->paginate does a SELECT COUNT(*) (which itself is redundant since we already have the count from the first query) and a SELECT table_name.*, table_name.*. ie. it's just excessively redundant and I'm trying to remove that redundancy.
Here's what I tried.
return $items right after $query->get(). Unfortunately, doing so yielded the following error:
Call to undefined method Illuminate\Database\Eloquent\Collection::getCollection()
$query->paginate() returns a \MyApp\Services\Pagination\Paginator object which itself extends (\Illuminate\Pagination\Paginator) whereas $query->get() returns a \Illuminate\Database\Eloquent\Collection object.
If I could somehow convert \Illuminate\Database\Eloquent\Collection to \Illuminate\Pagination\Paginator I think that'd do the trick but if that is possible idk how.
Doing return $query->paginate(99999999) right after the if ($limit === '-1'). That works but I'd rather not specify a hard limit. I don't specify a limit when doing a SELECT with SQL I'm writing and I don't think I should have to specify one here either. I tried return $query->paginate(-1) but that gave an error.
Any ideas?

Merging multiple objects which uses same id

I'm trying to merge multiple objects (like Receipts, Reports, etc) with Collection->merge().
This is the code I used:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->merge($reports);
This is the result:
The above screenshot shows two elements, but the third element is missing because it has the same id (id: "1") as the first one. What I'm trying to achieve is to display all three of them as a collection.
EDIT:
I need the result to be objects (collection) because I also use the code on my view, where I check the class to determine what to display. Also, I use this function to sort the objects in the collection.
$collection->sort(function($a, $b)
{
$a = $a->created_at;
$b = $b->created_at;
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
});
I know that this is an old question, but I will still provide the answer just in case someone comes here from the search like I did.
If you try to merge two different eloquent collections into one and some objects happen to have the same id, one will overwrite the other. I dunno why it does that and if that's a bug or a feature - more research needed. To fix this just use push() method instead or rethink your approach to the problem to avoid that.
Example of a problem:
$cars = Car::all();
$bikes = Bike::all();
$vehicles = $cars->merge($bikes);
// if there is a car and a bike with the same id, one will overwrite the other
A possible solution:
$collection = collect();
$cars = Car::all();
$bikes = Bike::all();
foreach ($cars as $car)
$collection->push($car);
foreach ($bikes as $bike)
$collection->push($bike);
Source: https://medium.com/#tadaspaplauskas/quick-tip-laravel-eloquent-collections-merge-gotcha-moment-e2a56fc95889
I know i'm bumping a 4 years old thread but i came across this and none of the answers were what i was looking for; so, like #Tadas, i'll leave my answer for people who will come across this. After Looking at the laravel 5.5 documentation thoroughly i found that concat was the go-to method.
So, in the OP's case the correct solution would be:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->concat($reports);
This way every element in the Report collection will be appended to every element in the Receipts collection, event if some fields are identical.
Eventually you could shuffle it to get a more visual appealing result for e.g. a view:
$collection->shuffle();
Another way to go about it is to convert one of your collections to a base collection with toBase() method. You can find it in Illuminate\Support\Collection
Method definition:
/**
* Get a base Support collection instance from this collection.
*
* #return \Illuminate\Support\Collection
*/
public function toBase()
{
return new self($this);
}
Usage:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->toBase()->merge($reports);
You could put all collections in an array and use this. Depends on what you want to do with the collection.
$list = array();
$list = array_merge($list, Receipt::all()->toArray());
$list = array_merge($list, Report::all()->toArray());

Codeigniter Pagination: Run the Query Twice?

I'm using codeigniter and the pagination class. This is such a basic question, but I need to make sure I'm not missing something. In order to get the config items necessary to paginate results getting them from a MySQL database it's basically necessary to run the query twice is that right?
In other words, you have to run the query to determine the total number of records before you can paginate. So I'm doing it like:
Do this query to get number of results
$this->db->where('something', $something);
$query = $this->db->get('the_table_name');
$num_rows = $query->num_rows();
Then I'll have to do it again to get the results with the limit and offset. Something like:
$this->db->where('something', $something);
$this->db->limit($limit, $offset);
$query = $this->db->get('the_table_name');
if($query->num_rows()){
foreach($query->result_array() as $row){
## get the results here
}
}
I just wonder if I'm actually doing this right in that the query always needs to be run twice? The queries I'm using are much more complex than what is shown above.
Unfortunately, in order to paginate you must know how many elements you are breaking up into pages.
You could always cache the result for the total number of elements if it is too computationally expensive.
Yeah, you have to run two queries, but $this->db->count_all('table_name'); is one & line much cleaner.
Pagination requires reading a record set twice:
Once to read the whole set so that it can count the total number records
Then to read a window of records to display
Here's an example I used for a project. The 'banner' table has a list of banners, which I want to show on a paginated screen:
Using a public class property to store the total records (public $total_records)
Using a private function to build the query (that is common for both activities). The parameter ($isCount) we pass to this function reduces the amount of data the query generate, because for the row count we only need one field but when we read the data window we need all required fields.
The get_list() function first calls the database to find the total and stores it in $total_records and then reads a data window to return to the caller.
Remember we cannot access $total_records without first calling the get_list() method !
class Banner_model extends CI_Model {
public $total_records; //holds total records for get_list()
public function get_list($count = 10, $start = 0) {
$this->build_query();
$query = $this->db->get();
$result = $query->result();
$this->total_records = count($result); //store the count
$this->build_query();
$this->db->limit($count, $start);
$query = $this->db->get();
$result = $query->result();
return $result;
}
private function build_query($isCount = FALSE) {
$this->db->select('*, b.id as banner_id, b.status as banner_status');
if ($isCount) {
$this->db->select('b.id');
}
$this->db->from('banner b');
$this->db->join('company c', 'c.id = b.company_id');
$this->db->order_by("b.id", "desc"); //latest ones first
}
And now from the controller we call:
$data['banner_list'] = $this->banner_model->get_list();
$config['total_rows'] = $this->banner_model->total_records;
Things get complicated when you start using JOINs, like in my example where you want to show banners from a particular company! You may read my blog post on this issue further:
http://www.azmeer.info/pagination-hitting-the-database-twise/

Resources