Count number of lines depending of criteria (similar to group by) - mysql

I am actually facing an issue with MySQL, I need to have a similar behavior of the GROUP BY but without grouping.
Here is an example for just one table named 'resident' with 2 columns: 'name', 'city'.
If i want to count the number residents for each city, i just need to do this :
SELECT resident.city, count(resident.name) as 'Nb residents'
FROM resident
GROUP BY resident.city
So the result would be like :
| city | Nb residents |
| NY | 3 |
| HK | 1 |
| Rome | 2 |
...
But i am looking for a way to display my result like that :
| name | city | Nb residents |
| Name1 | NY | 3 |
| Name2 | NY | 3 |
| Name3 | NY | 3 |
| Name4 | HK | 1 |
| Name5 | Rome | 2 |
| Name6 | Rome | 2 |
Is it possible to do that ?
Thanks for your help.

SELECT resident.name, resident.city, (select count(a.name) from resident a where a.city = resident.city) as 'Nb residents'
FROM resident

You could to this with the following query:
SELECT t1.`name`, t2.`city`, t3.`Nb residents`
FROM resident t1
JOIN ( SELECT resident.city, count(resident.name) as 'Nb residents' FROM resident GROUP BY resident.city ) t2
ON t1.`city`= t2.`city`
This extracts the amount of residents per city in a subquery and then joins this with the names of the very table.

Related

Counting "subcolumns" with mySQL

Guys let me make myself clear. I'm studying MYSQL and practicing the function "count()". I have a table called "City", where I have ID, name, CountryCode, district, and Population. My first idea was to know how many cities I have by country
SELECT *, Count(name) as "total" FROM world.city GROUP BY countrycode;
It worked, an extra column was created with the number of cities by each country. I would like to know how many countries I have by counting the number of distinct rows (I know that a have this information on the bottom of the WorkBench, but I would like to know to make this information appear on my query). I tried to add a Count(CountryCode), but it didn't work as I was expecting, a number 4079 appeared, which is the total number of cities that I have. I figured out that my "Count()" is calculating the number of rows inside each Country, not counting the number of codes that I have for each country. Is that possible to get this information?
(A mini-lesson for a Novice.)
The first thing to learn is that COUNT(*) is the usual way to use COUNT. And you get the number of rows. In contrast, COUNT(name) counts the number of rows with non-NULL name values.
Then comes the way to use DISTINCT. It is not a function. So COUNT(DISTINCT a,b) counts the number of different combinations of a and b. And COUNT(DISTINCT(a)) though it works 'fine' and 'correctly', the parens are redundant. So use COUNT(DISTINCT a).
Don't use * with GROUP BY. That is, SELECT *, ... GROUP BY ... is improper. The usual way to say something like your query is
SELECT countrycode, COUNT(*) AS "total"
FROM world.city
GROUP BY countrycode;
For provinces in Canada (which I happen to have a table of):
SELECT province, COUNT(*) AS "total" FROM world.canada GROUP BY province;
+---------------------------+-------+
| province | total |
+---------------------------+-------+
| Alberta | 573 |
| British Columbia | 716 |
| Manitoba | 299 |
| New Brunswick | 210 |
| Newfoundland and Labrador | 474 |
| Northwest Territories | 94 |
| Nova Scotia | 331 |
| Nunavut | 107 |
| Ontario | 891 |
| Prince Edward Island | 57 |
| Quebec | 1045 |
| Saskatchewan | 573 |
| Yukon | 114 |
+---------------------------+-------+
Note that a few cities show up in multiple provinces:
SELECT COUNT(DISTINCT city), COUNT(*) FROM world.canada;
+----------------------+----------+
| COUNT(DISTINCT city) | COUNT(*) |
+----------------------+----------+
| 5248 | 5484 |
+----------------------+----------+
Munch on this; there are some more lessons to learn:
SELECT city, COUNT(*) AS ct, GROUP_CONCAT(DISTINCT state)
FROM world.us
GROUP BY city
ORDER BY COUNT(*)
DESC LIMIT 11;
+-------------+----+----------------------------------+
| city | ct | GROUP_CONCAT(DISTINCT state) |
+-------------+----+----------------------------------+
| Springfield | 11 | FL,IL,MA,MO,NJ,OH,OR,PA,TN,VA,VT |
| Clinton | 10 | CT,IA,MA,MD,MO,MS,OK,SC,TN,UT |
| Madison | 8 | AL,CT,IN,ME,MS,NJ,SD,WI |
| Lebanon | 8 | IN,ME,MO,NH,OH,OR,PA,TN |
| Auburn | 7 | AL,CA,IN,ME,NH,NY,WA |
| Burlington | 7 | IA,MA,NC,NJ,VT,WA,WI |
| Washington | 7 | DC,IL,IN,MO,NC,PA,UT |
| Farmington | 7 | ME,MI,MN,MO,NH,NM,UT |
| Canton | 6 | GA,IL,MA,MI,MS,OH |
| Monroe | 6 | GA,LA,MI,NC,WA,WI |
| Lancaster | 6 | CA,NY,OH,PA,SC,TX |
+-------------+----+----------------------------------+
As for the number of cities in a country, that belongs in a the table Countries, not in the table Cities. Then use a JOIN when you want to put them together.

