Multiple where conditions on eloquent model with optional where on relationship - laravel

I have table training_schedules
id (int, pk)
course_reference (varchar)
start_date (date)
end_date (date)
year_id (int, fk)
and years
id (int, pk)
value (int)
Now I want to create a search query using the TrainingSchedule eloquent model. I want to search by course_reference (using like) and start_date(optional) based on month (dropdownlist on form) and year (optional)
optional conditions means I will check if the form data for the fields are filled on the search form. If not I will just skip those conditions.
How can I achieve this using Eloquent ORM? if not, how to do it on another way?

You can create a query over multiple calls. So something like the following will probably do what you're looking for:
// start query off with a non-optional query
$results = TrainingSchedule::whereLike('course_reference', Input::get('course_reference'));
// only if we were sent a start date, add it to the query
if (Input::has('start_date')) {
$results->whereStartDate(Input::get('start_date'));
}
// if we were sent a month, ensure start date's month is the same month as the one passed in
if (Input::has('month')) {
$results->where(DB::raw('MONTH(`start_date`)'), Input::get('month'));
}
// only if we were sent a year, add it to the query
if (Input::has('year')) {
$results->whereHas('year', function ($query) { $query->whereValue(Input::get('year')); });
}
// get actual results from db
$results = $results->get();

Related

Paginate count(*) query issue

Laravel Version: 8.0
PHP Version: 7.3.0
Database Driver & Version: MySQL 5.7.34
Describe the Bug
When I use paginate for pagination the data it call the count( * ) query every time even I pass the column name like count(['id']), and the count( * ) query scan all row in the table.
Table name is users and it has 45 column
Route
Route::get("users", "UsersController#index");
Controller
namespace App\Http\Controllers\Api\V1;
class UsersController extends Controller
{
public function index()
{
return User::paginate(10 , ['id']);
}
}
Call users route
Telescope showing me that two queries
Actual result
Expected result
Steps To Solution:
The following image shows that I had done changes as per our functionality, It will take the first column name from the passed in the paginate method array of $columns params and that query does not scan all columns of the users tables.
Final Results:
I have tired to solving this issue any other way or idea then please let me know
Its not recommended to ever touch the vendor files, you can always just override the functionality inside of your model, you can also pass in the columns to override the getCountForPagination() and you can also pass the columns to simplePaginate() that doesn't invoke any counting!
In order to optimize the query to count and paginate, you can do it like this:
//We will call the query on the model
$program = Program::query()->getQuery();
//count the query by specific columns
$thePaginationCount = $program->getCountForPagination(['id']);
//paginate the results using simplePaginate and select the columns we want:
$thePaginatedProgram = $program->simplePaginate(10, ['id', 'name']);
return 'test: '.$thePaginatedProgram;
Will result like this:
select count(`id`) as aggregate from `programs`
select `id`, `name` from `programs` limit 11 offset 0
As you can see it will only load what we specify and its very efficient!
Final Note:
If you just want to paginate without the count, you can always call Model::simplePaginate($amount, [$columns...])
https://laravel.com/docs/9.x/pagination#simple-pagination

Nesting queries in GraphQL

I'm trying to figure out if there is an easy way to nest a query in Graphql. I have two tables, one with the beach records and one with the definitions of the conditions.
What i'm trying to return is the conditionName and conditionDescription if the surfCondition matches (surfCondition = 2}
query MyQuery {
lifeguard {
beachID
id
surfCondition (match the second query)
conditionName (display)
conditionDescription (display)
updated_at
created_at
}
lifeguard_conditions {
surfCondition
conditionName
conditionDescription
}
}
Normally this would be handled on the GraphQL server to accept a filter.
So, for example, you can pass a variable to surfCondition such as
surfCondition(filter: 2)
…which would then return automatically the filtered data.

"Creation At" time in GORM Customise Join table

