How can I optimise this eloquent query to remove similar queries - mysql

An appointment can have many statuses, but the last created relation between appointment and status is the current status of the appointment. In my Appointment Datatable I want to display the related Agent, InstructionType, SignUpCustomer and the latest status. I want to paginate those records at 10 per page.
The relevant models are:
Appointment ( belongsTo agent, instruction_type and sign_up_customer, belongsToMany status)
Agent
InstructionType
SignUpCustomer
I have this query in my controller, which produces a result I send to Datatables.
$query = Appointment::with(['agent', 'instruction_type', 'sign_up_customer']);
$query->with(['statuses' => function ($query) {
$query->latest()->first();
}]);
$table = Datatables::of($query);
It is producing these two statements, the first one is fine, I don't need the last one. How can I optimise the query to remove that last statement?
select `statuses`.*, `appointment_status`.`appointment_id` as `pivot_appointment_id`, `appointment_status`.`status_id` as `pivot_status_id`, `appointment_status`.`created_at` as `pivot_created_at`, `appointment_status`.`updated_at` as `pivot_updated_at` from `statuses` inner join `appointment_status` on `statuses`.`id` = `appointment_status`.`status_id` where `appointment_status`.`appointment_id` in ('2') order by `created_at` desc limit 1
select `statuses`.*, `appointment_status`.`appointment_id` as `pivot_appointment_id`, `appointment_status`.`status_id` as `pivot_status_id`, `appointment_status`.`created_at` as `pivot_created_at`, `appointment_status`.`updated_at` as `pivot_updated_at`, `appointment_status`.`appointment_id` as `pivot_appointment_id`, `appointment_status`.`status_id` as `pivot_status_id`, `appointment_status`.`created_at` as `pivot_created_at`, `appointment_status`.`updated_at` as `pivot_updated_at` from `statuses` inner join `appointment_status` on `statuses`.`id` = `appointment_status`.`status_id` where `appointment_status`.`appointment_id` in ('2') order by `created_at` desc limit 1
I've also tried this:
Model:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function statuses()
{
return $this->belongsToMany(Status::class, 'appointment_status')->withTimestamps();
}
public function latestStatus()
{
return $this->statuses()->latest()->first();
}
Controller:
$query = Appointment::with(['agent', 'instruction_type', 'sign_up_customer', 'latestStatus']);
$table = Datatables::of($query);
But i get this error: BadMethodCallException in Builder.php line 2445:
Call to undefined method Illuminate\Database\Query\Builder::addEagerConstraints()

Can you not do this using a local scope? https://laravel.com/docs/5.4/eloquent#local-scopes
public function scopeLatest($query)
{
return $query->orderBy('created_at', 'DESC')->offset(0)->limit(1);
}
I've not actually done this before, but I see no reason why it wouldn't work (I also have no idea how optimised the resulting query would be).

Related

Write Eloquent queries in raw mysql

I want to know how can I write These two Eloquent Queries into raw SQL? also Is there a way to convert the Eloquent series into raw SQL series so that I could use those?
public function items()
{
return $this->belongsToMany(Product::class, 'order_items', 'order_id', 'product_id')->withPivot('quantity', 'price');
}
public function user()
{
return $this->belongsTo(User::class);
}
If we talk about the first query used in the items method. After changing it into raw SQL it will become
DB::select('select `products`.*, `order_items`.`order_id` as `pivot_order_id`, `order_items`.`product_id` as `pivot_product_id`, `order_items`.`quantity` as `pivot_quantity`, `order_items`.`price` as `pivot_price` from `products` inner join `order_items` on `products`.`id` = `order_items`.`product_id` where `order_items`.`order_id` = `order_id`');
and the second query inside the user method will become
DB::select('select * from `users` where `users`.`id` = `id`');

retrieve data by inner join with defined variable in Codeigniter

I want to get the following result of the mysql code by codeignitor.
It gives non equals id's(tbl_user) for the provided time frame in tbl_user_unavailability table
MySql code
SELECT tbl_user.id
FROM tbl_user
INNER JOIN tbl_user_unavailability
on tbl_user_unavailability.user_id <> tbl_user.id
WHERE '2018-02-02' BETWEEN begin_date AND end_date;
Result
22
25
find the mysql code result screenshot here
to get this result by codeignitor ,I have used following codes
Controller code
public function test(){
$current_date = '2018-02-02';
$available_users = $this->mod_users->test_test($current_date);
$data['available_users'] = $available_users;
//to test the result array
//var_dump($available_users);die();
$data['related_view']='user_unavailable_new';
$this->load->view('template', $data);
}
Model code
public function test_test($current_date){
$this->load->database();
$this->db->select('*');
$this->db->join('tbl_user_unavailability', 'tbl_user_unavailability.user_id <> tbl_user.id','inner');
$this->db->where("'$current_date' BETWEEN 'tbl_user_unavailability.begin_date' AND 'tbl_user_unavailability.end_date'");
$query = $this->db->get($this->table);
return $query->result_array();
}
Result
not showing anything
I have tested it with var_dump($available_users);die();
It shows empty array

