MYSQL Sorting By Multiple Join Children - mysql

I cannot seem to figure out a way to sort sql queries by joined children.
Database Example:
Table: posts
+------+---------+
| id | title |
+------+---------+
| 0 |'title1' |
| 1 |'title2' |
| 2 |'title3' |
+------+---------+
Table: post_meta
+------+---------+----------+---------+
| id | post_id | key | value |
+------+---------+----------+---------+
| 0 |0 |'coolness'| 5 |
| 1 |0 |'desc' |'random' |
| 2 |0 |'author' |'bill' |
| 3 |1 |'coolness'| 2 |
| 4 |1 |'desc ' |'random' |
| 5 |2 |'author' |'joe' |
| 6 |2 |'coolness'| 9 |
+------+---------+----------+---------+
I want a list of posts (or just post ids) ordered by their 'coolness' meta number (asc or desc). I dont know if I should be selecting from the posts table and joining on the meta table, or vise-versa. When I join on the post_meta table I only get data from one of the meta rows, so if I just add an order by post_meta.coolness nothing happens.
Thanks!

If you just want the id, you can use the post_meta table:
select pm.post_id
from post_meta pm
where pm.key = 'coolness'
order by pm.value + 0;
The + 0 is to convert the value (presumably a string) to a number.
If you need other columns related to the post, you can join in the posts table.

Related

Joining 4 separate tables with count()

I'm new to SQL and i've been stuck with this problem.
I have 4 tables. I've filled them with some mock information.
Games Table
|ID |Name |Price |
|---|----------|------|
|1 |TestGame1 |2500 |
|2 |TestGame2 |1500 |
|3 |TestGame3 |3500 |
User Table
|ID |Username |Email |
|---|---------|--------------------|
|1 |TestUser1|testEmail1#email.com|
|2 |TestUser2|testEmail2#email.com|
|3 |TestUser3|testEmail3#email.com|
UserOwnsGame Table
|GameID |UserID |
|-------|-------|
|1 |1 |
|2 |2 |
|1 |2 |
|3 |1 |
|2 |1 |
Review Table
|GameID |UserID |Rating |Comment |LastEdit |
|-------|-------|-------|---------------------------------|----------|
|1 |1 |5.0 |I love this game |2022-04-19|
|1 |2 |4.5 |Came short of a 5.0 |2022-04-19|
|2 |2 |2.7 |Above average but nothing special|2022-04-19|
I want to scan through the data on all tables using a single query and get a table like the following,
GameID
UserID
Username
UserReviewCount
UserGameCount
Rating
Comment
LastEdit
1
1
TestUser1
2
3
5.0
I love this game
2022-04-19
1
2
TestUser1
1
2
4.5
Came short of a 5.0
2022-04-19
2
2
TestUser2
1
2
2.7
Above average but nothing special
2022-04-19
I want it for all reviews in the review table. I've tried multiple times. I can figure out ways to get the data on seperate queries. I can't figure out how to combine it all into one table like this. Especially considering the count().
Here;
UserReviewCount - Number of reviews user has made. Count on Review table.
UserGameCount - Number of games user owns. Count on UserOwnsGame table.
I've been stuck on this for one or two days now. Thank you for your help!
We can use a sub-query to count the number of games owned. We could have used another sub-query to count the number of reviews but, as we are already using the table, it is easier to use the window function count() over.
create table Games(ID int,Name varchar(10),Price int);
insert into Games values(1,'TestGame1',2500 ),(2,'TestGame2',1500 ),(3,'TestGame3',3500 );
create table Users (ID int, Username varchar(10),Email varchar(25));
insert into Users values(1,'TestUser1','testEmail1#email.com'),(2,'TestUser2','testEmail2#email.com'),(3,'TestUser3','testEmail3#email.com');
create table UserOwnsGame (GameID int, UserID int);
insert into UserOwnsGame values(1,1),(2,2),(1,2),(3,1),(2,1);
create table Review (GameID int,UserID int,Rating decimal(3,2),Comment varchar(50),LastEdit date);
insert into Review values(1,1,5.0,'I love this game','2022-04-19'),(1,2,4.5,'Came short of a 5.0','2022-04-19'),(2,2,2.7,'Above average but nothing special','2022-04-19');
select
r.GameID,
u.ID,
u.Username,
count(r.GameID) over (partition by r.UserID)
as UserReviewCount,
uog.number_games UserGamescount,
r.Rating,
r.Comment,
r.LastEdit
from
Users u
join Review r
on u.ID = r.UserID
join (select UserID,count(GameID) number_games
from UserOwnsGame
group by UserID) uog
on u.ID = uog.UserID;
GameID | ID | Username | UserReviewCount | UserGamescount | Rating | Comment | LastEdit
-----: | -: | :-------- | --------------: | -------------: | -----: | :-------------------------------- | :---------
1 | 1 | TestUser1 | 1 | 3 | 5.00 | I love this game | 2022-04-19
1 | 2 | TestUser2 | 2 | 2 | 4.50 | Came short of a 5.0 | 2022-04-19
2 | 2 | TestUser2 | 2 | 2 | 2.70 | Above average but nothing special | 2022-04-19
db<>fiddle here

