MYSQL Query Matching Count - mysql

I have a MySQL-question which I can't seem to figure out.
I have two query's and some PHP and I think it should be possible to merge this into one query.
The thing I want to do is: show all users that completed all modules of a certain programm with id=1
Query 1 -> this gives all modules of the programm:
SELECT `id`
FROM `modules`
JOIN `linkModuleProgramm` ON `module`.`id` = `linkModuleProgramm`.`module`
WHERE `linkModuleProgramm`.`programm` = 1
Query 2 -> this gives the user and the number of modules of the programm that are completed by this user:
SELECT `user`.`id`,
COUNT(*) as 'count'
FROM `user`
JOIN `linkUserModule` ON `user`.`id` = `linkUserModule`.`user`
WHERE `linkUserModule`.`status` = 1
AND `linkUserModule`.`module` IN (Query1)
GROUP BY `user`.`id`
Then PHP filters the users from Query2 where 'count' is equal to the number of results from Query1.
Anyone has a suggestion?

Looks like I haven't understood your question right the first time. Another try, using two queries and MySQL variable:
SET #MODULESCOUNT = (
SELECT COUNT(*)
FROM `modules`
JOIN `linkModuleProgramm` ON `module`.`id` = `linkModuleProgramm`.`module`
WHERE `linkModuleProgramm`.`programm` = 1
);
SELECT `user`.`id`,
FROM `user`
JOIN `linkUserModule` ON `user`.`id` = `linkUserModule`.`user`
WHERE `linkUserModule`.`status` = 1
AND `linkUserModule`.`module` IN (Query1)
GROUP BY `user`.`id`
HAVING COUNT(*)=#MODULESCOUNT ;
There is also a possibility to use 'subquery' instead of variable:
SELECT `user`.`id`,
FROM `user`
JOIN `linkUserModule` ON `user`.`id` = `linkUserModule`.`user`
WHERE `linkUserModule`.`status` = 1
AND `linkUserModule`.`module` IN (Query1)
GROUP BY `user`.`id`
HAVING COUNT(*)=
(
SELECT COUNT(*)
FROM `modules`
JOIN `linkModuleProgramm` ON `module`.`id` = `linkModuleProgramm`.`module`
WHERE `linkModuleProgramm`.`programm` = 1
)
but I don't like subqueries inside 'where' or 'having' sections, mysql sometimes decides to do unreasonably inefficient things because of them.

Related

Query returning false PHP

