Yii2 MySQL Order By Relation Count - php

Table 1 - User:
ID Name
1 Jonh
2 Mark
3 King
Table 2 - Book:
ID user_idstatus ...
1 1 1 ...
2 1 1 ...
3 1 1 ...
4 2 1 ...
5 1 0 ...
6 1 0 ...
Code:
$query = User::find();
$query->joinWith('books');
$query->select(['user.*', 'COUNT(book.id) AS booksCount']);
$query->andWhere(['book.status' => 1]); // Problem Here!
$query->groupBy(['user.id']);
$query->orderBy(['booksCount' => SORT_DESC]);
Problem:
The query is working properly, but it's not returning the user with id = 3.
If I remove the line $query->andWhere(['book.status' => 1]); it works fine and return all users.
What should I change to list all users, even those who do not have a related book with status = 1?

I found the answer:
$query = User::find();
$query->joinWith(['books' => function ($subquery) {
$subquery->onCondition(['book.status' => 1]);
}]);
$query->select(['user.*', 'COUNT(book.id) AS booksCount']);
$query->groupBy(['user.id']);
$query->orderBy(['booksCount' => SORT_DESC]);

Instead of using COUNT(book.id), if the status of the book is either 0 or 1, you can use SUM(book.status) to get the number of books the user has. Then you can remove your WHERE book.status = 1 clause, and it will return all the users with the number of books they have, even in user 3's case where they have 0 books.
The Problem
The real problem is in your where clause. Because WHERE is processed before grouping and user 3 doesn't have any rows where book.status = 1, then the user has no rows which are included in the base query. Therefor the user isn't present during/after the grouping.
If you want a pretty good idea of a catch-all case where you can count rows based on a condition, using COUNT(CASE WHEN book.status IS NULL THEN NULL ELSE NULLIF(0,book.status) END) will also give you the result you're looking for. Because COUNT() will not count rows where the expression is NULL, this would allow the book.status to be -1, 1, 2, and any other number as long as it isn't 0 (or NULL in user 3's case), and still be included in the count.

Related

Finding the most repeating id by laravel eloquent group

$device = SalesItem::where('type', '=', 1)->get()->groupBy('product_id');
There is a list of products in the database. Here I am storing the product id. The same product can be idsi. I want to get the id data of the product with the same id at most.
Sample:
ID type product_id
1 1 1
2 1 1
3 1 1
4 1 1
5 1 1
6 1 2
7 1 2
8 1 1
9 1 1
0 1 1
output: 1
here I want the output to give product id 1. I couldn't find what to do after group by. Can you guide me on this?
I don't think there is an elegant way to do this in, but this should work for you:
Note: I'm unsure of your table name.
$device = DB::table('sales_item')
->select('product_id', DB::raw('COUNT(product_id) AS magnitude'))
->where('type', 1)
->groupBy('product_id)
->orderBy('magnitude', 'DESC')
->limit(1);
It goes into the table and selected the id, type, and the COUNT() of product_id. Which it then groups by the product_id and orders by the count('product_id') from high to low.
->limit(1) is used to only select the first (read highest) value.

Mysql how to use SUM and COUNT in the same query

I have the query below, which is supposed to get all the "record" fields from a mysql table called users. The record field's values must be bigger than 0 for it to count. and the query only returns true if 3 or more records found (where record > 0)
The query below makes sense to me, but its returning the following PHP error : Operand should contain 2 column(s)
$query = "
SELECT * FROM users u
WHERE (
SELECT COUNT(record) AS record,
SUM(CASE WHEN record > 0 THEN 1 ELSE 0 END)
FROM users
) >= 3
";
Can SUM and COUNT not be used in the same query? I've used them simultaneously in the past with no problems.
Any help would be great thank
EDIT ---------------------
Table : users
--------------
id value
--------------
1 1
2 1
3 1
4 0
5 0
6 -1
7 -10
8 0
I'd like to only return a result if the value field in the table above is bigger than 0. But I also only want to return a result if the total number of values found in the table (where value > 0) are 3 or more.
You can just use the count function to count, a where to limit the data, and the having function to check that you have the number of records you want.
select count(*) as counted
from users
where record > 0
having counted > 3
Demo: http://sqlfiddle.com/#!9/29ed41e/1
With the above query your PHP will have the results as the first index, or counted depending on how you fetch. You don't need to loop the fetch because there will only be 1 row returned.
Roughly:
$row = $query->fetch();
return $row['counted'];
The number of rows will be 1 so you don't want to count the number of rows, you want the actual returned value.

Count rows of each status - Laravel eloquent

There is a table named requests with a column named status and I have access to this table through a HasManyThrough relation.
The table looks like this:
id | status | request
--------------------------
1 | 1 | lorem ipsum
2 | 2 | lorem ipsum
3 | 1 | lorem ipsum
4 | 3 | lorem ipsum
5 | 1 | lorem ipsum
I need to count all different status rows in an efficient way, the result I'm looking for is something like this:
status | status_count
---------------------
1 | 3
2 | 1
3 | 1
Also I need to be able to do something like this:
$status[1] Or $status->id[1] // which prints out the count of that status
I Know this two option which both of them are ugly:
1. Using filters:
// Through a table named group which contains year column
$requests = Auth::user()->requests()->where('year', 2016)->get();
$status[1] = $requests->filter(
function($request){
return $request->status == 1;
})->count();
Now I have 10 status id, and I should repeat this code 10 times, what if i had 100 status... so it's not a good option.
2. Trying to create a right SQL:
As I know the correct SQL statement for doing this looks like this:
SELECT 'status', COUNT(*) FROM requests GROUP BY status
For creating this statement I'm doing this:
$groups = Group::where(['owner_id' => Auth::user()->id, 'year' => 2016])
->lists('id');
$requests = DB::table('requests')
->select( DB::raw('count(status) as status_count, status') )
->whereIn('group_id', $groups)
->groupBy('status')
->get();
And here is my dd output:
array:2 [▼
0 => {
+"status_count": "3"
+"status": "1"
}
1 => {
+"status_count": "1"
+"status": "2"
}
2 => {
+"status_count": "1"
+"status": "3"
}
]
Which I have no idea how can I use them, for example how can I know what is the count of status 2? I can use a foreach to create an array with status as a key and status_counts as a value but I'm looking fro a right way to do this.
Another thing is how can I create this SQL statement through eloquent and not DB?
Is there any other way that I'm missing?
Okay, here is what I've did:
First I managed to create a correct SQL only with eloquent and getting
the statuses and their count only by 1 request.
Here is the hasManyThrough relation:
public function requests()
{
return $this->hasManyThrough('App\Models\Request',
'App\Models\Group', 'owner_id', 'group_id');
}
Then to get the count of desired requests statuses:
$requests = Auth::user()->requests()->
select( DB::raw('count(requests.status) as count, requests.status') )->
groupBy('requests.status')->where('year', '2016')->get();
Again our result is exactly as we wanted:
status | status_count
---------------------
1 | 3
2 | 1
3 | 1
This time we have two option to work with counts, I go with the new one first:
1. Using where:
// returns null if there is no row with status of 1
$status = $projects->where('status_id', "1")->first();
// if $status is null there is no row with status of 1 so return 0
echo ( empty($status) ) ? 0 : $status->count;
Easy right? but it's not as clean as we want.
2. The foreach trick:
As I mentioned it in my question we can use a foreach to create an array of statuses and their count. but whats happens when we try to get the count of an status which does not exist in the result of our query? we are going to get an error: Undefined offset.
And here is my Idea:
First we will create an array to keep all status codes and their count then we
fill this array with zero.
My system only has 10 status code so:
$requests_counts = array_fill(1, 10, 0); // status 1 to 10 - default count: 0
Then with a foreach statement we only override the indexs that have a count in our query result:
foreach($requests as $request)
$requests_counts[$request->status] = $request->count;
So all status codes going to have a value even the ones that are not exist in
our query result.
*Count of all status codes
But what if we want to print out the number of all requests no matter what status code they have? should we create an other query and count all the results rows? nope we are going use a function named array_sum:
array_sum($requests_counts); // prints out number of all requests
$groups = Group::where(['owner_id' => Auth::user()->id, 'year' => 2016])
->groupBy('status')
->select( DB::raw('status , COUNT(*) as status_count') )
->get();
I would stick to your second option with little tweak.
$groups = Group::where(['owner_id' => Auth::user()->id, 'year' => 2016])
->lists('id');
$requests = DB::table('requests')
->select( DB::raw('count(status) as status_count, status') )
->whereIn('group_id', $groups)
->groupBy('status')
->get();
$output = [];
foreach ($requests as $a)
{
$output[$a->status] = $a->status_count;
}
Then in your ouput you have $output[1] count for status 1 etc...

Complex MySQL Conversation Group Query?

I have the following tables.
conversations
| id |
------
1
and
conversationMembers
| id | conversationId | userId | email
---------------------------------------
1 1 2 null
2 1 null test#test.com
3 1 7 null
Basically, I'm trying to construct a MySQL query that returns a row from the conversations table by an exact match of conversationMembers.
So, here's some examples of expected returns.
Let's say we want a a conversation id for a conversation between the exact following members: userId 2, userId 7, email test#test.com - This would see that in the conversationMembers table there's rows with the same conversation id and the exact match across all members of that conversation id that we're searching for. It would return conversations row with id 1.
Here's another example. We want a conversation id for a conversation between userId 2 and userId 7. This would see that there's not a conversation exclusively between userId 2 and userId 7, so it would not return anything.
And a final example. Let's say we want userId 7 and userId 9, this would also see there's no exclusive conversation between these 2 user id's and would return nothing.
What's the best way to go about doing it? I've played with subqueries but everything I've come up with doesn't seem to be able to handle the exact matching situation - I was having issues with selecting conversations for example - on userId 2 and 7 only (which should return nothing) and was getting conversationId 1 back, even though I didn't specify I wanted a conversation with test#test.com email as a part of it. I should only have gotten conversationId 1 back for if I searched on an exact match of all members in for conversationId.
One method is to use group by and having. This is nice because it is flexible with regards to what can be expressed. So, your first example is:
select conversionid
from conversationMembers
group by conversionid
having sum(userId = 2) > 0 and
sum(userId = 7) > 0 and
sum(email = 'test#test.com') > 0;
The condition being summed counts the number of members that match. The > 0 means there is at least one. For the second condition, the clause would be:
having sum(userId = 2) > 0 and
sum(userId = 7) > 0 and
sum(userId not in (2, 7)) = 0;
or alternatively:
select conversionid
from conversationMembers
group by conversionid
having sum(userId = 2) > 0 and
sum(userId = 7) > 0 and
count(distinct userId) = 2;

Mysql keep track of user likes and dislikes

I am creating a bulletin board application. Each bulletin can be liked or disliked by users of the site.To keep track of the likes and dislikes I have created the following database table
id user_id bulletin_id like_dislike
1 1 1 1
2 1 2 0
3 3 1 1
4 2 1 0
In the like_dislike column 1 means 'Like It', 0 means 'Don't like it'
I know how to ask.
- How many times was bulletin 1 liked (2)
- How many times was bulletin 1 disliked (1)
But How do I do a query to ask those two questions at the same time? That is, how many times was bulletin 1 liked and disliked
liked disliked
2 1
I have tried the query
SELECT count(like_dislike) AS likes, count(like_dislike) AS dislikes FROM bulletins_ld
where bulletins_id = 1 AND likes = 1 AND dislikes = 0
but all I get is two twice which is not surprising.
The only solution I can think of is having a separate like and dislike column
You can do this with an aggregate query, using opposing conditions on the single like_dislike column (I am assuming below that a '1' in that column means 'liked').
SELECT bulletin_id,
SUM(CASE WHEN like_dislike = 1 THEN 1 ELSE 0 END) AS likes,
SUM(CASE WHEN like_dislike = 0 THEN 1 ELSE 0 END) AS dislikes
FROM bulletins_ld
GROUP BY bulletin_id
Update: As per the discussion in the comments below, the like/dislike column could be normalized into its own table, like so (example deliberately silly...):
CREATE TABLE how_user_feels(
feeling_id INT,
feeling_desc VARCHAR(20)
)
INSERT INTO how_user_feels(feeling_id, feeling_desc) VALUES
(0, 'Undecided'),
(1, 'Likes It'),
(2, 'Could Do Without It')
The Likes_Dislikes column in the Bulletin table is then replaced by the foreign key feeling_id, with a default to 0. Let's say that you then enter a record in this table when a user first views a bulletin, making them "Undecided" by default, and update that record when they vote on the bulletin. You could query the results like so:
SELECT bulletin_id,
SUM(CASE WHEN feelings_id = 1 THEN 1 ELSE 0 END) AS likes,
SUM(CASE WHEN feelings_id = 2 THEN 1 ELSE 0 END) AS dislikes,
SUM(CASE WHEN feelings_id = 0 THEN 1 ELSE 0 END) AS doesnt_seem_to_care
FROM bulletins_ld b
INNER JOIN how_user_feels h ON b.feeling_id = h.feeling_id
GROUP BY bulletin_id
Keep in mind, this is just one approach, and may not be useful in your case. But if you ever decided to change or expand the model by which a user expresses their feelings for a bulletin, say to a five-star rating system, you could do that without changing the database schema - just alter the records in the how_user_feels table, and the associated queries.
For the record, there is another way to obtain the same result set, whether it is faster or slower depends on the platform. This uses a scalar subquery instead of an aggregate on the outer query. The idea is, this is supposed to be easier if you think in terms of sets. Or in terms of Dimension-Facts.
First we have to straighten out your names. Let's call your "table" bulletin_like and the main bulletin table bulletin (bulletin_id is a very silly name for either of them, that is more of a column name). And just call the boolean column like (if it is 1, like is true; if it is 0, like is false; that's what boolean means). Use the singular form for names. SELECT name AS bulletin,
(SELECT COUNT(like)
FROM bulletin_like bl
WHERE bl.bulletin_id = b.bulletin_id
AND like = 1
) AS like,
(SELECT COUNT(like)
FROM bulletin_like bl
WHERE bl.bulletin_id = b.bulletin_id
AND like = 0
) AS dislike
FROM bulletin b
You asked for the Normalisation tag,. That bulletin_like "table" is not Normalised. Get rid of theIdiot column, it serves no purpose other than a redundant column and an additional index. The PK is (bulletin_id, user_id).
Unless you want users to post multiple likes and dislikes per poster per bulletin.
This query worked for me:
SELECT bulletin_id,
sum(like_dislike) AS likes,
sum((1-like_dislike)) AS dislikes
FROM bulletins_ld
GROUP BY (bulletin_id);
This assumes that likedislike = 1 means "LIKE IT" and likedislike = 0 means "DOES NOT LIKE IT".
ID LIKES DISLIKES
1 2 1
2 0 1

Categories