How to get total number of rows, and latest row's value in MySql?

I have two tables named Posts and Comments.
Posts table looks like.
|id|user_id|content|created_at|updated_at|
|1 | 24 |demotxt|demo_date |demo_date |
|2 | 21 |domotxt|demo_date2|demo_date2|
|3 | 24 |domotxt|demo_date2|demo_date3|
|4 | 28 |dimotxt|demo_date3|demo_date5|
Comments table looks like
|id|user_id|post_id|comment |created_at|updated_at|
|1 | 24 | 3 |comment1|demo_date |demo_date |
|2 | 21 | 3 |xyadbsss|demo_date2|demo_date2|
|3 | 24 | 1 |okayokay|demo_date2|demo_date3|
|4 | 28 | 4 |somehtin|demo_date3|demo_date5|
What I am trying to achieve is to get first latest comment and total number of comments for each post. i.e.
|post_id|latest_comment |total_comments |
| 3 |xyadbsss |2 |
| 1 |okayokay |1 |
| 4 |somehtin |1 |
This is the sql query I have tried
SELECT post_id, count(post_id) total_comments, comment latest_comment
FROM `comments`
LEFT JOIN posts on comments.post_id = posts.id
GROUP BY post_id;
Which is giving me
|post_id|latest_comment |total_comments |
| 3 |comment1[not latest]|2 |
| 1 |okayokay |1 |
| 4 |somehtin |1 |
Can you try something like this, not sure if is best solution.
SELECT
c1.post_id,
count(c1.`post_id`) total_comments,
(
SELECT
c2.comment
FROM
comments as c2
WHERE
c2.post_id = c1.`post_id`
ORDER BY
c2.id DESC LIMIT 1
) as latest_comment
FROM
`comments` as c1
GROUP BY
c1.`post_id`;
I would rather loop the first data and do another query based on post id to get latest comment.
To get the latest record in database you can use this
Model::latest()->first();
So in your case to get the latest comment:
Comment::latest()->first()['comment'];
You can also use this method depending of your laravel version (if you use 5.7 or higher):
DB::table('comments')->latest('comment')->first();
Now to count all comments on a post you can looking for all comments with the post id and use the count property
$count = Comment::where('post_id',$the_id_of_the_post)->count();
One way to achieve this would be to use the latestOfMany method. Add the following to your Post model:
public function latestComment()
{
return $this->hasOne(Comment::class)->latestOfMany();
}
Then you can eager load the results:
$posts = Post::with('latestComment')->withCount('comments as total_comments')->get();

Mysql - Compare int field with comma separated field from another table

