GroupBy ignores orderBy when using leftjoin - mysql

I have two tables.
rooms with id, title
chats with id, content, room_id, created_at
My goal is fetch rooms and orderBy created_at of chats in desc mode. It's a simple query, yet it's giving a headache in Laravel. I cannot use the eloquent ORM way and do a sort in with method. Thus, I had to use a join.
My query looks like this:
$items = $items->select("rooms.*")->join("chats", "chats.room_id", "=", "rooms.id")->orderBy("chats.created_at", "desc")->groupBy('rooms.id');
Result:
I'm getting the data without any order for created_at.
removing groupBy will result in a correct order, but because it's missing the groupby, the room will exist as much as it has chats. So it's not a great approach.
What am I missing? If i need to include other information, please let me know in the comments.

Just like the comment said, I should use an aggregate function in the query while using a groupby function. Basically, it will look like this
$items = $items->select("rooms.*")
->selectRaw("max(chats.created_at) as latest_created_at")
->join("chats", "chats.room_id", "=", "rooms.id")
->orderBy("latest_created_at", "desc")
->groupBy('rooms.id');
Then, it will be ordered correctly. I'll leave the thread, maybe someone will use the same clumsy search keywords as mine and see this question :)

Related

Select a sum in Knex.js without using .raw

I am trying to rewrite some MySQL queries in Knex.js, and I feel like I'm running into .raw at every turn, which feels counter to the reason I want to use Knex in the first place.
Is it possible to write the following query without using .raw?
SELECT
product,
SUM(revenue)
FROM orders
Using raw, it works to write:
knex()
.select(
'product',
knex.raw('SUM(revenue)')
)
.from('orders')
but the idea of using Knex was to avoid using MySQL query strings, so I'm hoping there's another way. Or does everyone just use .raw everywhere, and I'm misunderstanding something? Very possible, I'm new to this.
You can use the sum method.
sum — .sum(column|columns|raw) Retrieve the sum of the values of a
given column or array of columns (note that some drivers do not
support multiple columns). Also accepts raw expressions.
knex('users').sum('products')
Outputs:
select sum("products") from "users"
Probably be something like this:
knex()
.select('product')
.sum('revenue')
.from('orders')
You should adjust to your specific case. You might need to use something like groupBy('product') to get total revenue per product.
You should really go over knex's documentation, it's pretty good and straight forward and you definitely should not be using raw all the time.
You can even specify the returning sum column name like this:
knex(tableName)
.select('product')
.sum({ total: 'revenue' })
.groupBy('product');

how to convert my MySql query in Laravel

