Eloquent ORM get latest items query - laravel

I am trying to do get all the latest items and sort by id descending (i.e. get all items that were just added with a limit and offset).
So I did this:
$products = Product::all()
->slice($request->get('offset'))
->take($request->get('limit'))
->sortByDesc('id')
->toBase();
However it seems when I have more that that limit, then I dont have the right order. It gets me say 10 products but not sorted corrected. Any idea how to do this with Eloquent ORM?

You are probably intending to have the database handle the offset and skipping and ordering instead of pulling all the possible records then taking only what you want, then sorting them ... if you were going to do it your way you would need to sort before you skip and take, by the way.
Using the database to the the filtering and ordering:
$products = Product::skip($request->input('offset'))
->take($request->input('limit'))
->orderBy('id', 'desc')
->get();

I think the issue is you're using ::all() first, which returns all Product instances in a Collection, then using collection methods. Since these methods act in order of usage, you're slicing and offsetting before sorting, so you'll get the same products each time. Use proper Builder syntax to handle this properly and more efficiently:
$products = Product::offset($request->input("offset"))
->limit($request->input("limit"))
->orderBy("id", "DESC")
->get();
Because this is a Builder instance, the query will be compiled and executed according to your Database's grammar logic, and in a single query. There's nothing wrong in using Collection logic, you'd simply have to use the correct order of methods (sortByDesc() first, then slice(), then take()), but this is incredibly inefficient as you have to handle every Product in your database.

Related

How to Sort Data In a collection in Laravel

I have the following relations
Transaction Table is the parent table for ETransaction and ATransaction and belongs to transactions table
$transactionA= Transaction::with('tAccount')->has('tAccount')->get();
$transactionE= Transaction::with('tExchange')->has('tExchange')->get();
$collection = collect([$transactionE,$transactionA]);
$sorted = $collection->sortBy('created_at') do not work for me
The main problem you encounter isn't necessarily about sorting, but about building up the initial collection.
Right now, you make two collections, transactionA and transactionE. You then initialize a third collection collection that contains both transactionA and transactionE.
You can think of this as transactionA being a box of records, and transactionE being another box of records. What you want is one big box of records, but the current code puts both boxes in another, bigger box. When sorting the bigger box, all you sort is the order in which the smaller boxes end up in the bigger box.
What you presumably want instead, is to merge the contents of both boxes and sort the ensemble. You can do so by merging the two collections:
$collection = $transactionA->merge($transactionE);
I'm not sure why you even need separate queries. Stratadox's answer shows how to query, merge and sort, but you can do that in a single query using Eloquent:
$collection = Transaction::with(["tAccount", "tExchange"])
->has("tAccount")
->orHas("tExchange")
->orderBy("created_at")
->get();
In a single query, this will look for all Transaction records that have either a tAccount or tExchange record associated, sort it by the created_at timestamp and return it in a single call. Pushing the logic to the Collection class can be inefficient, so let the database handle it when possible.

Laravel - Shuffle Merge and Paginate Collection

I am struggling with a pretty difficult thing and hope you can help me out.
Right now I've got the following:
$ads = Ad::where('status', 1)
->whereIn('ad_type', [1, 2, 3])
->where('expire_at', '>', date('Y-m-d H:i:s'))
->where('special_ad', 'standard_ad')
->orderByRaw(DB::raw("FIELD(ad_type,2,3,1)"));
Info:
This is working because it is an Eloquent Collection and I can Paginate this (needed for my Infinite Scroll)
But now I want to shuffle the ad_types in itself, meaning:
ad_type 1 could have, let's say, 30 entries. They will all be returned in the usual order. I want to shuffle those 30 every time I run this query.
I thought about doing ->toArray(); but then again, no pagination (Pagination only works on Eloquent Queries right?)
Then I thought, hey, let's merge this.
But as soon as I did that, the returned collection is no longer an Eloquent Collection but a Support Collection (right? I am not 100% sure it is a Support Collection) thus the Pagination does not work anymore.
I read upon many posts as how to solve this problem, and figured out one solution may be to "create my own paginator instance"
But heck, I am not that good yet. I do really not know, even after studying the laravel documentation, how to create my own paginator.
Important Infos you might need:
Using Laravel 5.2
$ads are dynamical, meaning depending on the case, the requests sent with Ajax, the query might differ at a later point (something might get included).
Thank you very much for your time reading this and hopefully, you can help me and future readers by solving this particular problem.
Greetings, and a great weekend to all of you.
Firstly just to note:
Pagination does not only work for database queries. You can manually paginate using LengthAwarePaginator but manually is the keyword here. The query builder (not Eloquent) can do it automatically for you using paginate.
You can "shuffle" the results by doing something like
$ads = Ad::where('status', 1)
->whereIn('ad_type', [1, 2, 3])
->where('expire_at', '>', date('Y-m-d H:i:s'))
->where('special_ad', 'standard_ad')
->orderByRaw("FIELD(ad_type,2,3,1)")
->orderByRaw("RAND()");
This will order by the ad_type field first and order by a random number (different for every row) as a secondary sort.

