How to get the last message in a thread SQL Query - mysql

I have two tables, listhdthreads and listhdmessages. The listhdthreads is the parent table, and each thread has messages in it as children.
CREATE TABLE `listhdthreads` (
`idx` int(11) NOT NULL AUTO_INCREMENT,
`fromUser` int(11) NOT NULL,
`respondingUser` int(11) DEFAULT NULL,
`attachments` varchar(300) DEFAULT NULL,
`customerId` int(11) NOT NULL,
`locationId` int(11) DEFAULT NULL,
`areaId` int(11) DEFAULT NULL,
`assetId` int(11) DEFAULT NULL,
`projectId` int(11) DEFAULT NULL,
`contactId` int(11) DEFAULT NULL,
`title` int(11) NOT NULL,
`priority` int(11) NOT NULL,
`status` int(11) NOT NULL,
`createdAt` datetime NOT NULL,
`ddGroup` int(1) DEFAULT NULL,
PRIMARY KEY (`idx`)
)
and
CREATE TABLE `listhdmessages` (
`idx` int(11) NOT NULL AUTO_INCREMENT,
`parentThreadId` int(11) NOT NULL,
`createdAt` datetime NOT NULL,
`sendingUser` int(11) NOT NULL,
`text` varchar(300) NOT NULL,
`adminMessage` bit(1) DEFAULT b'0',
`updatedAt` datetime DEFAULT NULL,
PRIMARY KEY (`idx`),
KEY `parentId` (`parentThreadId`),
CONSTRAINT `listhdmessages_ibfk_1` FOREIGN KEY (`parentThreadId`) REFERENCES `listhdthreads` (`idx`) ON DELETE CASCADE ON UPDATE CASCADE
)
Now I have this current query which populates a tabl with all the threads and I want to include the last time a message was sent (which I already have), and the content of the last message itself (this is what I need help with).
The current query as it stands is this (removed unnecessary columns selected)
SELECT MAX(m.createdAt) as latestMessage,
FROM listhdthreads as hd
LEFT JOIN listhdmessages m ON hd.idx = m.parentThreadId
LEFT JOIN listcustomers c ON hd.customerId = c.idx
LEFT JOIN listlocations l ON hd.locationId = l.idx
LEFT JOIN listprojects lp ON hd.projectId = lp.idx
LEFT JOIN listusers lu ON hd.contactId = lu.idx
LEFT JOIN listusers lus ON hd.fromUser = lus.idx
LEFT JOIN listusers lusu ON hd.respondingUser = lusu.idx
LEFT JOIN deschdtitles t ON hd.title = t.idx
LEFT JOIN deschdpriorities p ON hd.priority = p.idx
LEFT JOIN deschdstatus s ON hd.status = s.idx
LEFT JOIN helpmessageparticipants hmp ON hd.idx = hmp.parentThreadId
GROUP BY hd.idx
I am at a loss with trying to get get the latest message itself. How can I go about that? I am using MySQL 5.6.
EDIT: Sorry, I forgot to include the group by which is necessary for the rest of the query to work right. That's what is giving me a headache.
Edit 2: Figured it out. Turned my initial query into a subquery that I just order by in such a way that my top row of each thread is exactly what I want, and then group by outside of it.
SELECT * FROM
(SELECT hd.idx, hd.ddGroup, c.name as customer, IF(hd.locationId=-1,"None",l.name) as location, CONCAT(lu.firstname, ' ', lu.lastname) as contactId, t.title, lp.number,
UPPER(CONCAT(SUBSTRING(lus.firstname,1,1), SUBSTRING(lus.lastname,1,1))) as createdBy, IF(hd.respondingUser=-1,"---",UPPER(CONCAT(SUBSTRING(lusu.firstname,1,1), SUBSTRING(lusu.lastname,1,1)))) as forUser,
p.priority,s.status,hd.customerId,hd.locationId,hd.areaId,hd.assetId,hd.projectId,
m.createdAt as latestMessage, m.text as LastMessage
FROM listhdthreads as hd
LEFT JOIN listhdmessages m ON hd.idx = m.parentThreadId
LEFT JOIN listcustomers c ON hd.customerId = c.idx
LEFT JOIN listlocations l ON hd.locationId = l.idx
LEFT JOIN listprojects lp ON hd.projectId = lp.idx
LEFT JOIN listusers lu ON hd.contactId = lu.idx
LEFT JOIN listusers lus ON hd.fromUser = lus.idx
LEFT JOIN listusers lusu ON hd.respondingUser = lusu.idx
LEFT JOIN deschdtitles t ON hd.title = t.idx
LEFT JOIN deschdpriorities p ON hd.priority = p.idx
LEFT JOIN deschdstatus s ON hd.status = s.idx
LEFT JOIN helpmessageparticipants hmp ON hd.idx = hmp.parentThreadId
ORDER BY hd.idx, m.createdAt DESC) as t
GROUP BY t.idx

