How to get ranking using sub query in Laravel? - php

Version
Laravel : 7.28.3
mysql : Ver 14.14 Distrib 5.7.29, for osx10.15 (x86_64) using EditLine wrapper
Tables
contents (id)
content_views (id, content_id)
What I'm Trying To Do
I would like to get a rank of a content by how many it has content_views.
Code
App/Models/Content.php
/**
* Attribute of get rank by views
*
* #return Int
*/
public function getViewsRankingAttribute()
{
DB::statement(DB::raw('set #c=0'));
$result = collect(
DB::select('select rank from
(
select _ranking.*, #c:=#c+1 as rank from
(
select content_views.content_id, count(content_views.id) as views
from content_views
group by content_views.content_id
order by views desc
) as _ranking
) as ranking
where content_id = :contentId', [
'contentId' => $this->id
]))
->first();
return $result ? $result->rank : '-';
}
This is actually working in mysql 5.8 but it's not in 8.0.
Error
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'from ( select _ranking., #c:=#c+1 as rank from ' at line 1 (SQL: select rank from ( select _ranking., #c:=#c+1 as rank from ( select content_views.content_id, count(content_views.id) as views from content_views group by content_views.content_id order by views desc ) as _ranking ) as ranking where content_id = :contentId)

MySQL 8.0 introduced window functions, which make it much easier to perform ranking tasks. On the other hand, user variables are officially planned for deprecation in future versions.
Time to embrace the future! You can rewrite your query as follows:
select *
from (
select content_id, count(*) as views, rank() over(order by count(*) desc) rn
from content_views
group by content_id
) t
where content_id = :content_id

This actually worked!
public function getViewsRankingAttribute()
{
$rank = '-';
if ($this->views->isNotEmpty()) {
$views = $this->views->count();
$rank = Content::withCount('views')
->having('views_count', '>', $views)
->get()
->count();
$rank++;
}
return $rank;
}

Related

How to make a scope with a raw SQL query in Laravel 7?

Problem
I am trying to make a scope in my model with this SQL query:
SELECT *
FROM ro_container_events
WHERE (container_id, location_timestamp, id)
IN (
SELECT distinct container_id, MAX(location_timestamp) AS lts, MAX(id) AS rce_id
FROM ro_container_events
GROUP BY container_id
)
The model name is ContainerEvent and the name of the table in my database is ro_container_events.
Also, I know that the SQL query is valid because I ran it in my MySQL administration tool (HeidiSQL), and it returns the good rows.
What I've tried
My scope (in my ContainerEvent model) currently looks like this:
public function scopeLatestEventForContainers($query)
{
return $query->select(DB::raw('
SELECT *
FROM ro_container_events
WHERE (container_id, location_timestamp, id)
IN (
SELECT distinct container_id, MAX(location_timestamp) AS lts, MAX(id) AS rce_id
FROM ro_container_events
GROUP BY container_id
)'
)
);
}
But it doesn't return any rows?
My research
I've been searching about this topic for a while but can't seem to find what is wrong with my scope...
The Laravel documentation says that we can use :
DB::raw('...')
In order to make a specific SQL query.
And I've seen on some other threads that I should be able to make a scope with the following :
return $query->select(DB::raw('...');
try Eloquent:
//as your where in select this must return just one row with 3 field
$in = ContainerEvent::distinct('container_id', 'location_timestamp', 'id')->where(fn($q) => {
'id' => ContainerEvent::max('id')
'location_timestamp' => ContainerEvent::max('location_timestamp')
])->get();
but it may be wrong because there is ambitious in db structure.
I just try your SQL syntax using Laravel query builder, and the result using method toSql() returns the exact thing that you need.
SELECT * FROM ro_container_events WHERE (container_id, location_timestamp, id) IN (
SELECT distinct container_id, MAX(location_timestamp) AS lts, MAX(id) AS rce_id
FROM ro_container_events
GROUP BY container_id
)
Try this function:
public function scopeLatestEventForContainers($query)
{
return $query->whereIn(\DB::raw('(container_id, location_timestamp, id)'), function ($q) {
$q->distinct()->select(\DB::raw('
container_id,
MAX(location_timestamp) AS lts,
MAX(id) AS rce_id FROM `ro_container_events`
'))->groupBy('container_id');
});
}
Hope that's help :)
Best,
vreedom18

Why am is laravel throwing an SQLSTATE[42000]: Syntax error or access violation: 1064 Error

Am running a query builder on my laravel app using inner join
But Laravel keeps throwing errors, i tested the MySql Query
i wrote directly in my database using phpMyAdmin it works fine
SELECT
u.id, u.first_name, u.last_name, u.username, u.sponsor,u.deleted_at, i.id as investment_id, i.total_amount, i.created_at
FROM
users AS u
JOIN investments as i ON
i.id= ( SELECT i1.id FROM investments as i1 where u.id=i1.user_id and i1.status ='confirmed' ORDER BY i1.created_at LIMIT 1)
WHERE
u.sponsor = '901d08da-e6c4-476a-ae7b-9386990b8b9e' AND u.deleted_at is NULL
ORDER BY
created_at DESC
but when i write it using query builder it wont work.
$refered_users = DB::table('users')
->join('investments', function ($join) {
$join->on('users.id', '=', 'investments.user_id')
->where('investment.status', '=', 'confirmed')
->orderBy('investment.created_at','desc')->first();
})
->select('users.id,users.first_name,users.last_name,users.username,users.sponsor,investment.id AS investment_id,investment.total_amount,investment.created_at')
->where('users.sponsor','=',Auth::User()->id)
->orderBy('investment.created_at','desc')
->paginate(10);
i tried a RAW DB::select() and it works but i want to use the query builder so i can paginate the results.
this is how my table is arranged:
users
id, first_name, last_name, username, sponsor(id of another user), created_at, deleted_at
investments
id, total_amount , user_id(id of a user), status, created_at, deleted_at
Am not much Good with SQL Queries so if am writing it all wrong please don't scold just, try to explain a lil bit more so i can understand
this is the error coming out:
Illuminate\Database\QueryException
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the
manual that corresponds to your MySQL server version for the right
syntax to use near 'on users.id = investments.user_id and
investment.status = ? order by' at line 1 (SQL: select * on
users.id = investments.user_id and investment.status =
confirmed order by investment.created_at desc limit 1)
output of the dd
select first_name,last_name,username,deleted_at,total_amount,
investment_created_at,user_id from `users`
inner join (select user_id,total_amount,status,created_at AS investment_created_at from `investments`
where `status` = ? and `investments`.`deleted_at` is null) as `confirmed_investments`
on `users`.`id` = `confirmed_investments`.`user_id`
where `sponsor` = ? order by `investment_created_at` desc ◀`
`array:2 [▼ 0 => "confirmed" 1 => "901d08da-e6c4-476a-ae7b-9386990b8b9e" ]`
am using:
PHP version: 7.3.1
MySql Version: 8.0.18
You can get query of your code and compare it to your desired query like this:
$refered_users = DB::table('users')
->join('investments', function ($join) {
$join->on('users.id', '=', 'investments.user_id')
->where('investment.status', '=', 'confirmed')
->orderBy('investment.created_at','desc')->first();
})
->select('users.id,users.first_name,users.last_name,users.username,users.sponsor,investment.id AS investment_id,investment.total_amount,investment.created_at')
->where('users.sponsor','=',Auth::User()->id)
->orderBy('investment.created_at','desc')
->toSql();
Your join query is getting data for id by running another query. I think you need to use sub query. If you are using laravel > 6 it is on documentation.

WhereHas with WhereRaw on one to many relationship (Laravel)

I have an order table and an order_details table in my system.
Relationship between order table and order details table is one to many, means One order has many order details.
Now the problem is i am trying to filter the order with the quantity of items a that are stored in order_details table.
what i doing right know trying to access with whereHas
if ($request->has('quantity') && $request->quantity != null){
$query = $query->whereHas('orderDetails',function ($q) use ($request){
$q->whereRaw('SUM(Quantity) >= '.$request->quantity);
});
}
$orders = $query->orderBy('OrderID','desc')->get();
But it throws an error
General error: 1111 Invalid use of group function (SQL: select * from `orders` where `AddedToCart` = 0 and `PaymentSucceeded` = 1 and exists (select * from `order_details` where `orders`.`OrderID` = `order_details`.`OrderID` and SUM(Quantity) >= 12) order by `OrderID` desc)
I will be vary thankful if i get the solution
To be able to use sum function you need to group by data and as I see you are trying to group them by orderID.
An approach like this might help:
$ordersIDs = DB::table('orderDetails')
->groupBy('OrderID')
->havingRaw('SUM(Quantity)', '>=', 12)
->pluck('orderID')->toArray();
$orders = DB::table('orders')
->whereIn($ordersIDs)
->get();
The above code executes two SQL queries, you can mix them easily to make one.
Hope it helps.

Laravel 5.4 Eloquent ORM query triggers SQL Error 42000 [duplicate]

I have a strange problem with Eloquent which I'm trying to do the following:
$this->node = \DB::table('permission')
->select('permission.id',
'object.name as object_name',
'permission.created_at',
'object.id as object_id')
->join('object', 'object.id', '=', 'permission.object_id')
->join('action', 'action.id', '=', 'permission.action_id')
->where('permission.person_id', $this->person['id'])
->groupBy('permission.object_id')
->orderBy('permission.created_at', 'desc')
->paginate(5);
Laravel Framework report an Error:
QueryException in Connection.php line 761: SQLSTATE[42000]: Syntax
error or access violation: 1055 'permission.id' isn't in GROUP
BY (SQL: select permission.id, object.name as object_name,
permission.created_at, object.id as object_id from
permission inner join object on object.id =
permission.object_id inner join action on action.id =
permission.action_id where permission.person_id = 1 group by
permission.object_id order by permission.created_at desc limit
5 offset 0)
I've added an Eloquent debugging function DB::listen in AppServiceProvider:
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
DB::listen(function ($query) {
echo "<pre>";
print_r($query->sql);
echo "</pre>";
// $query->sql
// $query->bindings
// $query->time
});
}
...
And it does print this SQL query:
select `permission`.`id`,
`object`.`name` as `object_name`,
`permission`.`created_at`,
`object`.`id` as `object_id`
from `permission`
inner join `object` on `object`.`id` = `permission`.`object_id`
inner join `action` on `action`.`id` = `permission`.`action_id`
where `permission`.`person_id` = 1
group by `permission`.`object_id`
order by `permission`.`created_at` desc
limit 5 offset 0
Which is valid in MySQL through PhpMyAdmin and here is the output for the query:
Even So, I tested in mysql command directly and it does work just fine, look at mysql output:
Any idea?
Thanks
Faced same problem with laravel 5.3
They are trying to enforce strict query writing came with mysql-5.7
However to disabled this just go to config/database.php and change strict flag
'mysql' => [
.
.
.
'strict' => false,
//'strict' => true,
.
.
],
Hope this will solve your problem too.
PS - For details on strict query writing refer to #Shadow's answer
This query is against the sql standard and is only valid in mysql under certain sql mode settings. See mysql documentation on MySQL Handling of GROUP BY:
SQL92 and earlier does not permit queries for which the select list,
HAVING condition, or ORDER BY list refer to nonaggregated columns that
are neither named in the GROUP BY clause nor are functionally
dependent on (uniquely determined by) GROUP BY columns. For example,
this query is illegal in standard SQL92 because the nonaggregated name
column in the select list does not appear in the GROUP BY:
SELECT o.custid, c.name, MAX(o.payment) FROM orders AS o, customers
AS c WHERE o.custid = c.custid GROUP BY o.custid; For the query to
be legal in SQL92, the name column must be omitted from the select
list or named in the GROUP BY clause.
SQL99 and later permits such nonaggregates per optional feature T301
if they are functionally dependent on GROUP BY columns: If such a
relationship exists between name and custid, the query is legal. This
would be the case, for example, were custid a primary key of
customers.
You either need to disable the only_full_group_by sql mode (it is part of strict sql mode as well), or use any_value() function in the select list for non-aggregated fields that are not in the group by clause.
SELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;

general error: 2031 when uniting two queries (laravel datatables)

Those are the queries that form the collection to be pushed to the laravel datatables builder:
foreach (Session::get('trienios') as $trienio) {
$oeTrienios = $oeTrienios->where('data_trienio', $trienio->trienio)->whereHas('curso', function ($query) use ($trienio) {
$query->where('curso', $trienio->curso);
});
}
$union = Trienio::with('curso')->whereHas('curso', function ($query) use ($coordinatedCourse) {
$query->where('curso', $coordinatedCourse);
})->union($oeTrienios);
$trienios = \DB::table(\DB::raw("({$union->toSql()}) as x"))->select(['data_trienio']);
There is a tutorial on the official laravel-datatables site that "explains" how to work with united queries, however it is pretty vague and can't realistically explain anything, and besides, when I tried to add a code they had on the tutorial:
$trienios = \DB::table(\DB::raw("({$union->toSql()}) as x"))
It gives me the following error:
SQLSTATE[HY000]: General error: 2031 (SQL: select count(*) as aggregate from (select `data_trienio` from ((select * from `trienios` where exists (select * from `cursos` where `trienios`.`curso_id` = `cursos`.`id` and `curso` = ?)) union (select * from `trienios` where `data_trienio` = ? and exists (select * from `cursos` where `trienios`.`curso_id` = `cursos`.`id` and `curso` = ?) and `data_trienio` = ? and exists (select * from `cursos` where `trienios`.`curso_id` = `cursos`.`id` and `curso` = ?))) as x) count_row_table)
If I, however, attach the parameter ->get() to the ->union($oeTrienios), it will work just fine, however, the collection will come unorderable on the datatable.
How do I solve this issue? Any help would be very welcome.
P.S - Link to the demo: https://datatables.yajrabox.com/fluent/union
The subquery $union->toSql() has only sql code without parameters and you need call bindings. See here and code will be:
$trienios = \DB::table(\DB::raw("({$union->toSql()}) as x"))
->select(['data_trienio'])
->mergeBindings($union);

Categories