Laravel Eloquent Query With OR WHERE Condition - laravel

I am using Laravel 6 & mysql 7
I have below query
$tasks = Task::where('deleted_at', null)->where('company_id',$company_id);
$tasks = $tasks->where('created_by',$user_id);
$tasks = $tasks->orWhereIn('id',$task_ids);
It generates below raw query when i print it
SELECT * FROM `tasks` WHERE `deleted_at` IS NULL AND `company_id` = 25 AND `created_by` = 20 OR
`id` IN(112,...215) ORDER BY `id` DESC
Now Id 112 is deleted but still showing in result, although i have where('deleted_at', null) condition but it is not working
I want to apply all other conditions on $task_ids as well
How this can be achieved with optimized query?
UPDATED: Here is complete scenario
I want to select all records which is created by me or assigned to me. Here is my complete code.
$tasks = Task::where('deleted_at', null)->where('company_id',$company_id);
$tasks = $tasks->where('created_by',$user_id);
$task_ids = TaskUser::where('user_id',$user_id)->pluck('task_id')->all();
$tasks = $tasks->orWhereIn('id',$task_ids);

This is because the AND operator has a higher precedence than OR, which basically means that AND "sticks" together more than OR does. You query basically is interpredeted like this:
SELECT * FROM `tasks`
WHERE
(`deleted_at` IS NULL AND `company_id` = 25 AND `created_by` = 20)
OR
( `id` IN(112,...215) )
I am not entirly sure wheter you actually want to OR anything. If you really want to apply all conditions, you just need to change the orWhereIn to a whereIn.
In case you want all not-deleted tasks, that EITHER belong to a company and a auser OR whose id is in the list, you would need to update your query like this:
$tasks = Task::where('deleted_at', null);
$tasks = $tasks->where(function($q) use ($user_id, $task_ids){
$q->where(function($q2) use ($user_id, $task_ids) {
$q2->where('created_by',$user_id)
->where('company_id',$company_id);
})
->orWhereIn('id',$task_ids);
});
which should result in this query:
SELECT * FROM `tasks`
WHERE `deleted_at` IS NULL AND (
( `company_id` = 25 AND `created_by` = 20 )
OR
`id` IN(112,...215)
)
There is actually a chapter about parameter grouping in the excellent laravel documentation as well.

Related

Using same table in subquery

I am failing to convert next SQL code into laravel eloquent:
SELECT t1.template, t1.created_at
FROM sent_emails t1
where created_at = (
select max(created_at) from sent_emails t2 where t2.template = t1.template
)
group by t1.created_at, t1.template
or:
SELECT t1.template, t1.created_at
FROM sent_emails t1
JOIN
(
SELECT Max(created_at) date, template
FROM sent_emails
GROUP BY template
) AS t2
ON t1.template = t2.template
AND t1.created_at = t2.date
group by t1.created_at, t1.template
Both queries return same data set. Creating subquery in separate variable is not an option as I need multiple values to be returned from it.
I also don't know how can I set alias name if I create table using models (and not using DB::), so this is my unsuccessful try:
$sent_emails = SentEmail::where('created_at', function($query) {
SentEmail::where('template', 't1.template')->orderBy('created_at', 'desc');
})->groupBy('template', 'created_at')->get(['template', 'created_at']);
You query should be something like this (I'm not at a computer to test this, so it may require further editing)
$sent_emails = SentEmail::where('created_at', function($query) {
$query->where('created_at', SentEmail::->whereColumn('column_name', 'table.column_name')->orderBy('created_at', 'desc')->max('created_at'));
})->groupBy('template', 'created_at')->get(['template', 'created_at']);

How to make Multiple sub query in Laravel

I am new in Laravel.
Here below i mention my code how to make this in laravel query builder.
Using tables are ab,cd,ef,gh,ij
SELECT ab.* FROM ab
WHERE ab.id IN (
SELECT ab_id FROM cd
WHERE ef_id = 1 AND status = 1
AND deleted_at IS NULL
AND ab_id IN (
SELECT ab_id FROM gh
WHERE ij_id IN (
SELECT id FROM ij
WHERE ef_id = 1 AND deleted_at IS NULL
) AND deleted_at IS NULL
) AND ab_id IN (
SELECT id FROM ab
WHERE deleted_at IS NULL AND usertest != 1
)
)AND ab.deleted_at IS NULL
GROUP BY ab.id
ORDER BY ab.created_at DESC;
You can pass a closure to a where condition and query another table.
From the Laravel docs:
DB::table('users')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('orders')
->whereRaw('orders.user_id = users.id');
})
->get();

using raw query in where methods makes error in query generation

I have an eloquent query as
Role::where("(company_id = Auth::user()->company_id or name = 'admin')and id in(2,3)")->pluck('name');
According to my eloquent the sql should be as
select `name` from `roles` where ( company_id = 1 or name = admin ) and id IN (2, 3) and `roles`.`deleted_at` is null
But it executes as
select `name` from `roles` where ( company_id = 1 or name = admin ) and id IN (2, 3) is null and `roles`.`deleted_at` is null
Can anyone help me concluding why extra is null condition is applied in the query?
Note: I am using soft deletes
You should use whereRaw instead of where
Role::whereRaw("(company_id = Auth::user()->company_id or name = 'admin')and id in(2,3)")->pluck('name');

