how to optimize my mysql query - mysql

I have two tables: vehicle_c2c_car_source and vehicle_c2c_appoint_task.
The structure of vehicle_c2c_car_source:
/********************************/
`id` int(11) NOT NULL AUTO_INCREMENT
`title` varchar(200) NOT NULL DEFAULT
/******************************/
vehicle_c2c_appoint_task
/******************************/
`id` int(11) unsigned NOT NULL AUTO_INCREMENT
`car_source_id` int(11) NOT NULL DEFAULT '0'
`status` tinyint(4) NOT NULL DEFAULT '0'
The value of status can be 0,1,2. one car_source_id can have many status.
The first goal is to get all the car_source that the amount of status records is less than 3.
The second goal is to get the amount of the status records when status is equal to 1.
The output should something like that:
22222(id of status records) 1111(car_source_id) title(title) 4(amount of the status records when status = 1)
My current solution is first to get all the car_source that meet the first goal and then query vehicle_c2c_appoint_task table in a loop to accomplish the second goal.
SELECT cs.id, pt.car_source_id, cs.title,
FROM vehicle_c2c_car_source AS cs
JOIN vehicle_c2c_appoint_task AS pt ON cs.id = pt.car_source_id
WHERE 1
AND pt.appoint_status NOT IN (2,6)
GROUP BY pt.car_source_id HAVING count(pt.car_source_id) < 3
In the loop:
SELECT count(*) as count
FROM vehicle_c2c_appoint_task
WHERE status = 1
group by car_source_id
I think it is not a good idea to put the query in a loop. How can i get all the value in one query?

Your question is hard to follow. But you can combine the two queries by using conditional aggregation, both in the SELECT and the HAVING clause:
SELECT cs.id, pt.car_source_id, cs.title, SUM(status = 1) as cnt
FROM vehicle_c2c_car_source cs JOIN
vehicle_c2c_appoint_task pt
ON cs.id = pt.car_source_id
GROUP BY pt.car_source_id
HAVING SUM(pt.appoint_status NOT IN (2, 6)) < 3;

Related

mysql query to select all objectives of a goal and those objective which are done

goal id total occurance of goal id total occurance when status is 1
1 5 3
This is schema of the table
CREATE TABLE `goal_objectives` (
`objective_id` int(11) NOT NULL ,
`objective_name` varchar(255) NOT NULL,
`objective_description` tinytext NOT NULL,
`goal_id` int(11) NOT NULL,
`objective_status` tinyint(4) NOT NULL
);
select goal_id, count(objective_status)as objective_done
from goal_objectives
where objective_status='1' group by goal_id;
select goal_id,count(goal_id) as total_current_goals
from goal_objectives
group by goal_id
order by goal_id DESC ;
I just want to show the combine result of these two queries.
Individually it returns required result but when i try to merge them is does not work
See the output in the link below:
https://i.imgur.com/6Rnac89.png
Use conditional aggregation:
select goal_id, count(*) as total_current_goals,
sum( objective_status = 1 ) as objective_done
from goal_objectives
group by goal_id
order by goal_id desc ;
Note that objective_status is a number. The comparison value should be a number, not a string.

why is my mysql select returning an empty resultset?

I'm stuck on a MySQL query and don't know what I'm doing wrong (using MySQL 5.0.88).
Here is my Query:
SELECT b.some, b.thing, bt.else AS liefdatum_gewuenscht, bt.bar
FROM btable AS b
LEFT JOIN bschedule AS bt
ON bt.key= b.key
AND bt.type = b.type
WHERE
b.entry_from IN (1,2,3,4)
AND b.`status` = "2"
AND b.date >= 20120718
LIMIT 1,10
The query should return a single record, but the check for b.status causes an empty response, although the record in question has a status of 2 with 2 being a CHARfield.
If I comment out the line it returns the correct record, however I would like to know what I'm doing wrong?
Can anyone give me hint?
Thanks!
EDIT
After some more testing I think it b.status isn't the problem, because if I use any other column and value from the record in question it also return nothing.
E.g. the record will have columns
==== no ===== written_by =====
1111 john smith
so putting in:
AND b.no = 1111 OR b.written_by = "john smith"
also return an empty resultset.
EDIT2
Here is a sample dataset and the query. I left out the LEFT JOIN because it doesn't make any difference:
SELECT b.*
FROM bk AS b
WHERE 1
AND b.entry_from IN ( 9900000002985 )
AND b.`status` = "2 "
AND b.bestelldatum >= 20120718
LIMIT 1,10
Table:
CREATE TABLE `bk` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`entry_from` VARCHAR(13) NULL DEFAULT NULL,
`placed_at` VARCHAR(13) NULL DEFAULT NULL,
`type` VARCHAR(2) NULL DEFAULT 'SF',
`no` VARCHAR(35) NULL DEFAULT NULL,
`date` DATE NULL DEFAULT NULL,
`written_by` VARCHAR(35) NULL DEFAULT 'Händler',
`status` VARCHAR(3) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;
hm... how to paste a record... this is the record in question
id = 985
entry_from = 9900000002985
placed_at = 9900000003005
type = SF
no = 11
date = 2012-07-19
written_by "Fachhändler"
status = 2
Try LIMIT 0,10 instead. That should work.
Since it's a CHAR type comparison, you may want to use LIKE function or STRCMP function instead of = operator.
I think problem is with your b.date field value comparison.
If the data type of b.date field is date then you should compare like:
AND b.date >= '2012-07-18'
Otherwise just surround the value in single quotes.
AND b.date >= '20120718'
Try changing it and see the result.

