Laravel query builder - re-use query with amended where statement - php

My application dynamically builds and runs complex queries to generate reports. In some instances I need to get multiple, somewhat arbitrary date ranges, with all other parameters the same.
So my code builds the query with a bunch of joins, wheres, sorts, limits etc and then runs the query. What I then want to do is jump into the Builder object and change the where clauses which define the date range to be queried.
So far, I have made it so that the date range is setup before any other wheres and then tried to manually change the value in the relevant attribute of the wheres array. Like this;
$this->data_qry->wheres[0]['value'] = $new_from_date;
$this->data_qry->wheres[1]['value'] = $new_to_date;
Then I do (having already done it once already)
$this->data_qry->get();
Doesn't work though. The query just runs with the original date range. Even if my way worked, I still wouldn't like it though as it seems to be shot through with a precarious dependence (some sort of coupling?). Ie; if the date wheres aren't set up first then it all falls apart.
I could set the whole query up again from scratch, just with a different date range, but that seems ott as everything else in the query needs to be the same as the previous time it was used.
Any ideas for how to achieve this in the correct / neatest way are very welcome.
Thanks,
Geoff

You can use clone to duplicate the query and then run it with different where statements. First, build the query without the from-to constraints, then do something like this:
$query1 = $this->data_qry;
$query2 = clone $query1;
$result1 = $query1->where('from', $from1)->where('to', $to1)->get();
$result2 = $query2->where('from', $from2)->where('to', $to2)->get();

The suggestion from #lukasgeiter using clone is definitely the way to go; the reason is that an Eloquent\Builder object contains an internal reference to a Query\Builder that needs to be duplicated.
To keep the flow of your app and get back to a more functional style, you can use Laravel's with() helper, which simply returns the object passed in:
$result1 = with(clone $this->data_qry)->where('from', $from1)->where('to', $to1)->get();
$result2 = with(clone $this->data_qry)->where('from', $from2)->where('to', $to2)->get();

For the people who want a simpler and shorter syntax, you can daisy chain the clone() method on the query builder.
$result1 = $this->data_qry->clone()->where('from', $from1)->where('to', $to1)->get();
$result2 = $this->data_qry->clone()->where('from', $from2)->where('to', $to2)->get();

Related

How to customize Laravel whereBetween clause to make upper limit unlimited?