Joins in laravel 5.3 in simple and Eloquent

I want to add some extra filter in my left join but I don't know how so kindly help me.And also tell me how can make this query in Eloquent. My query is given below:
select * from `users`
join `halls` on `halls`.`user_id` = `users`.`id`
left join `bookings` on `bookings`.`hall_id` = `halls`.`id` AND month(`bookings`.`date`) = 2 and day(`bookings`.`date`) = 4 and year(`bookings`.`date`) = 2017
join `user_role` on `user_role`.`user_id` = `users`.`id`
join `roles` on `roles`.`id` = `user_role`.`role_id`
where
`roles`.`id` = 2 AND
(`bookings`.`id` is null OR `bookings`.`status` = 0 )
group by users.id
user and role has many to many relation, user and hall one to many and hall and bookings has also one to many relation
User Model Relation
/**
* Many-to-Many relations with Role.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function roles(){
return $this->belongsToMany(Role::class, 'user_role', 'user_id', 'role_id')->select('roles.name');
}
/**
* One-to-Many relations with halls.
*
* #return \Illuminate\Database\Eloquent\Relations\hasMany
*/
public function halls(){
return $this->hasMany(Hall::class);
}
Hall Model relation
public function user(){
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function bookings(){
return $this->hasMany(Booking::class);
}
Booking Model Realtion
public function hall(){
return $this->belongsTo(Hall::class)->distinct();
}
I dont know why you want to use group by without any aggregate function . Your ORM looks like below
Users::join('halls', 'users.id', '=', 'halls.user_id')
leftJoin('bookings', function($join){
$join->on('halls.id', '=', 'bookings.hall_id');
$join->on(DB::raw('month(`bookings`.`date`) = 2 and day(`bookings`.`date`) = 4 and year(`bookings`.`date`) = 2017'));
})
->join('user_role', 'users.id', '=', 'user_role.user_id')
->join('roles', 'roles.id', '=', 'user_role.role_id')
->whereRaw('where
`roles`.`id` = 2 AND
(`bookings`.`id` is null OR `bookings`.`status` = 0 )')->get();

Eloquent: Nested query to revert order in limited result

The following function (in my User Model) gives me the correct result for my chat system. Almost... I need to revert the order of the results.
public function getChatConv($cp, $page=1){
$limit = $page * 20;
$user = Authek::curUser();
$res = Chatmessage::where('receipient',$cp)->where('sender',$user->id)
->orWhere('receipient',$user->id)->where('sender',$cp)
->orderBy('created_at','desc')->take($limit)->get();
return $res;
}
It returns an object and I need an object as result. I tried already to convert the result to an array, revert the order and then convert the array back to object. This didn't work.
What I need is a nested query like the following raw SQL query:
SELECT *
FROM (
SELECT *
FROM chatmessages
WHERE (
receipient = '422'
AND sender = '22'
)
OR (
receipient = '22'
AND sender = '422'
)
ORDER BY created_at DESC
LIMIT 0 , 20
)faketable
ORDER BY created_at ASC
There are a few articles with nested queries, but I don't find a similar case and it would be good if someone could do this in Eloquent without the use of Raw queries... It must be possible.
Try this..
use take() and skip(),offset()
get 4 items from offset 3/4th:
Chatmessage::take(4)->offset(3)->get();
Or this (get 10 items from 8rd row):
Chatmessage::take(10)->skip(2)->get();
public function getChatConv($cp, $page=1){
$limit = $page * 20;
$user = Authek::curUser();
$res = Chatmessage::where('receipient',$cp)->where('sender',$user->id)
->orWhere('receipient',$user->id)->where('sender',$cp)
->orderBy('created_at','desc')->take(3)->skip(2)->get();
return $res;
}

How to control sql join order in Yii CDbCriteria "with"

I have the following criteria for a findAll statement
$with=array(
'tumor'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
),
'tumorLibraryType'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
'condition'=>'tumorLibraryType.id = 1 OR tumorLibraryType.id = 6',
),
'tumorPatient'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
)
);
$libPairs=LibraryPairs::model()->with($with)->findAll();
These are the relevant model relations:
'tumor' => array(self::BELONGS_TO, 'Libraries', array('tumor_library'=>'id')),
'normal' => array(self::BELONGS_TO, 'Libraries', array('normal_library'=>'id')),
// making separate AR routes for tumor and normal. only tumor used currently
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
'tumorLibrariesIsolates'=>array(self::HAS_MANY,'LibrariesIsolates',array('id'=>'library_id'),'through'=>'tumor'),
'tumorSamplesIsolates'=>array(self::HAS_MANY,'SamplesIsolates',array('isolate_id'=>'isolate_id'),'through'=>'tumorLibrariesIsolates'),
'tumorSamples'=>array(self::HAS_MANY,'Samples',array('sample_id'=>'id'),'through'=>'tumorSamplesIsolates'),
'tumorPatient'=>array(self::HAS_ONE,'Patients',array('patient_id'=>'id'),'through'=>'tumorSamples'),
This code generates the following sql:
SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0`
FROM `library_tumor_normal_pairs` `t`
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id)
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`)
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`)
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`)
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`)
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`)
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6)
But that sql throws an error:
"Column not found: 1054 Unknown column 'tumor.library_type_id' in 'on clause'. "
However if I simply move the tumor line in the sql query up to be the first join statement, and run the query manually, then the query works.
SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0`
FROM `library_tumor_normal_pairs` `t`
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`)
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id)
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`)
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`)
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`)
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`)
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6)
So my question is, how can I control the sql join order of "with" criteria in Yii? Is it possible? As you can see my 'with' array and relations have 'tumor' before the others, but the join order is not preserved.
I encountered a similar problem: Yii generates joins in such order that makes SQL statement invalid. This situation occurs, for example, when you try to write custom $CDBCriteria->join which relies on tables specified in relations passed by $CDBCriteria->with. This happens because join is processed in CJoinQuery::__constructor whereas all "standard" joins (from with) are generated by Yii in CJoinQuery::join, that is after the constructor.
Unfortunately there is no solution other than a patch. Here is an example of how I did it my copy of Yii (the code is provided "as is" - please, check if it's applicable for your case).
First, we need to add a field into CDbCriteria, which will switch on/off a new option.
CDbCriteria.php
class CDbCriteria extends CComponent
{
...
/**
* #var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
* For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
*/
public $join='';
/**
* Patch begins:
*/
public $joinreorder = false; // new option
...
Second, we need to extend CJoinQuery (please, note, it's in CActiveFinder.php):
CActiveFinder.php
class CJoinQuery
{
...
/**
* #var array list of join element IDs (id=>true)
*/
public $elements=array();
/**
* Patch begins:
*/
private $joinreorder = false; // the same new option
private $postjoins; // the variable to store our custom joins
...
Now we can alter the CJoinQuery constructor:
CActiveFinder.php (continuation)
public function __construct($joinElement,$criteria=null)
{
if($criteria!==null)
{
$this->joinreorder = $criteria->joinreorder;
$this->selects[]=$joinElement->getColumnSelect($criteria->select);
$this->joins[]=$joinElement->getTableNameWithAlias();
if($this->joinreorder) //
{ //
$this->postjoins=$criteria->join; // new lines
} //
else //
{ //
$this->joins[]=$criteria->join;
} //
$this->conditions[]=$criteria->condition;
$this->orders[]=$criteria->order;
If joinreorder is true we store custom joins in our new member variable postjoins. Otherwise, all should work as before.
And now the actual fix in CJoinQuery::createCommand:
CActiveFinder.php (continuation)
public function createCommand($builder)
{
$sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
$sql.=' FROM ' . implode(' ',$this->joins);
if($this->joinreorder) //
{ //
$sql .= $this->postjoins; // new lines
} //
...
Finally we add the custom joins into SQL statement, and they are appended (not prepended as in standard implementation) to other joins generated from Yii's relations.
Now we can use it like so:
$criteria = new CDbCriteria;
$criteria->joinreorder = true;
$criteria->with = array('product', 'shop');
$criteria->join = 'LEFT OUTER JOIN `shop2address` `s2a` ON (`shop`.`id` = `s2a`.`shop_id`)';
Without joinreorder = true this generates the error stating that shop.id is unknown column in ON clause, bacause the 'shop' table is not yet added into SQL-statement. With joinreorder = true it works as expected.
As for the cases when only with is used, and incorrect sequence of joins is generated, one should apply more complicated patch. It involves CJoinQuery::join method. It should, possibly, have an optional parameter $priority, which can be again populated via corresponding member added into CDbCriteria. Then in CJoinQuery::join change these lines:
$this->joins[$element->priority]=$element->getJoinCondition();
$this->joins[$element->priority]=$element->relation->join;
This allows for re-ordering joins in arbitrary manner according to specified priorities.
This line doesn't look correct:
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
Maybe it should be
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes',array('id'=>'library_type_id'),'through'=>'tumor'),
guys, I believe I'm late to the party
I had similar problem
I've criteria with merges:
$criteria = new CDbCriteria();
$criteria->with = [
'codebaseName' => [
'alias' => 'cn'
],
'codebaseProducer' => [
'alias' => 'cp'
],
'registrationDocumentLast' => [
'alias' =>'rdl'
]
];
It ended up in such order by statement:
ORDER BY COALESCE(cn.name_our,cn.name_supplier), id DESC LIMIT 50
I didn't specify ordering by id DESC explicitly!
After playing for around, I discovered that it came from relation registrationDocumentLast , which was defined as
'registrationDocumentLast' => [
self::HAS_ONE, RegistrationDocument::class, 'codebase_product_pharm_id',
'joinType' => 'LEFT JOIN',
'order' => 'id DESC'
],
Look at order key. Changing it from
'order' => 'id DESC'
to
'order' => 't.id DESC'
solved the problem