Simplify mysql union query - mysql

I have this query, which is slow. I get the count of each select and define them as columns free_cnt,plus_cnt, so I can manipulate in the parent select query.
$query = $this->db->query("SELECT movie_title,movie_id,MAX(x.free_cnt) as free_cnt, MAX(x.plus_cnt) as plus_cnt, ((MAX(x.free_cnt)*1) + (MAX(x.plus_cnt)*3)) AS score, (MAX(x.free_cnt) + MAX(x.plus_cnt)) AS total
FROM (
SELECT b.id as movie_id, b.movie_title as movie_title, COUNT(*) AS free_cnt, 0 as plus_cnt
FROM preview_movie_request a1
LEFT JOIN movies b on a1.movie_id=b.id
JOIN users c on c.email=a1.email
WHERE c.subsc_status='0' AND c.package_type='' AND b.movie_type=2 $where1
GROUP BY b.movie_title
UNION ALL
SELECT d.id as movie_id, d.movie_title as movie_title, 0 as free_cnt, COUNT(*) AS plus_cnt
FROM preview_movie_request a2
LEFT JOIN movies d on a2.movie_id=d.id
JOIN users e on e.email=a2.email
WHERE e.subsc_status='1' AND e.package_type!='' AND d.movie_type=2 $where2
GROUP BY d.movie_title
UNION ALL
) AS x
GROUP BY movie_title
$orderby
$limit");
Is there anyway to simplify query and make faster?

You can eliminate one subquery moving the count conditions in case
SELECT movie_title,movie_id,MAX(x.free_cnt) as free_cnt, MAX(x.plus_cnt) as plus_cnt, ((MAX(x.free_cnt)*1) + (MAX(x.plus_cnt)*3)) AS score, (MAX(x.free_cnt) + MAX(x.plus_cnt)) AS total FROM
(
SELECT b.id as movie_id, b.movie_title as movie_title,
SUM(CASE WHEN c.subsc_status='0' AND c.package_type='' AND b.movie_type=2 $where1 THEN 1 END ) AS free_cnt,
SUM(CASE WHEN c.subsc_status='1' AND c.package_type!='' AND b.movie_type=2 $where2 THEN 1 END ) AS plus_cnt,
FROM preview_movie_request a1
LEFT JOIN movies b on a1.movie_id=b.id
JOIN users c on c.email=a1.email
GROUP BY b.movie_title)x
GROUP BY movie_title
$orderby
$limit

Related

Sum of grouped COUNT IN MySQL

I wrote this query
SELECT
country,
COUNT(DISTINCT tmp_tbl.user_guid) AS number_of_customers
FROM complete_tests c INNER JOIN
( SELECT DISTINCT d.dog_guid,
u.user_guid,
u.country
FROM dogs d INNER JOIN users u ON d.user_guid = u.user_guid
WHERE (u.exclude = 0 OR u.exclude IS NULL)
AND (d.exclude = 0 OR d.exclude IS NULL)
)
AS tmp_tbl ON c.dog_guid = tmp_tbl.dog_guid
GROUP BY country
ORDER BY number_of_customers DESC
And I need to add another variable that calculates the percentage of total
when I add
number_of_customers/SUM(number_of_customers)
or SUM(COUNT(DISTINCT tmp_tbl.user_guid)) / COUNT(DISTINCT tmp_tbl.user_guid)
it gives me error
Analytic functions come in handy here. Assuming you are using MySQL 8+:
SELECT country,
COUNT(DISTINCT tmp_tbl.user_guid) AS number_of_customers,
100.0 * COUNT(DISTINCT tmp_tbl.user_guid) /
SUM(COUNT(DISTINCT tmp_tbl.user_guid)) OVER () AS pct_customers
FROM complete_tests c
INNER JOIN
(
SELECT DISTINCT d.dog_guid, u.user_guid, u.country
FROM dogs d
INNER JOIN users u ON d.user_guid = u.user_guid
WHERE (u.exclude = 0 OR u.exclude IS NULL) AND
(d.exclude = 0 OR d.exclude IS NULL)
) AS tmp_tbl
ON c.dog_guid = tmp_tbl.dog_guid
GROUP BY
country
ORDER BY
number_of_customers DESC;

MySQL include all results in ranking

I'm using the following query to sort, using Order by for 4 different ranks: id, avg_rating, total_sent & points
However, I'm having trouble with including all the members in the results. I'd like to include all members, including the ones that have 0: total_sent, total_received, points, avg_rating, votes
Please help me understand what I'm missing. Thank you.
SELECT m.id,
m.Name,
m.City,
m.Zip_Code,
m.url,
r.avg_rating,
r.votes,
froms.from_ct total_sent,
tos.to_ct total_received,
froms.from_ct - tos.to_ct `points`
FROM members m
JOIN (
SELECT id_rated,
avg(rating) avg_rating,
count(*) votes
FROM member_ratings
GROUP BY id_rated
) r ON r.id_rated = m.id
JOIN ( SELECT id_from, COUNT(*) AS from_ct FROM member_points GROUP BY 1
) AS froms ON froms.id_from = m.id
JOIN ( SELECT id_received, COUNT(*) AS to_ct FROM member_points GROUP BY 1
) AS tos ON tos.id_received = m.id
WHERE m.Account_Active = 'TRUE'
GROUP BY m.id,
m.Name
ORDER BY `avg_rating` DESC;
Use LEFT JOINs for all tables
SELECT m.id,
m.Name,
m.City,
m.Zip_Code,
m.url,
r.avg_rating,
r.votes,
froms.from_ct total_sent,
tos.to_ct total_received,
froms.from_ct - tos.to_ct `points`
FROM members m
LEFT JOIN (
SELECT id_rated,
avg(rating) avg_rating,
count(*) votes
FROM member_ratings
GROUP BY id_rated
) r ON r.id_rated = m.id
LEFT JOIN ( SELECT id_from, COUNT(*) AS from_ct FROM member_points GROUP BY 1
) AS froms ON froms.id_from = m.id
LEFT JOIN ( SELECT id_received, COUNT(*) AS to_ct FROM member_points GROUP BY 1
) AS tos ON tos.id_received = m.id
LEFT JOIN member_points mp ON mp.id_points = m.id
WHERE m.Account_Active = 'TRUE'
GROUP BY m.id,
m.Name
ORDER BY `avg_rating` DESC;

SQL : Calculating Percentage by joining a sub table to another

I've the above dataset, I need to report for each year the percentage of movies in that year with only female actors, and the total number of movies made that year. For example, one answer will be: 1990 31.81 13522 meaning that in 1990 there were 13,522 movies, and 31.81%
In order to get the moves with only female actors, wrote the following code:
SELECT a.year as Year, COUNT(a.title) AS Female_Movies, a.title
FROM Movie a
WHERE a.title NOT IN (
SELECT b.title from Movie b
Inner Join M_cast c
on TRIM(c.MID) = b.MID
Inner Join Person d
on TRIM(c.PID) = d.PID
WHERE d.Gender='Male'
GROUP BY b.title
)
GROUP BY a.year,a.title
Order By a.year asc
The total movies in each year , can be found using the following:
SELECT a.year, count(a.title) AS Total_Movies
FROM Movie a
GROUP BY a.year
ORDER BY COUNT(a.title) DESC
Combinig the both I wrote, the following code:
SELECT z.year as Year, count(z.title) AS Total_Movies, count(x.title) as Female_movies, count(z.title)/ count(x.title) As percentage
FROM Movie z
Inner Join (
SELECT a.year as Year, COUNT(a.title) AS Female_Movies, a.title
FROM Movie a
WHERE a.title NOT IN (
SELECT b.title from Movie b
Inner Join M_cast c
on TRIM(c.MID) = b.MID
Inner Join Person d
on TRIM(c.PID) = d.PID
WHERE d.Gender='Male'
GROUP BY b.title
)
GROUP BY a.year,a.title
Order By a.year asc
)x
on x.year = z.year
GROUP BY z.year
ORDER BY COUNT(z.title) DESC
However, in th output I'm seeing the years with only female movies correctly, but the count of total movies is equal to female_movies so I'm getting 1%, I tried debugging the code, but not sure where this is going wrong. Any insights would be appreciated.
You assume that your 'z' contains all movies but since you do an inner join on the female movies, they'll also only contain female movies. You could fix that with a 'left join'.
Assuming your two queries are correct, you can join on them with a 'WITH' like this:
WITH allmovies (year, cnt) as
(SELECT a.year, count(a.title) AS Total_Movies
FROM Movie a
GROUP BY a.year
ORDER BY COUNT(a.title) DESC)
,
femalemovies (year, cnt, title) as
(SELECT a.year as Year, COUNT(a.title) AS Female_Movies, a.title
FROM Movie a
WHERE a.title NOT IN (
SELECT b.title from Movie b
Inner Join M_cast c
on TRIM(c.MID) = b.MID
Inner Join Person d
on TRIM(c.PID) = d.PID
WHERE d.Gender='Male'
GROUP BY b.title
)
GROUP BY a.year,a.title
Order By a.year asc)
select * from allmovies left join femalemovies on allmovies.year = femalemovies.year
You can use conditional aggregation. In a CASE expression check if no cast member that isn't female exists with a correlated subquery. If the check is successful, return something not NULL and count() that to get the number of movies with only female cast members (or none at all).
SELECT m.year,
count(*) count_all,
count(CASE
WHEN NOT EXISTS (SELECT *
FROM m_cast c
INNER JOIN person p
ON p.pid = c.pid
WHERE c.mid = m.mid
AND p.gender <> 'Female') THEN
1
END)
/
count(*)
*
100 percentage_only_female
FROM movie m
GROUP BY m.year;
Since in MySQL Boolean expressions in numerical context evaluate to 1 if true and to 0 otherwise, you could also use a sum() over the NOT EXISTS.
SELECT m.year,
count(*) count_all,
sum(NOT EXISTS (SELECT *
FROM m_cast c
INNER JOIN person p
ON p.pid = c.pid
WHERE c.mid = m.mid
AND p.gender <> 'Female'))
/
count(*)
*
100 percentage_only_female
FROM movie m
GROUP BY m.year;
That however isn't compatible with most other DBMS in contrast to the first one.
I would use two levels of aggregation:
SELECT m.MID, m.title, m.year,
COUNT(*) as num_actors,
SUM(gender = 'Female') as num_female_actors
FROM Movie m JOIN
M_cast c
ON c.MID = b.MID JOIN
Person p
ON p.PID = c.PID
GROUP BY m.MID, m.title, m.year;
Then a simple outer aggregation:
SELECT year,
COUNT(*) as num_movies,
SUM( num_actors = num_female_actors ) as num_female_only,
AVG( num_actors = num_female_actors ) as female_only_ratio
FROM (SELECT m.MID, m.title, m.year,
COUNT(*) as num_actors,
SUM(gender = 'Female') as num_female_actors
FROM Movie m JOIN
M_cast c
ON c.MID = b.MID JOIN
Person p
ON p.PID = c.PID
GROUP BY m.MID, m.title, m.year
) m
GROUP BY year;
Notes:
Use meaningful table aliases, rather than arbitrary letters. You'll note that the table aliases are abbreviations for the table names.
Do not use functions when filtering or JOINing unless necessary. I removed the TRIM(). If you need it use it. Or better yet, fix the data.
SELECT m.Year,COUNT(m.Year),x.t,
(COUNT(m.Year)*1.0/x.t*1.0)*100
FROM Movie m LEFT JOIN
(SELECT Year,COUNT(Year) AS t FROM Movie GROUP BY year) AS x
ON m.Year=x.Year
WHERE m.MID IN
(SELECT MID FROM M_Cast WHERE PID in
(SELECT PID FROM Person WHERE Gender='Female')
AND m.MID NOT IN
(SELECT MID FROM M_Cast WHERE PID in
(SELECT PID FROM Person WHERE Gender='Male'))) GROUP BY m.year
Check if this is what you're looking for.
select movie.year, count(movie.mid) as Year_Wise_Movie_Count,cast(x.Female_Cast_Only as real) / count(movie.mid) As Percentage_of_Female_Cast from movie
inner join
(
SELECT Movie.year as Year, COUNT(Movie.mid) AS Female_Cast_Only
FROM Movie
WHERE Movie.MID NOT IN (
SELECT Movie.MID from Movie
Inner Join M_cast
on TRIM(M_cast.MID) = Movie.MID
Inner Join Person
on TRIM(M_cast.PID) = Person.PID
WHERE Person.Gender!='Female'
GROUP BY Movie.MID
)
GROUP BY Movie.year
Order By Movie.year asc
) x
on x.year = movie.year
GROUP BY movie.year
ORDER BY movie.year
Output:
year Year_Wise_Movie_Count Percentage_of_Female_Cast
---- --------------------- -------------------------
1939 2 0.5
1999 66 0.0151515151515152
2000 64 0.015625
2018 104 0.00961538461538462
Note:
This was executed in SQLIte3

Simplify slow MySQL query

This query calculates the columns free,plus,score and total based on the COUNT of columns in subquery.
SELECT movie_title,movie_id,MAX(x.free_cnt) as free, MAX(x.plus_cnt) as plus,
(MAX(x.free_cnt) + (MAX(x.plus_cnt)*3)) AS score, (MAX(x.free_cnt) + MAX(x.plus_cnt)) AS total
FROM (
SELECT b.id as movie_id, b.movie_title as movie_title, COUNT(*) AS free_cnt, 0 as plus_cnt
FROM subtitles_request a1
LEFT JOIN movies b on a1.movie_id=b.id
JOIN users c on c.email=a1.email
WHERE c.subsc_status='0'
GROUP BY b.movie_title
UNION ALL
SELECT d.id as movie_id, d.movie_title as movie_title, 0 as free_cnt, COUNT(*) AS plus_cnt
FROM subtitles_request a2
LEFT JOIN movies d on a2.movie_id=d.id
JOIN users e on e.email=a2.email
WHERE e.subsc_status='1'
GROUP BY d.movie_title
) AS x
GROUP BY movie_title
ORDER BY total DESC
LIMIT 10
It is slow performing and i'm wondering is there anyway i can simplify or change the query to speed up performance. I can't calculate the free,plus,score ,total columns outside of query due to being able to order by. Also i may incorporate date.
Anyway to simplify this query?
Try this:
SELECT b.movie_title, x.movie_id, MAX( x.free_cnt ) AS free, MAX( x.plus_cnt ) AS plus,
( MAX( x.free_cnt ) + ( MAX( x.plus_cnt ) * 3 ) ) AS score, ( MAX( x.free_cnt ) + MAX( x.plus_cnt ) ) AS total
FROM ( SELECT a.movie_id,
SUM( IF( c.subsc_status = '0', 1, 0 ) ) AS free_cnt,
SUM( IF( c.subsc_status = '1', 1, 0 ) ) AS plus_cnt
FROM subtitles_request a1
JOIN users c on c.email=a1.email
WHERE c.subsc_status in ('0','1')
GROUP BY a.movie_id
) AS x
LEFT JOIN movies b on x.movie_id = b.id
GROUP BY movie_title, movie_id
ORDER BY total DESC
LIMIT 10
Maybe I've simplified a bit too much. Moreover, I'm not used to grouping on only some of the non-aggregate fields, hence I added movie_id to what is being grouped by and thus changing your query a bit (if two films had the same name, but different ID, then only one of the id's would be returned in your original query, but I guess (being a MySQL newbie, I really don't know) the counts would be for both of them taken together).
HTH,
Set
Well, I have check your the subquery:
SELECT b.id as movie_id, b.movie_title as movie_title, COUNT(*) AS free_cnt, 0 as plus_cnt
FROM subtitles_request a1
LEFT JOIN movies b on a1.movie_id=b.id
JOIN users c on c.email=a1.email
WHERE c.subsc_status='0'
GROUP BY b.movie_title
UNION ALL
SELECT d.id as movie_id, d.movie_title as movie_title, 0 as free_cnt, COUNT(*) AS plus_cnt
FROM subtitles_request a2
LEFT JOIN movies d on a2.movie_id=d.id
JOIN users e on e.email=a2.email
WHERE e.subsc_status='1'
GROUP BY d.movie_title
The statement beside "UNION ALL" can be replaced with one statement with condition at c.subsc_status IN('0','1'). And you can try to use "CASE WHEN" statement at 0 as free_cnt, COUNT(*) AS plus_cnt, just like IFNULL((CASE WHEN e.subsc_status='1' THEN COUNT(*)),0) as free_cnt. It's not a complicated sql statement, I don't think it will take too much time to query. Is there too many datas?
As a matter of fact, I'm also a newer, but I just have some experence about it. Please forgive me if it doesn't work.