I have been working on laravel using eloquent query builders. I have a situation in which I am filtering records based on search queries. I have an integer field, for which I want to filter data in ranges. User can select any of available ranges for example;
0-15, 15-30 and 30 and above.
For this purpose I found Query-builders whereBetween() clause very helping. But It becomes difficult for me for last option when I want to select for 30 and above.
I would really appreciate if someone could help me with some trick make this query
->whereBetween('time_taken', [$lower-limit,$uper_limt])
working for all cases.
I don't want to write an additional line for this case, at which I can use simple where clause
->where('time_taken','>=',$uper_limt).
The practical solution here is to just choose the appropriate SQL condition (I'm using a ternary operator here to keep it more compact):
$query = App\Operation::query();
(empty($upper_limit)) ? $query->where('time_taken','>=', $lower_limit)
: $query->whereBetween('time_taken', [$lower_limit, $upper_limit]);
$results = $query->get();
Sure it would be nice to have just one line:
$results = App\Operation::whereBetween('time_taken', [$lower_limit, $upper_limit])->get();
But that's not possible in this case, not unless you want to extend the Laravel Query Builder and modify the way it handles empty parameters in the range passed as the value.
Writing clean and concise code is something we all strive to achieve, but one line solutions are not always possible. So my advice is to stop fixating (something I sometimes do myself) on things like this, because in some cases it's a lost cause that just ends up wasting time.
You can try any of these:
Method 1:
->whereBetween('time_taken', [$lower-limit,ModelName::max('time_taken')])
Method 2:
->whereBetween('time_taken', [$lower-limit,DB::table('table_name')->max('time_taken')])
Method 3:
$max = ModelName::max('time_taken');
//or
$max = DB::table('table_name')->max('time_taken');
//then
->whereBetween('time_taken', [$lower-limit,$max])
max() returns the highest value from your corespondent column.

Query Laravel Result Set

I am looking to do several queries using eloquent which are all to be returned to a single view and was wondering if there was a better way to do it than querying the database multiple times?
For example returning all the records then pulling sub sets of data from that?
At the moment I have something similar to:
$one = User::queryOne();
$two = User::queryTwo();
$three = User::queryThree();
etc
However I was thinking it would be better if it was possible to do something like:
$users = User::all();
$one = $users->where('created_at')...
$two = $users->where('modified_at')..
Obviously the above doesn't work but it it possible to do something like this?
Or is it best just to query the database separately each time?
From a pragmatic point of view, it's 'better' to do multiple queries, because it takes you less time to write them, and they are easier to read and debug. If you want to do it with one DB query, and then grabbing subsets out of them, you'd have to write your own convoluted logic, or perhaps a new Laravel collection class. And then someone else comes along and wonders, "What is going on in this code?"
Typically programmer time is the most constrained resource in a project. When you get done, if you find that the multiple database queries are a bottleneck, then think about re-writing it.
If you do decide to do one query, you can probably order the data by the fields you want for the criteria. Then loop through the result set, adding the rows to a new array each time the specified field's value changes. That's the simplest logic I can think of offhand.
What version of laravel are you using? In 5.1 you can do where on collections here. In 4.2 you can do so with callbacks here
EDIT
for 4.2 try something similar to this
$users = User::all();
$one = $users->filter(function($user)
{
if($user->age > 20){
return true;
}
return false;
});
Laravel Eloquent returns a Collection as a result.
You could use a foreach statement or use the build in Collections functions you could manipulate the results and create the sub-results.
For example you could use filter and do something like this:
$users = User::all();
$one = $collection->filter(function ($item) {
return $item->created_at >= time() - (24*60*60); // created the last day
});
$filtered->all();
Whether it is the best method depends on the application and the amount of data you are trying to fetch/process.
If you have only a few records from ::all(), then doing so might be a good approach (although using the collections functions you have to run three filters across your data).
If you have a lot of records from ::all() then it is preferably to use three different queries to the database (especially if the results will only be a few records).

Laravel - Execute function before query is fired

is there a way for Eloquent/raw queries to execute a function before a query is fired? It would also be nice if I could extend the functionality to pass a parameter if the function should be run before or not. Depending on the outcome of the function (true/false) the query shouldn't be executed.
I would be nice to use the principal of "DB::listen", but I'm not sure if I can stop or run the query from within this function.
The reason for this is that I would like to build a little data warehouse myself for permanently saving results to a warehouse (db) and not query a huge database all the time.
The method I'm would like to use is to create a hash of a query, check if the hash exists in the warehouse. If it exists, then the value is returned. If not the query is executed and the output is saved together with the hash into the warehouse.
Any ideas?
///// EDIT /////
I should clarify, that I would like to access the queries and update the value if the calculated value needs to be updated. i.e.: Number of cars in december: While I'm in december, I need to keep updating the value every so often. So I store the executed query in the db and just retrieve it, run it and then update the value.
//// EDIT 2 /////
Github: https://github.com/khwerhahn/datawarehouselibrary/blob/master/DataWareHouseLib.php
What I would like to achieve is to hook into Laravels query/Eloquent logic and use the data warehouse logic in the background.
Maybe something like this:
$invalid_until = '2014-12-31 23:59:59'; // date until query needs to be updated every ten minutes
$cars = Cars::where('sales_month', '=', 12)->dw($invalid_until)->get();
If the dw($date_parameter) is added I would like Laravel to execute the data warehouse logic in the background and if found in the db then not execute the query again.
You don't need to use events to accomplish this. From the 4.2 docs:
Caching Queries
You may easily cache the results of a query using the remember method:
$users = DB::table('users')->remember(10)->get();
In this example, the results of the query will be cached for ten
minutes. While the results are cached, the query will not be run
against the database, and the results will be loaded from the default
cache driver specified for your application.
http://laravel.com/docs/4.2/queries#caching-queries
You can also use this for Eloquent objects,
eg: User::where($condition)->remember(60)->get()
I get what you're trying to do, but as I view it (I might not still be getting it right, though) you still can get away with using rememberForever() (if you don't want a specific time limit)
So, let's pretend you have a Cars table.
$cars = Cars::where('sales_month', '=', 12)->rememberForever()->get();
To work around the problem of deleting the cache, you can assign a key to the caching method, and then retrievit by that key. Now, the above query becomes:
$cars = Cars::where('sales_month', '=', 12)->rememberForever('cars')->get();
Every time you run that query you will be getting the same results, first time from the DB, all the others from the cache.
Now you say you're going to update the table, and you want to reset the cache, right?
So, run your update, then forget() the Cache with the cars index:
// Update query
Cache::forget('cars');
Your next call to the Cars query will issue a new resultset, and it will be cached. In case you're wondering, the remember() and rememberForever() are methods of the QueryBuilder class that use the same Cache class you can see in the docs in its own section.
Alternatively, in fact, you could also use the Cache class directly (it gives you a better control):
if (null == $cars= Cache::get('cars')) {
$cars = Cars::where('sales_month', '=', 12)->get();
Cache::forever('cars', $cars);
}
By overriding the method runSelect exsists in Illuminate\Database\Query\Builder that runs in every select query in Laravel.
see this package:
https://github.com/TheGeekyM/caching-queries

