How to Use Union And Paginate in laravel - laravel

I Want To Union two Model And Paginate Result But Get Error, In Bellow See My Call Code And Result:
$this->archive = $this->archive
->select (['id','property_id','plan_id','pay_id','price','period','start_at','expire_at'])
->whereHas('property',function ($query) use ($user){
$query->select(['user_id','type_id','title'])->where('user_id',$user);
})->with(['property'=>function($query){
$query->select('title','id');
},'property.owner'=>function($query){
$query->select('name','family','id');
},'plan'=>function($query){
$query->select('title','price','id','status');
},'transaction'=>function($query){
$query->select('port','price','id','status','payment_date');
}]);
$this->model = $this->model
->select (['id', 'property_id','plan_id','pay_id','price','period','start_at','expire_at'])
->whereHas('property',function ($query) use ($user){
$query->select(['user_id','type_id','title'])->where('user_id',$user);
})->with(['property'=>function($query){
$query->select('title','id');
},'property.owner'=>function($query){
$query->select('name','family','id');
},'plan'=>function($query){
$query->select('title','price','id','status');
},'transaction'=>function($query){
$query->select('port','price','id','status','payment_date');
}])
->union($this->archive)->orderBy('expire_at','DESC')->paginate ($paginate);
And Result Of Them Is:
SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns
Please Help Me

you can union sql results like this,
$result = $query1->merge($query2);
$resultSorted = $result->sortByDesc('expire_at');
$count = $query1->count() + $query2->count();
$page = $request['page'];
$perPage = 20;
$resultSorted = new LengthAwarePaginator(
$resultSorted->forPage($page, $perPage), $count, $perPage, $page
);

Pagination with union is not supported in Laravel since there's no elegant way to do it without major drawbacks.

Related

how to convert sql to query builder laravel

