select the max value and other values from three different tables - mysql

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

Related

How to get the last message in a thread SQL Query

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

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".

Select 3 tables with count and join

I've 3 tables tb1, users, users_credits.
My gol is to combine two select (sel1, sel2) into a single view and
display 0 in the sel2 where there isn't rows (left join?)
sel1
SELECT
users.userid,
users.datareg,
users_credits.credits,
FROM
users,
users_credits,
WHERE
users.userid = users_credits.userid
Sel2
SELECT COUNT(*) FROM tb1 where tb1.id_user = users.userid
table structure
tb1
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_user` decimal(11,0) NOT NULL,
`datains` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
users
`userid` int(4) unsigned NOT NULL AUTO_INCREMENT,
`datareg` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`userid`)
users_credits
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) NOT NULL,
`credits` decimal(5,0) NOT NULL,
`data` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
What is the best way to do this?
Thanks.
SELECT users.userid,
users.datareg,
users_credits.credits,
COALESCE(c.totalCount,0) totalCount
FROM users
LEFT JOIN users_credits
ON users.userid = users_credits.userid
LEFT JOIN
(
SELECT id_user, COUNT(*) totalCount
FROM tb1
GROUP BY id_user
) c ON c.id_user = users.userid
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
UPDATE 1
SELECT users.userid,
users.datareg,
users_credits.credits,
COALESCE(c.totalCount,0) totalCount,
c.max_datains
FROM users
LEFT JOIN users_credits
ON users.userid = users_credits.userid
LEFT JOIN
(
SELECT id_user, MAX(datains) max_datains, COUNT(*) totalCount
FROM tb1
GROUP BY id_user
) c ON c.id_user = users.userid
UPDATE 2
you need to create two views for this:
1st View:
CREATE VIEW tbl1View
AS
SELECT id_user, MAX(datains) max_datains, COUNT(*) totalCount
FROM tb1
GROUP BY id_user
2nd View
CREATE VIEW FullView
AS
SELECT users.userid,
users.datareg,
users_credits.credits,
COALESCE(c.totalCount,0) totalCount,
c.max_datains
FROM users
LEFT JOIN users_credits
ON users.userid = users_credits.userid
LEFT JOIN tbl1View c ON c.id_user = users.userid

Writing a JOIN query with two counts from joined table

I've spent a few hours fighting with this, but I can't get the counts to work. Hopefully someone can help?!
I have a project table and task table, linked on the project_id. I can get the project_id, project_name, and the status_id with the query below:
SELECT
a.project_id,
a.project_name,
b.status_id
FROM project_list as a
INNER JOIN task_list as b
ON a.project_id=b.project_id
I'd like to select a single record for each project and add two count fields based on the status_id. In pseudo code:
SELECT
a.project_id,
a.project_name,
(SELECT COUNT(*) FROM task_list WHERE status_id < 3) as not_completed,
(SELECT COUNT(*) FROM task_list WHERE status_id = 3) as completed
FROM project_list as a
INNER JOIN task_list as b
ON a.project_id=b.project_id
GROUP BY project_id
My create table scripts are below:
CREATE TABLE `project_list` (
`project_id` int(11) NOT NULL AUTO_INCREMENT,
`topic_id` int(11) DEFAULT NULL,
`project_name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`project_id`)
)
CREATE TABLE `task_list` (
`task_id` int(11) NOT NULL AUTO_INCREMENT,
`project_id` int(11) DEFAULT NULL,
`task_name` varchar(45) DEFAULT NULL,
`status_id` int(11) DEFAULT '0',
PRIMARY KEY (`task_id`)
)
Any help is much appreciated. Thanks!
EDIT: ANSWER:
SELECT
a.project_id,
project_name,
SUM(status_id != 3) AS not_completed,
SUM(status_id = 3) AS completed,
SUM(status_id IS NOT NULL) as total
FROM tasks.project_list as a
INNER JOIN tasks.task_list as b
ON a.project_id=b.project_id
GROUP BY a.project_id
The problem is that in your subqueries you are counting all the rows in the whole table rather than just the rows that have the correct project_id. You could fix this by modifying the WHERE clause in each of your subqueries.
(SELECT COUNT(*)
FROM task_list AS c
WHERE c.status_id < 3
AND a.project_id = c.project_id)
However a simpler approach is to use SUM with a boolean condition instead of COUNT to count the rows that match the condition:
SELECT
a.project_id,
a.project_name,
SUM(b.status_id < 3) AS not_completed,
SUM(b.status_id = 3) AS completed,
FROM project_list as a
INNER JOIN task_list as b
ON a.project_id = b.project_id
GROUP BY project_id
This works because TRUE evaluates to 1 and FALSE evaluates to 0.

sql join on two fields in one table

i have bookings table which has two people- i want to return person_1 as a row, person_2 as a new row but with the person's id related to the people table
This is as far as i got-but doesnt pull in booking info
SELECT people.* FROM (
(select booking.person_1 as id from booking)
union ALL
(select booking.person_2 as id from booking)
) as peopleids
join people on people.id = peopleids.id;
heres my structure
CREATE TABLE IF NOT EXISTS `booking` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`slot` enum('morning_drive','afternoon_loop','return_drive') NOT NULL,
`type` enum('911','vintage_911') NOT NULL,
`car` int(11) NOT NULL,
`person_1` int(11) DEFAULT NULL,
`person_2` int(11) DEFAULT NULL,
`dated` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
CREATE TABLE IF NOT EXISTS `people` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(50) NOT NULL,
`last_name` varchar(50) NOT NULL,
`organisation` varchar(100) NOT NULL,
`event_date` date NOT NULL,
`wave` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;
any ideas on how i could get a result set like- person.first_name, person.last_name, person.organisation, booking.dated, person.car, person.slot. im struggling with having two fields and having them to relate them into the one list
update for anyone interested in this and joining a 3rd table
heres my final query with php vars to pull in my certain dates and slots and also join a third table
SELECT peopleids.id,
peopleids.car,
cars.nr,
p.first_name,
p.last_name,
p.organisation,
p.event_date,
p.wave
FROM (SELECT booking.car, booking.person_1 as id FROM booking WHERE booking.dated = '".$date."' AND booking.`slot` = '".$slot."'
union ALL SELECT booking.car, booking.person_2 as id FROM booking WHERE booking.dated = '".$date."' AND booking.`slot` = '".$slot."'
) as peopleids
LEFT JOIN people p ON p.id = peopleids.id LEFT JOIN cars on cars.id = peopleids.car;
SELECT
ag.id,
p.first_name,
p.last_name,
p.organisation,
p.event_date,
p.wave
FROM (
SELECT booking.person_1 as id, booking.Car as car FROM booking
union ALL
SELECT booking.person_2 as id, booking.Car as car FROM booking
) as ag
JOIN people p ON people.id = ag.id;
INNER | LEFT JOIN Cars c ON c.ID = ag.car
select people.first_name as firstname,
people.last_name as lastname,
people.organisation,
booking.dated,
booking.car,
booking.slot from booking
left join people on booking.person_1 = people.id
OR
select people.first_name as firstname,
people.last_name as lastname,
people.organisation,
booking.dated,
booking.car,
booking.slot
from booking
left join people on booking.person_1 = people.id or booking.person_2 = people.id
check that...if you still need help will help you