Laravel - Single query then splitting Vs Two queries - php

I am using laravel framework and I need to get 2 arrays, one with premium themes and one with free themes.. so I would do:
$premium_themes = \App\Theme::where('premium', '=', '1')->get();
$free_themes = \App\Theme::where('premium', '=', '0')->get();
This will work Ok, but will perform two queries on the database. Since I'm an optimization geek, I think it might be better to have a single query... which I would get all themes by using:
$themes = \App\Theme::all();
And then I'd to process this in php to split based on the theme premium property.
So I have 2 questions:
1) A single query is better than 2 queries in this case, or am I over-thinking this?
2) Is there a fast simple way to split the resulting collection into two collections based on the premium property? (I know Laravel has many shortcuts but I'm still new to the framework)

Single query would be better as both of the queries will go over all the rows in the database. Except the 2 queries to split them will go over them for a second time.
You can simply filter them like so;
The simple one line solution $themes = \App\Theme::all()->groupBy('premium');.
Or into separate collections if you need to filter by another element etc just add more to the following;
$themes = \App\Theme::all();
$premium = new Collection;
$free = new Collection;
$themes->each(function ($item) use ($premium, $free){
if($item->premium == '1'){
$premium->push($item);
}
else {
$free->push($item);
}
});
And your items will be filtered into the relevant Collection. Be sure you use the Collection class at the top.

The only reason I can think to keep it as separate queries would be if you need to paginate the data - not something you can do easily if its all mixed together.
A "short cut" would be to use the collection filter() method, I put short cut in quotes because it's not short per-se, more syntatic sugar - but Larvel is nothing if not full of sugar so why not?
Code would look something like this:
$allThemes = \App\Theme::all();
$premiumThemes = $allThemes->filter(function($theme)
{
return $theme->premium;
});
$freeThemes = $allThemes->filter(function($theme)
{
return !$theme->premium;
});
Edit: I'd recommend using Matt Burrow's answer, but I'll leave mine here as the solution is different.

Related

Laravel 8 Dynamic relationships

I'm using Laravel 8. I have relationships strings but I don't how to load the relationships inside a loop. I'd like to explode() my relationships on the dot and load each relationship to get the column
$userPostTitle = 'post.title';
$userAvatarFilename = 'profil.avatar.filename';
The idea is to use explode() on the dot explode('.', $userPostTitle) and add bracket object dynamically for each relationship :
// for the userPostTitle we need two level
$postTitle = $user->{explode[0]}->{explode[1]};
// for the userAvatarFilename we need three level
$avatarFilename = $user->{explode[0]}->{explode[1]}->{explode[2]};
How it's possible to add dynamically the exploded relationship ? {rel1}->{rel2}->{rel3} etc... Maybe there is a better solution than using explode()
You have to keep in mind that your solution possibly does a lot of queries if you haven't preloaded the relations beforehand. However I think you can use the Laravel helper object_get() for your problem:
$relation = 'profile.avatar.filename';
$avatarFilename = object_get($user, $relation);
// Which roughly translates to calling `$user->profile->avatar->filename`
It also accepts a third parameter which is a default value if the property turns out to be null.

How to replace collection in query result Mongo

I queried to get info from a table with a manytomany relationship like this
$userList = UserListing::where('user_id', $user->id)->with("objects")->paginate(10);
Now, i want to limit the amount of results in the "Objects" table, but at the same time i want to know how many objects are in total.
$userList = UserListing::where('user_id', $user->id)->with(["objects"=> function($query) {
$query->take(2);
}])->paginate(10);
But by doing this, i can't get the total of objects since i limited it to 2, then i tried to process the info like this
$userList = UserListing::where('user_id', $user->id)->with("objects")->paginate(10);
foreach ($userList as $key => $value) {
$l = count($value["objects"]);
$value["objects"] = $value["objects"]->take(2);
$value["number_objects"] = $l;
}
But apparently this did not replace the collection value["objects"], since it still returned 3 objects, despite supposedly being reduced with $value["objects"] = $value["objects"]->take(2);. How can i replace the collection with the reduced one?
So, i kept investigating, and noted that userList was a LengthAwarePaginator object, which by property apparently is inmutable in its original fields(Meaning you can add new ones, but not delete/modify the already existent). Knowing this, i searched a little more and found this answer:
https://stackoverflow.com/a/49133519/7228093
Which basically creates a new LenghtAwarePaginator from the original one, allowing you to modify the fields. If someone finds in this situation, this may be a good option(The transform method of collections did not work by the way, only this one).

Searching model with multiple 'orwhere' clauses

I have a function that takes a string and does a (very broad) match for it against multiple fields (aka. almost all of my DB fields in the table). This seems somewhat kludgy, but it works; however, this is not my primary concern at the moment.
Is it possible to: show which 'orwhere' returned the record into collection? I would like to show (on the results view) what part of the record the string matched.
$apps = Application::all();
$apps->where('bill_company_name', 'like', '%'.$request->filter.'%');
$apps->orwhere('bill_address', 'like', '%'.$request->filter.'%');
$apps->orwhere('bill_city', 'like', '%'.$request->filter.'%');
...
$apps = $apps->paginate();
$apps->withPath('custom/url');
return $apps;
I know I could probably do this on the view (via some more code grepping the filter against the record again), but this option sounds even more laborious.
Thank you!
You could do:
$records = Model::query();
$records = $records->where(‘field’, ‘value’);
…
$records_where = $records->orWhere(‘field’, ‘value’)->get();
if($records_where->isNotEmpty()){
// save into array this one that matched
}
$records = Model::query();
And then on view iterate over the ones that matched, but this process may take quiet a bit if you have too many fields…
In your model,
public function getMatch($str) {
if (strpos($this->bill_address, $str) !== false) {
return "bill_address";
} elseif (...) { ... }
}
Not really answers the question, so might be slightly off-topic, but might help you in the future: as you already noticed that what you are doing becomes a bit messy this way, i recommend solving the problem with a different technology. For example the lucene search engine (used by Solr and elastic-search) offers a debug functionality that can be used to show in detail how the score of a returned record was composed, so you can see what part of the query hit.
And its much faster :)
https://lucene.apache.org/solr/guide/6_6/common-query-parameters.html#CommonQueryParameters-ThedebugParameter
Can you try
orwhere -> orWhere
https://laravel.com/docs/5.5/queries#where-clauses