Laravel: Improved pivot query

I am successfully querying following and it create 130 queries, I want to optimise it and reduce the number of queries, I have set upped the model and controllers following way.
Post Modal
class Post extends Eloquent {
public function Categories () {
return $this->belongsToMany('Category', 'category_post');
}
}
Category Modal
class Category extends Eloquent {
public function posts () {
return $this->belongsToMany('Post', 'category_post');
}
}
and in the Controller, I am using following query, what following query does is, querying the results based on category id.
$category = Category::with('posts')->where('id','=',$id)->paginate(10)->first();
return Response::json(array('category' => $category));
If anyone can give me a hand to optimise the query, would be really greatful.
You are wrong, it doesn't create 130 queries.
It will create the following 3 queries:
select count(*) as aggregate from `categories` where `id` = '5';
select * from `categories` where `id` = '5' limit 10 offset 0;
select `posts`.*, `posts_categories`.`category_id` as `pivot_category_id`, `posts_categories`.`post_id` as `pivot_post_id` from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` in ('5');
But the question is what exactly you want to paginate. Now you paginate categories and it doesn't make much sense because there's only one category with selected $id.
What you probably want to get is:
$category = Category::where('id','=',$id)->first();
$posts = $category->posts()->paginate(10);
and this will again create 3 queries:
select * from `categories` where `id` = '5' limit 1;
select count(*) as aggregate from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` = '5';
select `posts`.*, `posts_categories`.`category_id` as `pivot_category_id`, `posts_categories`.`post_id` as `pivot_post_id` from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` = '5' limit 10 offset 0;
If you would like to improve it, you will probably need to not use Eloquent in this case and use join - but is it worth it? You would now need to manually paginate results without paginate() so it would probably won't be want you want to achieve.
EDIT
What you probably do is:
you get all posts that belongs to the category (but in fact you want to paginate only 10 of them)
for each post you want do display all categories it belongs to.
To lower number of queries you should use:
$category = Category::where('id','=',$id)->first();
$posts = $category->posts()->with('categories')->paginate(10);
and to display it you should use:
foreach ($posts as $p) {
echo $p->name.' '."<br />";
foreach ($p->categories as $c) {
echo $c->name."<br />";
}
}
It should lower your number queries to 4 from 130

Write union query in codeigniter style

How can I write the following query in Codeigniter style.
SELECT COUNT(`id`) AS reccount
FROM
(SELECT `id` FROM table1
WHERE tid= '101' AND `status` = 1
UNION ALL
SELECT `id` FROM table2
WHERE tid= '101' AND `status` = 1
UNION ALL
SELECT `id` FROM table3
WHERE tid= '101' AND `status` = 1) t
I have used the following way to execute it.
Is it the only correct way or do you have any suggestion to improve it?
$q = $this->db->query(SELECT COUNT(`id`) AS reccount
FROM
(SELECT `id` FROM table1
WHERE tid= '101' AND `status` = 1
UNION ALL
SELECT `id` FROM table2
WHERE tid= '101' AND `status` = 1
UNION ALL
SELECT `id` FROM table3
WHERE tid= '101' AND `status` = 1) t ");
Since CodeIgniter 3 it's been introduced in Active Record the function get_compiled_select() that gives the query string without actually executing the query.
This allows #MDeSilva method to use less resources, being adapted as follows:
function get_merged_result($ids){
$this->db->select("column");
$this->db->distinct();
$this->db->from("table_name");
$this->db->where_in("id",$model_ids);
$query1 = $this->db->get_compiled_select(); // It resets the query just like a get()
$this->db->select("column2 as column");
$this->db->distinct();
$this->db->from("table_name");
$this->db->where_in("id",$model_ids);
$query2 = $this->db->get_compiled_select();
$query = $this->db->query($query1." UNION ".$query2);
return $query->result();
}
You can use CI to generate a union query. However, latest versions made this much harder than before.
DB has a method called _compile_select, in previous versions of CI it was public, however now it is protected so you can't just call $this->db->_compile_select() from your controller. In order to do this properly one could:
Create custom loader class to be able to extend core/database classes (i.e. load MY_DB_active_record instead of CI_DB_active_record).
Create custom activerecord class, with just one method:
public function compile_select() {
return $this->_compile_select();
}
In your controller, create all necessary queries, compile them into a string array using our public method compile_select()
Join the array into single query: '(' . implode(') UNION (', $queries) . ')'. You can also wrap this into a separate method inside your custom AR class.
function get_merged_result($ids){
$this->db->select("column");
$this->db->distinct();
$this->db->from("table_name");
$this->db->where_in("id",$model_ids);
$this->db->get();
$query1 = $this->db->last_query();
$this->db->select("column2 as column");
$this->db->distinct();
$this->db->from("table_name");
$this->db->where_in("id",$model_ids);
$this->db->get();
$query2 = $this->db->last_query();
$query = $this->db->query($query1." UNION ".$query2);
return $query->result();
}

Resources