Can anyone help me out to convert this SQL to query builder!
SELECT topwords.*,
mw.word AS my_word
FROM topwords
LEFT JOIN (SELECT DISTINCT words.word
FROM definition_word
JOIN words
ON words.id = definition_word.word_id
WHERE definition_word.user_id = $user) AS mw
ON topwords.word = mw.word
I have a problem with how to use a subquery in leftjoin!
I tried something like this but it has error!
See error as image
DB::table('topwords')
->leftJoin(DB::raw("SELECT DISTINCT
words.word
FROM definition_word
JOIN words ON words.id = definition_word.word_id
WHERE definition_word.user_id = $user as mw"),"topwords.word", "=", "mw.word" )
->select(
"topwords.*",
"mw.word AS my_word"
)->orderBy('id','desc')->paginate(15);
you can use Join Sub query official document subquery-joins
$mw = DB::table('words')
->select('DISTINCT words.word')
->join('definition_word', function($join) use ($user)
{
$join->on('wordss.id', '=', 'definition_word.word_id')
->where('definition_word.user_id', $user);
});
$topwords = DB::table('topwords')
->joinSub($mw, 'mw',function ($join) {
$join->on('topwords.word', '=', 'mw.word');
})
->select('topwords.*','mw.word AS my_word')
->orderBy('id','desc')
->paginate(15);
You have this error because of paginate and aggregation
Try to make custom pagination, using LengthAwarePaginator
Here is example: Laracast
So you need to make something like this:
$query = DB::table('topwords')
->leftJoin(DB::raw("SELECT DISTINCT
words.word
FROM definition_word
JOIN words ON words.id = definition_word.word_id
WHERE definition_word.user_id = $user as mw"),"topwords.word", "=", "mw.word" )
->select(
"topwords.*",
"mw.word AS my_word"
);
$paginator = new LengthAwarePaginator($query->get(), $query->count(), $request->input('per_page', 15), $request->input('page', 1));
And then you can use it in the collection

Laravel order by eagerly loaded column

I am using laravel eager loading to load data on the jquery datatables. My code looks like:
$columns = array(
0 => 'company_name',
1 => 'property_name',
2 => 'amenity_review',
3 => 'pricing_review',
4 => 'sqft_offset_review',
5 => 'created_at',
6 => 'last_uploaded_at'
);
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company','notices']);
$company_search = $request->columns[0]['search']['value'];
if(!empty($company_search)){
$query->whereHas('company', function ($query) use($company_search) {
$query->where('name','like',$company_search.'%');
});
}
$property_search = $request->columns[1]['search']['value'];
if(!empty($property_search)){
$query->where('properties.property_name','like',$property_search.'%');
}
if(!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id',Auth::user()->company_id);
}
$query->orderBy($order,$dir);
if($limit != '-1'){
$records = $query->offset($start)->limit($limit);
}
$records = $query->get();
With this method I received error: Column not found: 1054 Unknown column 'company_name' in 'order clause' .
Next, I tried with following order condition:
if($order == 'company_name'){
$query->orderBy('company.name',$dir);
}else{
$query->orderBy($order,$dir);
}
However, it also returns similar error: Column not found: 1054 Unknown column 'company.name' in 'order clause'
Next, I tried with whereHas condition:
if($order == 'company_name'){
$order = 'name';
$query->whereHas('company', function ($query) use($order,$dir) {
$query->orderBy($order,$dir);
});
}else{
$query->orderBy($order,$dir);
}
But, in this case also, same issue.
For other table, I have handled this type of situation using DB query, however, in this particular case I need the notices as the nested results because I have looped it on the frontend. So, I need to go through eloquent.
Also, I have seen other's answer where people have suggested to order directly in model like:
public function company()
{
return $this->belongsTo('App\Models\Company')->orderBy('name');
}
But, I don't want to order direclty on model because I don't want it to be ordered by name everytime. I want to leave it to default.
Also, on some other scenario, I saw people using join combining with, but I am not really impressed with using both join and with to load the same model.
What is the best way to solve my problem?
I have table like: companies: id, name, properties: id, property_name, company_id, notices: title, slug, body, property_id
The issue here is that the Property::with(['company','notices']); will not join the companies or notices tables, but only fetch the data and attach it to the resulting Collection. Therefore, neither of the tables are part of the SQL query issued and so you cannot order it by any field in those tables.
What Property::with(['company', 'notices'])->get() does is basically issue three queries (depending on your relation setup and scopes, it might be different queries):
SELECT * FROM properties ...
SELECT * FROM companies WHERE properties.id in (...)
SELECT * FROM notices WHERE properties.id in (...)
What you tried in the sample code above is to add an ORDER BY company_name or later an ORDER BY companies.name to the first query. The query scope knows no company_name column within the properties table of course and no companies table to look for the name column. company.name will not work either because there is no company table, and even if there was one, it would not have been joined in the first query either.
The best solution for you from my point of view would be to sort the result Collection instead of ordering via SQL by replacing $records = $query->get(); with $records = $query->get()->sortBy($order, $dir);, which is the most flexible way for your task.
For that to work, you would have to replace 'company_name' with 'company.name' in your $columns array.
The only other option I see is to ->join('companies', 'companies.id', 'properties.company_id'), which will join the companies table to the first query.
Putting it all together
So, given that the rest of your code works as it should, this should do it:
$columns = [
'company.name',
'property_name',
'amenity_review',
'pricing_review',
'sqft_offset_review',
'created_at',
'last_uploaded_at',
];
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company', 'notices']);
$company_search = $request->columns[0]['search']['value'];
$property_search = $request->columns[1]['search']['value'];
if (!empty($company_search)) {
$query->whereHas(
'company', function ($query) use ($company_search) {
$query->where('name', 'like', $company_search . '%');
});
}
if (!empty($property_search)) {
$query->where('properties.property_name', 'like', $property_search . '%');
}
if (!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id', Auth::user()->company_id);
}
if ($limit != '-1') {
$records = $query->offset($start)->limit($limit);
}
$records = $query->get()->sortBy($order, $dir);

Laravel alternative to paginate on collection?

On my website, I have Submissions, and submissions can have comments.
Comments can have upvotes and downvotes, leading to a total "score" for the comment.
In this example, before passing the comments to the view, I sort them by score.
$comments = Comment::where('submission_id', $submission->id)->where('parent_id', NULL)->get();
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
});
This works fine. The higher the score of a comment, the higher it is sorted.
However, I want to paginate these results.
If I do ->paginate(10) instead get(), the following sortByDesc will only sort those 10 results.
So logically I would want to add the paginator after the sortByDesc like so:
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
})->paginate(10);
However this will return the error:
Method Illuminate\Database\Eloquent\Collection::paginate does not
exist.
as expected.
My question is, what is the alternative to using paginate in this situation?
EDIT:
When trying the response of #party-ring (and switching the double quotes and single quotes) I get the following error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an
error in your SQL syntax; check the manual that corresponds to your
MariaDB server version for the right syntax to use near '["upvotes"])
- count($comment["downvotes"]) desc limit 10 offset 0' at line 1 (SQL: select * from comments where submission_id = 1 and parent_id is
null order by count($comment["upvotes"]) -
count($comment["downvotes"]) desc limit 10 offset 0)
You are trying to paginate after the get, the solution i try on my website is this and it works
$users = User::where('votes', '>', 100)->get();
$page = Input::get('page', 1); // Get the ?page=1 from the url
$perPage = 15; // Number of items per page
$offset = ($page * $perPage) - $perPage;
return new LengthAwarePaginator(
array_slice($users->toArray(), $offset, $perPage, true), // Only grab the items we need
count($users), // Total items
$perPage, // Items per page
$page, // Current page
['path' => $request->url(), 'query' => $request->query()] // We need this so we can keep all old query parameters from the url
);
You could add a macro:
if (!Collection::hasMacro('paginate')) {
Collection::macro('paginate', function ($perPage = 25, $page = null, $options = []) {
$options['path'] = $options['path'] ?? request()->path();
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$this->count(),
$perPage,
$page,
$options
);
});
}
Then you can use a collection to paginate your items:
collect([1,2,3,4,5,6,7,8,9,10])->paginate(5);
See Extending Collections under Introduction
Give this a try:
$comments = Comment::where('submission_id', $submission->id)
->where('parent_id', NULL)
->orderBy(DB::raw("count($comment['upvotes']) - count($comment['downvotes'])"), 'desc')
->paginate(10);`
SortBy returns a Collection, whereas you can only call paginate on an instance of QueryBuilder. OrderBy should return an instance of QueryBuilder, and you should be able to do the subtraction using a DB::raw statement.
** edit
I have just read about orderByRaw, which might be useful in this scenario:
$comments = Comment::where('submission_id', $submission->id)
->where('parent_id', NULL)
->orderByRaw('(upvotes - downvotes) desc')
->paginate(10);`
You might have to play around a bit with your subtraction above as I don't know the structure of your comments table.
A couple of links which might be useful:
laravel orderByRaw() on the query builder
https://laraveldaily.com/know-orderbyraw-eloquent/