MySQL Query - SUM of COUNT from multiple tables

I have three tables:
customers: id, name
contracts_jewels: id, customer_id, paid, transferred, final_date
contracts_objects: id, customer_id, paid, transferred, final_date
As you see, the structure of the last two tables is the same.
The "paid" and the "transferred" fields contain the value 0 or 1.
What I need is to make a query which should return all the clients (no matter if they have contracts or not), and for each client:
id, name, count_contracts_all, count_contracts_active
where:
count_contracts_all would mean the sum of [SELECT COUNT( * ) FROM
contracts_jewels WHERE customer_id=3 (for example)] and [SELECT
COUNT( * ) FROM contracts_objects WHERE customer_id=3 (for example)]
count_contracts_active would mean the sum of [SELECT COUNT( * ) FROM
contracts_jewels WHERE customer_id=3 AND final_date>=Now() AND paid=0
AND transferred=0] and [SELECT COUNT( * ) FROM contracts_objects WHERE
customer_id=3 AND final_date>=Now() AND paid=0 AND transferred=0]
Any idea? Would you please help me? Thank you!
You can count the contracts separately and then just join them up to the customers:
SELECT
c.id,
COALESCE(oc.active_count,0) + COALESCE(jc.active_count,0) as count_contracts_active,
COALESCE(oc.total_count,0) + COALESCE(jc.total_count,0) as count_contracts_all
FROM customers c
LEFT JOIN (
SELECT
customer_id
COUNT(*) as total_count,
COUNT(IF(final_date>=Now() AND paid=0 AND transferred=0,1,NULL)) as active_count
FROM contracts_jewels
GROUP BY customer_id
) as oc ON oc.customer_id = c.id
LEFT JOIN (
SELECT
customer_id
COUNT(*) as total_count,
COUNT(IF(final_date>=Now() AND paid=0 AND transferred=0,1,NULL)) as active_count
FROM contracts_objects
GROUP BY customer_id
) as jc ON jc.customer_id = c.id
One fast solution I can think of right now is:
SELECT COUNT(`temp_table`.*) FROM (
SELECT * FROM contracts_jewels WHERE customer_id=3 UNION ALL
SELECT * FROM contracts_objects WHERE customer_id=3) AS `temp_table`
AND
SELECT COUNT(`temp_table`.*) FROM (
SELECT * FROM contracts_jewels WHERE customer_id=3 AND final_date>=Now() AND paid=0 AND transferred=0 UNION ALL
SELECT * FROM contracts_objects WHERE customer_id=3 AND final_date>=Now() AND paid=0 AND transferred=0) AS `temp_table`
You can join each of those tables twice and add their corresponding COUNTs in your result:
SELECT
c.id,
(COUNT(cj1.id)+COUNT(co1.id)) AS count_contracts_all,
(COUNT(cj2.id)+COUNT(co2.id)) AS count_contracts_active
FROM
customers c
LEFT OUTER JOIN contracts_jewels cj1 ON c.id = cj1.customer_id
LEFT OUTER JOIN contracts_objects co1 ON c.id = co1.customer_id
LEFT OUTER JOIN contracts_jewels cj2 ON
c.id = cj2.id AND
cj2.final_date >= NOW() AND
cj2.paid = 0 AND
cj2.transferred = 0
LEFT OUTER JOIN contracts_object co2 ON
c.id = co2.id AND
co2.final_date >= NOW() AND
co2.paid = 0 AND
co2.transferred = 0
GROUP BY c.id
Note: I haven't run this, but hopefully it sets you in the right direction.
simple solution:
SELECT SUM(c) FROM (
SELECT COUNT(1) as c FROM `tbl1` where ...
UNION
SELECT COUNT(1) as c FROM tbl2 where ...
UNION
SELECT COUNT(1) as c FROM tbl3 where ...
) al