How to count by joining two tables

I have 2 tables like this.
Table : Family Members
----------------------------------
|Address | Name |
----------------------------------
|North Jakarta City | Andra |
|North Jakarta City | Halim |
|South Jakarta City | Irma |
|Thousand Island Village | Dian |
----------------------------------
Table : Member Details
---------------
| Name | Age |
---------------
| Andra | 1 |
| Halim | 50 |
| Irma | 20 |
| Dian | 4 |
---------------
What is the correct query if I want to count members between the ages 0 and 4 who live in a 'city'? I've tried using this query but the result is incorrect. The correct result should be 1 since only Andra who lives in a city and between the ages 0 and 4. Please help me.
SELECT COUNT(family_members.name) AS total FROM family_members, member_details
WHERE family_members.address LIKE '%City%' AND member_details.age BETWEEN 0 AND 4
You need a join
SELECT COUNT(fm.name) AS total
FROM family_members fm
Join member_details md on md.Name = fm.Name
WHERE fm.address LIKE '%City%' AND md.age BETWEEN 0 AND 4
with you syntax, you may add this in the where clause (because your query will generate a cartesian product).
BUT : you should really use the JOIN syntax
AND family_members.Name = member_details.Name
EDIT
By the way, I would strongly suggest to use surrogate keys in your tables (a name is not really something unique)
You should consider redesigning your database like:
----------------------------------
| user_id | Name | Age | city_id |
----------------------------------
| 1 | Andra | 1 | 1 |
| 2 | Halim | 50 | 1 |
| 3 | Irma | 20 | 1 |
| 4 | Dian | 4 | 2 |
----------------------------------
------------------------------------
|city_name | city_id |
------------------------------------
|North Jakarta City | 1 |
|Thousand Island Village | 2 |
------------------------------------
SELECT COUNT(*) AS total
FROM family_member
JOIN city on family_member.city_id = city.city_id
WHERE city.city_name= 'City' AND family_member.age BETWEEN 0 AND 4;
Also you should add indexes.

Conditionals and Aggregates across Multiple Tables

I have table that looks like the following:
`units`
+----+------+-------+---------------+-------+
| id | tech | jobID | city | units |
+----+------+-------+---------------+-------+
| 1 | 1234 | 8535 | San Jose | 3 |
| 2 | 1234 | 8253 | San Francisco | 4 |
| 3 | 1234 | 2457 | San Francisco | 5 |
| 4 | 1234 | 8351 | Mountain View | 8 |
+----+------+-------+---------------+-------+
and a view that uses this data to do some computations:
`total`
+----+--------+------+-------+
| id | name | tech | total |
+----+--------+------+-------+
| 1 | Dan | 1234 | 12 |
| 2 | Dan SF | 1234 | 12 |
+----+--------+------+-------+ ...
My problem is that I am trying to sum up the amount of units Dan completed in San Francisco and the amount of units he did elsewhere (need to specifically track how many units were completed in SF). However, I'm unsure of how to do this within my select query and if you look at my current total table, you'll see that both total values are simply summing all of the units regardless of city.
I want to get the following:
`total`
+----+--------+------+-------+
| id | name | tech | total |
+----+--------+------+-------+
| 1 | Dan | 1234 | 11 |
| 2 | Dan SF | 1234 | 9 |
+----+--------+------+-------+ ...
I need help writing my SELECT because I'm unsure of how to use CASE to get the desired result. I've tried the following:
SELECT otherTable.name AS name, units.tech AS tech,
(CASE WHEN City = 'SAN FRANCISCO' THEN SUM(units)
ELSE SUM(units)
) AS total
FROM units, otherTable
GROUP BY name
but clearly this won't work since I'm not differentiating between cities in the two aggregates.
Any help is greatly appreciated.
EDIT: The SELECT query for my current view (with join info) is as follows:
SELECT otherTable.name, units.tech, SUM(units.units)
FROM units
LEFT JOIN otherTable ON otherTable.tech = units.tech
GROUP BY name
As for otherTable, it simply associates each tech ID with a name:
`otherTable`
+----+--------+------+-----------+
| id | name | tech | otherInfo |
+----+--------+------+-----------+
| 1 | Dan | 1234 | ...... |
+----+--------+------+-----------+
First off, it appears that your base query is wrong. There isn't nothing about the join between units and otherTable, but I don't know enough to put it in.
It seems strange to me that you would want it broken out into rows instead of columns, but you could do the following:
SELECT otherTable.name AS name, units.tech AS tech,
SUM(units) AS total
FROM units, otherTable
-- not sure if this section should exclude 'SAN FRANCISO' or not
GROUP BY name
UNION ALL
SELECT otherTable.name || ' SF' AS name, units.tech AS tech,
SUM(units) AS total
FROM units, otherTable
WHERE City = 'SAN FRANCISCO'
GROUP BY name
This would give you
+--------+------+-------+
| name | tech | total |
+--------+------+-------+
| Dan | 1234 | 11 |
| Dan SF | 1234 | 9 |
+--------+------+-------+
Or if you want separate columns, you could do this
SELECT otherTable.name AS name, units.tech AS tech,
SUM(units) AS total,
SUM(CASE WHEN City = 'SAN FRANCISCO' THEN units
ELSE 0
) AS sf_total
FROM units, otherTable
GROUP BY name
This would give you
+--------+------+-------+----------+
| name | tech | total | sf_total |
+--------+------+-------+----------+
| Dan | 1234 | 11 | 9 |
+--------+------+-------+----------+

