Sorry for asking here this but I need help and google is not being nice.
I have the following table Products
SELECT
COUNT(CASE when core.kits.Location = core.suppliers.id THEN 1 END) as total,
COUNT(CASE when core.kits.cp = 1 THEN 1 END) as used,
core.suppliers.id, core.suppliers.name, core.suppliers.email,
core.suppliers.cperson, core.suppliers.adress, core.suppliers.phone
FROM core.kits
LEFT join core.suppliers on core.kits.Location = core.suppliers.id
WHERE core.suppliers.id is not null
AND banned=0
GROUP BY core.suppliers.id
ORDER BY name ASC
LIMIT 1000 OFFSET 0
but does not give me all the suppliers with zeros for the ones who have no appearance in kits.
Then in I do
SELECT
COUNT(CASE when core.kits.Location = core.suppliers.id THEN 1 END) as total,
COUNT(CASE when core.kits.cp = 1 THEN 1 END) as used,
core.suppliers.id, core.suppliers.name, core.suppliers.email,
core.suppliers.cperson, core.suppliers.adress, core.suppliers.phone
FROM core.suppliers
LEFT join core.suppliers on core.suppliers.id = core.kits.Location
WHERE core.suppliers.id is not null
AND banned=0
GROUP BY core.suppliers.id
ORDER BY name ASC
LIMIT 1000 OFFSET 0
I get all suppliers and correct numbers but the query takes 8 seconds instead of 1s. Any ideas how can I get all the suppliers with the count of stocks in 1s?
cheers.
If you want all the suppliers, even those that do not appear in kits you should do a LEFT join of suppliers to kits:
SELECT COUNT(k.Location) AS total,
COUNT(CASE WHEN k.cp = 1 THEN 1 END) AS used,
s.id, s.name, s.email, s.cperson, s.adress, s.phone
FROM core.suppliers s LEFT JOIN core.kits k
ON k.Location = s.id
WHERE banned=0
GROUP BY s.id
ORDER BY s.name ASC
LIMIT 1000 OFFSET 0;
I assume that core.suppliers.id is the primary key of suppliers, so that the conition:
core.suppliers.id is not null
is not needed.
Also, if the column banned is contained in the table kits, then the condition should be moved in the ON clause:
ON k.Location = s.id AND k.banned=0
and the WHERE clause should be removed.
Related
I have the following scenario. An area has multiple territories, a territory has multiple addresses and an address is visited multiple times in a month. Now I want to generate a monthly report about an area. (How many times an area has been visited).I have written the query but the result set is producing less areas because some addresses are not visited. I have the following structure
tables
areas: id|name (180 rows) //name is unique
territories: id|name|area_id (1k rows)
addresses: id|name|territory_id (80k rows)
visiting_addresses: id|address_id|date|status (1M+ rows) //status => 1 = visited, 2 = pending
My query is following.
select ar.id as area_id, ar.name as area,
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from areas ar
left join territories t on t.area_id=ar.id
left join addresses a on a.territory_id=t.id
left join visiting_addresses va on va.address_id=a.id
where month(va.date) = '01'
and year(va.date)='2020'
group by ar.id
the area table contains 180 areas but the result set shows only 144 areas. Where is my mistake and what is the explanation to this? those areas are missing because they have no visiting.
Your WHERE clause is converting the LEFT JOIN with visiting_addresses to an INNER JOIN. And since it's the right most table in a LEFT-JOIN-chain, all joins will be converted to INNER JOINS. To prevent that, you should move the corresponding conditions from the WHERE clause to the ON clause:
select ar.id as area_id, ar.name as area,
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from areas ar
left join territories t on t.area_id=ar.id
left join addresses a on a.territory_id=t.id
left join visiting_addresses va
on va.address_id=a.id
and month(va.date) = '01'
and year(va.date)='2020'
group by ar.id
But since you have alot of rows, I would rather run two queries. First get only areas with adresses form the last month using inner joins. You should though change your conditions on va.date to utilize an index:
select ar.id as area_id, ar.name as area,
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from areas ar
join territories t on t.area_id=ar.id
join addresses a on a.territory_id=t.id
join visiting_addresses va on va.address_id=a.id
where va.date >= '2020-01-01'
and va.date < '2020-02-01'
group by ar.id
Make sure you have an index on visiting_addresses(date) or even better on visiting_addresses(date, address_id, status).
Then get all areas with a simple
select ar.id as area_id, ar.name as area
from areas ar
and add missing areas to the first result while setting visited, pending and total to zero (in application code).
The INNER JOIN should be much faster, because now the engine can start reading only the necessary rows from the visiting_addresses using an index for the WHERE conditions.
You could also use a more complex but single query. The Idea is to use a LEFT JOIN with a pre-aggregated subquery:
select ar.id as area_id, ar.name as area,
coalesce(visited, 0) as visited,
coalesce(pending, 0) as pending,
coalesce(total, 0) as total
from areas ar
left join (
select t.area_id
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from territories t
join addresses a on a.territory_id=t.id
join visiting_addresses va on va.address_id=a.id
where va.date >= '2020-01-01'
and va.date < '2020-02-01'
group by t.area_id
) x on x.area_id = ar.id
Try moving the logic in the WHERE clause to the ON clause of the appropriate join:
SELECT
ar.id AS area_id,
ar.name AS area,
COUNT(CASE WHEN va.status = 1 THEN 1 END) AS visited,
COUNT(CASE WHEN va.status = 2 THEN 1 END) AS pending,
COUNT(va.id) AS total
FROM areas ar
LEFT JOIN territories t ON t.area_id = ar.id
LEFT JOIN addresses a ON a.territory_id = t.id
LEFT JOIN visiting_addresses va ON va.address_id = a.id AND
va.date >= '2020-01-01' AND va.date < '2020-02-01'
GROUP BY
ar.id;
Note that selecting the name field while only aggregating by id is valid in MySQL, assuming that id be a unique field in the areas table.
You may also try adding the following index to the visiting_addresses table:
CREATE INDEX date_idx ON visiting_addresses (address_id, date, status);
This might help speed up the join to this table.
In my application the users can create campaigns for sending messages. When the campaign tries to send a message, one of the three things can happen:
The message is suppressed and not let through
The message can't reach the recipient and is considered failed
The message is successfully delivered
To keep track of this, I have the following table:
My problem is that when the application has processed a lot of messages (more than 10 million), the query I use for showing campaign statistics for the user slows down by a considerable margin (~ 15 seconds), even when there are only a few (~ 10) campaigns being displayed for the user.
Here is the query I'm using:
select `campaigns`.*, (select count(*) from `processed_messages`
where `campaigns`.`id` = `processed_messages`.`campaign_id` and `status` = 'sent') as `messages_sent`,
(select count(*) from `processed_messages` where `campaigns`.`id` = `processed_messages`.`campaign_id` and `status` = 'failed') as `messages_failed`,
(select count(*) from `processed_messages` where `campaigns`.`id` = `processed_messages`.`campaign_id` and `status` = 'supressed') as `messages_supressed`
from `campaigns` where `user_id` = 1 and `campaigns`.`deleted_at` is null order by `updated_at` desc;
So my question is: how can I make this query run faster? I believe there should be some way of not having to use sub-queries multiple times but I am not very experienced with MySQL syntax yet.
You should write this as a single join, using conditional aggregation:
SELECT
c.*,
COUNT(CASE WHEN pm.status = 'sent' THEN 1 END) AS messages_sent,
COUNT(CASE WHEN pm.status = 'failed' THEN 1 END) AS messages_failed,
COUNT(CASE WHEN pm.status = 'suppressed' THEN 1 END) AS messages_suppressed
FROM campaigns c
LEFT JOIN processed_messages pm
ON c.id = pm.campaign_id
WHERE
c.user_id = 1 AND
c.deleted_at IS NULL
GROUP BY
c.id
ORDER BY
c.updated_at DESC;
It should be noted that at first glance, doing SELECT c.* appears to be a violation of the GROUP BY rules which say that only columns which appear in the GROUP BY clause can be selected. However, assuming that campaigns.id is the primary key column, then there is nothing wrong with selecting all columns from this table, provided that we aggregate by the primary key.
Edit:
If the above answer does not run on your MySQL server version, with an error message complaining about only full group by, then use this version:
SELECT c1.*, c2.messages_sent, c2.messages_failed, c2.message_suppressed
FROM campaigns c1
INNER JOIN
(
SELECT
c.id
COUNT(CASE WHEN pm.status = 'sent' THEN 1 END) AS messages_sent,
COUNT(CASE WHEN pm.status = 'failed' THEN 1 END) AS messages_failed,
COUNT(CASE WHEN pm.status = 'suppressed' THEN 1 END) AS messages_suppressed
FROM campaigns c
LEFT JOIN processed_messages pm
ON c.id = pm.campaign_id
WHERE
c.user_id = 1 AND
c.deleted_at IS NULL
GROUP BY
c.id
) c2
ON c1.id = c2.id
ORDER BY
c2.updated_at DESC;
In below query (Mentors) are 13 which shows me 26, while (SchoolSupervisor) are 5 which shows me 10 which is wrong. it is because of the Evidence which having 2 evidance, because of 2 evidence the Mentors & SchoolSupervisor values shows me double.
please help me out.
Query:
select t.c_id,t.province,t.district,t.cohort,t.duration,t.venue,t.v_date,t.review_level, t.activity,
SUM(CASE WHEN pr.p_association = "Mentor" THEN 1 ELSE 0 END) as Mentor,
SUM(CASE WHEN pr.p_association = "School Supervisor" THEN 1 ELSE 0 END) as SchoolSupervisor,
(CASE WHEN count(file_id) > 0 THEN "Yes" ELSE "No" END) as evidence
FROM review_m t , review_attndnce ra
LEFT JOIN participant_registration AS pr ON pr.p_id = ra.p_id
LEFT JOIN review_files AS rf ON rf.training_id = ra.c_id
WHERE 1=1 AND t.c_id = ra.c_id
group by t.c_id, ra.c_id order by t.c_id desc
enter image description here
You may perform the aggregations in a separate subquery, and then join to it:
SELECT
t.c_id,
t.province,
t.district,
t.cohort,
t.duration,
t.venue,
t.v_date,
t.review_level,
t.activity,
pr.Mentor,
pr.SchoolSupervisor,
rf.evidence
FROM review_m t
INNER JOIN review_attndnce ra
ON t.c_id = ra.c_id
LEFT JOIN
(
SELECT
p_id,
COUNT(CASE WHEN p_association = 'Mentor' THEN 1 END) AS Mentor,
COUNT(CASE WHEN p_association = 'School Supervisor' THEN 1 END) AS SchoolSupervisor,
FROM participant_registration
GROUP BY p_id
) pr
ON pr.p_id = ra.p_id
LEFT JOIN
(
SELECT
training_id,
CASE WHEN COUNT(file_id) > 0 THEN 'Yes' ELSE 'No' END AS evidence
FROM review_files
GROUP BY training_id
) rf
ON rf.training_id = ra.c_id
ORDER BY
t.c_id DESC;
Note that this also fixes another problem your query had, which was that you were selecting many columns which did not appear in the GROUP BY clause. Under this refactor, there is nothing wrong with your current select, because the aggregation take place in a separate subquery.
try adding this to the WHERE part of your query
AND pr.p_id IS NOT NULL AND rf.training_id IS NOT NULL
You can add a group by pr.p_id to remove the duplicate records there. Since, the group by on pr is not present as of now, there might be multiple records of same p_id for same ra
group by t.c_id, ra.c_id, pr.p_id order by t.c_id desc
I'm trying to COUNT attendance_status of drivers according to its value.
this is my code as of moment.
SELECT *, COUNT(attendance_status) AS total_cars_dispatched
FROM driver_attendance da
LEFT JOIN collectible co ON (da.driver_attendance_id=co.driver_attendance_id)
LEFT JOIN driver_pondo dp ON (dp.collectible_id=co.collectible_id)
WHERE attendance_status=19 AND company_id=84 GROUP BY attendance_date DESC
I'd like to know how to make another COUNT of attendance_status when it's value is 4 using a single query.
Try this:
SELECT attendance_date,
SUM(CASE WHEN attendance_status = 19 THEN 1 ELSE 0 END) AS total_cars_dispatched,
SUM(CASE WHEN attendance_status = 4 THEN 1 ELSE 0 END) AS attendance_status_4
FROM driver_attendance da
LEFT JOIN collectible co ON da.driver_attendance_id=co.driver_attendance_id
LEFT JOIN driver_pondo dp ON dp.collectible_id=co.collectible_id
WHERE company_id=84
GROUP BY attendance_date DESC;
The following query fails in MySQL 5.1.56:
SELECT
shop_id, products.product_id AS
product_id, brand, title, price, image, image_width, image_height
FROM products, users LEFT JOIN
(
SELECT fav5.product_id AS product_id, SUM(CASE
WHEN fav5.current = 1 AND fav5.closeted = 1 THEN 1
WHEN fav5.current = 1 AND fav5.closeted = 0 THEN -1
ELSE 0
END) AS favorites_count
FROM favorites fav5
GROUP BY fav5.product_id
) AS fav6 ON products.product_id=fav6.product_id
WHERE products.product_id= 46876 AND users.user_id!=products.product_id
The error is
#1054 - Unknown column 'products.product_id' in 'on clause'
This modification without the user table does not fail:
SELECT
shop_id, products.product_id AS
product_id, brand, title, price, image, image_width, image_height
FROM products LEFT JOIN
(
SELECT fav5.product_id AS product_id, SUM(CASE
WHEN fav5.current = 1 AND fav5.closeted = 1 THEN 1
WHEN fav5.current = 1 AND fav5.closeted = 0 THEN -1
ELSE 0
END) AS favorites_count
FROM favorites fav5
GROUP BY fav5.product_id
) AS fav6 ON products.product_id=fav6.product_id
WHERE products.product_id= 46876
Neither query fails in MySQL 5.0.67. (I exported the database from 5.0.67 and imported into 5.1.56 so the structure should be identical.)
The products table does have a product_id column, of type int(10). The favorites table also has a product_id column of type int(10). What is going on?
As easy as swap from tables order:
FROM users, products LEFT JOIN
Be careful, you are mixing join notations.
JOIN processing operator precedence has changed in MySQL in 5.1. It's a common problem for people upgrading from 5.0
MySQL LEFT JOIN after 5.0.12 changes - How to rewrite query
This is your original query, reformatted a little and with two parentheses added:
SELECT shop_id, products.product_id AS
product_id, brand, title, price, image, image_width, image_height
FROM products,
( -- Parenthesis added
users LEFT JOIN
(
SELECT fav5.product_id AS product_id, SUM(CASE
WHEN fav5.current = 1 AND fav5.closeted = 1 THEN 1
WHEN fav5.current = 1 AND fav5.closeted = 0 THEN -1
ELSE 0
END) AS favorites_count
FROM favorites fav5
GROUP BY fav5.product_id
) AS fav6 ON products.product_id=fav6.product_id
) -- Parenthesis added
WHERE products.product_id= 46876 AND users.user_id!=products.product_id
The parentheses indicate how the SQL parser is interpreting the query, and there is no products table within the added parenthesis.
It is a bad idea to mix the old style and new (as in, since SQL-92) style joins.
Use:
SELECT shop_id, products.product_id AS
product_id, brand, title, price, image, image_width, image_height
FROM products JOIN users ON users.user_id != products.product_id
LEFT JOIN
(SELECT fav5.product_id AS product_id,
SUM(CASE WHEN fav5.current = 1 AND fav5.closeted = 1 THEN 1
WHEN fav5.current = 1 AND fav5.closeted = 0 THEN -1
ELSE 0
END) AS favorites_count
FROM favorites fav5
GROUP BY fav5.product_id
) AS fav6 ON products.product_id=fav6.product_id
WHERE products.product_id = 46876
The != join is going to be slow (it practically a Cartesian product).