How to bulk insert with RedBeanPhp?

I was hoping for an example on how to bulk insert new "beans" in readbeanphp without looping over each instance.
It shows an example creating and saving a beans here: http://redbeanphp.com/manual/create_a_bean
It makes mention of storeAll($beans) method, but I am unsure exactly how I am suppose to format the data in $beans.
I have tried googling for this and can not find anything related to bulk inserts. Maybe I have searched for the wrong terms.
I am new to this ORM, any help with would appreciated, thanks!
You are definitely right on track. Create a new bean using $bean=R::dispense('bean'); or multiple beans as an array $beans=R::dispense('bean',5);
Then you populate the beans with data:
$bean->title='Hello World!';
//or with an array
$beans[0]->title='Hello World!';
$beans[1]->title='Hello World! Bean 1';
//etc
Then store the bean(s):
R::store($bean);
//or
R::storeAll($beans);
All the beans must be the same type if you have multiples as far as I know, so you can do something like:
$beans=array();
$beans[]=R::dispense('bean');
$beans[]=R::dispense('bean');
$beans[0]->title='Hello World!';
$beans[1]->title='Hello World!1';
R::storeAll($beans);
I could be wrong about that though. The main thing is that this is all a typical ORM, but redbean also supports regular SQL if you need to use it. Hope that helps!
Some real data behind this approach.
FIRST APPROACH.
foreach item found
$bean = R::dispense('bean');
$bean->title = "hello";
R::store("bean");
time taken for 5660 rows = 43s on my mac
SECOND APPROACH.
$beans=array();
$beans[]=R::dispense('bean');
$beans[]=R::dispense('bean');
$beans[0]->title='Hello World!';
$beans[1]->title='Hello World!1';
R::storeAll($beans);
For 5660 rows, 46s. The storeAll is where all the time is. So its taking ages to store these beans.
THIRD APPROACH
$beans=R::dispense('bean',5560);
for loop
$bean[$i]->title = "hello world";
end for
R::storeAll($beans);
For 5660 rows 45s.
Result. None of these approaches are any quicker. : (
RedBean Transactions didn't seem to make this any quicker either
From the creator of RedBean https://stackoverflow.com/a/18811996/445492 Bulk Insert is not supported, use pure sql.
FOURTH APPROACH
for loop
R::exec("insert into bean(title) values (1,'hello world')");
end for
for 5660 rows 7.3s <----- WOW
(please note: I am actually doing some stuff prior so all these results are -4.3 seconds.)
Hence every bean needs to be created first and the method to create a bean is dispense
$bean = R::dispense('customers');
$bean->name = "John";
R::store($bean);
$bean->name = "Walter"
R::store($bean);
the code above creates only one bean even after storing it. Still $bean refers to the same object, so for each record you have to create a new been by using dispense method.
Luckily we have storeAll method that stores all the beans but it requires an array of beans. So we create a bean in each iteration and push it to the array and then at the end of loop we just pass that array to storeAll function.
//create empty array
$beans = array();
//for each customer post create a new bean as a row/record
foreach ($post as $customer) {
$bean = R::dispense('customers');
//assign column values
$bean->firstName = $customer['first_name'];
$bean->lastName = $customer['last_name'];
//push row to array
$beans[] = $bean;
}
//store the whole array of beans at once
R::storeAll($beans);
In the approaches 1, 2 and 3 suggested by John Ballinger, one way to optimize the run time is to put all the insertions performed by storeAll($beans) inside one database transaction. This could be done as follows: replace the line "R::storeAll($beans)" by the following three lines:
R::begin();
R::storeAll($beans);
R::commit();
This approach reduces dramatically the run time when the array $beans is large, AND is not necessary to use SQL "explicitly".

Mongodb like statement with array

I am trying to save some db action by compiling a looped bit of code with a single query, Before I was simply adding to the the like statements using a loop before firing off the query but i cant get the same idea going in Mongo, id appreciate any ideas....
I am basically trying to do a like, but with the value as an array
('app', replaces 'mongodb' down to my CI setup )
Here's how I was doing it pre mongofication:
foreach ($workids as $workid):
$this->ci->app->or_like('work',$workid) ;
endforeach;
$query = $this->ci->db->get("who_users");
$results = $query->result();
print_r($results);
and this is how I was hoping I could get it to work, but no joy here, that function is only designed to accept strings
$query = $this->ci->app->like('work',$workids,'.',TRUE,TRUE)->get("who_users");
print_r($query);
If anyone can think of a way any cunning methods I can get my returned array with a single call again it would be great I've not found any documentation on this sort of query, The only way i can think of is to loop over the query and push it into a new results array.... but that is really gonna hurt if my app scales up.
Are you using codeigniter-mongodb-library? Based on the existing or_like() documentation, it looks like CI wraps each match with % wildcards. The equivalent query in Mongo would be a series of regex matches in an $or clause:
db.who_users.find({
$or: [
{ work: /.*workIdA.*/ },
{ work: /.*workIdB.*/ },
...
]});
Unfortunately, this is going to be quite inefficient unless (1) the work field is indexed and (2) your regexes are anchored with some constant value (e.g. /^workId.*/). This is described in more detail in Mongo's regex documentation.
Based on your comments to the OP, it looks like you're storing multiple ID's in the work field as a comma-delimited string. To take advantage of Mongo's schema, you should model this as an array of strings. Thereafter, when you query on the work field, Mongo will consider all values in the array (documented discussed here).
db.who_users.find({
work: "workIdA"
});
This query would match a record whose work value was ["workIdA", "workIdB"]. And if we need to search for one of a set of ID's (taking this back to your OR query), we can extend this example with the $in operator:
db.who_users.find({
work: { $in: ["workIdA", "workIdB", ...] }
});
If that meets your needs, be sure to index the work field as well.

Categories