Count three posibilities in one table - mysql

I have a table with subscribers which have two fields, actived and suscribed. I need to get the total number of subscribers, the number of activated and the number of suscribed in the same consult. I've tried to do this with a double left join but i need to group by a field that is not the primary key and i get error. I have my consult now like this:
FROM (SELECT subscribers.mailing_list, subscribers.mailing_list AS suscriptores, s2.inactivos AS inactivos, s3.excluidos AS excluidos
FROM subscribers
LEFT JOIN (SELECT id, mailing_list, COUNT(*) AS inactivos FROM subscribers WHERE subscribed = false GROUP BY id) s2 ON subscribers.id = s2.id
LEFT JOIN (SELECT id, mailing_list, COUNT(*) AS excluidos FROM subscribers WHERE excluded = true GROUP BY id) s3 ON subscribers.id = s3.id
) AS subs
GROUP BY subs.mailing_list```

Instead of needing 3 queries, you need case expressions inside an aggregate function, this is known as "conditional aggregates" e.g.
SELECT
mailing_list
, COUNT( CASE WHEN subscribed = FALSE THEN 1 END ) AS inactivos
, COUNT( CASE WHEN subscribed = TRUE THEN 1 END ) AS excluidos
, COUNT( * ) AS Total
FROM subscribers
GROUP BY
mailing_list

In MySQL, you can simplify this logic to:
SELECT mailing_list,
SUM( subscribed = FALSE ) AS inactivos,
SUM( subscribed = TRUE ) AS excluidos,
COUNT( * ) AS Total
FROM subscribers
GROUP BY mailing_list;
If subscribed is in fact boolean, you can further simplify this to:
SELECT mailing_list,
SUM( NOT subscribed ) AS inactivos,
SUM( subscribed ) AS excluidos,
COUNT( * ) AS Total
FROM subscribers
GROUP BY mailing_list;

Related

MySQL with multiple SELECT and GROUP BY

I have a problem with my database in mysql. I would like to have a table from my database with date, pat, dureeP, dureeC but this function doesn't GROUP BY :
select *
from (SELECT date_format(p.date, "%Y-%m") AS date
,p.pat
,AVG(a) AS dureeP
FROM timing as t, patient as p
WHERE t.id_p = p.id_p
AND t.pos=6
AND t.heure_fin IS NOT NULL
GROUP BY p.pat, MONTH(p.date), YEAR(p.datede)
) as T1,
(SELECT AVG(b) AS dureeC
FROM timing as t, patient as p
WHERE t.id_p = p.id_p
AND t.pos=3
AND t.heure_fin IS NOT NULL
GROUP BY p.pathologie, MONTH(p.date), YEAR(p.date)
) as T2
With one SELECT I can have what I want but with multiple SELECT I can Group By.
Do you have an idea?
Thank you
To simplify your query you might be able to use "conditional aggregates" which basically means placing a case expression inside an aggregate function
SELECT
p.pathologie
, MONTH(p.date)
, YEAR(p.date)
, AVG(CASE WHEN t.pos = 3 THEN b END) AS dureeC
, AVG(CASE WHEN t.pos = 6 THEN a END) AS dureeP
FROM timing AS t
INNER JOIN patient AS p ON t.id_p = p.id_p
WHERE t.heure_fin IS NOT NULL
GROUP BY
p.pathologie
, MONTH(p.date)
, YEAR(p.date)

MySQL counting elements without using count(*)

I'm practicing MySQL for an upcoming exam and need some help.
I have this db:
USER(Code, Name, Surname, Age)
THEATRE(Name, City, Capacity)
SUBSCRIPTION(ID, UserCode, TheatreName, Amount)
With these referential integrity constraints:
SUBSCRIPTION.UserCode->USER.Code
SUBSCRIPTION.TheatreName->THEATRE.Name
For exercise I need to write the query which determines code, name and surname of the users older than 50 and who has more than one subscription WITHOUT using the COUNT function.
I know that maybe a self-join could help but I really don't know how. Can anyone help me? Thank you very much.
You can use
EXISTS:
SELECT u.Code, u.Name, u.Surname
FROM USER u
WHERE u.Age > 50
AND EXISTS (
SELECT 1 FROM SUBSCRIPTION s WHERE u.Code = s.UserCode
)
Or JOIN
SELECT DISTINCT u.Code, u.Name, u.Surname
FROM USER u
JOIN SUBSCRIPTION s
ON u.Code = s.UserCode
WHERE u.Age > 50
Edited:
SELECT DISTINCT u.Code, u.Name, u.Surname
FROM USER u
JOIN SUBSCRIPTION s1
ON u.Code = s1.UserCode
JOIN SUBSCRIPTION s2
ON u.Code = s2.UserCode
WHERE s1.ID <> s2.ID
AND u.Age > 50
I believe the simplest way to accomplish this is to essentially redesign the count function into a sum function with a case statement thusly:
SELECT
u.NAME
, u.SURNAME
, u.CODE
, SUM(CASE WHEN t.SUBSCRIPTION IS NOT NULL THEN 1 ELSE 0 END) as TOTAL_SUBSCRIPTIONS -- IDENTICAL TO COUNT(s.*)
, COUNT(s.*) -- SHOULD MATCH THE TOTAL_SUBSCRIPTIONS
FROM
USER AS u
LEFT JOIN SUBSCRIPTION AS s
ON u.CODE = s.USERCODE
-- LEFT JOIN THEATRE AS t -- commented because I don't see a requirement for this table to be brought forward.
-- ON s.THEATRENAME = t.NAME
WHERE u.AGE > 50
HAVING SUM(CASE WHEN t.SUBSCRIPTION IS NOT NULL THEN 1 ELSE 0 END) > 1
Without using a CASE statment:
SELECT
u.NAME
, u.SURNAME
, u.CODE
, SUM( (select SUM(1) from SUBSCRIPTION WHERE s.USERCODE = u.CODE) ) as TOTAL_SUBSCRIPTIONS -- IDENTICAL TO COUNT(s.*)
FROM
USER AS u
WHERE u.AGE > 50

Finding top 5 results for multiple values in sql result

I have the following sql query:
SELECT v.venue_id, s.zip, COUNT( * )
FROM bcs_scans s
JOIN bcs_scanners sc ON s.uuid = sc.uuid
JOIN bcs_venues v ON sc.venue_id = v.venue_id
WHERE v.banlist_id = '625'
AND s.del =0
GROUP BY s.zip
ORDER BY COUNT( * ) DESC
Which returns the count of individual zip codes, their count, and associated venue.
How do I go about selecting the top 5 zip codes for each unique venue id?
I believe I can run a subquery that groups results by venue id with the top 5 zip counts, but I am unsure of where to start
Could be you select the result in this way ... a bit complex ..
using the having for extract the value that match the max count group by venue_id from your original query ..
SELECT v.venue_id as venue_id, s.zip as , COUNT( * ) as num
FROM bcs_scans s
JOIN bcs_scanners sc ON s.uuid = sc.uuid
JOIN bcs_venues v ON sc.venue_id = v.venue_id
WHERE v.banlist_id = '625'
AND s.del =0
GROUP BY s.zip
HAVING ( v.venue_id, COUNT( * )) in
(select venue_id, max(num)
from
(SELECT v.venue_id as venue_id, s.zip as , COUNT( * ) as num
FROM bcs_scans s
JOIN bcs_scanners sc ON s.uuid = sc.uuid
JOIN bcs_venues v ON sc.venue_id = v.venue_id
WHERE v.banlist_id = '625'
AND s.del =0
GROUP BY s.zip
ORDER BY COUNT( * ) DESC ) a t
group by venue_id)
ORDER BY COUNT( * ) limit 5

MySQL Filter result again

The goal here is to:
1. Fetch the row with the most recent date from EACH store for EACH ingredient.
2. From this result, compare the prices to find the cheapest store for EACH ingredient.
I can accomplish either the first or second goal in separate queries, but not in the same.
How can i filter out a selection and then apply another filter on the previous result?
EDIT:
I've been having problems with results that i get from MAX and MIN since it just fetches the rest of the data arbitrarily. To avoid this im supposed to join tables on multiple columns (i guess). Im not sure how this will work with duplicate dates etc.
I've included an image of a query and its output data.
If we use ingredient1 as an example, it exists in three separate stores (in one store twice on different dates).
In this case the cheapest current price for ingredient1 would be store3. If the fourth row dated 2013-05-25 was even cheaper, it would still not "win" due to it being out of date.
(Disregard brandname, they dont really matter in this problem.)
Would appreciate any help/input you can offer!
This question is really interesting!
So, first, we get the row with the most recent date from EACH store for EACH ingredient. (It is possible that the most recent dates from each store can be different.)
Then, we compare the prices from each store (regardless of the date) to find the least price for each ingredient.
The query below uses the GROUP_CONCAT function in good measure. Here's a SO question regarding the use of the function.
SELECT
i.name as ingredient_name
, MIN(store_price.price) as price
, SUBSTRING_INDEX(
GROUP_CONCAT(store_price.date ORDER BY store_price.price),
',',
1
) as date
, SUBSTRING_INDEX(
GROUP_CONCAT(s.name ORDER BY store_price.price),
',',
1
) as store_name
, SUBSTRING_INDEX(
GROUP_CONCAT(b.name ORDER BY store_price.price),
',',
1
) as brand_name
FROM
ingredient i
JOIN
(SELECT
ip.ingredient_id as ingredient_id
, stip.store_id as store_id
, btip.brand_id as brand_id
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.ingredient_price_id ORDER BY ip.date DESC),
',',
1
), UNSIGNED INTEGER) as ingredient_price_id
, MAX(ip.date) as date
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.price ORDER BY ip.date DESC),
',',
1
), DECIMAL(5,2)) as price
FROM ingredient_price ip
JOIN store_to_ingredient_price stip ON ip.ingredient_price_id = stip.ingredient_price_id
JOIN brand_to_ingredient_price btip ON ip.ingredient_price_id = btip.ingredient_price_id
GROUP BY
ip.ingredient_id
, stip.store_id) store_price
ON i.ingredient_id = store_price.ingredient_id
JOIN store s ON s.store_id = store_price.store_id
JOIN brand b ON b.brand_id = store_price.brand_id
GROUP BY
store_price.ingredient_id;
You can check the implementation on this SQL Fiddle.
The version below, which ignores the brand, is slightly smaller:
SELECT
i.name as ingredient_name
, MIN(store_price.price) as price
, SUBSTRING_INDEX(
GROUP_CONCAT(store_price.date ORDER BY store_price.price),
',',
1
) as date
, SUBSTRING_INDEX(
GROUP_CONCAT(s.name ORDER BY store_price.price),
',',
1
) as store_name
FROM
ingredient i
JOIN
(SELECT
ip.ingredient_id as ingredient_id
, stip.store_id as store_id
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.ingredient_price_id ORDER BY ip.date DESC),
',',
1
), UNSIGNED INTEGER) as ingredient_price_id
, MAX(ip.date) as date
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.price ORDER BY ip.date DESC),
',',
1
), DECIMAL(5,2)) as price
FROM ingredient_price ip
JOIN store_to_ingredient_price stip ON ip.ingredient_price_id = stip.ingredient_price_id
GROUP BY
ip.ingredient_id
, stip.store_id) store_price
ON i.ingredient_id = store_price.ingredient_id
JOIN store s ON s.store_id = store_price.store_id
GROUP BY
store_price.ingredient_id;
References:
Simulating First/Last aggregate functions in MySQL
This probably needs a couple of sub queries joined together.
This isn't tested (as I don't have your table definitions, nor any test data), but something like this:-
SELECT i.name AS ingredient,
ip.price,
ip.date,
s.name AS storename,
b.name AS brandname
FROM ingredient i
INNER JOIN ingredient_price ip
ON ingredient.ingredient_id = ingredient_price.ingredient_id
INNER JOIN store_to_ingredient_price stip
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
INNER JOIN store s
ON store_to_ingredient_price.store_id = store.store_id
INNER JOIN brand_to_ingredient_price btip
ON ingredient_price.ingredient_price_id = brand_to_ingredient_price.ingredient_price_id
INNER JOIN brand b
ON brand_to_ingredient_price.brand_id = brand.brand_id
INNER JOIN
(
SELECT i.ingredient_id,
stip.store_id,
ip.date,
MIN(ip.price) AS lowest_price
FROM ingredient i
INNER JOIN ingredient_price ip
ON ingredient.ingredient_id = ingredient_price.ingredient_id
INNER JOIN store_to_ingredient_price stip
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
INNER JOIN
(
SELECT i.ingredient_id,
stip.store_id,
MAX(ip.date) AS latest_date
FROM ingredient i
INNER JOIN ingredient_price ip
ON ingredient.ingredient_id = ingredient_price.ingredient_id
INNER JOIN store_to_ingredient_price stip
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
GROUP BY ingredient_id, store_id
) Sub1
ON i.ingredient_id = Sub1.ingredient_id
AND stip.store_id = Sub1.store_id
AND ip.date = Sub1.latest_date
GROUP BY i.ingredient_id, stip.store_id, ip.date
) Sub2
ON i.ingredient_id = Sub2.ingredient_id
AND stip.store_id = Sub2.store_id
AND ip.date = Sub2.date
AND ip.price = Sub2.lowest_price
Try this:
SELECT `newest`.ingredient, `newest`.store,
`newest`.brand, `newest`.price, `newest`.`latest_date`
FROM
(SELECT ingredient.name AS ingredient, store.name AS store,
brand.name AS brand, ingredient_price.price,
MAX( ingredient_price.date ) AS `latest_date`
FROM ingredient
LEFT OUTER JOIN ingredient_price
ON ingredient.ingredient_id = ingredient_price.ingredient_id
LEFT OUTER JOIN store_to_ingredient_price
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
LEFT OUTER JOIN store
ON store_to_ingredient_price.store_id = store.store_id
LEFT OUTER JOIN brand_to_ingredient_price
ON ingredient_price.ingredient_price_id = brand_to_ingredient_price.ingredient_price_id
LEFT OUTER JOIN brand
ON brand_to_ingredient_price.brand_id = brand.brand_id
GROUP BY ingredient.name) `newest`
ORDER BY `newest`.price
LIMIT 1

What's wrong on this query?

I'm selecting total count of villages, total count of population from my tables to build statistics. However, there is something wrong. It returns me everything (530 pop (there are 530 pop in total), (106 villages (there are 106 users in total)) in first row, next rows are NULLs
SELECT s1_users.id userid, (
SELECT count( s1_vdata.wref )
FROM s1_vdata, s1_users
WHERE s1_vdata.owner = userid
)totalvillages, (
SELECT SUM( s1_vdata.pop )
FROM s1_users, s1_vdata
WHERE s1_vdata.owner = userid
)pop
FROM s1_users
WHERE s1_users.dp >=0
ORDER BY s1_users.dp DESC
Try removing s1_users from inner SELECTS
You're already using INNER JOINs. Whan you list tables separated with comma, it is a shortcut for INNER JOIN.
Now, the most obvious answer is that your subqueries using aggregating functions (COUNT and SUM) are missing a GROUP BY clauses.
SELECT s1_users.id userid, (
SELECT count( s1_vdata.wref )
FROM s1_vdata, s1_users
WHERE s1_vdata.owner = userid
GROUP BY s1_vdata.owner
)totalvillages, (
SELECT SUM( s1_vdata.pop )
FROM s1_users, s1_vdata
WHERE s1_vdata.owner = userid
GROUP BY s1_vdata.owner
)pop
FROM s1_users
WHERE s1_users.dp >=0
ORDER BY s1_users.dp DESC
However, using subqeries in column list is really inefficient. It casues subqueries to be run once for each row in outer query.
Try like this instead
SELECT
s1_users.id AS userid,
COUNT(s1_vdata.wref) AS totalvillages,
SUM(s1.vdata.pop) AS pop
FROM
s1_users, s1_vdata --I'm cheating here! There's hidden INNER JOIN in this line ;P
WHERE
s1_users.dp >= 0
AND s1_users.id = s1_vdata.owner
GROUP BY
s1_users.id
ORDER BY
s1_users.dp DESC
SELECT s1_users.id AS userid,
(
SELECT COUNT(*)
FROM s1_vdata
WHERE s1_vdata.owner = userid
) AS totalvillages,
(
SELECT SUM(pop)
FROM s1_vdata
WHERE s1_vdata.owner = userid
) AS pop
FROM s1_users
WHERE dp >= 0
ORDER BY
dp DESC
Note that this is less efficient than this query:
SELECT s1_users.id AS user_id, COUNT(s1_vdata.owner), SUM(s1_vdata.pop)
FROM s1_users
LEFT JOIN
s1_vdata
ON s1_vdata.owner = s1_users.id
GROUP BY
s1_users.id
ORDER BY
dp DESC
since the aggregation needs to be done twice in the former.
SELECT userid,totalvillages,pop from
(
SELECT s1_users.id as userid, count( s1_vdata.wref ) as totalvillages
FROM s1_vdata, s1_users
WHERE s1_vdata.owner = userid
GROUP BY s1_users.id) tabl1 INNER JOIN
(
SELECT s1_users.id as userid, SUM( s1_vdata.pop ) as pop
FROM s1_users, s1_vdata
WHERE s1_vdata.owner = userid
GROUP BY s1_users.id) tabl2 on tabl1.userid = tabl2.userid