Laravel 4 Build Query where clause on the fly

I am porting my code from CodeIgniter to Laravel. and have some question regarding the query builder.
In codeigniter, I can just add where clause to the active record object, as I initialize each property in a class like
$this->db->where('xxxx','bbbb');
in one property initialize function, and
$this->db->where('yyyy','aaaa');
in another property function, and it will all chain up until i fire off the query. But this doesn't seem to be the case of Laravel.
Here is what I do in laravel in each property initialize function
DB::table($this->table)->where('xxxx','bbbb');
DB::table($this->table)->where('yyyy','aaa');
and when a actual method is call from outside, it runs
DB:table($this->table)->get();
but this gives me a SELECT * FROM TABLENAME without anywhere clause. So what am I doing wrong here :x or I just shouldn't treat laravel same as codeigniter and think of something totally different to handle this kind of dynamic where clause?
Also in codeigniter, you can set a section of the query to cache, so even after you fire off the query , those section retains for next query, usually the where clause. Is there a similar function in Laravel? Thank you!
You can assign your current workings to a variable, and build upon that, let me show you an example based on your example:
Instead of this
DB::table($this->table)->where('xxxx','bbbb');
DB::table($this->table)->where('yyyy','aaa');
Try this...
$query = DB::table($this->table)->where('xxxx','bbbb');
$query->where('yyyy','aaa');
$results = $query->get();
I just shouldn't treat laravel same as codeigniter and think of something totally different to handle this kind of dynamic where clause?
This is not dynamic where clause.
and please, make a habit of reading the documentation.
From the docs of Fluent query builder
$users = DB::table('users')->where('votes', '>', 100)->get();
you can set a section of the query to cache, so even after you fire off the query , those section retains for next query, usually the where clause. Is there a similar function in Laravel?
$users = DB::table('users')->remember(10)->get();
Next time, just open up the docs. they contain all this.

PHP / MySQL Run Function From Multiple Results In Array

