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

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.

Related

Inner join with 3 tables (the second and third "assigned" to the first - not all together)

I have 3 tables which I would like to "inner join" - but always to the first table!
SELECT *
FROM scene_block AS sb
INNER JOIN roles AS r ON (sb.selected_block = r.id
AND sb.block_type = 'role'
AND r.id NOT IN (21))
INNER JOIN script_actors AS sa ON (sb.selected_block = sa.id
AND sb.block_type = 'actor')
WHERE
sb.scene_id = '1'
GROUP BY
sb.id
ORDER BY
sb.position
The same query with LEFT JOIN returns all results as expected with the r.id 21 "NULL", the query with INNER JOIN returns 0 results (as there are all inner joins together)
Result Left Join
But that's not what I want to achieve...
I would like to achieve, that If on scene_block "block_type='role'" the roles table is gonna be inner joined, if block_type='actor' the script_actors is gonna be inner joined... and the link between the rows is alsways scene_block.selected_id = .id
Tables
CREATE TABLE `scene_block` (
`id` int(11) UNSIGNED NOT NULL,
`scene_id` int(11) NOT NULL,
`block_type` enum('actor','role') NOT NULL,
`selected_block` int(11) DEFAULT NULL,
`content` text NOT NULL,
`hideable` enum('0','1') NOT NULL DEFAULT '1',
`position` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `script_actors` (
`id` int(11) NOT NULL,
`script_id` int(11) NOT NULL,
`realname` varchar(200) NOT NULL,
`actorname` varchar(200) NOT NULL,
`description` text NOT NULL,
`position` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `roles` (
`id` int(11) UNSIGNED NOT NULL,
`location_id` int(11) NOT NULL,
`title` varchar(256) NOT NULL,
`color` varchar(7) NOT NULL,
`color_live` varchar(7) NOT NULL,
`position` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Without having tested it on your data I presume that you get closer to your desired result if you use INNER JOINs instead of LEFT JOINs.
SELECT *
FROM scene_block AS sb
INNER JOIN roles AS r ON (sb.selected_block = r.id
AND sb.block_type = 'role'
AND r.id NOT IN (21))
INNER JOIN script_actors AS sa ON (sb.selected_block = sa.id
AND sb.block_type = 'actor')
WHERE
sb.scene_id = '1'
GROUP BY
sb.id
ORDER BY
sb.position
Inner joins don't work here, because a scene block type cannot be 'role' and 'actor' at the same time. You need outer joins to get either the role or the actor. Then add a condition to get rid of scene blocks that have no match.
SELECT *
FROM scene_block AS sb
LEFT JOIN roles AS r ON sb.selected_block = r.id
AND sb.block_type = 'role'
AND r.id <> 21
LEFT JOIN script_actors AS sa ON sb.selected_block = sa.id
AND sb.block_type = 'actor'
WHERE sb.scene_id = 1
AND (sa.id IS NOT NULL OR r.id IS NOT NULL)
ORDER BY sb.position;

MySQL returning all matches from a table and indicating if an id is on another table

How can I return, on a select, a field that indicates that an id was found?
My goal is to return all songs(song) from a specific source(source) checking if an user(user) has it or not (user_song).
The query I made almost works. If I remove 'hasSong' (which Im trying to indicate that an user has a song or not), I can see all songs.
If I keep 'hasSong', I see all songs repeating the song for each user.
QUERY:
SELECT DISTINCT(song.id) AS id_song, CONCAT(song.article, ' ', song.name) AS name
FROM `song`
LEFT JOIN `user_song` ON `song`.`id` = `user_song`.`id_song`
LEFT JOIN `user` ON `user`.`id` = `user_song`.`id_user`
JOIN `song_source` ON `song`.`id` = `song_source`.`id_song`
WHERE `song_source`.`id_source` = '1'
AND ( `user_song`.`id_user` = '3' OR song.id = song_source.id_song )
ORDER BY `song`.`name` ASC
DB:
CREATE TABLE `song` (
`id` int(11) NOT NULL,
`article` varchar(10) NOT NULL,
`name` varchar(150) NOT NULL,
`shortname` varchar(150) NOT NULL,
`year` int(11) NOT NULL,
`artist` int(11) NOT NULL,
`duration` int(11) NOT NULL,
`genre` int(11) NOT NULL,
`updated` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `song_source` (
`id_song` int(11) NOT NULL,
`id_source` int(11) NOT NULL
)
CREATE TABLE `source` (
`id` int(11) NOT NULL,
`article` varchar(10) NOT NULL,
`name` varchar(150) NOT NULL,
`updated` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_song` (
`id_user` int(11) NOT NULL,
`id_song` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The specification isn't entirely clear, ...
To return all songs (with no repeated values of song.id) that are from a particular source (id_source='1'),
along with an indicator, a value of 0 or 1, that tells us if there's a row in user_song that matches on id_song and is related to a particular user,(id_user = '3')
something like this:
SELECT s.id AS id_song
, MAX( CONCAT(s.article,' ',s.name) ) AS name
, MAX( IF(us.id_user = '3' ,1,0) ) AS has_song
FROM `song` s
JOIN `song_source` ss
ON ss.id_song = s.id
AND ss.id_source = '1'
LEFT
JOIN `user_song` us
ON us.id_song = s.id
AND us.id_user = '3'
GROUP BY s.id
ORDER BY MAX(s.name)
There are a couple of other query patterns that will return an equivalent result. For example, we could use a correlated subquery in the SELECT list.
SELECT s.id AS id_song
, MAX( CONCAT(s.article,' ',s.name) ) AS name
, ( SELECT IF( COUNT(us.id_user) >0,1,0)
FROM `user_song` us
WHERE us.id_song = s.id
AND us.id_user = '3'
) AS has_song
FROM `song` s
JOIN `song_source` ss
ON ss.id_song = s.id
AND ss.id_source = '1'
GROUP BY s.id
ORDER BY MAX(s.name)
These queries are complicated by the fact that there are no guarantees of uniqueness in any of the tables. If we had guarantees, we could eliminate the need for a GROUP BY and aggregate functions.
Please consider adding PRIMARY and/or UNIQUE KEY constraints on the tables, to prevent duplication. The way the tables are defined, we could add multiple rows to song with the same id value. (And those could have different name values.)
(And the queries would be much simpler if we had some guarantees of uniqueness.)

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

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;

Get the last item for a many-to-many relation in MySQL

I have 3 tables: categories, items and items_categories. It's a many-to-many relation. The structure of this tables is the next:
CREATE TABLE `categories` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
)
CREATE TABLE `items_categories` (
`item_id` int(10) unsigned NOT NULL,
`category_id` int(10) unsigned NOT NULL
)
CREATE TABLE `items` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`thumb` varchar(255) COLLATE utf8_unicode_ci NOT NULL
)
I've indexes but I removed because aren't needed for this example.
I need a query that returns me all categories joined with the last item for each of it and that it's NOT repeated. As items can have many categories, say that item #300 (the latest) has categories #1 and #2 in the final results category #1 and #2 will be joined with different items.
For now I have this query but it doesn't do what I exactly want:
SELECT category_id, c.name, c.slug, item_id, i.name, i.thumb
FROM items_categories AS ic
INNER JOIN categories AS c ON c.id = ic.category_id
INNER JOIN (SELECT * FROM items ORDER BY id DESC) AS i ON i.id = ic.item_id
GROUP BY c.id
ORDER BY c.id ASC
It returns me duplicated results and it returns the first item and not the last.
Dummy data: http://pastebin.com/D75tr4Ry
How I can do it? Thanks!
You just forgot to add limit in subquery. After using limit you can get all categories for last item.
Try this :
SELECT category_id, c.name, c.slug, item_id, i.name, i.thumb
FROM items_categories AS ic
INNER JOIN categories AS c ON ic.category_id = c.id
INNER JOIN (SELECT * FROM items ORDER BY id DESC LIMIT 1) AS i ON ic.item_id = i.id
GROUP BY c.id
ORDER BY c.id ASC

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;