Top 3 countries

My question is surely banal but i can't set up an sql query that allows me to make a list of top 3 countries for a sport-event summary table.
I explain me better: in a sport event i have a lot of athletes from different countries and i need to produce a summary table showing countries that won more medals.
Here is an example:
--------------------------------------------
|id | name | activity | country |
--------------------------------------------
| 1 | John | 100m | USA |
| 2 | Andy | 200m | CANADA |
| 3 | Frank | 400m | USA |
| 4 | Ian | 400m | GERMANY |
| 5 | Anthony | 100m | USA |
| 6 | Eric | 400m | CANADA |
| 7 | Mike | 200m | UK |
| 8 | Dave | 200m | GERMANY |
| 9 | Richard | 100m | USA |
| 10| Max | 100m | USA |
| 11| Randy | 100m | USA |
| 12| Maurice | 400m | CANADA |
| 13| Col | 100m | UK |
| 14| Jim | 400m | USA |
| 15| Adam | 200m | BRAZIL |
| 16| Ricky | 100m | UK |
| 17| Emily | 400m | USA |
| 18| Serge | 200m | UK |
| 19| Alex | 400m | FRANCE |
| 20| Enamuel | 100m | USA |
--------------------------------------------
The summary table i wish to obtain is the following:
Top 3 countries
--------------------------------------
| position | country | medals |
--------------------------------------
| 1 | USA | 9 |
| 2 | UK | 4 |
| 3 | CANADA | 3 |
--------------------------------------
How can build the qsl query?
Thanks in advance for your kind answer.
Mattew
Without the position column, this is quite easy. Just do the following
SELECT Country,COUNT(*) AS medals
FROM MyTable
GROUP BY Country
ORDER BY COUNT(*) DESC
LIMIT 3;
There is some more complicated code for getting the "position" column out, but unless you need it, it probably isn't necessary, and you can just get those numbers using a counter on the processing code. If you're interested, the code would be something like this.
SELECT #rownum:=#rownum+1 AS Position,Country,Medals FROM
(
SELECT Country,COUNT(*) AS medals
FROM Medals
GROUP BY Country
ORDER BY COUNT(*) DESC
LIMIT 3
) AS Stats, (SELECT #rownum:=0) RowNum;
The above query has been tested and appears to be working as you need it to be.
CREATE TABLE IF NOT EXISTS top_three_countries
(position INT NOT NULL AUTO_INCREMENT, country VARCHAR(30), medals INT);
TRUNCATE TABLE top_three_countries;
INSERT INTO top_three_countries (country, medals)
SELECT country, count(*) total
FROM medal
GROUP BY country
ORDER BY total DESC
LIMIT 3;
This will produce a summary table (top_three_countries) as you describe.
It's much simpler without the rank, if you can add that in your programs' logic instead:
SELECT `country`, COUNT(*) total
FROM medal
GROUP BY country
ORDER BY total DESC
LIMIT 3
Looks like Kibbee beat me too it but for a guaranteed GROUP BY compatible query you can wrap the above in a SELECT of its own:
SELECT #n:=#n+1 AS rank, country, total
FROM
(
SELECT `country`, COUNT(*) total
FROM medal
GROUP BY country
ORDER BY total DESC
LIMIT 3
) t1,
(SELECT #n:=0) t2

MySQL - No result when joining tables using a NULL value

Similar question here but this is slightly different...
I have two tables that I want to join:
location
---------------------------
| id | city | state_id |
---------------------------
| 1 | Denver | 6 |
| 2 | Phoenix | 2 |
| 3 | Seattle | NULL |
---------------------------
state
-------------------
| id | name |
-------------------
| 1 | Alabama |
| 2 | Alaska |
| 3 | Arizona |
| 4 | Arkansas |
| 5 | California |
| 6 | Colorado |
-------------------
SELECT
location.id,
location.city,
state.name
FROM
location
JOIN
state ON state.id = location.state_id;
However, in the case where location.state_id happens to be NULL (perhaps the person inputting the data forgot to select a state), the query would not return a result, but that doesn't mean the location doesn't exist.
How do I get around this problem and somehow display all the locations, even though the state_id might be NULL ?
Use a LEFT OUTER JOIN
SELECT
location.id,
location.city,
state.name
FROM
location
LEFT OUTER JOIN
state ON state.id = location.state_id;