Count with conditions in different tables - mysql

I have 4 tables (songs, albums and two relation tables).
I have this 2 queries that I need to merge:
-QUERY 1)
SELECT l.name_language, count(s.id_song)
FROM language as l
LEFT JOIN song_has_languages as s ON l.id_language = s.id_language
GROUP BY l.id_language
HAVING COUNT(s.id_song) > 0
ORDER BY name_language ASC
The output:
name_language | songs
English | 5
Spanish | 1
-QUERY 2)
SELECT l.name_language, count(a.id_album)
FROM language as l
LEFT JOIN album_has_languages as a ON l.id_language = a.id_language
GROUP BY l.id_language
HAVING COUNT(a.album)> 0
ORDER BY name_language ASC
The output:
name_language | albums
English | 5
French | 2
My goal is this output:
name_language | total |
English | 10 |
Spanish | 1 |
French | 2 |
I want to print only the languages with a song or an album.

You can do what you want with union all and aggregation:
select l.name_language, sum(num_songs + num_albums) as total
from language l left join
((select shl.id_language, count(*) as num_songs, 0 as num_albums
from song_has_languages shl
group by shl.id_language
) union all
(select ahl.id_language, 0 as num_songs, count(*) as num_albums
from album_has_languages ahl
group by ahl.id_language
)
) sa
on sa.id_language = l.id_language
group by l.id_language, l.name_language;
You could also express this with LEFT JOINs:
select l.name_language,
( coalesce(num_songs, 0) + coalesce(num_albums, 0) ) as total
from language l left join
(select shl.id_language, count(*) as num_songs
from song_has_languages shl
group by shl.id_language
) shl
on shl.id_language = l.id_language left join
(select ahl. id_language, count(*) as num_albums
from album_has_languages ahl
group by ahl.id_language
) ahl
on ahl.id_language = l.id_language;

Related

Query to group without lost IF function

