Searching model with multiple 'orwhere' clauses - php

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

Related

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).

Laravel: External variable in callback for chunk

I am trying to implement search in Laravel. I want to search on models.
Since there can be many records in the database, I am trying the chunk function.
public function searchLeads($param)
{
$results = array();
// get all leads
Lead::chunk(100, function($leads) use ($results, $param) {
// search in name
$results = array_merge($results, $this->repository->searchInName($leads, $param));
// search in email
$results = array_merge($results, $this->repository->searchInEmail($leads, $param));
// and so on ...
}
// eliminate duplicates
$collection = collect($results);
$collection = $collection->unique();
$results = $collection->all();
dd($results);
// return $results;
}
Note: The searchIn... functions return array of results and are working as expected.
When I try the above code, I get an empty array. So I changed the following line (added a reference & to $results).
Lead::chunk(100, function($leads) use (&$results, $param) {
Now I get the expected output.
My question is, have I stumbled upon the right solution, or am I missing something that might introduce bugs in the code?
Note:
I know using where clause is a better and efficient way; but I cannot use where for this.
I got the solution after reading a bit about php closures. My real worry was that I am not using the $results array correctly.
But after reading php docs, I know I am doing it correctly.
Please note that I am aware of where clause and it's usage would be much more efficient here. But that's not what I was trying to ask.
to use the search, you could do something like:
$results = Lead::where('mail','like', $param)->orWhere('name','like', $param)->get();
then mysql does the search, which i believe is the fastes way of doing it.
depending on how the search should be done:
...where('mail','like', %$param%)...
https://laravel.com/docs/5.1/queries#where-clauses
else try to describe what the end goal of your search is?

Avoid deep nesting code in grouping logic

I need some advice or recommended practices here.
I have a multiple field (six or more) group by SQL query. I'm getting the expected results in the right order.
My default approach to this is to iterate over the ordered results saving the last loop element to test it on next iteration. If it changed then I add it to the resulting list and move on. I think this is a common way to solve this kind of problem.
Up to two fields the I think my approach is fine. But when I group by more than two fields it seems overcomplicated. The code begins to rot and looks difficult to maintain as the number of nested conditions grows.
Is there a better way at all to accomplish this?
My default approach (one field)
$groupOne = null;
$lastId = null;
$result = array();
foreach($orderedData as $item) {
if ($lastId !== $item['id']) {
if ($groupOne !== null) {
$resultList[] = $groupOne;
}
$groupOne = new stdClass();
}
//rest of the logic
$lastId = $item['id'];
}
Actually, I found a better way. In the beginning of loop instead of validating all the group breaks I just validate the last one. If the last one changed then it's logical that all other ones before must be updated too. This simplifies my code. But I'm always open to suggestions and other examples. Thanks.

Laravel - Single query then splitting Vs Two queries

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.

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