Laravel how do I get the row number of an object using Eloquent?

I'd like to know the position of a user based on its creation date. How do I do that using Eloquent?
I'd like to be able to do something like this:
User::getRowNumber($user_obj);
I suppose you want MySQL solution, so you can do this:
DB::statement(DB::raw('set #row:=0'));
User::selectRaw('*, #row:=#row+1 as row')->get();
// returns all users with ordinal 'row'
So you could implement something like this:
public function scopeWithRowNumber($query, $column = 'created_at', $order = 'asc')
{
DB::statement(DB::raw('set #row=0'));
$sub = static::selectRaw('*, #row:=#row+1 as row')
->orderBy($column, $order)->toSql();
$query->remember(1)->from(DB::raw("({$sub}) as sub"));
}
public function getRowNumber($column = 'created_at', $order = 'asc')
{
$order = ($order == 'asc') ? 'asc' : 'desc';
$key = "userRow.{$this->id}.{$column}.{$order}";
if (Cache::get($key)) return Cache::get($key);
$row = $this->withRowNumber($column, $order)
->where($column, '<=',$this->$column)
->whereId($this->id)->pluck('row');
Cache::put($key, $row);
return $row;
}
This needs to select all the rows from the table till the one you are looking for is found, then selects only that particular row number.
It will let you do this:
$user = User::find(15);
$user->getRowNumber(); // as default ordered by created_at ascending
$user->getRowNumber('username'); // check order for another column
$user->getRowNumber('updated_at', 'desc'); // different combination of column and order
// and utilizing the scope:
User::withRowNumber()->take(20)->get(); // returns collection with additional property 'row' for each user
As this scope requires raw statement setting #row to 0 everytime, we use caching for 1 minute to avoid unnecessary queries.
$query = \DB::table(\DB::raw('Products, (SELECT #row := 0) r'));
$query = $query->select(
\DB::raw('#row := #row + 1 AS SrNo'),
'ProductID',
'ProductName',
'Description',
\DB::raw('IFNULL(ProductImage,"") AS ProductImage')
);
// where clauses
if(...){
$query = $query->where('ProductID', ...));
}
// orderby clauses
// ...
// $query = $query->orderBy('..','DESC');
// count clause
$TotalRecordCount = $query->count();
$results = $query
->take(...)
->skip(...)
->get();
I believe you could use Raw Expresssions to achieve this:
$users = DB::table('users')
->select(DB::raw('ROW_NUMBER() OVER(ORDER BY ID DESC) AS Row, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
However, looking trough the source code looks like you could achieve the same when using SQLServer and offset. The sources indicates that if you something like the following:
$users = DB::table('users')->skip(10)->take(5)->get();
The generated SQL query will include the row_number over statement.
[For Postgres]
In your model
public function scopeWithRowNumber($query, $column = 'id', $order = 'asc'){
$sub = static::selectRaw('*, row_number() OVER () as row_number')
->orderBy($column, $order)
->toSql();
$query->from(DB::raw("({$sub}) as sub"));
}
In your controller
$user = User::withRowNumber()->get();

Laravel pagination not working with group by clause

It seems Laravel pagination does not working properly with group by clause. For example:
$users = Subject::select(DB::raw('subjects.*, count(user_subjects.id) as total_users'))
->join('user_subjects', 'user_subjects.subject_id', '=', 'subjects.id')
->whereNull('user_subjects.deleted_at')
->groupBy('subjects.id')
->orderBy('subjects.updated_at', 'desc')
->paginate(25);
Produced
select subjects.*, count(user_subjects.id) as total_users
from `subjects` inner join `user_subjects` on `user_subjects`.`subject_id` = `subjects`.`id`
where `subjects`.`deleted_at` is null and `user_subjects`.`deleted_at` is null
group by `subjects`.`id`
order by `subjects`.`updated_at` desc
note that, there is no limit clause on the query.
Working fine if no group by clause in the query:
$users = Subject::select(DB::raw('subjects.*, count(user_subjects.id) as total_users'))
->join('user_subjects', 'user_subjects.subject_id', '=', 'subjects.id')
->whereNull('user_subjects.deleted_at')
->orderBy('subjects.updated_at', 'desc')
->paginate(25);
produced the following query:
select subjects.*, count(user_subjects.id) as total_users from `subjects`
inner join `user_subjects` on `user_subjects`.`subject_id` = `subjects`.`id`
where `subjects`.`deleted_at` is null and `user_subjects`.`deleted_at` is null
order by `subjects`.`updated_at` desc
limit 25 offset 0
does anyone has any idea how can i fix this?
Check the documentation
https://laravel.com/docs/5.2/pagination
Currently, pagination operations that use a groupBy statement cannot
be executed efficiently by Laravel. If you need to use a groupBy with
a paginated result set, it is recommended that you query the database
and create a paginator manually.
I know it is an old question, by I am sharing my solution for future reference.
I managed to write a function based on this link which does the heavy job of determining the pagination of a complex query. Just pass the 'QueryBuilder' and it will return the paginated object/collection.
Additionally, this procedure can track and maintain the other parameters except for page=.
public function mergeQueryPaginate(\Illuminate\Database\Eloquent\Builder $query): \Illuminate\Pagination\LengthAwarePaginator
{
$raw_query = $query;
$totalCount = $raw_query->get()->count();
$perPage = request('per-page', 10);
$page = request('page', 1);
$skip = $perPage * ($page - 1);
$raw_query = $raw_query->take($perPage)->skip($skip);
$parameters = request()->getQueryString();
$parameters = preg_replace('/&page(=[^&]*)?|^page(=[^&]*)?&?/', '', $parameters);
$path = url(request()->getPathInfo() . '?' . $parameters);
$rows = $raw_query->get();
$paginator = new LengthAwarePaginator($rows, $totalCount, $perPage, $page);
$paginator = $paginator->withPath($path);
return $paginator;
}
This works for me in laravel 5.2
Select(\DB::RAW("assignment_descendant_child.assignment_descendant_child_id, assignment_descendant_child.assignment_descendant_child_name, COUNT(assignment_descendant.assignment_descendant_id) as xNum"))
->leftJoin(
'assignment_descendant',
'assignment_descendant.assignment_descendant_child_id',
'=',
'assignment_descendant_child.assignment_descendant_child_id'
)
->orderBy('assignment_descendant_child_name')
->groupBy('assignment_descendant_child.assignment_descendant_child_id')
->paginate(\Config::get('constants.paginate_org_index'))
create a database view namedvw_anything. MySql query will be like
create view vw_anything as select subjects.*, count(user_subjects.id) as total_users from subjects inner join user_subjects on user_subjects.subject_id = subjects.id
where subjects.deleted_at is null and user_subjects.deleted_at is null group by subjects.id;
Now create a new model named UserSubModel for this view, protected $table = 'vw_anything';
Now your paginate query will be like UserSubModel::orderBy('subjects.updated_at', 'desc')->paginate(25);
.
To answer this questioin Laravel Pagination group by year and month only
View query will be :
create view vw_anything as select gallery.*, DATE_FORMAT(created_at, "%Y-%m") as tanggal,count(created_at) as jumlah from gallery group by tanggal;
Let you model is VwModel then your paginate query will be
VwModel::where('type','Foto')->orderBy('tanggal','desc')->paginate(2);
This works if you want to group by and paginate.
$code = DB::table('sources')
->select(DB::raw('sources.id_code,sources.title,avg(point) point'))
->join('rating','sources.id_code','rating.id_code')
->groupBy('sources.id_code')
->groupBy('sources.title')
->groupBy('sources.language')
->groupBy('sources.visited')
->paginate(5);

Resources