I created a query to search for all my stock products that are in orders placed, and I created an alias "total_vendido" that adds the products when they are kits or units, so far this is ok. But now I need to group the sizes and add this "total_vendido" alias by size.
Query:
SELECT `gp`.`id`, `gp`.`data`, `gp`.`status`, `gp`.`situacao`, `gp`.`nome`,
`gp`.`razao_social`, `gp`.`email`, `gp`.`telefone`,
`itens`.*,
IF(itens.tipo = 'K',
SUM(itens.qtde_prod) * itens.qtde_lote,
SUM(itens.qtde_prod)
) AS total_vendido,
`estoq`.`titulo`
FROM `ga845_pedidos_view` `gp`
JOIN `ga845_pedido_itens` `itens` ON `itens`.`pedido_id` = `gp`.`id`
JOIN `ga845_produtos` `prod` ON `prod`.`id` = `itens`.`produtos_id`
JOIN `ga845_produtos_estoque` `estoq` ON `estoq`.`id` = `prod`.`estoques_id`
WHERE `gp`.`situacao` IN('Pedido Realizado', 'Pagamento Aprovado',
'Pedido em Separação', 'Pedido Separado')
AND date(gp.data) >= '2020-07-25'
AND date(gp.data) <= '2020-07-25'
AND `estoq`.`id` IN('24')
GROUP BY `itens`.`tamanho_prod`, `estoq`.`id`
ORDER BY `estoq`.`id` ASC, `itens`.`tamanho_prod` ASC
Current result (only important columns)
tamanho_prod | tipo | total_vendido
G | K | 5
G | U | 1
M | K | 1
P | U | 8
Expected result (only important columns)
tamanho_prod | total_vendido
G | 6
M | 1
P | 8
Code related to Expected result (only important columns)
SELECT
, `itens`.`tamanho_prod`
, SUM( IF(itens.tipo = 'K',
itens.qtde_prod * itens.qtde_lote,
itens.qtde_prod
) AS total_vendido
FROM `ga845_pedidos_view` `gp`
JOIN `ga845_pedido_itens` `itens` ON `itens`.`pedido_id` = `gp`.`id`
JOIN `ga845_produtos` `prod` ON `prod`.`id` = `itens`.`produtos_id`
JOIN `ga845_produtos_estoque` `estoq` ON `estoq`.`id` = `prod`.`estoques_id`
WHERE `gp`.`situacao` IN('Pedido Realizado', 'Pagamento Aprovado',
'Pedido em Separação', 'Pedido Separado')
AND date(gp.data) >= '2020-07-25'
AND date(gp.data) <= '2020-07-25'
AND `estoq`.`id` IN('24')
GROUP BY `itens`.`tamanho_prod`
ORDER BY `itens`.`tamanho_prod` ASC
if you want an aggregated result just for itens.tamanho_prod .. then you should use group by only for this column ... and move the SUM() outside the if condition

Mysql Bayesian and sort by star ratings

Say I have two tables. businesses and reviews for businesses.
businesses table:
+----+-------+
| id | title |
+----+-------+
reviews table:
+----+-------------+---------+------+
| id | business_id | message | rate |
+----+-------------+---------+------+
each review has a rate ( 1 to 5 stars )
I want to sort businesses by their reviews rates, based on Bayesian Ranking with condition of having at least 2 reviews.
Here is my query:
SELECT b.id,
(SELECT COUNT(r.rate) as rr FROM reviews r WHERE r.business_id = b.id) as rr,
(SELECT
((COUNT(r.rate) / (COUNT(r.rate) + 2)) AVG(r.rate) +
(2 /(COUNT(r.rate) + 2)) 4)
FROM reviews r where r.business_id = b.id AND rr > 2
) as score
FROM businesses b
order by score desc
LIMIT 4
this will output me:
+------+----+------------+
| id | rr | score |
+------+----+------------+
| 992 | 14 | 4.31250000 |
+------+----+------------+
| 237 | 3 | 4.2000000 |
+------+----+------------+
| 19 | 5 | 4.0000000 |
+------+----+------------+
| 1009 | 12 | 3.9285142 |
+------+----+------------+
I have two questions:
as you see in ((COUNT(r.rate) / (COUNT(r.rate) + 2)) AVG(r.rate) +
(2 /(COUNT(r.rate) + 2)) 4) FROM reviews r where r.business_id = b.id AND rr > 2 ) some functions are running more than once, like COUNT or AVG. are they running once in background and maybe caches the resuslt? OR run for every single call?
is there any equivalent query for this but more optimize?
thanks in advance.
I would hope that MySQL would optimise the multiple counts away, but not certain.
However you could rearrange you query to join against a sub query. This way you are not performing 2 sub queries for every row.
SELECT b.id,
sub0.rr,
sub0.score
FROM businesses b
INNER JOIN
(
SELECT r.business_id,
COUNT(r.rate) AS rr ,
((COUNT(r.rate) / (COUNT(r.rate) + 2)) AVG(r.rate) + (2 /(COUNT(r.rate) + 2)) 4) AS score
FROM reviews r
GROUP BY r.business_id
HAVING rr > 2
) sub0
ON sub0.business_id = b.id
ORDER BY score DESC
LIMIT 4
Note that the result here are very slightly different as it will exclude records with only 2 reviews, while your query will still return them but with a score of NULL. I have left in the apparent missing operators (ie, before AVG(r.rate) and before 4) AS score from your original query.
Using the above idea you could recode it to return both the count and the average rate in the sub query, and just use the values of those returned columns for the calculation.
SELECT b.id,
sub0.rr,
((rr / (rr + 2)) arr + (2 /(rr + 2)) 4) AS score
FROM businesses b
INNER JOIN
(
SELECT r.business_id,
COUNT(r.rate) AS rr ,
AVG(r.rate) AS arr
FROM reviews r
GROUP BY r.business_id
HAVING rr > 2
) sub0
ON sub0.business_id = b.id
ORDER BY score DESC
LIMIT 4

Inner join with where