I am having trouble with a MySQL query. The query is as follows:
SET #catlocation = (SELECT id FROM categories WHERE url_name='hexcode');
SELECT
subs.display_name AS display,
subs.url_name AS url,
(
SELECT title
FROM threads
WHERE location = subs.id
ORDER BY time_submitted DESC
LIMIT 1
) AS title,
(
SELECT username
FROM users
WHERE uid = (
SELECT uid
FROM threads
WHERE location = subs.id
ORDER BY time_submitted DESC
LIMIT 1
)
LIMIT 1
) AS author,
(
SELECT COUNT(*)
FROM threads
WHERE location = subs.id
ORDER BY time_submitted DESC
LIMIT 1
) AS thread_count
FROM (
SELECT *
FROM categories
WHERE parent_id = #catlocation
) AS subs
When I try to run this through PHP I get a false result and an error of:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT subs.display_name AS display, subs.url_name AS url, ( SELECT threads.' at line 7
I have no idea what the syntax error could be, if someone could point it out to me that would be wonderful.
EDIT: Could this be caused by having two select statements (The one that sets #catlocation and the main query?)
You can refactor your request with joins to increase performance.
SELECT s.display_name display, s.url_name url,
t1.title, u.username author,
COUNT(t2.title) total
FROM categories s
LEFT JOIN threads t1 ON t1.id = (SELECT id FROM threads
WHERE location = s.id
ORDER BY time_submitted DESC
LIMIT 1)
LEFT JOIN users u ON u.uid = t1.uid
LEFT JOIN threads t2 ON t2.location = s.id
WHERE s.parent_id = #catlocation
GROUP BY s.display_name, s.url_name, t1.title, u.username
In a ansi SQL you need to declare a tag for each table or omit it if there is only one. Try taking out the "threads." everywhere, it is not needed
It appears the first SELECT statement which set #catlocation was causing the problem. I moved it into a subquery and the query executed successfully
The new query is as follows:
SELECT categories.display_name display,
categories.url_name url,
threads.title title,
users.username author,
( SELECT COUNT(title)
FROM threads
WHERE location = categories.id
) total
FROM categories
LEFT JOIN threads
ON threads.tid = ( SELECT tid
FROM `threads`
WHERE location = categories.id
ORDER BY time_submitted DESC
LIMIT 1 )
LEFT JOIN users ON users.uid = threads.uid
WHERE categories.parent_id = ( SELECT id
FROM `categories`
WHERE url_name='hexcode'
LIMIT 1 );
I will continue to refactor the query by using JOINs (once I learn how to use them). Thanks to all that suggested fixes, I didn't understand the JOIN answer and still couldn't get it to run without error.

Invalid query results with mysql 5.7

I'm struggling to find a solution for my problem.
I basically have 3 tables - campaigns, users, campaign_user (pivot table - with campaign_id, user_id)
I have this query:
select * from `campaigns`
where `id` = 91
and (select count(*)
from `users`
inner join `campaign_user` on `users`.`id` = `campaign_user`.`user_id`
where `campaign_user`.`campaign_id` = `campaigns`.`id`
and `user_id` = 1) >= 1
That returns 0 results. I have checked that relevent row in campaign_user table exists.
Weird thing is that if I run the same query for another campaign id (89) it does return the expected result. Some campaign ids return as expected and some return 0.. weird and fraustrating.
This does not happen in production server which runs mysql 5.5
But it happens in my VM that runs mysql 5.7
I have no idea what is the cause of that. A help would be really appreciated!
The most likely explanation is that the data is different on the two servers. However, you can simplify the query, which is why I'm answering. The users table is not needed in the subquery. So:
select c.*
from `campaigns` c
where c `id` = 91 and
(select count(*)
from campaign_user cu
where cu.`campaign_id` = c.`id` and cu.user_id = 1
) >= 1;
This, in turn, can be simplified and made more efficient by using exists (or a left join) instead of count(*):
select c.*
from campaigns c
where c id = 91 and
exists (select 1
from campaign_user cu
where cu.campaign_id = c.id and cu.user_id = 1
) ;

MySQL Count returns More Rows than it Should

I am attempting to count the number of rows from a given query. But count returns more rows than it should. What is happening?
This query returns only 1 row.
select *
from `opportunities`
inner join `companies` on `opportunities`.`company_id` = `companies`.`id`
left join `opportunityTags` on `opportunities`.`id` = `opportunityTags`.`opportunity_id`
where `opportunities`.`isPublished` = '1' and `opportunities`.`Company_id` = '1'
group by `opportunities`.`id` ;
This query returns that there are 3 rows.
select count(*) as aggregate
from `opportunities`
inner join `companies` on `opportunities`.`company_id` = `companies`.`id`
left join `opportunityTags` on `opportunities`.`id` = `opportunityTags`.`opportunity_id`
where `opportunities`.`isPublished` = '1' and `opportunities`.`Company_id` = '1'
group by `opportunities`.`id`;
When you select count(*) it is counting before the group by. You can probably (unfortunately my realm is SQL Server and I don't have a mySQL instance to test) fix this by using the over() function.
For example:
select count(*) over (partition by `opportunities`.`id`)
EDIT: Actually doesn't look like this is available in mySQL, my bad. How about just wrapping the whole thing in a new select statement? It's not the most elegant solution, but will give you the figure you're after.

Joining tables is giving incorrect COUNT(*) values

For the longest time I couldn't figure out why an incorrect COUNT(*) values were being returned. After incrementally removing parts of my query I finally realized that joining tables were the reason behind the incorrect values.
This is the query I'm working with:
SELECT `profiles`.`logo` AS logo,
`companies`.`company_name`,
`companies`.`url_slug`,
count(*)
FROM (`companies`)
JOIN `users` ON `users`.`id` = `companies`.`user_id`
JOIN `categories` ON `categories`.`company_id` = `companies`.`id`
JOIN `products` ON `products`.`company_id` = `companies`.`id`
JOIN `profiles` ON `profiles`.`company_id` = `companies`.`id`
WHERE `users`.`last_login` IS NOT NULL
AND `categories`.`category_id` = '3'
AND `products`.`active` = 1
AND `products`.`xmp_1` = 1
AND `products`.`xmp_2` = 1
AND `profiles`.`field_a` = 1
GROUP BY `companies`.`id`
Running this in my SQL program returns 28 rows, but the COUNT(*) row returns something like 1400. I'm not sure where to head from here. I need a column returned that returns the 28 instead of 1400.
SQL Fiddle with sample data: http://sqlfiddle.com/#!2/97d2d/9
There's no way to do this with a simple query. Chris Hayes shows a way to do this with sub-queries and there are a variety of ways to do that.
The reason is that aggregation functions (like COUNT) only work on the GROUP that a row represents... in this case, each row represents the set of results that have the same company id. There's no way for field in a row to show aggregation information that extends beyond its content, in a simple query.
With subqueries, you can generate the first table, and then aggregate over that. Or, you could just use the fact that the total number of rows returned is included as metadata on the data that is sent back to whichever client submitted the query in the first place, thereby removing the need to include it in the data.
The following query works:
SELECT *, COUNT(*) FROM (
SELECT `profiles`.`logo` AS logo,
`companies`.`company_name`,
`companies`.`url_slug`
FROM (`companies`)
JOIN `users` ON `users`.`id` = `companies`.`user_id`
JOIN `categories` ON `categories`.`company_id` = `companies`.`id`
JOIN `products` ON `products`.`company_id` = `companies`.`id`
JOIN `profiles` ON `profiles`.`company_id` = `companies`.`id`
WHERE `users`.`last_login` IS NOT NULL
#AND `categories`.`category_id` = '3'
AND `products`.`active` = 1
#AND `products`.`xmp_1` = 1
#AND `products`.`xmp_2` = 1
AND `profiles`.`field_a` = 1
GROUP BY `companies`.`id`
) AS stuff
I think somehow your original COUNT(*) is looking at pre-join-condition or pre-grouping amounts.

optimize Mysql: get latest status of the sale

In the following query, I show the latest status of the sale (by stage, in this case the number 3). The query is based on a subquery in the status history of the sale:
SELECT v.id_sale,
IFNULL((
SELECT (CASE WHEN IFNULL( vec.description, '' ) = ''
THEN ve.name
ELSE vec.description
END)
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
WHERE veh.id_sale = v.id_sale
AND vec.id_stage = 3
ORDER BY veh.id_record DESC
LIMIT 1
), 'x') sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
WHERE 1 =1
AND v.flag =1
AND v.id_quarters =4
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
the query delay 0.0057seg and show 1011 records.
Because I have to filter the sales by the name of the state as it would have to repeat the subquery in a where clause, I have decided to change the same query using joins. In this case, I'm using the MAX function to obtain the latest status:
SELECT
v.id_sale,
IFNULL(veh3.State3,'x') AS sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
LEFT JOIN (
SELECT veh.id_sale,
(CASE WHEN IFNULL(vec.description,'') = ''
THEN ve.name
ELSE vec.description END) AS State3
FROM t_record veh
INNER JOIN (
SELECT id_sale, MAX(id_record) AS max_rating
FROM(
SELECT veh.id_sale, id_record
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign AND vec.id_stage = 3
) m
GROUP BY id_sale
) x ON x.max_rating = veh.id_record
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
) veh3 ON veh3.id_sale = v.id_sale
WHERE v.flag = 1
AND v.id_quarters = 4
This query shows the same results (1011). But the problem is it takes 0.0753 sec
Reviewing the possibilities I have found the factor that makes the difference in the speed of the query:
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
If I remove this clause, both queries the same time delay... Why it works better? Is there any way to use this clause in the joins? I hope your help.
EDIT
I will show the results of EXPLAIN for each query respectively:
q1:
q2:
Interesting, so that little statement basically determines if there is a match between t_record.id_sale and t_sale.id_sale.
Why is this making your query run faster? Because Where statements applied prior to subSelects in the select statement, so if there is no record to go with the sale, then it doesn't bother processing the subSelect. Which is netting you some time. So that's why it works better.
Is it going to work in your join syntax? I don't really know without having your tables to test against but you can always just apply it to the end and find out. Add the keyword EXPLAIN to the beginning of your query and you will get a plan of execution which will help you optimize things. Probably the best way to get better results in your join syntax is to add some indexes to your tables.
But I ask you, is this even necessary? You have a query returning in <8 hundredths of a second. Unless this query is getting ran thousands of times an hour, this is not really taxing your DB at all and your time is probably better spent making improvements elsewhere in your application.