MYSQL SELECT 2 counts on same field

Imagine this db structure:
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`correct` tinyint(1) NOT NULL DEFAULT '0'
I want to get the count of all entries with correct = 1, and the count of all entries with correct = 0, in one query.
How do I do this?
Using GROUP BY should solve the problem:
SELECT correct, COUNT(*) FROM table GROUP BY correct;
select count(case when correct = 0 then 1 end) as ZeroCount,
count(case when correct = 1 then 1 end) as OneCount
from MyTable
If you want the counts in one row:
SELECT SUM(correct=0) as number_of_zeros,SUM(correct=1) as number_of_ones
FROM table;
If you want them in multiple rows:
SELECT correct,COUNT(*)
FROM table
GROUP BY correct;

MySQL: Can I do a left join and pull only one row from the join table?

I wrote a custom help desk for work and it's been running great... until recently. One query has really slowed down. It takes about 14 seconds now! Here are the relevant tables:
CREATE TABLE `tickets` (
`id` int(11) unsigned NOT NULL DEFAULT '0',
`date_submitted` datetime DEFAULT NULL,
`date_closed` datetime DEFAULT NULL,
`first_name` varchar(50) DEFAULT NULL,
`last_name` varchar(50) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`description` text,
`agent_id` smallint(5) unsigned NOT NULL DEFAULT '1',
`status` smallint(5) unsigned NOT NULL DEFAULT '1',
`priority` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `date_closed` (`date_closed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `solutions` (
`id` int(10) unsigned NOT NULL,
`ticket_id` mediumint(8) unsigned DEFAULT NULL,
`date` datetime DEFAULT NULL,
`hours_spent` float DEFAULT NULL,
`agent_id` smallint(5) unsigned DEFAULT NULL,
`body` text,
PRIMARY KEY (`id`),
KEY `ticket_id` (`ticket_id`),
KEY `date` (`date`),
KEY `hours_spent` (`hours_spent`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
When a user submits a ticket, it goes into the "tickets" table. Then, as the agents work through the problem, they record the actions they took. Each entry goes into the "solutions" table. In other words, tickets have many solutions.
The goal of the query that has slowed down is to pull all the fields from the "tickets" table and also the latest entry from the "solutions" table. This is the query I've been using:
SELECT tickets.*,
(SELECT CONCAT_WS(" * ", DATE_FORMAT(solutions.date, "%c/%e/%y"), solutions.hours_spent, CONCAT_WS(": ", solutions.agent_id, solutions.body))
FROM solutions
WHERE solutions.ticket_id = tickets.id
ORDER BY solutions.date DESC, solutions.id DESC
LIMIT 1
) AS latest_solution_entry
FROM tickets
WHERE tickets.date_closed IS NULL
OR tickets.date_closed >= '2012-06-20 00:00:00'
ORDER BY tickets.id DESC
Here is an example of what the "latest_solution_entry" field looks like:
6/20/12 * 1337 * 1: I restarted the computer and that fixed the problem. Yes, I took an hour to do this.
In PHP, I split up the "latest_solution_entry" field and format it correctly.
When I noticed that the page that runs the query had slowed way down, I ran the query without the subquery and it was super fast. I then ran an EXPLAIN on the original query and got this:
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
| 1 | PRIMARY | tickets | index | date_closed | PRIMARY | 4 | NULL | 35804 | Using where |
| 2 | DEPENDENT SUBQUERY | solutions | ref | ticket_id | ticket_id | 4 | helpdesk.tickets.id | 1 | Using where; Using filesort |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
So I'm looking for a way to make my query more efficient, yet still achieve the same goal. Any ideas?
Let me sum up what I understood: you'd like to select each ticket and its last solution.
I like using the following pattern for this kind of question as it avoids the subquery pattern and is therefore rather good where performance is needed. The drawback is that it is a bit tricky to understand:
SELECT
t.*,
s1.*
FROM tickets t
INNER JOIN solutions s1 ON t.id = s1.ticket_id
LEFT JOIN solutions s2 ON s1.ticket_id = s2.ticket_id AND s2.id > s1.id
WHERE s2.id IS NULL;
I wrote only the heart of the pattern for a better understanding.
The keys are:
the LEFT JOIN of the solutions table with itself with the s1.ticket_id = s2.ticket_id condition: it emulates the GROUP BY ticket_id.
the condition s2.id > s1.id : it is the SQL for "I only want the last solution", it emulates the MAX(). I assumed that in your model, the last means with the greatest id but you could use here a condition on the date. Note that s2.id < s1.id would give you the first solution.
the WHERE clause s2.id IS NULL: the weirdest one but absolutely necessary... keeps only the records you want.
Have a try and let me know :)
Edit 1: I just realised that the second point assumption was oversimplifying the problem. That makes it even more interesting :p I'm trying to see how this pattern may work with your date, id ordering.
Edit 2: Ok, it works great with a little twist. The condition on the LEFT JOIN becomes:
LEFT JOIN solutions s2 ON s1.ticket_id = s2.ticket_id
AND (s2.date > s1.date OR (s2.date = s1.date AND s2.id > s1.id))
When you have a inline view in the SELECT clause it must perform that select for every single row. I find its better in cases like this to put an inline view in the FROM clause instead which will perform the select once.
SELECT t.*,
Concat_ws(" * ", Date_format(s.date, "%c/%e/%y"), s.hours_spent,
Concat_ws(":", s.agent_id, s.body))
FROM tickets t
INNER JOIN (SELECT solutions.ticket_id,
Max(solutions.date) maxdate
FROM solutions
GROUP BY solutions.ticket_id) last_solutions
ON t.id = last_solutions.ticket_id
INNER JOIN (SELECT solutions.ticket_id,
solutions.date,
Max(solutions.id) maxid
FROM solutions
GROUP BY solutions.ticket_id,
solutions.date) last_solution
ON last_solutions.ticket_id = last_solution.ticket_id
and last_solutions.maxDate = last_solution.Date
INNER JOIN solutions s
ON last_solution.maxid = s.id
WHERE t.date_closed IS NULL
OR t.date_closed >= '2012-06-20 00:00:00'
ORDER BY t.id DESC
Note: You may need to make it a LEFT join depending on your needs
Try this:
SELECT *
FROM (
-- for each ticket get the most recent solution date
SELECT ticket_id, MAX(solutions.date) as date
FROM solutions
GROUP BY ticket_id
) t
JOIN tickets ON t.ticket_id = tickets.id
WHERE tickets.date_closed IS NULL OR tickets.date_closed >= '2012-06-20 00:00:00'
ORDER BY tickets.id DESC
Note, that if there is a ticket with 2 solutions of the same date you will have duplicate records in your result set. You will need another join to remove those duplicates or use an absolute sequence like a serial (incrementing primary key).
depending on the purpose, I give a idea :
SELECT DISTINCT s1.ticket_id, t.*, s1.*
FROM tickets t
LEFT JOIN solutions s1 ON t.id = s1.ticket_id

MySQL check if two conditions return at least one result

Problem: I am trying to optimize the following into one query.
Desired Result: If both queries return at least 1 row, output True or 1. If only one query returns a result, output false or 0. Any help is appreciated!
SELECT item_id
FROM `category_item`
WHERE `category_id` =39610 AND `item_id` = 31
SELECT item_id
FROM `category_item`
WHERE `category_id` =37 AND `item_id` = 31
SELECT COUNT(DISTINCT `category_id`)
FROM `category_item`
WHERE `category_id` IN (39610,37) AND `item_id` = 31
you can add == 2 or > 1 for that matter
EDIT:
just to clarify:
SELECT (COUNT(DISTINCT `category_id`) > 1) AS both_have
will result in return 1 or 0
The simplest way to check for rows is to use exists clause, and we can combine the two queries with and to make sure both queries return rows:
select
exists (SELECT item_id
FROM `category_item`
WHERE `category_id` =39610 AND `item_id` = 31
) AND
exists (SELECT item_id
FROM `category_item`
WHERE `category_id` =37 AND `item_id` = 31
) as `both_exist`;
Demo: http://www.sqlize.com/cx00raoMa7