I need to get Max bid and that username but result is coming in wrong way
Here is my two tables
Product
id | name | username
1 | A | deen
2 B | ann
Bid
id | c_bid | username
1 | 10 | ann
1 | 12 | ann
1 | 13 | ann
2 | 10 | ann
1 | 15 | Hel
1 | 16 | Hel
SQL
SELECT name, bid.username AS username, MAX(bid.c_bid) AS c_bid FROM product INNER JOIN bid
ON gems.id= bidding.id WHERE bid.id = '1'
Result
name | c_bid | username
A | 16 | ann
Why is this result coming on this sql?
Since you are using an aggregate function you need to include a GROUP BY.
SELECT name,
bid.username AS username,
MAX(bid.c_bid) AS c_bid
FROM product
INNER JOIN bid
ON product.id= bid.id
WHERE bid.id = '1'
GROUP BY name, bid.username
See SQL Fiddle with Demo
You are getting strange results because MySQL uses an Extension to GROUP BY that allows you to use an aggregate function without using a GROUP BY but this could cause your result to be incorrect.
The GROUP BY makes it so you will return the max(bid) for each item that you are grouping by, in your case it is name and `username.
If you want to return only one max(bid) for each product id, then you could use:
SELECT name,
bid.username AS username,
bid.c_bid
FROM product
INNER JOIN bid
ON product.id= bid.id
INNER JOIN
(
SELECT max(c_bid) c_bid, id
FROM bid
GROUP BY id
) b
on bid.id = b.id
and bid.c_bid = b.c_bid
WHERE bid.id = '1'
See SQL Fiddle with Demo
Use GROUP BY.
SELECT name, bid.username AS username, MAX(bid.c_bid) AS c_bid FROM product INNER JOIN bid
ON gems.id= bidding.id WHERE bid.id = '1' GROUP BY bid.username;
SELECT
p.username,
MAX(b.c_bid) AS max_c_bid
FROM product p
LEFT JOIN bid b ON (p.username = b.username)
GROUP BY p.username;
SELECT
pr.name,
mb.c_bid,
pr.username
FROM
(
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY c_bid DESC), ',', 1) AS id,
MAX(c_bid) AS c_bid
FROM bid
GROUP BY id
) AS mb
JOIN product AS pr ON(pr.id = mb.id)
Example: http://sqlfiddle.com/#!2/0a6c9/2

How to query and group every continuous number series in MySQL?

I have this freight.or_nos table which contains series of receipt numbers. I want to list all the or's being issued excluding the status='Cancelled' making the series broken in groups.
For example I have this receipt stab 125001-125050, and 125020 is cancelled so the listing result would be:
+-------------------------------------------------------+
| OR Start | OR End | Quantity | Amount |
+-------------------------------------------------------+
| 125001 | 125019 | 19 | |
+-------------------------------------------------------+
| 125021 | 125050 | 30 | |
+-------------------------------------------------------+
This seems to be a tough query.
Thanks for reading but I already made it, just now! :)
Here's my query(disregard the other characters it's form our CGI):
{.while SELECT `start`,`end`,or_prefix,or_suffix,SUM(a.amount) AS g_total,COUNT(*) AS qcount FROM (SELECT l.id AS `start`,( SELECT MIN(a.id) AS id FROM ( SELECT a.or_no AS id FROM freight.`or_nos` a WHERE a.status!='Cancelled' AND a.log_user = 0#user_teller AND DATE(a.or_date)='#user_date`DATE' AND IF(a.status='Default' AND a.amount=0,0,1) ) AS a LEFT OUTER JOIN ( SELECT a.or_no AS id FROM freight.`or_nos` a WHERE a.status!='Cancelled' AND a.log_user = 0#user_teller AND DATE(a.or_date)='#user_date`DATE' AND IF(a.status='Default' AND a.amount=0,0,1) ) AS b ON a.id = b.id - 1 WHERE b.id IS NULL AND a.id >= l.id ) AS `end` FROM ( SELECT a.or_no AS id FROM freight.`or_nos` a WHERE a.status!='Cancelled' AND a.log_user = 0#user_teller AND DATE(a.or_date)='#user_date`DATE' AND IF(a.status='Default' AND a.amount=0,0,1) ) AS l LEFT OUTER JOIN ( SELECT a.or_no AS id FROM freight.`or_nos` a WHERE a.log_user = 0#user_teller AND DATE(a.or_date)='#user_date`DATE' AND IF(a.status='Default' AND a.amount=0,0,1) ) AS r ON r.id = l.id - 1 WHERE r.id IS NULL) AS k LEFT JOIN freight.`or_nos` a ON a.`or_no` BETWEEN k.start AND k.end AND DATE(a.`or_date`)='#user_date`DATE' AND a.log_user =0#user_teller AND IF(a.status='Default' AND a.amount=0,0,1) AND a.status!='Cancelled' GROUP BY `start`}
{.start}{.x.24.12:end}{.x`p0.40.-5:qcount}{.x`p2.57.-15:g_total}{.asc 255}
{.wend}{.asc 255}