I am trying to customize many2many table join. I have two tables from which I want to have taken the ids and want another field, which will tell me when the entry in the join table was made. The ids are coming fine, but the "created_at" is not updating and shows "Null" instead of time.
// this is the table join struct which I want to make
type UserChallenges struct {
gorm.JoinTableHandler
CreatedAt time.Time
UserID int
ChallengeID int
}
//hook before create
func (UserChallenges) BeforeCreate(Db \*gorm.DB) error {
Db.SetJoinTableHandler(&User{}, "ChallengeId", &UserChallenges{})
return nil
}
This is not giving any error on the build. Please tell me what I am missing so that I can get the creation time field in this.
PS - The documentation of GORM on gorm.io is still showing SetupJoinTable method but it is deprecated in the newer version. There is a SetJoinTableHandler but there is no documentation available for it anywhere.
The thing to get about using a Join Table model is that if you want to access fields inside the model, you must query it explicitly.
That is using db.Model(&User{ID: 1}).Association("Challenges").Find(&challenges) or db.Preload("Challenges").Find(&users), etc. will just give you collections of the associated struct and in those there is no place in which to put the extra fields!
For that you would do:
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Find(&joins)
// now joins contains all the records in the join table pertaining to user.ID,
// you can access joins[i].CreatedAt for example.
If you wanted also to retrieve the Challenges with that, you could modify your join struct to integrate the BelongsTo relation that it has with Challenge and preload it:
type UserChallenges struct {
UserID int `gorm:"primaryKey"`
ChallengeID int `gorm:"primaryKey"`
Challenge Challenge
CreatedAt time.Time
}
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Joins("Challenge").Find(&joins)
// now joins[i].Challenge is populated

Laravel Get previous records by date (optimization)

I have a bookings table with fields:
name | String
from | Datetime
to | Datetime
I select some of those bookings and display them as a list. I also check if the previous booking is less than 30 days apart.
I do this by querying for each booking the previous booking:
#if ($booking->previousBooking()) // Simplified version but you get the idea
The underlying code:
public function previousBooking()
{
return Booking::where('from', '<', $this->from)
->orderByDesc('from')
->first();
}
You might have guessed it already: it adds a query for each booking.
The best scenario would be to kind of eager load the "previous booking" (with) so that it is accessible like:
$booking->previous_booking->from
Is there any possible way to do it like this?
Constraints:
I can't query all bookings, order them by "from" and then just get the previous index
Take a look at this article by Jonathan Reinink. You can apply his solution by simulating a previous_booking_id on your Booking model. Then add the previousBooking() relationship and query that using with('previousBooking'). Let me know if you need any help on the implementation.
Update
Add the following scope to your Booking model:
public function previousBooking()
{
return $this->belongsTo(Booking::class);
}
public function scopeWithPreviousBooking($query)
{
$query->addSelect(['previous_booking_id' => Booking::select('id')
->whereColumn('previous_booking.from', '<', 'bookings.to')
->orderByDesc('from')
->take(1)
])->with('previousBooking');
}
You can now get all your bookings with their previous booking id included using this:
$bookings = Booking::withPreviousBooking()->get();
All bookings should now have a previous_booking_id. We add the with('previousBooking') to the end of the scope as if we query a relation of the booking. Eloquent does not care whether that previous_booking_id is in the database or not, as long as it's available in the model (which we've done by the addSelect() method).
You should now be able to use your requested solution in your view:
$booking->previousBooking->from
Note: you can only access the previousBooking relation if you've applied the scope. If not, the previous_booking_id is not available, and the relation will therefore be null. If you always want to load the previous booking, consider to add it as a global scope. However, I recommend to just apply the scope where it's needed.

Find Data Using Laravel Framework

I want match data from "jobTable" based on job id then i got 5 data within this 5 data i have userId i want to show data from "userTable" based on userID. Then i will catch it on variable and i want to show it on my view then i will show it using loop.
If i do this below code it shows all data from my userTable not show based on matched JobID base UserId Details.
public function applyUser($jobId){
$applyUsers=ApplyInfo::where('job_id', $jobId)->get();
//5 rows found, with in this 5 rows everybody has userId I want to show data from user table based on this found userId and pass value a variable then i will loop it on view page.
foreach ($applyUsers as $applyUser){
$applyUsersDtials=Employee::find($applyUser->user_id)->get();
}
return view('front.user.apply-user-details', ['applyUsersDtials'=>$applyUsersDtials]);
}
//when i do that that time show all data based on my user table not show based on my JobId
First of all in your code $applyUsersDtials is a variable it should be an array. Next when you need to find a single row you just need to use find(). Try this-
public function applyUser($jobId){
$applyUsers = ApplyInfo::where('job_id', $jobId)->get();
$applyUsersDtials = [];
foreach ($applyUsers as $applyUser){
$applyUsersDtials[] = Employee::find($applyUser->user_id);
}
return view('front.user.apply-user-details',
['applyUsersDtials' => $applyUsersDtials]);
}

Resources