3 Mysql Inner Joins with last join being the ORDER BY Clause - mysql

I have 3 tables I'm trying to inner join (ambitious I know). The first query of the join, just basically queries my members table to pipe into the second query, which is the post table that actually holds the posts for those members (users search by member info to see their posts). The third and final query is simply ordering by the frequency of the most viewed posts. I have these two queries working separately:
$sql_string = "
SELECT m.id
, m.username
, m.gender
, p.*
FROM members m
JOIN posts p
ON p.member_id = m.id
WHERE m.active='y'
AND m.gender='M'
AND m.city='Los Angeles'
AND m.state='California'
AND p.active='y';
";
which accomplishes the first 2 queries and this final query:
$sql_string2 = "SELECT post_id FROM post_views GROUP BY post_id ORDER BY COUNT(*) DESC";
Which accomplishes the final query. I just need to combine the 2, but when I do that:
$final_sql_string = "SELECT members.id, members.username, members.gender, posts.* FROM members INNER JOIN posts ON members.id = posts.member_id WHERE members.active='y' AND members.gender='M' AND members.city='Los Angeles' AND members.state='California' AND posts.active='y' INNER JOIN post_views ON posts.id = post_views.post_id GROUP BY post_views.post_id ORDER BY COUNT(*) DESC";
I get an error:
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 'INNER JOIN post_views ON posts.id = post_views.post_id GROUP BY post_views.post_' at line 1
Any ideas? Here are my tables for anyone interested:
CREATE TABLE IF NOT EXISTS `members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`age` varchar(3) NOT NULL,
`gender` varchar(1) NOT NULL,
`city` varchar(20) NOT NULL,
`state` varchar(50) NOT NULL,
`active` enum('y','n') NOT NULL DEFAULT 'y',
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`member_id` int(11) NOT NULL,
`title` text NOT NULL,
`comments` enum('y','n') NOT NULL DEFAULT 'y',
`post_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`list_weight` double NOT NULL,
`active` enum('y','n') NOT NULL DEFAULT 'y',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=47 ;
CREATE TABLE IF NOT EXISTS `post_views` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post_id` int(11) NOT NULL,
`member_id` int(11) NOT NULL,
`post_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=287 ;

The following should work:
SELECT m.id
, m.username
, m.gender
, p.*
, pc.post_count
FROM members m
JOIN posts p
ON p.member_id = m.id
LEFT JOIN (
SELECT post_id, COUNT(*) post_count FROM post_views GROUP BY post_id
) pc ON p.id = pc.post_id
WHERE m.active='y'
AND m.gender='M'
AND m.city='Los Angeles'
AND m.state='California'
AND p.active='y'
ORDER BY post_count DESC;
"join in" the counts you gathered and order by those.
If you want to keep your "style" you may use:
SELECT m.id
, m.username
, m.gender
, p.*
FROM members m
JOIN posts p
ON p.member_id = m.id
WHERE m.active='y'
AND m.gender='M'
AND m.city='Los Angeles'
AND m.state='California'
AND p.active='y'
ORDER BY (SELECT COUNT(*) FROM post_views WHERE post_id = p.id) DESC;

Related

Mysql query with inner subquery and where,order,group not use indexes