You can use a correlated subquery in the ON clause. In the subquery you get the last (MAX) idx of messages from the thread.
SELECT hd.*, m.* -- select columns you need
FROM listhdthreads as hd
LEFT JOIN listhdmessages m ON m.idx = (
SELECT MAX(mmax.idx)
FROM listhdmessages mmax
WHERE mmax.parentThreadId = hd.idx
)
-- JOIN more tables
It is better and simpler to use the AUTO_INCREMENT column than createdAt. The fact that createdAt is not UNIQUE, and two messages from the same thread can be posted at the same second, is reason enough.

SELECT m.createdAt as latestMessage
FROM listhdthreads as hd
LEFT JOIN listhdmessages m ON hd.idx = m.parentThreadId
...
GROUP BY m.parentThreadId
ORDER BY m.createdAt DESC LIMIT 1
OR
SELECT hd.idx, m.createdAt as latestMessage
FROM listhdthreads as hd
LEFT JOIN listhdmessages m ON hd.idx = m.parentThreadId
...
GROUP BY hd.idx, m.createdAt
ORDER BY m.createdAt DESC LIMIT 1

Related

LEFT JOIN count from another table

I am trying to count how many transactions have been complete for a course, I am trying to left join the training_transactions with a count of all the rows where the training_transaction_course = course_id and where training_transaction_status = 'completed' Here's the code I have so far:
SELECT training.*,
Count(DISTINCT training_transactions.training_transaction_course) AS completed_training_payments
left JOIN users
ON training.course_user = users.user_id
LEFT JOIN training_transactions
ON training.course_user = training_transactions.training_transaction_user
FROM training
WHERE course_id = ?
AND training_transactions.training_transaction_status = 'complete'
AND course_enabled = 'enabled'
My tables:
training transactions
CREATE TABLE IF NOT EXISTS `training_transactions` (
`training_transaction_id` int(11) NOT NULL,
`training_transaction_user` int(11) NOT NULL,
`training_transaction_course` int(11) NOT NULL,
`training_transaction_status` varchar(50) NOT NULL,
`training_transaction_enabled` varchar(50) NOT NULL DEFAULT 'enabled',
`training_transaction_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
training
CREATE TABLE IF NOT EXISTS `training` (
`course_id` int(11) NOT NULL,
`course_user` int(11) NOT NULL,
`course_type` varchar(255) NOT NULL,
`course_name` varchar(255) NOT NULL,
`course_location` varchar(255) NOT NULL,
`course_duration` varchar(255) NOT NULL,
`course_fitness_type` varchar(255) NOT NULL,
`course_instructor_name` varchar(255) NOT NULL,
`course_price` int(15) NOT NULL,
`course_start_date` date NOT NULL,
`course_max_attendees` int(8) NOT NULL,
`course_accommodation` varchar(255) NOT NULL,
`course_accommodation_price` varchar(255) NOT NULL,
`course_status` varchar(50) NOT NULL,
`course_enabled` varchar(10) NOT NULL DEFAULT 'enabled'
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=latin1;
As you can see I am trying to get the count of completed transactions as a count to deduct from course_max_attendees, and then I can check if there's any places left.
You want to select trainings. So select from training. You want to show the transaction count with it, which you can do in a subquery in your select clause:
select
t.*,
(
select count(*)
from training_transactions tt
where tt.training_transaction_user = t.course_user
and tt.training_transaction_status = 'complete'
) as completed_training_payments
from training t
where t.course_id = ?
and t.course_enabled = 'enabled';
And here is the same with a join:
select
t.*, coalesce(tt.cnt, 0) as completed_training_payments
from training t
left join
(
select training_transaction_status, count(*) as cnt
from training_transactions
where training_transaction_status = 'complete'
group by training_transaction_status
) tt on tt.training_transaction_user = t.course_user
where t.course_id = ?
and t.course_enabled = 'enabled';
First, if you want to know how many completed transactions have taken place for each course, you can't get the User table involved. You will be aggregating away any user information.
Then, you must start with the course table, which looks like you have named training. Now you want to count every completed transaction for each course. A left join works just about perfectly for this:
select t.Name, count( * ) as completed_training_payments
from training t
left join training_transactions tt
on tt.user = t.course_user
and tt.status = 'complete'
where t.course_status = 'enabled'
group by t.Name;
The problem with this is that it will give a count value of "1" for every course with one completed transaction but also those with no completed transactions at all! So every row with a count of "1" would be suspect. The solution is to count keys, not rows. This is done with the sum rather than the count function.
select t.Name, sum( case when tt.course_user is null then 0 else 1 end ) as completed_training_payments
from training t
left join training_transactions tt
on tt.user = t.course_user
and tt.status = 'complete'
where t.course_status = 'enabled'
group by t.Name;
Since tt.course_user will only be null when there are no completed transactions at all, that course will show a "count" of "0".

How to improve sub query performance in MySQL

I have a CRM system that generates attributes using an EAV model. The problem as you may be very aware that EAV model require a complex queries to pull the data. Each attribute have to be returned in a separate column.
When using sub queries, MySQL performance sucks. I have to find a better way to write my queries by analyzing them using the giving where clause, sort order and the limit "if any"!
By sub query I am refereeing to a query that look like this
SELECT a.account_name, a.account_type, a.status, a.account_id, s.fieldValue, s2.last_training_on, s3.fieldValue
FROM accounts AS a
INNER JOIN clients AS c ON c.client_id = a.client_id
LEFT JOIN (
SELECT p.related_to AS account_id, decimal_value AS fieldValue
FROM df_answers_text AS p
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id
WHERE p.field_id = '19' AND r.client_id = '7';
) AS s ON s.account_id = a.account_id
LEFT JOIN (
SELECT p.related_to AS account_id, datetime_value AS last_training_on
FROM df_answers_text AS p
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id
WHERE p.field_id = '10' AND r.client_id = '7';
) AS s2 ON s2.account_id = a.account_id
LEFT JOIN (
SELECT
p.related_to
, CAST(GROUP_CONCAT(o.label SEPARATOR " | ") AS CHAR(255)) AS fieldValue
FROM df_answer_predefined AS p
INNER JOIN df_fields_options AS o ON o.option_id = p.option_id
INNER JOIN df_field_to_client_relation AS r ON r.field_id = o.field_id
WHERE o.is_place_holder = 0 AND o.field_id = '16' AND r.field_id = '16' AND r.client_id = '7'
GROUP BY p.related_to;
) AS s3 ON s3.related_to = a.account_id
WHERE c.client_id = '7' AND c.status = 'Active' AND ( a.account_type = 'TEST' OR a.account_type = 'VALUE' OR s2.last_training_on > '2015-01-01 00:00:00') AND (s.fieldValue = 'Medium' OR s.fieldValue = 'Low' OR a.expType = 'Very High')
ORDER BY a.account_name
LIMIT 500;
I thought about creating a temporary table using MEMORY engine with the content of the sub query like this
CREATE TEMPORARY TABLE s (KEY(account_id, fieldValue)) ENGINE = MEMORY
SELECT p.related_to AS account_id, decimal_value AS fieldValue
FROM df_answers_text AS p
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id
WHERE p.field_id = '19' AND r.client_id = '7';
CREATE TEMPORARY TABLE s2 (KEY(account_id, INDEX USING BTREE last_training_on)) ENGINE = MEMORY
SELECT p.related_to AS account_id, datetime_value AS last_training_on
FROM df_answers_text AS p
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id
WHERE p.field_id = '10' AND r.client_id = '7';
CREATE TEMPORARY TABLE s3 (KEY(related_to, fieldValue)) ENGINE = MEMORY
SELECT
p.related_to
, CAST(GROUP_CONCAT(o.label SEPARATOR " | ") AS CHAR(255)) AS fieldValue
FROM df_answer_predefined AS p
INNER JOIN df_fields_options AS o ON o.option_id = p.option_id
INNER JOIN df_field_to_client_relation AS r ON r.field_id = o.field_id
WHERE o.is_place_holder = 0 AND o.field_id = '16' AND r.field_id = '16' AND r.client_id = '7'
GROUP BY p.related_to;
CREATE TEMPORARY TABLE s3 (KEY(related_to)) ENGINE = MEMORY
SELECT
p.related_to
, CAST(GROUP_CONCAT(o.label SEPARATOR " | ") AS CHAR(255)) AS fieldValue
FROM df_answer_predefined AS p
INNER JOIN df_fields_options AS o ON o.option_id = p.option_id
INNER JOIN df_field_to_client_relation AS r ON r.field_id = o.field_id
WHERE o.is_place_holder = 0 AND o.field_id = '16' AND r.field_id = '16' AND r.client_id = '7'
GROUP BY p.related_to;
Then my new query will look like this
SELECT a.account_name, a.account_type, a.status, a.account_id, s.fieldValue, s2.last_training_on, s3.fieldValue
FROM accounts AS a
INNER JOIN clients AS c ON c.client_id = a.client_id
LEFT JOIN s ON s.account_id = a.account_id
LEFT JOIN s2 ON s2.account_id = a.account_id
LEFT JOIN s3 ON s2.related_to = a.account_id
WHERE c.client_id = '7' AND c.status = 'Active' AND ( a.account_type = 'TEST' OR a.account_type = 'VALUE' OR s2.last_training_on > '2015-01-01 00:00:00') AND (s.fieldValue = 'Medium' OR s.fieldValue = 'Low' OR a.expType = 'Very High')
ORDER BY a.account_name
LIMIT 500;
DROP TEMPORARY TABLE s, s2;
The problem that I am facing now of that the temporary table will create a temporary table of the entire data available in the database which consume lots of time. but my outer query is only looking for 500 records sorted by the a.account_name. If the temporary table has 1 million records that will be waste of time and obviously give me bad performance.
I am looking to find a better way to pass on the clause to the sub query so that way I would only create a temporary table with the needed data for the outer query
Note: these queries are generated dynamic using a GUI. I am unable to figure out how to extract the logic/clause and properly pass them to the sub query.
QUESTIONS
How can I look at the where clause, parse them and pass them to the sub query to refuse the amount of the data in the sub quires? If the call the clause where "AND" then my life will be easier but since I have a mix or "AND" and "OR" it is very complex.
Is there a better approach to this problem rather than using Temporary tables.
EDITED
Here are my table definitions
CREATE TABLE df_answer_predefined (
answer_id int(11) unsigned NOT NULL AUTO_INCREMENT,
field_id int(11) unsigned DEFAULT NULL,
related_to int(11) unsigned DEFAULT NULL,
option_id int(11) unsigned DEFAULT NULL,
created_by int(11) unsigned NOT NULL,
created_on datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (answer_id),
UNIQUE KEY un_row (field_id,option_id,related_to),
KEY field_id (field_id),
KEY related_to (related_to),
KEY to_delete (field_id,related_to),
KEY outter_view (field_id,option_id,related_to)
) ENGINE=InnoDB AUTO_INCREMENT=4946214 DEFAULT CHARSET=utf8;
`CREATE TABLE df_fields_options (
option_id int(11) unsigned NOT NULL AUTO_INCREMENT,
field_id int(11) unsigned NOT NULL,
label varchar(255) DEFAULT NULL,
is_place_holder tinyint(1) NOT NULL DEFAULT '0',
is_default tinyint(1) NOT NULL DEFAULT '0',
sort smallint(3) NOT NULL DEFAULT '1',
status tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (option_id),
KEY i (field_id),
KEY d (option_id,field_id,is_place_holder)
) ENGINE=InnoDB AUTO_INCREMENT=155 DEFAULT CHARSET=utf8;`
`CREATE TABLE df_field_to_client_relation (
relation_id int(11) unsigned NOT NULL AUTO_INCREMENT,
client_id int(11) unsigned DEFAULT NULL,
field_id int(11) unsigned DEFAULT NULL,
PRIMARY KEY (relation_id),
UNIQUE KEY unique_row (field_id,client_id),
KEY client_id (client_id),
KEY flient_id (field_id)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;`
`CREATE TABLE df_answers_text (
answer_id int(11) unsigned NOT NULL AUTO_INCREMENT,
notes varchar(20000) DEFAULT NULL,
datetime_value datetime DEFAULT NULL,
date_value date DEFAULT NULL,
us_phone_number char(10) DEFAULT NULL,
field_id int(11) unsigned DEFAULT NULL,
related_to int(11) unsigned DEFAULT NULL,
created_by int(11) unsigned NOT NULL,
created_on datetime DEFAULT CURRENT_TIMESTAMP,
modified_by int(11) DEFAULT NULL,
modified_on datetime DEFAULT NULL,
big_unsigned_value bigint(20) DEFAULT NULL,
big_signed_value bigint(19) DEFAULT NULL,
unsigned_value int(11) DEFAULT NULL,
signed_value int(10) DEFAULT NULL,
decimal_value decimal(18,4) DEFAULT NULL,
PRIMARY KEY (answer_id),
UNIQUE KEY unique_answer (field_id,related_to),
KEY field_id (field_id),
KEY related_to (related_to),
KEY big_unsigned_value (big_unsigned_value),
KEY big_signed_value (big_signed_value),
KEY unsigned_value (unsigned_value),
KEY signed_value (signed_value),
KEY decimal_Value (decimal_value)
) ENGINE=InnoDB AUTO_INCREMENT=2458748 DEFAULT CHARSET=utf8;`
The query that takes the most time is the third sub query with the alias s3
Here is the execution plan for the query that us taking long time "2 seconds"
UNIQUE(a,b,c)
INDEX (a)
DROP the INDEX, since the UNIQUE key is an INDEX and the INDEX is a prefix of the UNIQUE.
PRIMARY KEY(d)
UNIQUE(a,b,c)
Why have d at all? Simply say PRIMARY KEY(a,b,c).
FROM ( SELECT ... )
JOIN ( SELECT ... ) ON ...
optimizes poorly (until 5.6.6). Whenever possible turn JOIN ( SELECT ) into a JOIN with the table. As you suggested, using tmp tables may be better, if you can add a suitable index to the tmp table. Best is to try to avoid more than one "table" that is a subquery.
In a many-to-many relation table, don't include an id for the table, instead have only
PRIMARY KEY (a,b), -- for enforcing uniqueness, providing a PK, and going one direction
INDEX (b,a) -- for going the other way.
The EXPLAIN does not seem to match the SELECT you provided. Each is useless without the other.
Another approach that might help... Instead of
SELECT ..., s2.foo, ...
...
JOIN ( SELECT ... FROM x WHERE ... ) AS s2 ON s2.account_id = a.account_id
see if you can reformulate it as:
SELECT ...,
( SELECT foo FROM x WHERE ... AND related = a.account_id) AS foo, ...
...
That is, replace the JOIN subquery with a correlated subquery for the one value you need.
The bottom line is that the EAV model sucks.
Hmmm... I don't see the need for this at all, since r is not used elsewhere in he query...
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id
WHERE p.field_id = '19' AND r.client_id = '7'
It seems to be equivalent to
WHERE EXISTS ( SELECT * FROM df_field_to_client_relation
WHERE field_id = '19' AND client_id = '7' )
but why bother checking for existence?

MySQL: Count in other tables

in my MySQL database I have three tables:
CREATE TABLE favorites (
id int(11) NOT NULL AUTO_INCREMENT,
user_id int(11) NOT NULL,
location_id int(11) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE locations (
id int(20) NOT NULL,
`name` varchar(150) NOT NULL,
pos_lat float NOT NULL,
pos_lon float NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE ratings (
id int(11) NOT NULL AUTO_INCREMENT,
location_id int(11) NOT NULL,
user_id int(11) NOT NULL
stars int(11) NOT NULL,
review text,
PRIMARY KEY (id)
);
Now I want to select some of the locations and calculate the number of ratings, the average number of stars and the number of favorites in an efficient way.
My approach is this one but it gives me totally wrong values for the COUNTs.
SELECT l.id AS location_id,
COUNT(DISTINCT r.id), AVG(r.stars), COUNT(DISTINCT f.id)
FROM locations l, ratings r, favorites f
WHERE (l.id=r.location_id OR l.id=f.location_id)
AND l.id IN (7960,23713,...,18045,24247)
GROUP BY l.id
Can you help me?
The problem has to do with your join condition using OR:
WHERE (l.id=r.location_id OR l.id=f.location_id)
When it finds ONE record where l.id = r.location_id it will be true for ALL rows in f because of the OR. Similarly when it finds 1 record with l.id = f.location_id you will match ALL rows in r.
Instead, use a LEFT JOIN for each of r and f:
SELECT l.id AS location_id,
COUNT(DISTINCT r.id), AVG(r.stars), COUNT(DISTINCT f.id)
FROM locations l
LEFT JOIN ratings r ON (l.id = r.location_id)
LEFT JOIN favorites f ON (l.id = f.location_id)
WHERE l.id IN (7960,23713,...,18045,24247)
GROUP BY l.id

How would I avoid using two separate queries for this purpose?

I am using MySQL to create a database of articles and categories. Each article has a category. I would like to make a feature for the admin panel that lists all the categories, but also includes the latest article for each category. The method I usually use is to fetch rows from the category table, loop through the results, and then create another query using something like FROM articlesWHERE category_id = {CATEGORY_ID} ORDER BY article_id DESC LIMIT 1. That method just seems like overkill to me and I am wondering if it can be done in one query(Maybe with joins and subqueries?).
This is the current query I have that fetches categories:
SELECT * FROM article_categories ORDER BY category_title ASC
These are my tables:
CREATE TABLE IF NOT EXISTS `articles` (
`article_id` int(15) NOT NULL AUTO_INCREMENT,
`author_id` int(15) NOT NULL,
`category_id` int(15) NOT NULL,
`modification_id` int(15) NOT NULL,
`title` varchar(125) NOT NULL,
`content` text NOT NULL,
`type` tinyint(1) NOT NULL,
`date_posted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint(1) NOT NULL,
`attachment_id` int(15) NOT NULL,
`youtube_id` varchar(32) DEFAULT NULL,
`refs` text NOT NULL,
`platforms` varchar(6) NOT NULL,
PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `article_categories` (
`category_id` int(15) NOT NULL AUTO_INCREMENT,
`parent_id` int(15) NOT NULL,
`title` varchar(50) NOT NULL,
`description` text NOT NULL,
`attachment_id` text NOT NULL,
`enable_comments` tinyint(1) NOT NULL,
`enable_ratings` tinyint(1) NOT NULL,
`guest_reading` tinyint(1) NOT NULL,
`platform_assoc` tinyint(1) NOT NULL,
`allowed_types` varchar(6) NOT NULL,
PRIMARY KEY (`category_id`,`title`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
This is the query I have come up with so far:
SELECT
c.category_id, c.title, c.description,
a.article_id, a.category_id, a.title, COUNT(a.article_id) AS total_articles
FROM article_categories AS c
LEFT JOIN articles AS l ON (
SELECT
article_id AS article_id, category_id, title AS article_title
FROM articles AS l
WHERE l.category_id = c.category_id
ORDER BY l.article_id
DESC LIMIT 1)
LEFT JOIN articles AS a ON (c.category_id = a.category_id)
GROUP BY c.category_id
ORDER BY c.title ASC
The above query gives me the following SQL error:
Operand should contain 1 column(s)
Why is this happening?
You can return list of all the categories and recent article in each category using one query, Try this
SELECT C.*, A.*
FROM article_categories C
LEFT OUTER JOIN articles A ON c.category_id = A.category_id
WHERE
(
A.category_id IS NULL OR
A.article_id = (SELECT MAX(X.article_id)
FROM articles X WHERE X.category_id = C.category_id)
)
This will restrict the articles to just the highest article_id per category and make use of the indexes on those tables:
select
ac.category_id, ac.title, newa.article_id, newa.title article_title
from article_categories ac
left join articles newa on ac.category_id = newa.category_id
left join articles olda on newa.category_id = olda.category_id
and olda.article_id > newa.article_id
where olda.article_id is null
;
See this Demonstrated at SQLFiddle
Shoelace, I was browsing your other questions and saw that this was unresolved so I've decided to take a crack at it.
This is a little tricky, but I don't think it's too bad, assuming I understand your question correctly. First, get the latest article date for each category:
SELECT a.category_id, MAX(a.date_posted)
FROM articles a
JOIN article_categories c ON c.category_id = a.category_id
GROUP BY a.category_id;
Then, join that with your articles table on the condition that the category_id and date are equal and you have what you need:
SELECT ar.*
FROM articles ar
JOIN(SELECT a.category_id, MAX(a.date_posted) AS latestDateForCategory
FROM articles a
JOIN article_categories c ON c.category_id = a.category_id
GROUP BY a.category_id) t
ON t.category_id = ar.category_id AND t.latestDateForCategory = ar.date_posted;
SQL Fiddle.

select the max value and other values from three different tables

I have three tables with the following structure:-
CREATE TABLE `contract` (
`conid` int(11) NOT NULL AUTO_INCREMENT,
`servName` int(11) NOT NULL,
`cid` int(11) NOT NULL,
`term` int(11) DEFAULT NULL,
`monthly_charge` double NOT NULL,
`start_date` date NOT NULL,
`expiry_Date` date NOT NULL,
`next_PayDate` date DEFAULT NULL,
`status` tinyint(4) NOT NULL,
PRIMARY KEY (`conid`),
UNIQUE KEY `servName` (`servName`,`cid`)
)
CREATE TABLE `servicetype` (
`sid` int(11) NOT NULL AUTO_INCREMENT,
`serviceName` varchar(255) NOT NULL,
PRIMARY KEY (`sid`)
)
CREATE TABLE `transactions` (
`tid` int(11) NOT NULL AUTO_INCREMENT,
`conid` int(11) NOT NULL,
`amount` double NOT NULL,
`paidate` date NOT NULL,
`descr` text NOT NULL,
PRIMARY KEY (`tid`)
)
What I want to get is the latest transaction for a particular user i.e:-
conid, serviceName, cid, term, monthly_charge, start_date, expiry_Date, next_PayDate, amount, paidate, descr
And this is the select statement I am using the get to that information:-
SELECT c.conid, serviceName, cid, term, monthly_charge, start_date, expiry_Date, next_PayDate, status, amount, paidate, descr
FROM servicetype s
LEFT JOIN contract c on s.sid = c.servName
LEFT JOIN transactions t ON c.conid=t.conid
WHERE cid = 4 AND status = 1
The statement works but, it is giving me all transactions belonging to cid 4 and all I want to display is only the latest transaction belonging to the said contract it (conid).
Thanking you in advance for your time and effort.
Sounds like you want to use MySQL's GROUP BY to group all of the results by a specific cid, and then use a HAVING condition to get the MAX() transaction:
SELECT
c.conid, serviceName, cid, term, monthly_charge, start_date, expiry_Date, next_PayDate, status, amount, paidate, descr
FROM servicetype s
LEFT JOIN contract c on s.sid = c.servName
LEFT JOIN transactions t ON c.conid=t.conid
WHERE
cid = 4 AND status = 1
GROUP BY cid
HAVING t.paidate = MAX(t.paidate)
You can JOIN twice on the transactions table. The first join gets the max() date for each conid and then the second join will return the details of that max transaction:
select c.conid,
s.serviceName,
c.cid,
c.term,
c.monthly_charge,
c.start_date,
c.expiry_date,
c.next_PayDate,
c.status,
t2.amount,
t1.paidate,
t2.descr
FROM servicetype s
LEFT JOIN contract c
on s.sid = c.servName
LEFT JOIN
(
SELECT max(paidate) paidate, conid
FROM transactions
GROUP BY conid
) t1
ON c.conid=t1.conid
LEFT JOIN transactions t2
ON t1.paidate = t2.paidate
AND t1.conid = t2.conid
WHERE c.cid = 4
AND c.status = 1
Edit, based on your comments the query should be:
select c.conid,
s.serviceName,
c.cid,
c.term,
c.monthly_charge,
c.start_date,
c.expiry_date,
c.next_PayDate,
c.status,
t3.amount,
t3.paidate,
t3.descr
FROM servicetype s
LEFT JOIN contract c
on s.sid = c.servName
LEFT JOIN
(
SELECT max(paidate) paidate, conid, max(tid) tid
FROM transactions t
GROUP BY conid
) t1
on c.conid = t1.conid
LEFT JOIN transactions t3
on t1.conid = t3.conid
and t1.paidate = t3.paidate
and t1.tid = t3.tid
where c.cid = 4
and c.status = 1
GROUP BY c.conid;
See SQL Fiddle With Demo