I'm not sure that I have the terminology correct but basically I have a website where members can subscribe to topics that they like and their details go into a 'favorites' table. Then when there is an update made to that topic I want each member to be sent a notification.
What I have so far is:
$update_topic_sql = "UPDATE topics SET ...My Code Here...";
$update_topic_res = mysqli_query($con, $update_topic_sql)or die(mysqli_error());
$get_favs_sql = "SELECT member FROM favourites WHERE topic = '$topic'";
$get_favs_res = mysqli_query($con, $get_favs_sql);
//Here I Need to update the Members selected above and enter them into a notes table
$insert_note_sql = "INSERT INTO notes ....";
Does anyone know how this can be achieved?
Ok, so we've got our result set of users. Now, I'm going to assume from the nature of the question that you may be a bit of a newcomer to either PHP, SQL(MySQL in this case), web development, or all of the above.
Your question part 1:
I have no idea how to create an array
This is easier than what you may think, and if you've already tried this then I apologize, I don't want to insult your intelligence. :)
Getting an array from a mysqli query is just a matter of a function call and a loop. When you ran your select query and saved the return value to a variable, you stored a mysqli result set. The mysqli library supports both procedural and object oriented styles, so since you're using the procedural method, so will I.
You've got your result set
$get_favs_res = mysqli_query($con, $get_favs_sql);
Now we need an array! At this point we need to think about exactly what our array should be of, and what we need to do with the contents of the request. You've stated that you want to make an array out of the results of the SELECT query
For the purposes of example, I'm going to assume that the "member" field you've returned is an ID of some sort, and therefore a numeric type, probably of type integer. I also don't know what your tables look like, so I'll be making some assumptions there too.
Method 1
//perform the operations needed on a per row basis
while($row = mysqli_fetch_assoc($get_favs_res)){
echo $row['member'];
}
Method 2
//instead of having to do all operations inside the loop, just make one big array out of the result set
$memberArr = array();
while($row = mysqli_fetch_assoc($get_favs_res)){
$memberArr[] = $row;
}
So what did we do there? Let's start from the beginning to give you an idea of how the array is actually being generated. First, the conditional in the while loop. We're setting a variable as the loop condition? Yup! And why is that? Because when PHP (and a lot of other languages) sets that variable, the conditional will check against the value of the variable for true or false.
Ok, so how does it get set to false? Remember, any non boolean false, non null, non 0 (assuming no type checking) resolves to true when it's assigned to something (again, no type checking).
The function returns one row at a time in the format of an associative array (hence the _assoc suffix). The keys to that associative array are simply the names of the columns from the row. So, in your case, there will be one value in the row array with the name "member". Each time mysqli_fetch_assoc() is called with your result set, a pointer is pointed to the next result in the set (it's an ordered set) and the process repeats itself. You essentially get a new array each time the loop iterates, and the pointer goes to the next result too. Eventually, the pointer will hit the end of the result set, in which case the function will return a NULL. Once the conditional picks up on that NULL, it'll exit.
In the second example, we're doing the exact same thing as the first. Grabbing an associative array for each row, but we're doing something a little differently. We're constructing a two dimensional array, or nested array, of rows. In this way, we can create a numerically indexed array of associative arrays. What have we done? Stored all the rows in one big array! So doing things like
$memberArr[0]['member'];//will return the id of the first member returned
$memberArr[1]['member'];//will return the id of the second member returned
$lastIndex = sizeof($memberArr-1);
$memberArr[$lastIndex]['member'];//will return the id of the last member returned.
Nifty!!!
That's all it takes to make your array. If you choose either method and do a print_r($row) (method 1) or print_r($memberArr) (method 2) you'll see what I'm talking about.
You question part 2:
Here I Need to update the Members selected above and enter them into a notes table
This is where things can get kind of murky and crazy. If you followed method 1 above, you'd pretty much have to call
mysqli_query("INSERT INTO notes VALUES($row['member']);
for each iteration of the loop. That'll work, but if you've got 10000 members, that's 10000 inserts into your table, kinda crap if you ask me!
If you follow method two above, you have an advantage. You have a useful data structure (that two dim array) that you can then further process to get better performance and make fewer queries. However, even from that point you've got some challenges, even with our new processing friendly array.
The first thing you can do, and this is fine for a small set of users, is use a multi-insert. This just involves some simple string building (which in and of itself can pose some issues, so don't rely on this all the time) We're gonna build a SQL query string to insert everything using our results. A multi insert query in MySQL is just like a normal INSERT except for one different: INSERT INTO notes VALUES (1),(2),(x)
Basically, for each row you are inserted, you separate the value set, that set delineated by (), with a comma.
$query = 'INSERT INTO notes VALUES ';
//now we need to iterate over our array. You have your choice of loops, but since it's numerically indexed, just go with a simple for loop
$numMembers = sizeof($memberArr);
for($i = 0; $i < $numMembers; $i++){
if($i > 0){
$query .= ",({$membersArr[$i]['member']})";//all but the first row need a comma
}
else {
$query .= "({$membersArr[$i]['member']})";//first row does not need a comma
}
}
mysqli_query($query);//add all the rows.
Doesn't matter how many rows you need to add, this will add them. However, this is still going to be a costly way to do things, and if you think your sets are going to be huge, don't use it. You're going to end up with a huge string, TONS of string concats, and an enormous query to run.
However, given the above, you can do what you're trying to do.
NOTE: These are grossly simplified ways of doing things, but I do it this way because I want you to get the fundamentals down before trying something that's going to be way more advanced. Someone is probably going to comment on this answer without reading this note telling me I don't know what I'm doing for going about this so dumbly. Remember, these are just the basics, and in no way reflect industrial strength techniques.
If you're curious about other ways of generating arrays from a mysqli result set:
The one I used above
An even easier way to make your big array but I wanted to show you the basic way of doing things before giving you the shortcuts. This is also one of those functions you shouldn't use much anyway.
Single row as associative(as bove), numeric, or both.
Some folks recommend using loadfiles for SQL as they are faster than inserts (meaning you would dump out your data to a file, and use a load query instead of running inserts)
Another method you can use with MySQL is as mentioned above by using INSERT ... SELECT
But that's a bit more of an advanced topic, since it's not the kind of query you'd see someone making a lot. Feel free to read the docs and give it a try!
I hope this at least begins to solve your problem. If I didn't cover something, something didn't make sense, or I didn't your question fully, please drop me a line and I'll do whatever I can to fix it for you.

Categories