I have users, conversations, conversation_user and messages table:
Before making a new conversation with $data array of user id's, I am trying to find existing one:
$data = [2,3]; // without auth()->id() -> 1 => total of 3 users per conversation
auth()->user()->conversations()->has('messages')->whereHas('users', function ($query) use ($data) {
$query->whereIn('user_id', $data);
})->whereHas('users', function ($query) use ($data) {
$query->groupBy('conversation_id', 'conversation_user.id')
->havingRaw('count(conversation_id) = ' . count($data) + 1); // total count is 3
})->first()
Now first whereHas returns even if I have conversation between auth()->id() and ID-2, because user_id 2 is in (2,3).. So it would retrieve the wrong conversation, where I need to count for users per conversation as well.
The second whereHas is for counting however if I use $query->groupBy('conversation_id') I get mysql SQL_MODE error for grouping, meaning I need to add $query->groupBy('conversation_id', 'conversation_user.id') as well, but with all that I get no record from database even if there are some.
What am I missing here?
[Updated with generated sql]
select * from `conversations`
inner join `conversation_user` on `conversations`.`id` = `conversation_user`.`conversation_id`
where `conversation_user`.`user_id` = 1 and exists (
select * from `conversation_messages`
where `conversations`.`id` = `conversation_messages`.`conversation_id`
and `conversation_messages`.`deleted_at` is null
) and exists (
select * from `users`
inner join `conversation_user` on `users`.`id` = `conversation_user`.`user_id`
where `conversations`.`id` = `conversation_user`.`conversation_id`
and `user_id` in (2, 3) and `users`.`deleted_at` is null
) and exists (
select * from `users`
inner join `conversation_user` on `users`.`id` = `conversation_user`.`user_id`
where `conversations`.`id` = `conversation_user`.`conversation_id`
and `users`.`deleted_at` is null
group by `conversation_id`, `conversation_user`.`id`
having count(conversation_id) = 3
) and `conversations`.`deleted_at` is null
[Update with table structures]
users -> id, name, email
conversations -> id, slug, subject
conversation_user -> id, user_id, conversation_id
messages -> id, conversation_id, user_id, body
[Another update]
Seems like this works also, in case someone need:
auth()->user()->conversations()->has('messages')->whereHas('users', function ($query) use ($data) {
$query->whereIn('user_id', $data);
})->whereDoesntHave('users', function ($query) use ($data) {
$query->whereNotIn('user_id', $data);
})->first()
I think this is the SQL you want -
SELECT c.*
FROM conversation_user cu
JOIN conversations c
ON cu.conversation_id = c.id
WHERE cu.user_id IN (1, 2, 3)
AND NOT EXISTS (
SELECT *
FROM conversation_user
WHERE conversation_id = cu.conversation_id
AND user_id NOT IN (1, 2, 3)
)
GROUP BY cu.conversation_id
HAVING COUNT(DISTINCT cu.user_id) = 3
Not sure if this is correct as I am not a Laravel user -
$data = [1, 2, 3];
$conv = DB::table('conversation_user cu')
->select('c.*')
->join('conversations c', 'cu.conversation_id', '=', 'c.id')
->whereIn('cu.user_id', $data)
->whereNotExists(function($query) use ($data) {
$query->select(DB::raw(1))
->from('conversation_user')
->whereColumn('conversation_id', 'cu.conversation_id')
->whereNotIn('user_id', $data);
})
->groupBy('cu.conversation_id')
->havingRaw('COUNT(DISTINCT cu.user_id) = ?', count($data))
->get();
So I have this table.
I want to select the last row (by ID) with student_id = 1 and compare it with a specific status_id (which in my code is $status_id). (taken from a HTML form).
Expected behavior:
for student_id = 1 and status_id = 1:
return nothing
for student_id = 1 and status_id = 6:
return the row with student_id = 1, status_id = 6
Explanation:
The last row created has a status_id of 6, so return data only when the user sends the status_id of 6.
I tried using it this way:
$q->latest('id')->first()->where('status_id', $status);
But it won't work because first() returns an object not a QueryBuilder anymore, so the ->where() clause won't work anymore.
My question is: how can I get the first row in this case while still being in a QueryBuilder, or if it is not possbile, how can I do this query in Laravel?
Assuming your table name is ‘students’.
Option 1:
$student_id = 1;
$status_id = 6;
$students = DB::table('students')
->where('id', function($query) use ($student_id) {
$query->selectRaw('max(id)') // select highest id
->from('students')
->where('student_id', $student_id); // where student_id=1
})
->where('status_id', $status_id) // only if status_id=6
->first();
// returns ROW if highest id with student_id 1 has status_id 6, otherwise null
dd($students);
Option 2:
$student_id = 1;
$status_id = 6;
$students = DB::table('students')
->where('id', function($query) use ($student_id) {
$query->select('id') // select all id's
->from('students')
->where('student_id', $student_id) // where student_id=1
->orderByDesc('id') // sort it newest to oldest
->first(); // and get us the first
})
->where('status_id', $status_id) // only if status_id=6
->first();
// returns ROW if highest id with student_id 1 has status_id 6, otherwise null
dd($students);
In both options we combine where 'highest id (given by a subquery) which has student_id' and where 'status_id=6'. Option 1 is better, because it directly selects the highest id. Option 2 first selects all rows with student_id=8, sorts it from newest to oldest and take the first.
Edit: I misunderstood the question first. Now edited based on conversation in comments.
$demos = Demo::whereIn('store_id', [1,2,4], function($query){
$query->where('status', 1);
})->paginate(10);
I know that this thats not working, But how can I work with this logic..?
[Select * from 'demos' where store_id in 1,2,4 and status = 1 ]
If I understand correctly you need something like this.
$demos = Demo::whereIn('store_id', [1, 2, 4])->where('status', 1)->paginate(10);
Chained "where" Eloquent methods == "AND" database query conditions.
Sometimes, it is better not use ORM`ish approach.
They say, old plain SQL is your best friend. So.
$data = \DB::select(
\DB::raw("SELECT * FROM `demos` WHERE `store_id` IN (:id1, :id2, :id3)"), array( ':id1' => 1, ':id2' => 2, ':id3' => 3 )
);
Or, if you have unknown count of store_id entries, you could:
$ids = array(1, 2, 3, 4, ...); // looks like unknown ammount of entries
$data = \DB::select(
\DB::raw("SELECT * FROM `demos` WHERE `store_id` IN (".rtrim(str_repeat('?,', count($ids)),',').")"), $ids
);
I have entity Travel, let's say it has 30 entries for example :
1-
2-
3-
4-
.
.
.
30-
I'd like to select 6 entity randomly, for example : 2, 14, 7, 25, 16, 1
I have tried this code but it works, but the results are always displayed by order ASC (3,4,5,6,7,8,).
public function getRandomTravelsFrontend()
{
$count = $this->createQueryBuilder('t')
->select('COUNT(t)')
->getQuery()
->getSingleScalarResult();
$qb = $this->createQueryBuilder('t')
->leftJoin('t.image', 'i')
->addSelect('i')
->Where('t.enabled = 1')
->setMaxResults(6)
->setFirstResult(rand(0, $count - 6));
return $qb->getQuery()->getResult();
}
How to display result by random order ? and is it possible to select 6 entities like this : 2, 14, 7, 25, 16, 1 ?
You can use ORDER BY rand() in your SQL select statement.
Brief:
I am trying to union 2 tables recipes and posts then add ->paginate(5) to the queries.
But for some reason I get this error:
Cardinality violation: 1222 The used SELECT statements have a
different number of columns (SQL: (select count(*) as aggregate from
posts
Code:
$recipes = DB::table("recipes")->select("id", "title", "user_id", "description", "created_at")
->where("user_id", "=", $id);
$items = DB::table("posts")->select("id", "title", "user_id", "content", "created_at")
->where("user_id", "=", $id)
->union($recipes)
->paginate(5)->get();
Am i doing something wrong?
Without ->paginate(5) the query works fine.
You're right, pagination cause problem. Right now, you can create a view and query the view instead of the actual tables, or create your Paginator manually:
$page = Input::get('page', 1);
$paginate = 5;
$recipes = DB::table("recipes")->select("id", "title", "user_id", "description", "created_at")
->where("user_id", "=", $id);
$items = DB::table("posts")->select("id", "title", "user_id", "content", "created_at")
->where("user_id", "=", $id)
->union($recipes)
->get();
$slice = array_slice($items->toArray(), $paginate * ($page - 1), $paginate);
$result = Paginator::make($slice, count($items), $paginate);
return View::make('yourView',compact('result'));
I faced this kind of issue already. I found a thread also not about pagination but about unions.
Please see this link : Sorting UNION queries with Laravel 4.1
#Mohamed Azher has shared a nice trick and it works on my issue.
$query = $query1->union($query2);
$querySql = $query->toSql();
$query = DB::table(DB::raw("($querySql order by foo desc) as a"))->mergeBindings($query);
This creates an sql like below:
select * from (
(select a as foo from foo)
union
(select b as foo from bar)
) as a order by foo desc;
And you can already utilize Laravel's paginate same as usual like $query->paginate(5). (but you have to fork it a bit to fit to your problem)
Reiterating jdme's answer with a more elegant method from Illuminate\Database\Query\Builder.
$recipes = DB::table("recipes") ..
$items = DB::table("posts")->union($recipes) ..
$query = DB::query()
->fromSub($items, "some_query_name");
// Let's paginate!
$query->paginate(5);
I hope this helps!
The accepted answer works great for Query Builder.
But here's my approach for Laravel Eloquent Builder.
Assume that we're referring to same Model
$q1 = Model::createByMe(); // some condition
$q2 = Model::createByMyFriend(); // another condition
$q2->union($q1);
$querySql = $q2->toSql();
$query = Model::from(DB::raw("($querySql) as a"))->select('a.*')->addBinding($q2->getBindings());
$paginated_data = $query->paginate();
I'm using Laravel 5.6
order by
$page = Input::get('page', 1);
$paginate = 5;
$recipes = DB::table("recipes")->select("id", "title", "user_id", "description", "created_at")
->where("user_id", "=", $id);
$items = DB::table("posts")->select("id", "title", "user_id", "content", "created_at")
->where("user_id", "=", $id)
->union($recipes)
->orderBy('created_at','desc')
->get();
$slice = array_slice($items, $paginate * ($page - 1), $paginate);
$result = Paginator::make($slice, count($items), $paginate);
return View::make('yourView',compact('result'))->with( 'result', $result );
View page :
#foreach($result as $data)
{{ $data->your_column_name;}}
#endforeach
{{$result->links();}} //for pagination
its help to more peoples.. because nobody cant understand show data in
view page union with pagination and orderby .. thank u
I know this answer is too late. But I want to share my problems and my solution.
My problems:
Join with many tables at the same time
UNION
Paginate (Must use, because I have to use a common theme to show pagination. If I made own custom for pagination, it will not match to current. And in the future, a common theme may be changed.)
Big data: view took 4 seconds, page load took 4 seconds => total is 8 seconds. (But if I set condition inside that view, it was least than 1 second for total.)
Query
※This is the sample.
MariaDB, about 146,000 records.
SELECT A.a_id
, A.a_name
, B.organization_id
, B.organization_name
FROM customers A
LEFT JOIN organizations B ON (A.organization_id = B.organization_id)
UNION ALL
SELECT A.a_id
, A.a_name
, B.organization_id
, B.organization_name
FROM employees A
LEFT JOIN organizations B ON (A.organization_id = B.organization_id)
Solution
Reference from www.tech-corgi.com (やり方2), I updated my PHP code to filter inside my query, and then call paginate normally.
I must add a condition (filter) before getting large records. In this example is organization_id.
$query = "
SELECT A.a_id
, A.a_name
, B.organization_id
, B.organization_name
FROM customers A
LEFT JOIN organizations B ON (A.organization_id = B.organization_id)
WHERE 1 = 1
AND B.organization_id = {ORGANIZATION_ID}
UNION ALL
SELECT A.a_id
, A.a_name
, B.organization_id
, B.organization_name
FROM employees A
LEFT JOIN organizations B ON (A.organization_id = B.organization_id)
WHERE 1 = 1
AND B.organization_id = {ORGANIZATION_ID}
";
$organization_id = request()->organization_id;
$query = str_replace("{ORGANIZATION_ID}", $organization_id, $query);
But it still cannot be used in paginate(). There is a trick to solve this problem. See below.
Final code
Trick: put query inside (). For example: (SELECT * FROM TABLE_A).
Reason: paginage() will generate and run Count query SELECT count(*) FROM (SELECT * FROM TABLE_A), if we did not put inside brackets, Count query would not be a correct query.
$query = "
( SELECT A.a_id
, A.a_name
, B.organization_id
, B.organization_name
FROM customers A
LEFT JOIN organizations B ON (A.organization_id = B.organization_id)
WHERE 1 = 1
AND B.organization_id = {ORGANIZATION_ID}
UNION ALL
SELECT A.a_id
, A.a_name
, B.organization_id
, B.organization_name
FROM employees A
LEFT JOIN organizations B ON (A.organization_id = B.organization_id)
WHERE 1 = 1
AND B.organization_id = {ORGANIZATION_ID}
) AS VIEW_RESULT
";
$organization_id = request()->organization_id;
$query = str_replace("{ORGANIZATION_ID}", $organization_id, $query);
$resultSet = DB::table(DB::raw($query))->paginate(20);
Now I can use it normally:
SELECT, JOIN, UNION
paginate
High performance: Filter data before getting
Hope it help!!!
Getting the total count for pagination is the problem here. This is the error I got when used $builder->paginate()
"SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns (SQL: (select count(*) as aggregate from `institute_category_places` where `status` = approved and (`category_id` in (3) or `name` LIKE %dancing class% or `description` LIKE %dancing class% or `address_line1` LIKE %dancing class% or `address_line2` LIKE %dancing class% or `city` LIKE %dancing class% or `province` LIKE %dancing class% or `country` LIKE %dancing class%) and `institute_category_places`.`deleted_at` is null) union (select * from `institute_category_places` where `status` = approved and (`category_id` in (3, 4) or `name` LIKE %dancing% or `description` LIKE %dancing% or `address_line1` LIKE %dancing% or `address_line2` LIKE %dancing% or `city` LIKE %dancing% or `province` LIKE %dancing% or `country` LIKE %dancing% or `name` LIKE %class% or `description` LIKE %class% or `address_line1` LIKE %class% or `address_line2` LIKE %class% or `city` LIKE %class% or `province` LIKE %class% or `country` LIKE %class%) and `institute_category_places`.`deleted_at` is null))"
If you want to paginate without total count you can use
$builder->limit($per_page)->offset($per_page * ($page - 1))->get();
to get only set of rows in the page.
Getting all the rows and counting total is memory inefficient. So I used following approach to get total count.
$bindings = $query_builder->getBindings();
$sql = $query_builder->toSql();
foreach ($bindings as $binding) {
$value = is_numeric($binding) ? $binding : "'" . $binding . "'";
$sql = preg_replace('/\?/', $value, $sql, 1);
}
$sql = str_replace('\\', '\\\\', $sql);
$total = DB::select(DB::raw("select count(*) as total_count from ($sql) as count_table"));
Then we have to paginate the result manually.
$page = Input::get('page', 1);
$per_page = 15;
$search_results = $query_builder->limit($per_page)->offset($per_page * ($page - 1))->get();
$result = new LengthAwarePaginator($search_results, $total[0]->total_count, $per_page, $page, ['path' => $request->url()]);
If you can use raw sql queries, it is much more CPU and memory efficient.
Using Eloquent
I adapted jdme's answer in order to use it with Eloquent. I created a class extending the default Eloquent Builder and overiding the union method to fix the issue with paginate.
Create app\Builder\BuilderWithFixes.php:
<?php
namespace App\Builder;
use Illuminate\Database\Eloquent\Builder;
class BuilderWithFixes extends Builder
{
/**
* Add a union statement to the query.
*
* #param \Illuminate\Database\Query\Builder|\Closure $query
* #param bool $all
* #return \Illuminate\Database\Query\Builder|static
*/
public function union($query, $all = false)
{
$query = parent::union($query, $all);
$querySql = $query->toSql();
return $this->model->from(\DB::raw("($querySql) as ".$this->model->table))->select($this->model->table.'.*')->addBinding($this->getBindings());
}
}
In you Model (for example app\Post.php), include the method newEloquentBuilder below to replace the default Eloquent Builder with \App\Builder\BuilderWithFixes:
<?php
namespace App;
use Eloquent as Model;
class Post extends Model
{
// your model stuffs...
public function newEloquentBuilder($query)
{
return new \App\Builder\BuilderWithFixes($query);
}
}
Now you can use union + paginate at the same time within your model (in this case Post) normally, like:
$recipes = Recipe::select("id", "title", "user_id", "description", "created_at")
->where("user_id", "=", $id);
$items = Post::select("id", "title", "user_id", "content", "created_at")
->where("user_id", "=", $id)
->union($recipes)
->paginate(5);
I had this same problem, and unfortunately I couldn't get the page links with {{ $result->links() }}, but I found another way to write the pagination part and the page links appears
Custom data pagination with Laravel 5
//Create a new Laravel collection from the array data
$collection = new Collection($searchResults);
//Define how many items we want to be visible in each page
$perPage = 5;
//Slice the collection to get the items to display in current page
$currentPageSearchResults = $collection->slice($currentPage * $perPage, $perPage)->all();
//Create our paginator and pass it to the view
$paginatedSearchResults= new LengthAwarePaginator($currentPageSearchResults, count($collection), $perPage);
return view('search', ['results' => $paginatedSearchResults]);
for paginate collection do this:
add this to boot function in \app\Providers\AppServiceProvider
/**
* Paginate a standard Laravel Collection.
*
* #param int $perPage
* #param int $total
* #param int $page
* #param string $pageName
* #return array
*/
Collection::macro('paginate', function($perPage, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
});
From hereafter for all collection you can paginate like your code
$items = DB::table("posts")->select("id", "title", "user_id", "content", "created_at")
->where("user_id", "=", $id)
->union($recipes)
->paginate(5)
For those who may still look for the answer, I have tried union and paginate together and got right result under laravel 5.7.20. This will be better than merging collections then paginate which will not work on big amount of data.
Some demo code (in my case, I will deal with multiple databases with same table name):
$dbs=["db_name1","db_name2"];
$query=DB::table("$dbs[0].table_name");
for($i=1;$i<count($log_dbs);$i++){
$query=DB::table("$dbs[$i].table_name")->union($query);
}
$query=$query->orderBy('id','desc')->paginate(50);
I haven't tried on other higher version of laravel. But at least it could work now!
More information
My previous version of laravel is 5.7.9 which will report the Cardinality violation error. So the laravel team solved this issue in some version of 5.7.x.
$page = Input::get('page', 1);
$paginate = 5;
$recipes = DB::table("recipes")->select("id", "title", "user_id", "description", "created_at")
->where("user_id", "=", $id);
$items = DB::table("posts")->select("id", "title", "user_id", "content", "created_at") ->where("user_id", "=", $id)->union($recipes)->get()->toArray();
$slice = array_slice($items, $paginate * ($page - 1), $paginate);
$result = new Paginator($slice , $paginate);