i am little bit confused that how my query convert in laravel..
select users.username,users.photo, questions.*,
(Select count(*) from answers
where answers.q_id=questions.id) as aAccount from questions
INNER JOIN users ON users.id=questions.user_id
Use Raw query
These expressions will be injected into the query as strings, so be careful not to create any SQL injection points! To create a raw expression, you may use the DB::raw method
DB::table('questions')
->join('users', 'users.id', '=', 'questions.user_id')
->select('users.username','users.photo', 'questions.*',
DB::raw("
( Select count(*) from answers
where answers.q_id=questions.id
)as 'aAccount'")
->get();
I upvoted JYoThl answer as it's exactly how I'd breakdown this query into eloquent, though in instances where a portion of your Eloquent query becomes raw, I personally prefer keeping the entire sql raw. You can still inject variables into it if required.
In my experience, the amount of time spent normalizing a more complex query like this will be used when you go back to the code to re-read it.
Here's how you would pass your sql in it's raw format. I also like to convert the array into a collection, as collections offer a variety of methods. Hope this helps!
$questions = collect(DB::select( DB::raw("
select users.username,users.photo, questions.*,
(Select count(*) from answers where answers.q_id=questions.id) as aAccount
from questions
INNER JOIN users ON users.id=questions.user_id")
));
You would add variables into an array() area if you want to inject a variable. If you wanted to do that you would do something like this:
DB::select( DB::raw("select * from user where user = :user"), array('user' => $user))

Laravel MySQL how to order results in the same order as in whereIn clause

I have two queries, the first one gives me an array of ids, that is in a specific order. Then that array of ids I pass it to the second query like so:
Operation::whereIn('id', $ids)->get();
But when I output the result of that query, the order has changed, if the array $ids was something like (4,2,6,9) which is the order I wanted the results to be in, the output will give me 2,4,6,9. How can I avoid that?
MySQL way of sorting with order same as in where in clause:
$ids; // array of ids
$placeholders = implode(',',array_fill(0, count($ids), '?')); // string for the query
Operation::whereIn('id', $ids)
->orderByRaw("field(id,{$placeholders})", $ids)->get();
You can do
$idsImploded = implode(',',$ids);
Operation::whereIn('id', $ids)->orderByRaw("FIND_IN_SET('id','$idsImploded')")->get();
It's a problem where MySql doesn't return the result in the order you specify them, so you need to reorder them after that.
A similar solution can be found here: avoid Sorting by the MYSQL IN Keyword
If you have the sorting order in 4,2,6,9, you can fetch these rows, then use php to sort.

Eloquent count distinct returns wrong totals

i'm having an issue with how eloquent is formulation a query that i have no access to. When doing something like
$model->where('something')
->distinct()
->paginate();
eloquent runs a query to get the total count, and the query looks something like
select count(*) as aggregate from .....
The problem is that if you use distinct in the query, you want something like
select count(distinct id) as aggregate from .....
to get the correct total. Eloquent is not doing that though, thus returning wrong totals. The only way to get the distinct in count is to pass an argument through the query builder like so ->count('id') in which case it will add it. Problem is that this query is auto-generated and i have no control over it.
Is there a way to trick it into adding the distinct on the count query?
P.S Digging deep into the builders code we find an IF statement asking for a field on the count() method in order to add the distinct property to the count. Illuminate\Database\Query\Grammars\BaseGrammar#compileAggregate
if ($query->distinct && $column !== '*')
{
$column = 'distinct '.$column;
}
return 'select '.$aggregate['function'].'('.$column.') as aggregate';
P.S.1 I know that in SQL you could do a group by, but since i'm eager loading stuff it is not a good idea cause it will add a IN (number of id's found) to each of the other queries which slows things down significantly.
I faced the exact same problem and found two solutions:
The bad one:
$results = $model->groupBy('foo.id')->paginate();
It works but it will costs too much memory (and time) if you have a high number of rows (it was my case).
The better one:
$ids = $model->distinct()->pluck('foo.id');
$results = $query = $model->whereIn('foo.id', $ids)->paginate();
I tried this with 100k results, and had no problem at all.
This seems to be a wider problem, discussed here:
https://github.com/laravel/framework/issues/3191
https://github.com/laravel/framework/pull/4088
Untill the fixes are shipped with one of the next Laravel releases, you can always try using the raw expressions, like below (I didnt test it, but should work)
$stuff = $model->select(DB::raw('distinct id as did'))
->where('whatever','=','whateverelse')
->paginate();
Reference: http://laravel.com/docs/queries#raw-expressions
$model->where('something')->distinct()->count('id')->paginate();

mysql group_concat in where

I am having a problem with the following query(if this is a duplicate question then i'm terribly sorry, but i can't seem to find anything yet that can help me):
SELECT d.*, GROUP_CONCAT(g.name ORDER BY g.name SEPARATOR ", ") AS members
FROM table_d AS d LEFT OUTER JOIN table_g AS g ON (d.eventid = g.id)
WHERE members LIKE '%p%';
MySQL apparently can't handle a comparison of GROUP_CONCAT columns in a WHERE clause.
So my question is very simple. Is there a workaround for this, like using sub-query's or something similar? I really need this piece of code to work and there is not really any alternative to use other than handling this in the query itself.
EDIT 1:
I won't show the actual code as this might be confidential, I'll have to check with my peers. Anyway, I just wrote this code to give you an impression of how the statement looks like although I agree with you that it doesn't make a lot of sense. I'm going to check the answers below in a minute, i'll get back to you then. Again thnx for all the help already!
EDIT 2:
Tried using HAVING, but that only works when i'm not using GROUP BY. When I try it, it gives me a syntax error, but when I remove the GROUP BY the query works perfectly. The thing is, i need the GROUP BY otherwise the query would be meaningless to me.
EDIT 3:
Ok, so I made a stupid mistake and put HAVING before GROUP BY, which obviously doesn't work. Thanks for all the help, it works now!
Use HAVING instead of WHERE.
... HAVING members LIKE '%peter%'
WHERE applies the filter before the GROUP_CONCAT is evaluated; HAVING applies it later.
Edit: I find your query a bit confusing. It looks like it's going to get only one row with all of your names in a single string -- unless there's nobody in your database named Peter, it which case the query will return nothing.
Perhaps HAVING isn't really what you need here...
Try
SELECT ...
...
WHERE g.name = 'peter'
instead. Since you're just doing a simple name lookup, there's no need to search the derived field - just match on the underlying original field.
GROUP_CONCAT is an aggregate function. You have to GROUP BY something. If you just want all the rows that have %peter% in them try
SELECT d.*, g.name
FROM table_d AS d
LEFT OUTER JOIN table_g AS g
ON (d.eventid = g.id)
WHERE g.name LIKE '%peter%';