I have two tables in a MySQL database like this:
User:
userid |userid | Username | Plan(VARCHAR) | Status |
-----------+------------+--------------+---------------+---------+
1 | 1 | John | 1,2,3 |1 |
2 | 2 | Cynthia | 1,2 |1 |
3 | 3 | Charles | 2,3,4 |1 |
Plan: (planid is primary key)
planid(INT) | Plan_Name | Cost | status |
-------------+----------------+----------+--------------+
1 | Tamil Pack | 100 | ACTIVE |
2 | English Pack | 100 | ACTIVE |
3 | SportsPack | 100 | ACTIVE |
4 | KidsPack | 100 | ACTIVE |
OUTPUT
id |userid | Username | Plan | Planname |
---+-------+----------+------------+-------------------------------------+
1 | 1 | John | 1,2,3 |Tamil Pack,English Pack,SportsPack |
2 | 2 | Cynthia | 1,2 |Tamil Pack,English Pack |
3 | 3 | Charles | 2,3,4 |English Pack,Sportspack, Kidspack |
Since plan id in Plan table is integer and the user can hold many plans, its stored as comma separated as varchar, so when i try with IN condition its not working.
SELECT * FROM plan WHERE find_in_set(plan_id,(select user.planid from user where user.userid=1))
This get me the 3 rows from plan table but i want the desired output as above.
How to do that.? any help Please
A rewrite off your query what should work is as follows..
Query
SELECT
all columns you need
, GROUP_CONCAT(Plan.Plan_Name ORDER BY Plan.planid) AS Planname
FROM
Plan
WHERE
FIND_IN_SET(Plan.plan_id,(
SELECT
User.Plan
FROM
user
WHERE User.userid = 1
)
)
GROUP BY
all columns what are in the select (NOT the GROUP_CONCAT function)
You also can use FIND_IN_SET on the ON clause off a INNER JOIN.
One problem is that the join won't ever use indexes.
Query
SELECT
all columns you need
, GROUP_CONCAT(Plan.Plan_Name ORDER BY Plan.planid) AS Planname
FROM
User
INNER JOIN
Plan
ON
FIND_IN_SET(Plan.id, User.Plan)
WHERE
User.id = 1
GROUP BY
all columns what are in the select (NOT the GROUP_CONCAT function)
Like i said in the comments you should normalize the table structures and add the table User_Plan whats holds the relations between the table User and Plan.

Combining a LEFT JOIN with GROUP BY, whilst also including NULLs

I'm having trouble combining tables that have a one-to-many mapping using LEFT JOIN and GROUP BY.
I have the following table with a unique ID (in the illustrative example this is house_number)
Houses:
|house_number| bedrooms|
|0 | 4 |
|1 | 3 |
|2 | 1 |
And I want to LEFT JOIN with a second table USING the unique ID, where the second table may or may not have multiple entries per unique ID. E.g,
Occupants:
| house_number | occupant_id | type |
| 0 | 3 | 19 |
| 0 | 1 | 20 |
| 0 | 2 | 21 |
| 2 | 7 | 20 |
Now what I want to achieve is exactly ONE entry per house number, but giving a preference in the LEFT JOIN to occupants with a type of 20, whilst also keeping those houses which do not have any occupants listed, e.g,
|house_number| bedrooms| occupant_id | type |
|0 | 4 | 1 | 20 |
|1 | 3 | null | null |
|2 | 1 | 7 | 20 |
I can use a GROUP BY to achieve only one entry per house, however, I need to ensure that the occupant row returned with it (if it exists) has type = 20.
If I simply use a WHERE (type = 20), then I wouldn't get an entry returned for house_number = 1.
How would I achieve this final table?
What about trying WHERE (type = 20 OR type is null) condition instead?
SELECT h.house_number,h.bedrooms
, o.occupant_id,o.ztype
FROM houses h
LEFT JOIN occupants o ON h.house_number = o.house_number
AND o.ztype =20
;
BTW I had to replace "type" by "ztype" because type is a reserved word in Postgres.

SQL: Compare if column values in two tables are equal

I'm working on mysql and have two tables with the same schema:
preTrial
|id|accusedId|articleid|
------------------------
|1 | 1 | 1 |
|2 | 1 | 2 |
|3 | 1 | 3 |
|4 | 2 | 1 |
|5 | 2 | 2 |
trial
|id|accusedId|articleid|
------------------------
|1 | 1 | 1 |
|2 | 1 | 2 |
|3 | 2 | 1 |
|4 | 2 | 2 |
I want to get those accusedIds where all the articleIds of the first and the second tables are equal.
The above example should only return the accusedId 2, cause for accusedId 1 there is no articleId 3 in the second table.
I hope you understand what i mean. I'm currently writing my thesis in law, and the the time i was into sql is long gone by. Of course i already did some research, and tried several joins, but i was not able to find a solution. Hopefully you can help me.
Try something like this:
select a.accusedId , sum(a.accusedid) as cnt_a, sum(coalesce(b.accusedId, 0)) as cnt_b
from a left join b on a.accusedId = b.accusedId and a.articleId = b.articleId
group by accusedId
having cnt_a = cnt_b
I haven't even run that, so it might be a little off, but give it a lash. What it's doing is returning zeroes for a row in a not matched by b, so the HAVING clause will filter your grouped results to those where the article counts are equal.