doing a where() after a get()

I'm trying to add a new feature to an existing Laravel codebase and in that codebase there's this:
$hasGAP = (new \App\Models\Policy)->where('leadID', $leadId)
->where('policystatus', '!=', 'Canceled')
->get()->where('product.name', 'GAP Insurance')->count() > 1;
So this is doing an SQL query on the table referenced by the \App\Models\Policy model. It's doing WHERE policystatus != 'Canceled' and then it's getting the result. And then it's doing a WHERE on the result? That doesn't make sense to me.
Also, product.name isn't a column in the table. Indeed, it seems like the period (.) operator would be an illegal character..
Does this code actually work and if so what is it actually doing?
The ->get() ends the query and returns the results in a collection.
The subsequent ->where(..) and ->count() are then calls on the collection.
The dot notation is widely used in Laravel for getting sub fields of arrays, objects and similar data structures (example: array_get()) and works in ->where() (on a collection) as well.
So the posted code should work. I assume the Policy belongsTo (or hasOne) a product and the dot notation is used to search by the related product name.

Magento collection complex condition or union approach

Long story short, I need to get a collection that should be filtered using the following condition: where (A and (B or C) or (D and (E or F)).
I can do them separately using:
$collection = Mage::getModel('customModule/customModel')
->getCollection()
->addFieldToFilter('fieldA', valA)
->addFieldToFilter(
array('fieldB', 'fieldC'),
array(
array('eq' => valB),
array('eq' => valC)
)
);
But i don't know if I can make a single collection which would combine the two. I haven't found a "Magento" way of doing this. I also though of performing a union between them using
$collection1->getSelect()->union(array($collection2->getSelect()));
but it doesn't work. After the above statement, printing the $collection1->getSelect()->__toString() displays:
SELECT `main_table`.*SELECT `main_table`.* FROM `customModel` AS `main_table` WHERE..
which throws an error. For some reasons it concatenates the two select clauses rather then the two queries.
Can anyone suggest a fix in either direction? If I can either use Magento's native functions to perform the complex condition or how to fix the union?
I've also searched far & wide for this but unfortunately it is not implemented.
There are some problems with using UNION without DISTINCT, because the entity_id in a collection has to be unique otherwise you'll get an exception from some code layers bellow yours.
The Magento / Zend way:
It is possible by getting the underlying Zend_Select object and adding a union join in the Zend way. Meaning that the resulting query object(s) will be a Zend_Query which you will have to execute and fetch results on (the Zend way).
The pitfall of this is that the result is not a Magento Collection and the objects inside are not Magento model objects but Zend_Db_Table_Row.
The way I solved it:
I created multiple separate collections for which I called getAllIds() (this triggers a separate query which only fetches the ids) and then I merged the resulting arrays of ids and created one "master collection" WHERE entity_id IN(/*...*/).

query magento limit + order by rand()

function getIdModelsSliderJuwels(){
$collection = Mage::getModel("catalog/product")->getCollection();
$collection->addAttributeToFilter("attribute_set_id", 27);
$collection->addAttributeToSelect('modellijnen');
// $collection->setRandomOrder();
// $collection->getSelect()->limit( 5 );
return $collection;
}
Hi there,
I'd like to know how to set a limit to your query running in Magento because
$collection->getSelect()->limit( 5 ); doesn't work.
Also how to select randomly, $collection->setRandomOrder(); also doesn't work.
txs.
setRandomOrder does not work for collections of products, only for related products. You'll have to add it yourself with this code:
$collection->getSelect()->order(new Zend_Db_Expr('RAND()'));
A shortcut for setting both page size and number at the same time is:
$collection->setPage($pageNum, $pageSize);
As clockworkgeek said, use the $collection->getSelect()->order(...) method to randomize the order. To limit it to just $n number of items you can also use
$collection->getSelect()->limit($n);
try to use
$collection->setPageSize(5)->setCurPage(1);
Using ORDER BY RAND() to return a list of items in a random order will require a full table scan and sort. It can negatively affect performance on large number of rows in the table.
There are several alternative solutions possible of how to optimize this query. Magento provides a native solution for that.
The orderRand() method of Varien_Db_Select and the database adapter allows to specify a random order and leverage index for ORDER BY. Specify a name of some integer indexed column to be used in the ORDER BY clause, for example:
$collection->getSelect()->orderRand('main_table.entity_id');
See Varien_Db_Adapter_Pdo_Mysql::orderRand() for implementation details.

Resources