I am creating a small message board and I am stuck
I can select the subject, the original author, the number of replies but what I can't do is get the username, topic or date of the last post.
There are 3 tables, boards, topics and messages.
I want to get the author, date and topic of the last message in the message table. The author and date field are already fields on the messages table but i would need to join the messages and topics table on the topicid field.
this is my query that selects the subject, author, and number of replies
SELECT t.topicname, t.author, count( message ) AS message
FROM topics t
INNER JOIN messages m
ON m.topicid = t.topicid
INNER JOIN boards b
ON b.boardid = t.boardid
WHERE b.boardid = 1
GROUP BY t.topicname
Can anyone please help me get this finished?
This is what my tables look like
CREATE TABLE `boards` (
`boardid` int(2) NOT NULL auto_increment,
`boardname` varchar(255) NOT NULL default '',
PRIMARY KEY (`boardid`)
);
CREATE TABLE `messages` (
`messageid` int(6) NOT NULL auto_increment,
`topicid` int(4) NOT NULL default '0',
`message` text NOT NULL,
`author` varchar(255) NOT NULL default '',
`date` timestamp(14) NOT NULL,
PRIMARY KEY (`messageid`)
);
CREATE TABLE `topics` (
`topicid` int(4) NOT NULL auto_increment,
`boardid` int(2) NOT NULL default '0',
`topicname` varchar(255) NOT NULL default '',
`author` varchar(255) NOT NULL default '',
PRIMARY KEY (`topicid`)
);
if your SQL supports the LIMIT clause,
SELECT m.author, m.date, t.topicname FROM messages m
JOIN topics t ON m.topicid = t.topicid
ORDER BY date desc LIMIT 1
otherwise:
SELECT m.author, m.date, t.topicname FROM messages m
JOIN topics t ON m.topicid = t.topicid
WHERE m.date = (SELECT max(m2.date) from messages m2)
EDIT: if you want to combine this with the original query, it has to be rewritten using subqueries to extract the message count and the date of last message:
SELECT t.topicname, t.author,
(select count(message) from messages m where m.topicid = t.topicid) AS messagecount,
lm.author, lm.date
FROM topics t
INNER JOIN messages lm
ON lm.topicid = t.topicid AND lm.date = (SELECT max(m2.date) from messages m2)
INNER JOIN boards b
ON b.boardid = t.boardid
WHERE b.boardid = 1
GROUP BY t.topicname
also notice that if you don't pick any field from table boards, you don't need the last join:
SELECT t.topicname, t.author,
(select count(message) from messages m where m.topicid = t.topicid) AS messagecount,
lm.author, lm.date
FROM topics t
INNER JOIN messages lm
ON lm.topicid = t.topicid AND lm.date = (SELECT max(m2.date) from messages m2)
WHERE t.boardid = 1
GROUP BY t.topicname
EDIT: if mysql doesn't support subqueries in the field list, you can try this:
SELECT t.topicname, t.author, mc.messagecount, lm.author, lm.date
FROM topics t
JOIN (select m.topicid, count(*) as messagecount from messages m group by m.topicid) as mc
ON mc.topicid = t.topicid
JOIN messages lm
ON lm.topicid = t.topicid AND lm.date = (SELECT max(m2.date) from messages m2)
WHERE t.boardid = 1
GROUP BY t.topicname
If you want to get the latest entry in a table, you should have a DateTime field that shows when the entry was created (or updated). You can then sort on this column and select the latest one.
But if your id field is a number, you could find the highest. But I would recommend against this because it makes many assumptions and you would be fixed to numerical ids in the future.
You can use a subselect. Eg.:
select * from messages where id = (select max(id) from messages)
edit: And if you identify the newest record by a timestamp, you'd use:
select * from messages where id = (
select id
from messages
order by post_time desc
limit 1)
With MySQL this should work:
SELECT author, date, topicname as topic FROM messages LEFT JOIN topics ON messages.topicid = topics.topicid ORDER BY date DESC, LIMIT 0, 1;
Related
I've mysql tables that looks like :
user_messages
id | user_id | phone_number | message | direction | created_at
users
id | name
I want to 'group by' user_messages two times and UNION the result. Why I want to do this? because user_id sometimes has a valid user id (anything but '-1') then I group by it, if it has -1, then group by phone_number.
I also want to left join the result with users table to get the user name in case user_id is set to a valid user
I'm almost done with the query, but the problem is:
- I want the result to have the record that results from group by to be the latest one, which means, the biggest created_at value
select * from (
(
select *, count(*) as `total` from
(select `user_id`, `message`, `created_at`, `phone_number`,`direction` from `users_messages` where `user_id` != -1 order by `created_at` desc)
as `t1` group by `user_id`
)
union
(
select *, count(*) as `total` from
(select `user_id`, `message`, `created_at`, `phone_number`,`direction` from `users_messages` where `user_id` = -1 order by `created_at` desc)
as `t2` group by `phone_number`
)
) as `t3`
left join (select `id`,`name` from `users`) as `t4` on `t3`.`user_id` = `t4`.`id` order by `created_at` desc
What this gets me is the results not sorted by created_at DESC
Update:
The query actually works in my local machine but not on the production server. In my local machine I have 5.5.42 - Source distribution and in server Ver 14.14 Distrib 5.7.17, for Linux (x86_64) using EditLine wrapper ... What could be wrong?
In local machine it correctly returns me the max created_at but in server it returns the FIRST created for the grouped by record
Something like this should work:
SELECT s.`user_id`, um.`phone_number`, s.msgCount
, um.`message`, um.`created_at`, um.`direction`
, u.`name` AS userName
FROM (
SELECT `user_id`, IF(`user_id` = -1, `phone_number`, '') AS altID, MAX(`created_at`) AS lastCreatedAt, COUNT(*) AS msgCount
FROM `users_messages`
GROUP BY user_id, altID
) AS s
INNER JOIN `users_messages` AS um
ON s.user_id = um.user_id
AND s.altID = IF(um.`user_id` = -1, um.`phone_number`, '')
AND s.lastCreatedAt = um.created_at
LEFT JOIN `users` AS u
ON s.user_id = u.user_id
ORDER BY um.created_at DESC
;
The s subquery gets the summary information for each user and userless phone number; the summary information calculated includes the most recent created_at value for use in the following....
The join to um gets the row data for their last messages (by including the lastCreatedAt value from s in the join criteria)
The final join to users is used to get the user.name for the known users (and assumes there will be no -1 user, or that such a user would have an appropriate 'unknown' name.)
Since you're grouping by user_id and phone_number, you can't keep message or direction. Add a max function for created_at in each subquery. I think this would work.
select * from (
(
select user_id
,'' as phone_number
,max('created_at') as 'created_at'
,count(*) as `total` from
(select `user_id`
,`created_at`
from `users_messages`
where `user_id` != -1)
as `t1` group by `user_id`
)
union
(
select '' as user_id
,phone_number
,max('created at') as 'created_at'
,count(*) as `total` from
(select `created_at`
,`phone_number'
from `users_messages`
where `user_id` = -1)
as `t2` group by `phone_number`
)
) as `t3`
left join (select `id`,`name` from `users`) as `t4`
on `t3`.`user_id` = `t4`.`id`
order by `created_at` desc
I have two tables:
users and users_img.
I need to create one query to select all users from users table that have zip column not empty AND have img column empty in users_images (both tables have user ids: users.id and users_img.user_id so those could be joined).
users
id
name
zip
last_time
users_ids
id
user_id (same as id in users)
img
I tried this:
SELECT * FROM `users` JOIN `users_ids` on users.id = users_ids.user_id
WHERE `zip` != '' AND `img` = '' ORDER BY `last_time` DESC
with no luck. I know is supposed to be quite simple.
it will also work for you
SELECT * FROM `users` as `u`
JOIN `users_ids` as `uid` on `u`.`id` = `uid`.`user_id`
WHERE `u`.`zip` IS NOT NULL
AND `uid`.`img` IS NULL
ORDER BY `u`.`last_time` DESC
I have modified your query to check for Null values also in OR
Try this:
SELECT * FROM `users`
LEFT JOIN `users_ids` on users.id = users_ids.user_id
WHERE (TRIM(`zip`) != '' OR `zip` is not null) AND
(TRIM(`img`) = '' OR `img` is null) ORDER BY `last_time` DESC
This worked:
SELECT * FROM `users` as `u`
LEFT JOIN `users_ids` as `uid` on `u`.`id` = `uid`.`user_id`
WHERE `u`.`zip` != ''
AND `uid`.`img` IS NOT NULL
ORDER BY `u`.`last_time` DESC
I've a table structured somewhat similar to this:
CREATE TABLE `user`
(`id` int, `name` varchar(7));
CREATE TABLE `email`
(`id` int, `email_address` varchar(50), `verified_flag` tinyint(1),`user_id` int);
CREATE TABLE `social`
(`id` int,`user_id` int);
INSERT INTO `user`
(`id`, `name`)
VALUES
(1,'alex'),
(2,'jon'),
(3,'arya'),
(4,'sansa'),
(5,'hodor')
;
INSERT INTO `email`
(`id`,`email_address`,`verified_flag`,`user_id`)
VALUES
(1,'alex#gmail.com','1',1),
(2,'jon#gmail.com','0',1),
(3,'arya#gmail.com','0',3),
(4,'sansa#gmail.com','1',4),
(5,'reek#gmail.com','0',3),
(6,'hodor#gmail.com','0',5),
(7,'tyrion#gmail.com','0',1)
;
INSERT INTO `social`
(`id`,`user_id`)
VALUES
(1,4),
(2,4),
(3,5),
(4,4),
(5,4)
;
What I want to get is all emails:
which are not verified
which belongs to a user who has no, i.e 0, verified emails
which belongs to a user who has no, i.e 0, social records
With the below query I'm able to apply the 1st and 3rd condition but not the 2nd one:
SELECT *
FROM `email`
INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
LEFT JOIN `social` ON `user`.`id` = `social`.`user_id`
WHERE `email`.`verified_flag` = 0
GROUP BY `email`.`user_id`,`email`.`email_address`
HAVING COUNT(`social`.`id`) = 0
How can I achieve the result?
Here's the sqlfiddle as well
Interesting and tricky one.
I see you've got something going on there. But having and sub queries becomes a VERY bad idea when your tables become large.
See below for an approach. Don't forget to set up your indexes!
SELECT * from email
LEFT JOIN social on email.user_id = social.user_id
-- tricky ... i'm going back to email table to pick verified emails PER user
LEFT JOIN email email2 on email2.user_id = email.user_id AND email2.verified_flag = 1
WHERE
-- you got this one going already :)
email.verified_flag = 0
-- user does not have any social record
AND social.id is null
-- email2 comes in handy here ... we limit resultset to include only users that DOES NOT have a verified email
AND email2.id is null
ORDER BY email.user_id asc;
You can use the following query:
SELECT e.`id`, e.`email_address`, e.`verified_flag`, e.`user_id`
FROM (
SELECT `id`,`email_address`,`verified_flag`,`user_id`
FROM `email`
WHERE `verified_flag` = 0) AS e
INNER JOIN (
SELECT `id`, `name`
FROM `user` AS t1
WHERE NOT EXISTS (SELECT 1
FROM `email` AS t2
WHERE `verified_flag` = 1 AND t1.`id` = t2.`user_id`)
AND
NOT EXISTS (SELECT 1
FROM `social` AS t3
WHERE t1.`id` = t3.`user_id`)
) AS u ON u.`id` = e.`user_id`;
This query uses two derived tables:
e implements the first condition, i.e. returns all emails which are not verified
u implements the 2nd and 3rd condition, i.e. it returns a set of all users that have no verified emails and have no social records.
Performing an INNER JOIN between e and u returns all emails satisfying condition no. 1 which belong to users satisfying conditions no. 2 and 3.
Demo here
You can alternatively use this query:
SELECT *
FROM `email`
WHERE `user_id` IN (
SELECT `email`.`user_id`
FROM `email`
INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
LEFT JOIN `social` ON `user`.`id` = `social`.`user_id`
GROUP BY `email`.`user_id`
HAVING COUNT(`social`.`id`) = 0 AND
COUNT(CASE WHEN `email`.`verified_flag` = 1 THEN 1 END) = 0 )
The subquery is used in order to select all user_id satisfying conditions no. 2 and 3. Condition no. 1 is redundant since if the user has no verified emails, then there is no way a verified email is related to this user.
Demo here
Simply run a Union Query:
SELECT `user_id`, `email_address`, `verified_flag`, 'No Email' as `Type`
FROM `email` RIGHT JOIN `user` ON `user`.`id` = `email`.`user_id`
WHERE `email`.`user_id` IS NULL
UNION
SELECT `user_id`, `email_address`, `verified_flag`, 'Not Verified' as `Type`
FROM `email` INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
WHERE `email`.`verified_flag` = 0
UNION
SELECT `user_id`, `email_address`, `verified_flag`, 'No Social' as `Type`
FROM `email` INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
LEFT JOIN `social` ON `user`.`id` = `social`.`user_id`
GROUP BY `user_id`, `email_address`, `verified_flag`
HAVING COUNT(IFNULL(`social`.`id`, 0)) = 0;
SELECT
u.id AS u_id
, u.name AS u_name
, e.email_address AS e_email
, e.verified_flag AS e_verify
, e.user_id AS e_uid
, s.id AS s_id
, s.user_id AS u_id
, COALESCE(ver_e.ver_email_count,0) as ver_email_count
FROM
email as e
LEFT OUTER JOIN
user as u
ON u.id = e.user_id
LEFT OUTER JOIN
social AS s
ON u.id = s.user_id
LEFT OUTER JOIN
(
SELECT
COUNT(email_address) AS ver_email_count
, user_id
FROM
email
) AS ver_e
ON u.id = ver_e.user_id
GROUP BY
e.user_id
HAVING e.verified_flag = 0
AND
ver_email_count = 0
AND
ISNULL(s.id)
Uses one derived table to get the number of verified email addresses each user has got
How to check in MySQL table if my ID exist in the results and return TRUE, but this is a little complicated as I would like to check GROUP of records and not the last one from that GROUP, code below for the table and query which doesn't work, I would like to check if my $session_user_id exist in the GROUP OF auction_bid_item_id but not check last row as the last row is a WINNING ROW.
CREATE TABLE `auction_bids` (
`auction_bid_id` int(11) NOT NULL AUTO_INCREMENT,
`auction_bid_user_id` int(11) NOT NULL,
`auction_bid_seller_id` int(11) NOT NULL,
`auction_bid_item_id` int(11) NOT NULL,
PRIMARY KEY (`auction_bid_id`)
);
MySQL Query:
SELECT * FROM auction_bids
WHERE auction_bid_user_id = '$session_user_id'
GROUP BY auction_bid_item_id;
Join against a subselect which determines if the user is in that group.
Ie, the following will get you the bids and a field saying if the user is found.
SELECT auction_bids.*, IF(Sub1.auction_bid_item_id IS NULL, 'User not found', 'User found')
FROM auction_bids
LEFT OUTER JOIN
(
SELECT DISTINCT auction_bid_item_id
FROM auction_bids
WHERE auction_bid_user_id = '$session_user_id'
) Sub1
ON auction_bids.auction_bid_item_id = Sub1.auction_bid_item_id
GROUP BY auction_bids.auction_bid_item_id
EDIT - try this to try and ignore the latest bid
SELECT auction_bids.*, IF(Sub1.auction_bid_item_id IS NULL, 'User not found', 'User found')
FROM auction_bids
LEFT OUTER JOIN
(
SELECT DISTINCT auction_bids.auction_bid_item_id
FROM auction_bids
LEFT OUTER JOIN
(
SELECT auction_bid_item_id, MAX(auction_bid_id) AS LastBid
FROM auction_bids
GROUP BY auction_bid_item_id
) Sub2
ON auction_bids.auction_bid_item_id = Sub2.auction_bid_item_id
AND auction_bids.auction_bid_id = Sub2.LastBid
WHERE Sub2.auction_bid_item_id IS NULL
AND auction_bid_user_id = '$session_user_id'
) Sub1
ON auction_bids.auction_bid_item_id = Sub1.auction_bid_item_id
GROUP BY auction_bids.auction_bid_item_id
Something like this? (with php)
$id_exists = false;
$id = mysql_real_escape_string($_GET['id']);
$select = "SELECT auction_bid_id FROM auction_bids WHERE auction_bid_id ='$id'";
$result = mysql_query($select);
if(mysql_num_rows($result) > 0){
$id_exists = true;
}else{
$id_exists = false;
}
I have the following tables:
CREATE TABLE `data` (
`date_time` decimal(26,6) NOT NULL,
`channel_id` mediumint(8) unsigned NOT NULL,
`value` varchar(40) DEFAULT NULL,
`status` tinyint(3) unsigned DEFAULT NULL,
`connected` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`channel_id`,`date_time`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `channels` (
`channel_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`channel_name` varchar(40) NOT NULL,
PRIMARY KEY (`channel_id`),
UNIQUE KEY `channel_name` (`channel_name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
I was wondering if anyone could give me some advice on how to optimize or rewrite the following query:
SELECT channel_name, t0.date_time, t0.value, t0.status, t0.connected, t1.date_time, t1.value, t1.status, t1.connected FROM channels,
(SELECT MAX(date_time) AS date_time, channel_id, value, status, connected FROM data
WHERE date_time <= 1300818330
GROUP BY channel_id) AS t0
RIGHT JOIN
(SELECT MAX(date_time) AS date_time, channel_id, value, status, connected FROM data
WHERE date_time <= 1300818334
GROUP BY channel_id) AS t1
ON t0.channel_id = t1.channel_id
WHERE channels.channel_id = t1.channel_id
Basically I am getting the value, status and connected fields for each channel_name at two different times. Since t0 is always <= t1, the fields could exist for t1, but not t0, and I want that to be shown. That is why I am using the RIGHT JOIN. If it does not exist for t1, then it won't exist for t0, so no row should be returned.
The problem seems to be that since I am joining sub queries, no index can be used? I tried rewriting it to do a self join on the channel_id of the data table first but that is millions of rows.
It would also be nice to be able to add a boolean field to each of the final rows that is true when t0.value = t1.value & t0.status = t1.status & t0.connected = t1.connected.
Thank you very much for your time.
You can reduce the two sub-queries to one
SELECT channel_id,
MAX(date_time) AS t1_date_time,
MAX(case when date_time <= {$p1} then date_time end) AS t0_date_time
FROM data
WHERE date_time <= {$p2}
GROUP BY channel_id
GROUP BY is notoriously misleading in MySQL. Imagine if you had MIN() and MAX() in the same select, which row should the non-grouped columns come from? Once you understand this, you will see why it is not deterministic.
To get the full t0 and t1 rows
SELECT x.channel_id,
t0.date_time, t0.value, t0.status, t0.connected,
t1.date_time, t1.value, t1.status, t1.connected
FROM (
SELECT channel_id,
MAX(date_time) AS t1_date_time,
MAX(case when date_time <= {$p1} then date_time end) AS t0_date_time
FROM data
WHERE date_time <= {$p2}
GROUP BY channel_id
) x
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time
And finally a join to get the channel name
SELECT c.channel_name,
t0.date_time, t0.value, t0.status, t0.connected,
t1.date_time, t1.value, t1.status, t1.connected,
t0.value=t1.value AND t1.status=t0.status
AND t0.connected=t1.connected name_me
FROM (
SELECT channel_id,
MAX(date_time) AS t1_date_time,
MAX(case when date_time <= {$p1} then date_time end) AS t0_date_time
FROM data
WHERE date_time <= {$p2}
GROUP BY channel_id
) x
INNER JOIN channels c on c.channel_id = x.channel_id
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time
EDIT
To perform an RLIKE on channel name, it looks simple enough to add a WHERE clause at the end of the query on c.channel_name. It may however perform better to filter it at the subquery, making use of MySQL feature of processing comma-notation joins left to right.
SELECT x.channel_name,
t0.date_time, t0.value, t0.status, t0.connected,
t1.date_time, t1.value, t1.status, t1.connected,
t0.value=t1.value AND t1.status=t0.status
AND t0.connected=t1.connected name_me
(
SELECT c.channel_id, c.channel_name,
MAX(d.date_time) AS t1_date_time,
MAX(case when d.date_time <= {$p1} then d.date_time end) AS t0_date_time
FROM channels c, data d
WHERE c.channel_name RLIKE {$expr}
AND c.channel_id = d.channel_id
AND d.date_time <= {$p2}
GROUP BY c.channel_id
) x
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time