MysqL big table query optimization - mysql

I have a chatting application. I have an api which returns list of users who the user talked. But it takes a long to mysql return a list messages when it reachs 100000 rows of data.
This is my messages table
CREATE TABLE IF NOT EXISTS `messages` (
`_id` int(11) NOT NULL AUTO_INCREMENT,
`fromid` int(11) NOT NULL,
`toid` int(11) NOT NULL,
`message` text NOT NULL,
`attachments` text NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`date` datetime NOT NULL,
`delete` varchar(50) NOT NULL,
`uuid_read` varchar(250) NOT NULL,
PRIMARY KEY (`_id`),
KEY `fromid` (`fromid`,`toid`,`status`,`delete`,`uuid_read`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=118561 ;
and this is my users table (simplified)
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`login` varchar(50) DEFAULT '',
`sex` tinyint(1) DEFAULT '0',
`status` varchar(255) DEFAULT '',
`avatar` varchar(30) DEFAULT '0',
`last_active` datetime DEFAULT NULL,
`active` tinyint(1) DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=15523 ;
And here is my query (for user with id 1930)
select SQL_CALC_FOUND_ROWS `u_id`, `id`, `login`, `sex`, `birthdate`, `avatar`, `online_status`, SUM(`count`) as `count`, SUM(`nr_count`) as `nr_count`, `date`, `last_mesg` from
(
(select `m`.`fromid` as `u_id`, `u`.`id`, `u`.`login`, `u`.`sex`, `u`.`birthdate`, `u`.`avatar`, `u`.`last_active` as online_status, COUNT(`m`.`_id`) as `count`, (COUNT(`m`.`_id`)-SUM(`m`.`status`)) as `nr_count`, `tm`.`date` as `date`, `tm`.`message` as `last_mesg` from `messages` as m inner join `messages` as tm on `tm`.`_id`=(select MAX(`_id`) from `messages` as `tmz` where `tmz`.`fromid`=`m`.`fromid`) left join `users` as u on `u`.`id`=`m`.`fromid` where `m`.`toid`=1930 and `m`.`delete` not like '%1930;%' group by `u`.`id`)
UNION
(select `m`.toid as `u_id`, `u`.`id`, `u`.`login`, `u`.`sex`, `u`.`birthdate`, `u`.`avatar`, `u`.`last_active` as online_status, COUNT(`m`.`_id`) as `count`, 0 as `nr_count`, `tm`.`date` as `date`, `tm`.`message` as `last_mesg` from `messages` as m inner join `messages` as tm on `tm`.`_id`=(select MAX(`_id`) from `messages` as `tmz` where `tmz`.`toid`=`m`.`toid`) left join `users` as u on `u`.`id`=`m`.`toid` where `m`.`fromid`=1930 and `m`.`delete` not like '%1930;%' group by `u`.`id`)
order by `date` desc ) as `f` group by `u_id` order by `date` desc limit 0,10
Please help to optimize this query
What I need,
Who user talked to (name, sex, and etc)
What was the last message (from me or to me)
Count of messages (all)
Count of unread messages (only to me)
The query works well, but takes too long.
The output must be like this

You have some design problems on your query and database.
You should avoid keywords as column names, as that delete column or the count column;
You should avoid selecting columns not declared in the group by without an aggregation function... although MySQL allows this, it's not a standard and you don't have any control on what data will be selected;
Your not like construction may cause a bad behavior on your query because '%1930;%' may match 11930; and 11930 is not equal to 1930;
You should avoid like constructions starting and ending with % wildcard, which will cause the text processing to take longer;
You should design a better way to represent a message deletion, probably a better flag and/or another table to save any important data related with the action;
Try to limit your result before the join conditions (with a derived table) to perform less processing;
I tried to rewrite your query the best way I understood it. I've executed my query in a messages table with ~200.000 rows and no indexes and it performed in 0,15 seconds. But, for sure you should create the right indexes to help it perform better when the amount of data increase.
SELECT SQL_CALC_FOUND_ROWS
u.id,
u.login,
u.sex,
u.birthdate,
u.avatar,
u.last_active AS online_status,
g._count,
CASE WHEN m.toid = 1930
THEN g.nr_count
ELSE 0
END AS nr_count,
m.`date`,
m.message AS last_mesg
FROM
(
SELECT
MAX(_id) AS _id,
COUNT(*) AS _count,
COUNT(*) - SUM(m.status) AS nr_count
FROM messages m
WHERE 1=1
AND m.`delete` NOT LIKE '%1930;%'
AND
(0=1
OR m.fromid = 1930
OR m.toid = 1930
)
GROUP BY
CASE WHEN m.fromid = 1930
THEN m.toid
ELSE m.fromid
END
ORDER BY MAX(`date`) DESC
LIMIT 0, 10
) g
INNER JOIN messages AS m ON 1=1
AND m._id = g._id
LEFT JOIN users AS u ON 0=1
OR (m.fromid <> 1930 AND u.id = m.fromid)
OR (m.toid <> 1930 AND u.id = m.toid)
ORDER BY m.`date` DESC
;

Related

Optimizing a query for loading message history in a chat app

I have 2 tables, which are a users table, and a messages table
`users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(35) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=859312 DEFAULT CHARSET=utf8
`messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sender_id` int(11) NOT NULL,
`receiver_id` int(11) NOT NULL,
`message` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `by_sender_id_and_receiver_id` (`sender_id`,`receiver_id`),
KEY `by_sender_id` (`sender_id`),
KEY `by_receiver_id` (`receiver_id`)
) ENGINE=MyISAM AUTO_INCREMENT=56762871 DEFAULT CHARSET=latin1
When a user (whose user id is 108) loads their chat history, I am currently using the following query to list all the people that user has messaged, ordered by most recent.
SELECT u.username, m.sender_id, m.receiver_id, m.date
FROM messages m
JOIN users u ON ( u.id = m.sender_id
AND m.receiver_id = 108
OR u.id = m.receiver_id
AND m.sender_id = 108 )
GROUP BY u.id
ORDER BY m.date DESC
When I use EXPLAIN, I get the following results
I am wondering if there are any obvious ways to optimize this query, whether it is by altering indexes or rewriting the query itself. My messages table has over 50 million rows.
(from Comment) The GROUP BY is to only select the last message from each user.
The OR criterion in the join is a real performance killer. One way to workaround this would be to phrase the query using a union:
SELECT u.username, m.sender_id, m.receiver_id, m.date
FROM messages m
INNER JOIN users u ON u.id = m.receiver_id
WHERE m.sender_id = 108
UNION ALL
SELECT u.username, m.receiver_id, m.sender_id, m.date
FROM messages m
INNER JOIN users u ON u.id = m.sender_id
WHERE m.receiver_id = 108;
The above query can be optimized by adding the following indices to the messages table:
CREATE INDEX msg_idx_1 ON messages (sender_id, receiver_id, date);
CREATE INDEX msg_idx_2 ON messages (receiver_id, sender_id, date);
These indices should speed up the joins in the two halves of the union query above.
Note that I dropped the GROUP BY clause, which wasn't doing anything and also seemed not needed.

How to get NULL at the top of the sort?

Two tables are defined:
CREATE TABLE `users` (
`user_id` mediumint(6) unsigned NOT NULL AUTO_INCREMENT,
`score` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`user_id`)
);
CREATE TABLE `online` (
`user_id` mediumint(6) unsigned NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
PRIMARY KEY (`user_id`)
);
How to combine the tables so that the result would be sorted by the score field from the largest to the smallest but at the top there were records with the value NULL?
This query does not sort the second sample:
(SELECT * FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NULL)
UNION
(SELECT * FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NOT NULL ORDER BY `score` DESC)
Use two keys in the sort:
SELECT *
FROM `online` o JOIN
`users`
USING (user_id)
ORDER BY (`score` IS NULL) DESC, Score DESC;
MySQL treats booleans as numbers in a numeric context, with "1" for true and "0" for false. So, DESC puts the true values first.
Incidentally, your version would look like it works if you used UNION ALL rather than UNION. However, it is not guaranteed that the results are in any particular order unless you explicitly have an ORDER BY.
The UNION incurs overhead for removing duplicates and in doing so rearranges the data.
Try:
select * from online join users using (user_id) order by ifnull(score, 10) desc;
You can use order by Nulls Last in the end of your sql to show nulls on the first.
You can try below -
select * from
(
SELECT *,1 as ord FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NULL
UNION
SELECT *,2 FROM `online` JOIN `users` USING(`user_id`) WHERE `score` IS NOT NULL
)A ORDER BY ord asc,`score` DESC

Improve speed of MySQL query with 5 left joins

Working on a support ticketing system with not a lot of tickets (~3,000). To get a summary grid of ticket information, there are five LEFT JOIN statements on custom field table (j25_field_value) containing about 10,000 records. The query runs too long (~10 seconds) and in cases with a WHERE clause, it runs even longer (up to ~30 seconds or more).
Any suggestions for improving the query to reduce the time to run?
Four tables:
j25_support_tickets
CREATE TABLE `j25_support_tickets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL DEFAULT '0',
`user_id` int(11) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`subject` varchar(255) DEFAULT NULL,
`message` text,
`modified_date` datetime DEFAULT NULL,
`priority_id` tinyint(3) unsigned DEFAULT NULL,
`status_id` tinyint(3) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3868 DEFAULT CHARSET=utf8
j25_support_priorities
CREATE TABLE `j25_support_priorities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
j25_support_statuses
CREATE TABLE `j25_support_statuses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
j25_field_value (id, ticket_id, field_id, field_value)
CREATE TABLE `j25_support_field_value` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ticket_id` int(11) DEFAULT NULL,
`field_id` int(11) DEFAULT NULL,
`field_value` tinytext,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=10889 DEFAULT CHARSET=utf8
Also, ran this:
SELECT LENGTH(field_value) len FROM j25_support_field_value ORDER BY len DESC LIMIT 1
note: the result = 38
The query:
SELECT DISTINCT t.id as ID
, (select p.title from j25_support_priorities p where p.id = t.priority_id) as Priority
, (select s.title from j25_support_statuses s where s.id = t.status_id) as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
ALTER TABLE j25_support_field_value
ADD INDEX (`ticket_id`,`field_id`,`field_value`(50))
This index will work as a covering index for your query. It will allow the joins to use only this index to look up the values. It should perform massively faster than without this index, since currently your query would have to read every row in the table to find what matches each combination of ticket_id and field_id.
I would also suggest converting your tables to InnoDB engine, unless you have a very explicit reason for using MyISAM.
ALTER TABLE tablename ENGINE=InnoDB
As above - a better index would help. You could probably then simplify your query into something like this too (join to the table only once):
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, case when v.field_id=1 then v.field_value else null end as IssueType
, case when v.field_id=2 then v.field_value else null end as Version
, case when v.field_id=3 then v.field_value else null end as UserType
, case when v.field_id=4 then v.field_value else null end as Company
, case when v.field_id=5 then v.field_value else null end as RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value v ON t.id = v.ticket_id
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
You can do away with the subqueries for starters and just get them from another join. You can add an index to j25_support_field_value
alter table j25_support_field_value add key(id, field_type);
I assume there is an index on id in j25_support_tickets - if not and if they are unique, add a unique index alter table j25_support_tickets add unique key(id); If they're not unique, remove the word unique from that statement.
In MySQL, a join usually requires an index on the field(s) that you are using to join on. This will hold up and produce very reasonable results with huge tables (100m+), if you follow that rule, you will not go wrong.
are the ids in j25_support_tickets unique? If they are you can do away with the distinct - if not, or if you are getting exact dupicates in each row, still do away with the distinct and add a group by t.id to the end of this:
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
Switch to InnoDB.
After switching to InnoDB, make the PRIMARY KEY for j25_support_field_value be (ticket_id, field_id) (and get rid if id). (Tacking on field_value(50) will hurt, not help.)
A PRIMARY KEY is a UNIQUE KEY, so don't have both.
Use VARCHAR(255) instead of the nearly-equivalent TINYTEXT.
EAV schema sucks. My ran on EAV.

How to join two tables without messing up the query

I have this query for example (good, it works how I want it to)
SELECT `discusComments`.`memberID`, COUNT( `discusComments`.`memberID`) AS postcount
FROM `discusComments`
GROUP BY `discusComments`.`memberID` ORDER BY postcount DESC
Example Results:
memberid postcount
3 283
6 230
9 198
Now I want to join the memberid of the discusComments table with that of the discusTopic table (because what I really want to do is only get my results from a specific GROUP, and the group id is only in the topic table and not in the comment one hence the join.
SELECT `discusComments`.`memberID`, COUNT( `discusComments`.`memberID`) AS postcount
FROM `discusComments`
LEFT JOIN `discusTopics` ON `discusComments`.`memberID` = `discusTopics`.`memberID`
GROUP BY `discusComments`.`memberID` ORDER BY postcount DESC
Example Results:
memberid postcount
3 14789
6 8678
9 6987
How can I stop this huge increase happening in the postcount? I need to preserve it as before.
Once I have this sorted I want to have some kind of line which says WHERE discusTopics.groupID = 6, for example
CREATE TABLE IF NOT EXISTS `discusComments` (
`id` bigint(255) NOT NULL auto_increment,
`topicID` bigint(255) NOT NULL,
`comment` text NOT NULL,
`timeStamp` bigint(12) NOT NULL,
`memberID` bigint(255) NOT NULL,
`thumbsUp` int(15) NOT NULL default '0',
`thumbsDown` int(15) NOT NULL default '0',
`status` int(1) NOT NULL default '1',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=7190 ;
.
CREATE TABLE IF NOT EXISTS `discusTopics` (
`id` bigint(255) NOT NULL auto_increment,
`groupID` bigint(255) NOT NULL,
`memberID` bigint(255) NOT NULL,
`name` varchar(255) NOT NULL,
`views` bigint(255) NOT NULL default '0',
`lastUpdated` bigint(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `groupID` (`groupID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=913 ;
SELECT `discusComments`.`memberID`, COUNT( `discusComments`.`memberID`) AS postcount
FROM `discusComments`
JOIN `discusTopics` ON `discusComments`.`topicID` = `discusTopics`.`id`
GROUP BY `discusComments`.`memberID` ORDER BY postcount DESC
Joining the topicid in both tables solved the memberID issue. Thanks #Andiry M
You need to use just JOIN not LEFT JOIN and you can add AND discusTopics.memberID = 6 after ON discusComments.memberID = discusTopics.memberID
You can use subqueries lik this
SELECT `discusComments`.`memberID`, COUNT( `discusComments`.`memberID`) AS postcount
FROM `discusComments` where `discusComments`.`memberID` in
(select distinct memberid from `discusTopics` WHERE GROUPID = 6)
If i understand your question right you do not need to use JOIN here at all. JOINs are needed in case when you have many to many relationships and you need for each value in one table select all corresponding values in another table.
But here you have many to one relationship if i got it right. Then you can simply do select from two tables like this
SELECT a.*, b.id FROM a, b WHERE a.pid = b.id
This is simple request and won't create a giant overhead as JOIN does
PS: In the future try to experiment with your queries, try to avoid JOINs especially in MySQL. They are slow and dangerous in their complexity. For 90% of cases when you want to use JOIN there is simple and much faster solution.

MySQL joins involving aggregate data

What I require is a SQL query which can report on data from aggregate and singular tables. The current database I have is as follows.
CREATE TABLE IF NOT EXISTS `faults_days` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`employee_id` int(11) NOT NULL,
`day_date` date NOT NULL,
`actioned_calls_out` int(11) NOT NULL,
`actioned_calls_in` int(11) NOT NULL,
`actioned_tickets` int(11) NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_departments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`team_id` int(11) NOT NULL,
`name` varchar(127) NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_qos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`qos_date` datetime NOT NULL,
`employee_id` int(11) NOT NULL,
`score` double NOT NULL,
)
CREATE TABLE IF NOT EXISTS `faults_teams` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`department_id` int(11) NOT NULL,
`name` varchar(40) NOT NULL,
)
A row in Day tracks a single employee's performance for a single day (number of calls taken, number of tickets actioned). A Qos is a measure of an employee's quality on a day (there can be more than one Qos per day - what I need to obtain is the average score). Also, a Qos can be performed on a day where the employee has no performance entry in the database, and this will still need to be shown on the report.
The required end result is 4 reports, which show the employee performance grouped by different columns. A breakdown of a single employee's performance per day, an employee's total performance over a period of time, a team's performance over a period of time, and a whole department's performance over a period of time.
My problem, is that my current queries are a little convoluted, and require two separate queries for the Day data, and the Qos data. My PHP application then combines the data before outputting the report. What I would like, is a single query which returns both total performance, and average quality scores.
The current queries I have to show employee performances are:
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name` ,
SUM( `Day`.`actioned_calls_in` ) + SUM( `Day`.`actioned_calls_out` ) ,
SUM( `Day`.`actioned_tickets` )
FROM
`faults_days` AS `Day`
JOIN
`faults_employees` AS `Employee` ON `Day`.`employee_id` = `Employee`.`id`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
WHERE
`Day`.`day_date` >= '2011-06-01'
AND `Day`.`day_date` <= '2011-06-07'
GROUP BY `Employee`.`id`
WITH ROLLUP
and
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name` ,
COUNT( `Qos`.`score` ) ,
AVG( `Qos`.`score` )
FROM
`faults_qos` AS `Qos`
JOIN
`faults_employees` AS `Employee` ON `Qos`.`employee_id` = `Employee`.`id`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
WHERE
`Qos`.`qos_date` >= '2011-06-01'
AND `Qos`.`qos_date` <= '2011-06-07'
GROUP BY `Employee`.`id`
WITH ROLLUP
I have also tried simply joining the Qos table, but because it returns multiple rows it messes up the SUM() totals, and also has problems due to the missing FULL OUTER JOIN functionality.
EDIT:
I've made some small progress with this. It looks like using subqueries is the way to go, but everything I'm doing is pure guesswork. Here's what I've got so far, its only showing a row if there's an entry in both the Day and Qos tables, which is not what I want, and I've no idea how to expand it to include the various groupings described above.
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name`,
`Day`.`Calls`,
`Day`.`Tickets`,
`Qos`.`NumQos`,
`Qos`.`Score`
FROM `faults_employees` AS `Employee`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
JOIN
(SELECT
`Day`.`employee_id` AS `eid`,
SUM(`Day`.`actioned_calls_in`) + SUM(`Day`.`actioned_calls_out`) AS `Calls`,
SUM(`Day`.`actioned_tickets`) AS `Tickets`
FROM `faults_days` AS `Day`
WHERE
`Day`.`day_date` = '2011-03-02'
GROUP BY `Day`.`employee_id`
) AS `Day`
ON `Day`.`eid` = `Employee`.`id`
JOIN
(SELECT
`Qos`.`employee_id` AS qid,
COUNT(`Qos`.`id`) AS `NumQos`,
AVG(`Qos`.`score`) AS `Score`
FROM `faults_qos` AS `Qos`
WHERE
`Qos`.`qos_date` = '2011-03-02'
GROUP BY `Qos`.`employee_id`
) AS `Qos`
ON `Qos`.`qid` = `Employee`.`id`
GROUP BY `Employee`.`id`
You do want the left joins on the fault_qos and fault_days subqueries. That's what will give you a result even if there isn't a corresponding row in one or both. A left join says that the value is necessary in the table(s) to the left of the join that are involved in the join but not the one on the right. I haven't tested this, and it's late, so I might not be thinking clearly, but if you change your query to this it should work:
SELECT
`Employee`.`name` ,
`Team`.`name` ,
`Department`.`name`,
`Day`.`Calls`,
`Day`.`Tickets`,
`Qos`.`NumQos`,
`Qos`.`Score`
FROM `faults_employees` AS `Employee`
JOIN
`faults_teams` AS `Team` ON `Employee`.`team_id` = `Team`.`id`
JOIN
`faults_departments` AS `Department` ON `Team`.`department_id` = `Department`.`id`
LEFT JOIN
(SELECT
`Day`.`employee_id` AS `eid`,
SUM(`Day`.`actioned_calls_in`) + SUM(`Day`.`actioned_calls_out`) AS `Calls`,
SUM(`Day`.`actioned_tickets`) AS `Tickets`
FROM `faults_days` AS `Day`
WHERE
`Day`.`day_date` = '2011-03-02'
GROUP BY `Day`.`employee_id`
) AS `Day`
ON `Day`.`eid` = `Employee`.`id`
LEFT JOIN
(SELECT
`Qos`.`employee_id` AS qid,
COUNT(`Qos`.`id`) AS `NumQos`,
AVG(`Qos`.`score`) AS `Score`
FROM `faults_qos` AS `Qos`
WHERE
`Qos`.`qos_date` = '2011-03-02'
GROUP BY `Qos`.`employee_id`
) AS `Qos`
ON `Qos`.`qid` = `Employee`.`id`
GROUP BY `Employee`.`id`