Help with sorting results from database (JOIN?) in php - php

Im upgrading my forum a bit, and want to change the way the topics is listed.
My topics is stored in this table:
CREATE TABLE `forum_emner` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type_forum` CHAR(9) NOT NULL,
`gjengid` INT(10) UNSIGNED NULL DEFAULT '0',
`sticky` TINYINT(1) UNSIGNED NULL DEFAULT '0',
`emne` VARCHAR(255) NOT NULL,
`innlegg` TEXT NOT NULL,
`brukerid_starter` MEDIUMINT(8) UNSIGNED NOT NULL,
`startet_dato` INT(10) UNSIGNED NOT NULL,
`antall_lest` INT(10) UNSIGNED NULL DEFAULT '0',
`antall_svar` INT(10) UNSIGNED NULL DEFAULT '0',
PRIMARY KEY (`id`),
INDEX `type_forum` (`type_forum`),
INDEX `gjengid` (`gjengid`),
INDEX `sticky` (`sticky`),
INDEX `brukerid_starter` (`brukerid_starter`)
)
COLLATE='latin1_swedish_ci'
ENGINE=MyISAM
ROW_FORMAT=DEFAULT
Answers to topics is stored in this table (emneid equals id in the forum_emner table):
CREATE TABLE `forum_svar` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`emneid` INT(10) UNSIGNED NOT NULL,
`brukerid_av` MEDIUMINT(8) UNSIGNED NOT NULL,
`innlegg` TEXT NOT NULL,
`dato` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
INDEX `emneid` (`emneid`),
INDEX `brukerid_av` (`brukerid_av`)
)
COLLATE='latin1_swedish_ci'
ENGINE=MyISAM
ROW_FORMAT=DEFAULT
dato is when the answer was posted (time() in php).
What i want: I want to sort the topics after the field, dato in forum_svar. The topic with the newest answer is on the top, and so on. But if a topic is created after the top topics last answer this topic should be on the top (how a forum actually works).
I've tried myself, but its not working as it should.
SELECT *, `forum_emner`.id AS UnikTradID FROM `forum_emner`
LEFT JOIN `forum_svar` ON (`forum_emner`.id = `forum_svar`.emneid)
WHERE `forum_emner`.type_forum = :type AND `forum_emner`.sticky = 0
ORDER BY `forum_svar`.dato DESC LIMIT :p1, :p2
Any help? :)

A side note: you should use AS as I do bellow to make the query look more readable.
And you shouldn't keep your MySQL field names in something other than English. What if you hire someone not speaking your language? :)
The first thing to do is to get the newest thread ids.
We can do that with such query:
SELECT MAX(fs.id) AS LastThreadId FROM forum_svar AS fs GROUP BY fs.emneid ORDER BY LastThreadId
Now we need to join this result set with the threads to get full thread information.
So we do it like this by putting the first query as a subquery here:
SELECT fs.* FROM forum_svar AS fs LEFT JOIN (SELECT MAX(fs.id) AS LastThreadId FROM forum_svar AS fs GROUP BY fs.emneid ORDER BY LastThreadId DESC) n ON (n.LastThreadId = fs.id) WHERE n.LastThreadId IS NOT NULL
This will now get you the newest threads. Enjoy! :)
P.S. Don't forget to accept the answer if it worked out!

Related

Optimization Needed For Dual Left Join Query

I've always struggled with mysql joins but have started incorporating more but struggling to understand despite reading dozens of tutorials and mysql manual.
My situation is I have 3 tables:
/* BASICALLY A TABLE THAT HOLDS FAN RECORDS */
CREATE TABLE `fans` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) DEFAULT NULL,
`middle_name` varchar(255) DEFAULT NULL,
`last_name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`join_date` datetime DEFAULT NULL,
`twitter` varchar(255) DEFAULT NULL,
`twitterCrawled` datetime DEFAULT NULL,
`twitterImage` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=20413 DEFAULT CHARSET=latin1;
/* A TABLE OF OUR TWITTER FOLLOWERS */
CREATE TABLE `twitterFollowers` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`screenName` varchar(25) DEFAULT NULL,
`twitterId` varchar(25) DEFAULT NULL,
`customerId` int(11) DEFAULT NULL,
`uniqueStr` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique` (`uniqueStr`)
) ENGINE=InnoDB AUTO_INCREMENT=13426 DEFAULT CHARSET=utf8;
/* TABLE THAT SUGGESTS A LIKELY MATCH OF A TWITTER FOLLOWER BASED ON THE EMAIL / SCREEN NAME COMPARISON OF THE FAN vs OUR FOLLOWERS
IF SOMEONE (ie. a moderator) CONFIRMS OR DENIES THAT IT'S A GOOD MATCH THEY PUT A DATESTAMP IN `dismissed` */
CREATE TABLE `contentSuggestion` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`userId` int(11) DEFAULT NULL,
`fanId` int(11) DEFAULT NULL,
`twitterAccountId` int(11) DEFAULT NULL,
`contentType` varchar(50) DEFAULT NULL,
`contentString` varchar(255) DEFAULT NULL,
`added` datetime DEFAULT NULL,
`dismissed` datetime DEFAULT NULL,
`uniqueStr` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unstr` (`uniqueStr`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
What I'm trying to get is:
SELECT [fan columns]
WHERE fan screen name IS IN twitterfollowers
AND WHERE fan screen name IS NOT IN contentSuggestion (with a datestamp in dismissed)
My attempts so far:
~33 seconds
SELECT fans.id, tf.screenName as col1, tf.twitterId as col2 FROM fans
LEFT JOIN twitterFollowers tf ON tf.screenName = fans.emailUsername
LEFT JOIN contentSuggestion cs ON cs.contentString = tf.screenName WHERE dismissed IS NULL
GROUP BY(fans.id) HAVING col1 != ''
~14 seconds
SELECT id, emailUsername FROM fans WHERE emailUsername IN(SELECT DISTINCT(screenName) FROM twitterFollowers) AND emailUsername NOT IN(SELECT DISTINCT(contentString) FROM contentSuggestion WHERE dismissed IS NULL) GROUP BY (fans.id);
9.53 seconds
SELECT fans.id, tf.screenName as col1, tf.twitterId as col2 FROM fans
LEFT JOIN twitterFollowers tf ON tf.screenName = fans.emailUsername WHERE tf.uniqueStr NOT IN(SELECT uniqueStr FROM contentSuggestion WHERE dismissed IS NULL)
I hope there is a better way. I've been struggling to really use JOINS outside of a single LEFT JOIN which has already helped me speed up other queries by a significant amount.
Thanks for any help you can give me.
I would go with a variation of the second method. Instead of IN, use EXISTS. Then add the correct indexes and remove the aggregation:
SELECT f.id, f.emailUsername
FROM fans f
WHERE EXISTS (SELECT 1
FROM twitterFollowers tf
WHERE f.emailUsername = tf.screenName
) AND
NOT EXISTS (SELECT 1
FROM contentSuggestion cs
WHERE f.emailUsername = cs.contentString AND
cs.dismissed IS NULL
) ;
Then be sure you have the following indexes: twitterFollowers(screenName) and contentSuggestion(contentString, dismissed).
Some notes:
When using IN, don't use SELECT DISTINCT. I'm not 100% sure that MySQL is always smart enough to ignore the DISTINCT in the subquery (it is redundant).
Historically, EXISTS was faster than IN in MySQL. The optimizer has improved in recent versions.
For performance, you need the correct indexes.
Then be sure you have the following indexes: twitterFollowers(screenName) and contentSuggestion(contentString, dismissed).
Assuming that fan.id is unique (a very reasonable assumption), you don't need the final group by.

Mysql query to sort and merge two tables

I have two tables in my mysql database called forum_topics and forum_replies.
I am looking to have an overview of the latest posts / replies added, like this: http://prntscr.com/6ixtz4
to do so, i need a way to makee it sort by the time in both tables, and if a forum_reply is in the result it need to get its topic from the forum_topics.
How can i make this work? I have no mysql query to referer to to make this work.
CREATE TABLE IF NOT EXISTS `forum_topics` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`topic` varchar(255) NOT NULL,
`thread` varchar(255) NOT NULL,
`level` int(2) NOT NULL DEFAULT '0',
`creator` int(255) NOT NULL,
`time` varchar(255) NOT NULL,
`innlegg` text NOT NULL,
`ip` varchar(255) NOT NULL,
`locked` enum('yes','no') NOT NULL DEFAULT 'no',
`deleted` enum('yes','no') NOT NULL DEFAULT 'no',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
CREATE TABLE IF NOT EXISTS `forum_replies` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`topicid` int(255) NOT NULL,
`userid` int(255) NOT NULL,
`time` int(255) NOT NULL,
`reply` text NOT NULL,
`deleted` enum('yes','no') NOT NULL DEFAULT 'no',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=20 ;
exampledata:
in table forum_topics
id |topic| time|
1 | test | 10
2 | test2 | 29
in table forum replies:
id | topicid | time |
1 | 1 | 18
2 | 2 | 28
As in this example i would like the outcome to be sorted something like this:
10
18
28
29
if forum_replies is one of the results it would need some data from forum_topics, but if forum_topics is one of the results it wouldnt need any data from the forum_replies.
The topicid is the id from the forum_topics of the topic that the reply is a response to.
So in the end i want to create the example output as in the image.
EDIT:
it would be a result containing in following order:
10
18
28
29
By loading the data in the example data sets, this will get the time from both tables, but sort the time from both as if they were in one table. The below query gets the results, and then orders them in order of time. Since both tables have the same structure, you can "union" them together as if they were one table and sort by time using "order_by"
select time
from forum_topics
UNION
select time
from forum_replies
order by time
Note: I was able to load both tables in the above request but without the last line on each. In other words I trimmed the part referring to the engine, and then just created the tables, and then entered the data provided..
I'm sorry I have to say the table structure of a forum post is incorrect. In your case, you will be very hard to sort a post based on topic and reply. I would suggest a table structure like this:
CREATE TABLE IF NOT EXISTS `forum_topics` (
`tid` int(255) NOT NULL AUTO_INCREMENT, // Topic ID
`tsubject` varchar(255) NOT NULL, // Topic Subject
`level` int(2) NOT NULL DEFAULT '0',
`creator` int(255) NOT NULL,
`innlegg` text NOT NULL, // I don't know what this means ...
`locked` enum('yes','no') NOT NULL DEFAULT 'no',
`deleted` enum('yes','no') NOT NULL DEFAULT 'no',
PRIMARY KEY (`tid`),
UNIQUE KEY `id` (`tid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5;
CREATE TABLE IF NOT EXISTS `forum_posts` (
`pid` int(255) NOT NULL AUTO_INCREMENT,
`tid` int(255) NOT NULL,
`userid` int(255) NOT NULL,
`time` int(255) NOT NULL,
`pcontent` text NOT NULL, // Post Content
`isauthorpost` int(1) NOT NULL, // Optional: If this is the first post of the topic
`deleted` enum('yes','no') NOT NULL DEFAULT 'no',
PRIMARY KEY (`pid`),
UNIQUE KEY `id` (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=20 ;
So now you will have a topic id to follow by the first. And you can just search for all replies with ORDER BY pid, for the topic details, you can use LEFT JOIN forum_topics or INNER JOIN to finish.

How can I find the missing figure in a confrontation between two MySQL tables?

I have two tables, one table and a table users notifications. I have to select the users who have not yet received notifications. I have tried in various ways, such as by using this query:
SELECT user.registration_id FROM user
LEFT JOIN notify ON user.registration_id = notify.registration_id
But it does not work. How can I solve?
Also you link the structures of the two tables that use. Thanks in advance.
CREATE TABLE IF NOT EXISTS `user` (
`id` int(15) NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`registration_id` varchar(200) NOT NULL,
`login_username` varchar(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS `notify` (
`id` int(11) unsigned NOT NULL,
`registration_id` varchar(200) NOT NULL,
`user_id` int(15) unsigned NOT NULL,
`start_notify` datetime NOT NULL,
`stop_notify` datetime NOT NULL,
`partial_score` int(15) unsigned NOT NULL
);
You need a WHERE statement to find the users, who haven't had a notification yet:
SELECT user.registration_id
FROM user
LEFT JOIN notify
ON user.registration_id = notify.registration_id
WHERE notify.id IS NULL;
This will retrieve all user ids for users who do not have an entry in the notify table.

MySql Properly Join Complex Data/Tables

Abstract:
Every client is given a specific xml ad feed (publisher_feed table). Everytime there is a query or a click on that feed, it gets recorded (publisher_stats_raw table) (Each query/click will have multiple rows depending on the subid passed by the client (We can sum the clicks together)). The next day, we pull stats from an API to grab the previous days revenue numbers (rev_stats table) (Each revenue stat might have multiple rows depending on the country of the click (We can sum the revenue together)). Been having a hard time trying to link together these three tables to find the average RPC for each client for the previous day.
Table Structure:
CREATE TABLE `publisher_feed` (
`publisher_feed_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`alias` varchar(45) DEFAULT NULL,
`user_id` int(10) unsigned DEFAULT NULL,
`remote_feed_id` int(10) unsigned DEFAULT NULL,
`subid` varchar(255) DEFAULT '',
`requirement` enum('tq','tier2','ron','cpv','tos1','tos2','tos3','pv1','pv2','pv3','ar','ht') DEFAULT NULL,
`status` enum('enabled','disabled') DEFAULT 'enabled',
`tq` decimal(4,2) DEFAULT '0.00',
`clicklimit` int(11) DEFAULT '0',
`prev_rpc` decimal(20,10) DEFAULT '0.0000000000',
PRIMARY KEY (`publisher_feed_id`),
UNIQUE KEY `alias_UNIQUE` (`alias`),
KEY `publisher_feed_idx` (`remote_feed_id`),
KEY `publisher_feed_user` (`user_id`),
CONSTRAINT `publisher_feed_feed` FOREIGN KEY (`remote_feed_id`) REFERENCES `remote_feed` (`remote_feed_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `publisher_feed_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=124 DEFAULT CHARSET=latin1$$
CREATE TABLE `publisher_stats_raw` (
`publisher_stats_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`unique_data` varchar(350) NOT NULL,
`publisher_feed_id` int(10) unsigned DEFAULT NULL,
`date` date DEFAULT NULL,
`subid` varchar(255) DEFAULT NULL,
`queries` int(10) unsigned DEFAULT '0',
`impressions` int(10) unsigned DEFAULT '0',
`clicks` int(10) unsigned DEFAULT '0',
`filtered` int(10) unsigned DEFAULT '0',
`revenue` decimal(20,10) unsigned DEFAULT '0.0000000000',
PRIMARY KEY (`publisher_stats_id`),
UNIQUE KEY `unique_data_UNIQUE` (`unique_data`),
KEY `publisher_stats_raw_remote_feed_idx` (`publisher_feed_id`)
) ENGINE=InnoDB AUTO_INCREMENT=472 DEFAULT CHARSET=latin1$$
CREATE TABLE `rev_stats` (
`rev_stats_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`remote_feed_id` int(10) unsigned DEFAULT NULL,
`typetag` varchar(255) DEFAULT NULL,
`subid` varchar(255) DEFAULT NULL,
`country` varchar(2) DEFAULT NULL,
`revenue` decimal(20,10) DEFAULT NULL,
`tq` decimal(4,2) DEFAULT NULL,
`finalized` int(11) DEFAULT '0',
PRIMARY KEY (`rev_stats_id`),
KEY `rev_stats_remote_feed_idx` (`remote_feed_id`),
CONSTRAINT `rev_stats_remote_feed` FOREIGN KEY (`remote_feed_id`) REFERENCES `remote_feed` (`remote_feed_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=latin1$$
Context:
Each remote_feed has a specific subid/typetag given to it. So we need to match up the both the remote_feed_id and the subid columsn from the publisher_feed table to the remote_feed_id and typetag columns in the revenue stats table.
My current, non working, implementation:
SELECT
pf.publisher_feed_id, psr.date, sum(clicks), sum(rs.revenue)
FROM
xml_network.publisher_feed pf
JOIN
xml_network.publisher_stats_raw psr
ON
psr.publisher_feed_id = pf.publisher_feed_id
JOIN
xml_network.rev_stats rs
ON
rs.remote_feed_id = pf.remote_feed_id
WHERE
pf.requirement = 'tq'
AND
pf.subid = rs.typetag
AND
psr.date <> date(curdate())
GROUP BY
psr.date
ORDER BY
psr.date DESC
LIMIT 1;
The above keeps pulling the wrong data out of the rev_stats table (pulls the sum of the correct stats, but repeats it over because of a join). Any help with how I would be able to properly pull the correct data would be greatly helpful ( I could use multiple queries and PHP to get the correct results, but what's the fun in that!)
Figured out a way to get this accomplished. Its def not a fast method by any means, needing 4 selects to get it done, but it works flawlessly =)
SELECT
pf.publisher_feed_id,
round(
(
SELECT
SUM(rs.revenue)
FROM
xml_network.rev_stats rs
WHERE
rs.remote_feed_id = pf.remote_feed_id
AND
rs.typetag = pf.subid
AND
rs.date = subdate(current_date, 1)
),10)as revenue,
(
SELECT
MAX(rs.tq)
FROM
xml_network.rev_stats rs
WHERE
rs.remote_feed_id = pf.remote_feed_id
AND
rs.typetag = pf.subid
AND
rs.date = subdate(current_date, 1)
) as tq,
(
SELECT
SUM(psr.clicks)-SUM(psr.filtered)
FROM
xml_network.publisher_stats_raw psr
WHERE
psr.publisher_feed_id = pf.publisher_feed_id
AND
psr.date = subdate(current_date, 1)
) as clicks
FROM
xml_network.publisher_feed pf
WHERE
pf.requirement = 'tq';

mysql - php - Need to get only the latest record from a self referencing data

Hi,
I need some help with SQL. Attached is the image of my table.
If you see rootmessageid column there are 4 records of 99s. All these 4 makes one complete conversation.
Similarly the 2 records of 119 makes an other conversation.
116, 117, 118 are single message conversation.
Now I need to get all the records where msgfrom = 7 or msgto = 7 (this was the easy part)
Now the complicated bit. I want the only the latest record (based on datetimecreated) from each conversation.
Following the script to create this table.
CREATE TABLE IF NOT EXISTS `selectioncommunication` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`comactionid` int(11) NOT NULL,
`usercomment` varchar(2048) DEFAULT NULL,
`msgfrom` int(11) NOT NULL,
`msgto` int(11) NOT NULL,
`projectid` int(11) NOT NULL,
`parentmessageid` int(11) NOT NULL,
`datetimecreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`rootmessageid` int(11) NOT NULL,
`isread` tinyint(1) NOT NULL DEFAULT '0',
`isclosed` tinyint(1) DEFAULT '0',
`relative_date_time` datetime DEFAULT NULL,
`consultant_response` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=121 );
You want the groupwise maximum:
SELECT s.*
FROM selectioncommunication s NATURAL JOIN (
SELECT parentmessageid, MAX(datetimecreated) datetimecreated
FROM selectioncommunication
WHERE msgfrom = 7 OR msgto = 7
GROUP BY parentmessageid
) t
WHERE s.msgfrom = 7 OR s.msgto = 7
use ORDER BY datetime ASC/DESC
this will sort your results in order then add LIMIT 1 to the end of your query to only get the first record in your list.
Here is your SQl Fiddle without Join
SELECT *
FROM selectioncommunication k
WHERE datetimecreated = (SELECT
MAX(datetimecreated)
FROM selectioncommunication s
WHERE s.rootmessageid = k.rootmessageid
GROUP BY s.rootmessageid
ORDER BY s.id)

Categories