difficulties getting a 3 table join to return expected results

I'm having some difficulty getting to the bottom of this sql query.
Tables:
--Tickets-- --Finance-- --Access--
id_tickets id_finance id_access
name_tickets id_event id_event
cat_tickets id_tickets id_tickets
sold_finance scan_access
Finance and Access both contain a row for multiple of each ticket type as listed in tickets.
and I'm trying to get:
cat_tickets | total_sold | total_scan
-------------------------------------
single | 3043 | 2571
season | 481 | 292
comp | 114 | 75
-------------------------------------
total | 3638 | 2938
The closest I've been to the result I've used:
SELECT tickets.cat_tickets, COALESCE(SUM(finance.sold_finance), 0) AS total_sold, COALESCE(SUM(access.scan_access), 0) AS total_scan
FROM finance INNER JOIN tickets ON finance.id_tickets = tickets.id_tickets
INNER JOIN access ON access.id_tickets = tickets.id_tickets
WHERE access.id_event = 235 AND finance.id_event = access.id_event
GROUP BY tickets.cat_tickets
ORDER BY tickets.cat_tickets DESC
but that just returns:
cat_tickets | total_sold | total_scan
-------------------------------------
single | 4945 | 4437
season | 954 | 599
comp | 342 | 375
-------------------------------------
total | 6241 | 5411
Any ideas where I could be going wrong?
Thanks!
The problem is the relation between access and finance tables, you have to join them. Even if you LEFT JOIN the table the predicate finance.id_event = access.id_event will make it INNER JOIN. As a work around, use UNION like this:
SELECT
tickets.cat_tickets,
SUM(CASE WHEN a.Type = 'f' THEN num ELSE 0 END) AS total_sold,
SUM(CASE WHEN a.Type = 'a' THEN num ELSE 0 END) AS total_scan
FROM tickets
LEFT JOIN
(
SELECT 'f' Type, id_tickets, sold_finance num
FROM finance f
WHERE id_event = 1
UNION ALL
SELECT 'a', id_tickets, scan_access
FROM access
WHERE id_event = 1
) a ON a.id_tickets = tickets.id_tickets
GROUP BY tickets.cat_tickets;
SQL Fiddle Demo
Although I am fully clear on what you want, just try this query if the result of this is what you are expecting.
SELECT tickets.cat_tickets, COALESCE(SUM(finance.sold_finance), 0) AS total_sold, COALESCE(SUM(access.scan_access), 0) AS total_scan
FROM finance LEFT JOIN tickets ON finance.id_tickets = tickets.id_tickets
LEFT JOIN access ON access.id_tickets = tickets.id_tickets
WHERE access.id_event = 235
GROUP BY tickets.cat_tickets
ORDER BY tickets.cat_tickets DESC
Disclaimer: This query is not tested due to incomplete data on the question.
SELECT z.Cat_tickets,
COALESCE(x.total_sold,0) total_sold,
COALESCE(y.total_scan,0) total_scan
FROM tickets z
LEFT JOIN
(
SELECT a.id_tickets,
a.cat_tickets,
SUM(b.sold_finance) total_sold
FROM tickets a
INNER JOIN finance b
ON a.id_tickets = b.id_tickets
WHERE id_event = 235
GROUP BY a.id_tickets, a.cat_tickets
) x ON z.id_tickets = x.id_tickets
LEFT JOIN
(
SELECT aa.id_tickets,
aa.cat_tickets,
SUM(bb.scan_access) total_scan
FROM tickets aa
INNER JOIN Access bb
ON aa.id_tickets = bb.id_tickets
WHERE id_event = 235
GROUP BY aa.id_tickets, aa.cat_tickets
) y ON z.id_tickets = y.id_tickets