all.
I have 2 mysql tables type myisam
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `users_ratings` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`created_date` varchar(255) DEFAULT NULL,
`user_id` mediumint(9) DEFAULT NULL,
`rating1` mediumint(9) DEFAULT NULL,
`rating2` mediumint(9) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Dump can be downloaded here
I want all users to select and sort the list by rating2 and rating2 must be > 1000
SELECT * FROM users as u
INNER JOIN (SELECT rating2, user_id,MAX(created_date) as maxdate FROM `users_ratings` GROUP BY user_id) as x ON x.user_id = u.id
INNER JOIN users_ratings as ur ON(ur.created_date = x.maxdate and ur.user_id = u.id)
WHERE x.rating2 > 1000
ORDER by ur.rating2
Without such an index the query is executed for 9 seconds.
All possible codes, which I thought was allowed to reduce the time of the request to 1-2 seconds.
Help the right to place indexes
I suspect you want the following query. Assuming you do, and performance is still an issue, provide the EXPLAIN for same...
(this assumes a Unique Key on user_id,[rating2,]created_date, which hasn't actually been specified)
SELECT u.*
, x.*
FROM users u
JOIN users_ratings x
ON x.user_id = u.id
JOIN
( SELECT user_id
, MAX(created_date) maxdate
FROM users_ratings
WHERE rating2 > 1000
GROUP
BY user_id
) y
ON y.user_id = x.user_id
AND y.maxdate = x.created_date
ORDER
BY x.rating2;

Joining different tables based on column value

I have a table called notifications:
CREATE TABLE `notifications` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`type` varchar(20) NOT NULL DEFAULT '',
`parent_id` int(11) DEFAULT NULL,
`parent_type` varchar(15) DEFAULT NULL,
`type_id` int(11) DEFAULT NULL,
`etc` NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
Each notification is related to a different table, the value of parent_type field specifies the name of the table that I want to * join the table with. All target tables have several similar columns:
CREATE TABLE `tablename` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`is_visible` tinyint(1) NOT NULL,
`etc` NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
Currently I'm using this query for selecting notifcations that their related row in the target table exists and their is_visible field is 1:
SELECT n.id,
FROM notifications n
LEFT JOIN books b ON n.parent_id = b.id AND n.parent_type = 'book' AND b.is_visible = 1
LEFT JOIN interviews i ON n.parent_id = i.id AND n.parent_type = 'interview' AND i.is_visible = 1
LEFT JOIN other tables...
WHERE n.user_id = 1
GROUP BY n.id
But since it is a LEFT JOIN it returns the notification if it matches any table or not, how can I rewrite it so it doesn't return notifications that don't match with any row in the target table? I have also tried the CASE statement unsuccessfully.
I'm not 100% sure the syntax is right and I have no chance to test it right now, but the idea should be clear.
SELECT DISTINCT n.id
FROM notifications n
JOIN (
(SELECT b.id, 'book' AS type FROM books b WHERE b.is_visible = 1)
UNION
(SELECT i.id, 'interview' AS type FROM interviews i WHERE i.is_visible = 1)
) ids ON n.parent_id = ids.id AND n.parent_type = ids.type
WHERE n.user_id = 1

Join two tables, matching a column with multiple values

I am trying to get a product matching some custom parameters.
So I have to three tables - products, parameters and parametersitems.
Products table:
CREATE TABLE `products` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT
`Title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`Content` longtext COLLATE utf8_unicode_ci NOT NULL,
`Price` float(10,2) unsigned NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Parameter table:
CREATE TABLE `parameters` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Label` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Parameter items table:
CREATE TABLE `parametersitems` (
`ProductID` int(10) unsigned NOT NULL DEFAULT '0',
`ParameterID` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`ProductID`,`ParameterID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
So my question is how can I get only the products matching all the parameters.
The only way I could think of is joining the parameteritems table couple of times.
For example, here is a query to get the products matching two parameters:
SELECT
products.*
FROM
products
INNER JOIN
parametersitems AS paritems1
ON
paritems1.ItemID = products.ID
AND paritems1.ParameterID = 7
INNER JOIN
parametersitems AS paritems2
ON
paritems2.ItemID = products.ID
AND paritems2.ParameterID = 11
My only concern is that the SELECT query will get slower and slower if there more parameters selected.
So is there a better way to handle this problem?
Thank you
Adjust the value tested in the HAVING clause to match the number of values listed in the IN clause.
SELECT p.*
FROM products p
WHERE p.ID IN (SELECT pi.ItemID
FROM parameteritems pi
WHERE pi.ItemID = p.ID
AND pi.ParameterID IN (7,11)
GROUP BY pi.ItemID
HAVING COUNT(DISTINCT pi.ParameterID) = 2)
select p.*
from products p
inner join (
select ItemID
from parametersitems
where ParameterID in (7, 11)
group by ItemID
having count(distinct ParameterID) = 2
) pm on p.ID = pm.ItemID
SELECT
p.ID, p.Title, p.Content, p.Price
FROM
products AS p
INNER JOIN
parametersitems AS pi ON pi.ProductID = p.ID
GROUP BY
p.ID, p.Title, p.Content, p.Price
HAVING COUNT(DISTINCT pi.ParameterID) = (SELECT COUNT(ID) FROM parameters);
This will always get you products matching every parameter no matter how many parameters you add. (This could become bogus if you delete a parameter without deleting the corresponding rows in paramatersitems. This is what constraints are for.)

How to Optimize a Query With GROUP BY and ORDER BY

I have got a POSTS table, the structure is like this:
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_turkish_ci DEFAULT NULL,
`content` longtext COLLATE utf8_turkish_ci,
`excerpt` longtext COLLATE utf8_turkish_ci,
`link` longtext COLLATE utf8_turkish_ci,
`original_link` longtext COLLATE utf8_turkish_ci,
`mime_type` longtext COLLATE utf8_turkish_ci,
`language_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`site_id` int(11) DEFAULT NULL,
`type` varchar(255) COLLATE utf8_turkish_ci DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`modified_at` datetime DEFAULT NULL,
`is_deleted` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `type` (`type`),
KEY `created_at` (`created_at`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_turkish_ci AUTO_INCREMENT=52487 ;
And a USERS table, structed like this:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8_turkish_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_turkish_ci AUTO_INCREMENT=9422 ;
I'm using this query to get latest "page, file or post" posts ordered by descending time and grouping by user to not show all latest posts from a user:
SELECT p.*, u.*
FROM posts p
LEFT JOIN users u ON p.user_id = u.id
WHERE p.type IN ('post', 'page', 'file')
GROUP BY p.user_id
ORDER BY p.created_at DESC
LIMIT 30
But it is too slow, even limited to 30 records.
now, how can i speed up this query? which columns to index or any other ideas? thanks.
The first thing to do is to add an index on posts.user_id (or maybe posts.user_id + posts.type). And another index on posts.created_at
UPDATE
I've just payed attention that your query grabs all fields from both tables, and posts table has 6 long text columns. So I believe you have a poor performance because mysql has to create quite a large temporary table or temp file to get all rows for satisfying your group by + order by clauses.
I think the following query should help.
SELECT u.*, p1.* FROM
users u
INNER JOIN
(
SELECT p.user_id, p.created_at, p.id FROM posts p
WHERE p.type IN ('post', 'page', 'file') GROUP by p.user_id
ORDER BY p.created_at DESC LIMIT 30
)xxx ON xxx.user_id = u.id
INNER JOIN posts p1 ON (p1.id = xxx.id)
In terms of indices, I would suggest creating ones on posts.type (WHERE), posts.created_at (ORDER). That should help speed up the sorting.
You can try this:
SELECT p., u.
FROM (SELECT * FROM posts WHERE p.type IN ('post', 'page', 'file')) p
LEFT JOIN users u ON p.user_id = u.id
GROUP BY p.user_id ORDER BY p.created_at DESC
LIMIT 30
MySQL first proccess the inner query, and with its result process the outter query with less records.
Try #Gabriel's answer, but with the LIMIT in the inner query.
SELECT p., u.
FROM (SELECT * FROM posts WHERE type IN ('post', 'page', 'file') ORDER BY created_at DESC LIMIT 30) p
LEFT JOIN users u ON p.user_id = u.id
ORDER BY p.created_at;

mysql Subquery error Unknown column 'tv.last_time' in 'where clause'

When I use mysql to do subqueries, I get Unknown column 'tv.last_time' in 'where clause', what should I do? I want to use external conditions to filter out non-conforming records when doing subqueries
SELECT
info_topic.*, b.newMessage
FROM
info_topic
LEFT JOIN `info_topic_visit` AS tv ON tv.topic_id = info_topic.id
AND tv.user_id = 225
LEFT JOIN (
SELECT
p.topic_id,
count(*) AS newMessage
FROM
info_post p
WHERE
p.create_time > tv.last_time
GROUP BY
p.topic_id
) AS b ON b.topic_id = info_topic.id
ORDER BY b.newMessage DESC
database schema:
CREATE TABLE `info_topic` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT 'topic name',
`summary` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT 'summary`',
`create_time` datetime DEFAULT NULL COMMENT '',
`sort` int(11) DEFAULT '0' COMMENT '',
PRIMARY KEY (`id`),
);
CREATE TABLE `info_topic_visit` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`topic_id` int(11) DEFAULT '0' COMMENT '',
`user_id` int(11) DEFAULT '0' COMMENT '',
`last_time` datetime DEFAULT NULL COMMENT '',
PRIMARY KEY (`id`)
);
The problem with your subquery is that the tv alias has no meaning there, so you can't reference anything from the table to which that alias corresponds. One approach would be to instead convert your subquery into a correlated subquery in the select clause:
SELECT
i.*,
(SELECT COUNT(*) FROM info_post p
WHERE p.create_time > tv.last_time AND p.topic_id = i.id) AS newMessage
FROM info_topic i
LEFT JOIN info_topic_visit AS tv
ON tv.topic_id = info_topic.id AND tv.user_id = 225
LEFT JOIN info_topic_member tm
ON i.id = tm.topic_id AND
tm.del_flag = 0 AND
tm.apply_status = i.open_type AND
tm.user_id = 225
WHERE
i.del_flag = 0 AND
i.id > 0 AND
tm.id IS NOT NULL
ORDER BY
newMessage DESC;
There might also be a way to restructure your query so that you don't need to use correlated subqueries, which are inefficient.