I have a mysql table "lessons" from which I want to output a course list for each teacher (see picture).
Up to now I wanted to use GROUP BY and GROUP_CONCAT for the output, but I failed so far. I have no idea at the moment. Can anyone help me?
If I understand correctly, lessons that are at the same time (based on the last two columns) and for the same teacher and have the same name should be combined as a "course".
If this is the case, you can use two levels of aggregation:
select group_concat(ids) as ids,
group_concat(class_id) as class_ids,
teacher, name, weekday_hours
from (select l.class_id, l.teacher_id, l.name,
group_concat(l.weekday, ':', l.hour order by l.weekday, l.hour) as weekday_hours,
group_concat(l.id order by l.id) as ids
from lessons l
group by l.class_id, l.teacher_id, l.name
) l
group by teacher, name, weekday_hours;
Related
I'm using MySQL and am currently stuck trying to understand how a piece of code works. This is regarding the Hackerrank SQL question titled "Challenges". The problem statement is as follows
Julia asked her students to create some coding challenges. Write a
query to print the hacker_id, name, and the total number of challenges
created by each student. Sort your results by the total number of
challenges in descending order. If more than one student created the
same number of challenges, then sort the result by hacker_id. If more
than one student created the same number of challenges and the count
is less than the maximum number of challenges created, then exclude
those students from the result.
I have found a working MySQL solution courtesy of this page That uses the following code:
SELECT c.hacker_id, h.name, COUNT(c.challenge_id) AS cnt
FROM Hackers AS h JOIN Challenges AS c ON h.hacker_id = c.hacker_id
GROUP BY c.hacker_id, h.name HAVING
cnt = (SELECT COUNT(c1.challenge_id) FROM Challenges AS c1 GROUP BY c1.hacker_id ORDER BY COUNT(*) DESC LIMIT 1) OR
cnt NOT IN (SELECT COUNT(c2.challenge_id) FROM Challenges AS c2 GROUP BY c2.hacker_id HAVING c2.hacker_id <> c.hacker_id)
ORDER BY cnt DESC, c.hacker_id;
As of now I understand the problem statement up until "If more than one student created the same number of challenges and the count is less than the maximum number of challenges created, then exclude those students from the result." I simply have no clue how to structure a query to solve that statement.
In the code provided above, I understand everything it does until this section
cnt NOT IN (SELECT COUNT(c2.challenge_id) FROM Challenges AS c2 GROUP BY c2.hacker_id HAVING c2.hacker_id <> c.hacker_id)
Can anyone please help me understand what this line accomplishes and the logic behind it? Specifically I don't know what c2.hacker_id <> c.hacker_id is supposed to do. I'm guessing the whole line selects the number of challenge_ids done by particular hacker_ids who aren't the same person, but I have no clue how that solves the query.
Suppose you get a list of hacker ids and sub counts out of the query when you don't have this clause:
hacker, counter
1, 10
2, 9
3, 9
Two and three shouldn't be in there because they're tied on count, so we can implement it as excluding anyone who counted 9
Consider that conceptually the database will run the query for every row in the results: when processing hacker 2 row the query gets a list of challenge counts where someone whose id isnt 2. This means when considering hacker 2, the dB will pull back a list of the following counts:
10, --it comes from hacker 1
9 --it comes from hacker 3
The database then goes "i'm processing hacker 2, whose count is 9. I may only include hacker 2 in the results if hacker 2's count(9) is not in the following list of values: 10, 9. Oh, 9 is in the list of banned values. I'll exclude hacker 2 from the results
Repeat for hacker 3, this time a 9 count comes from hacker 2 so 3 is also excluded
Analytic functions greatly help with a question like this, so I will offer a solution using MySQL 8+, which, moving forward, will be the likely database which a reader of your question would be using (and HackerRank will at some point also be using MySQL 8+).
WITH cte AS (
SELECT
c.hacker_id,
h.name,
COUNT(c.challenge_id) AS cnt,
ROW_NUMBER() OVER (ORDER BY COUNT(c.challenge_id) DESC) rn,
MIN(c.hacker_id) OVER (PARTITION BY COUNT(c.challenge_id)) hacker_id_min,
MAX(c.hacker_id) OVER (PARTITION BY COUNT(c.challenge_id)) hacker_id_max
FROM Hackers AS h
INNER JOIN Challenges AS c
ON h.hacker_id = c.hacker_id
GROUP BY
c.hacker_id,
h.name
)
SELECT
hacker_id,
name,
cnt
FROM cte
WHERE
rn = 1 OR hacker_id_min = hacker_id_max
ORDER BY
cnt DESC,
c.hacker_id;
This answer words by computing a row number, sorted in descending order by the count. It also computes the min and max hacker_id values for each partition of challenge counts. Records are retained if they belong to the highest count, regardless of ties for first place. And records are also retained if the given count is only associated with a single user.
Hello I started using MySQL and I seem to be having trouble trying to nest formulas. I'm working on a problem and the question is, What country has the most cities?
I have two tables:
CITY:
city
city_id
country_id
COUNTRY:
country
country_id
I am able to join the two tables together to get the cities to match with the countries but after that I don't know how to count to the country that has the most cities.
My current code is:
SELECT city.city, country.country
FROM city, country
WHERE city.country_id = country.country_id
From there I don't know how to add a count function without it coming back as as error. I dont fully understand the basics of nesting.
Thank you, any help is appreciated.
You do not need to do nesting necessarily. To simply know, which country has most number of cities, just use group by:
select country_id, count(1)
from city
group by country_id
This will give you the number of cities in each country. Then you could use a CTE to get the country with the largest number of cities.
You need to GROUP BY if you want to use aggregate functions.
Given the fact that you're very new to this I think you'll get a lot more out of this if you spend a few minutes reading up on some documentation. Don't worry, this is easy stuff so you'll understand this in no time. Please have a look at the following basic info (MySQL GROUP BY basic info) regarding the use of GROUP BY in MySQL. Your questions are answered in the topic regarding 'MySQL GROUP BY with aggregate functions'.
Basic group by:
SELECT
status, COUNT(*)
FROM
orders
GROUP BY status;
Group by using a join:
SELECT
status, SUM(quantityOrdered * priceEach) AS amount
FROM
orders
INNER JOIN
orderdetails USING (orderNumber)
GROUP BY status;
SELECT x.country
FROM country x
JOIN city y
ON y.country_id = x.country_id
GROUP
BY x.country
ORDER
BY COUNT(*) DESC LIMIT 1;
On the (fantastically unlikely) chance that the most civilised countries have equal numbers of cities, you would have to amend this a little.
What makes this difficult is possible ties, i.e. two or more countries sharing the maximum number of cities. As of MySQL 8 you can use window functions to help you with this. Here I compare the country counts and their maximum and then pick the rows were the two match.
select *
from country
where (country_id, true) in -- true means it is a maximum city country
(
select country_id, count(*) = max(count(*)) over()
from city
group by country_id
);
Sorry it was my first post, my code looks terrible.
following my code again.
select
country.country_id,
count(city.city_id)
from
country
inner join
city
on
city.country_id=country.country_id
group by
city.country_id
having
count(city.city_id) =
(SELECT
max(count(city.city_id))
FROM
city
GROUP BY
city.city_id);
Best regards,
Jens
Try the following code:
**select
country.country_id,
count(city.city_id)
from
country
inner join
city
on
city.country_id=country.country_id
group by
city.country_id
having
count(city.city_id) =
(SELECT
max(count(city.city_id))
FROM
city
GROUP BY
city.city_id);**
You need to group by country_id to make sure all cities that are connected to one country_id can be counted.
Where is a good approach, however it does not work together with "group by" as it will be accounted before the "group by" command.
The way you joined your data from different tables is not a very proper yet working way. I suggest to use the inner join in this case to make the command more obvious/better readable.
Count() is used to count the number of cities that accumulate on one country
max() is used to get the country with the most cities (highest count()).
SELECT country_id, count(1)
FROM city
GROUP BY country_id
ORDER BY count(1) desc;
Try following Code -
SELECT *,
(SELECT COUNT(*) FROM cities WHERE cities.country_id=C.country_id) as cities_count
FROM country C
ORDER BY cities_count DESC
LIMIT 0,1
Also is joining the two tables necessary? In your query, you said you need to find What country has the most cities?
Above query will only return one country with max cities.
You can find the country like this:
SELECT MAX(c.id) FROM (SELECT COUNT(id) AS id
FROM city group by country_id) c
I have 3 tables which is:
Courses
courses_id
name
QnAs
qna_id
student_id
courses_id
name
question
Students
student_id
name
Now I'm trying to count how many qna's there are for each courses. How do i make the query?
I've tried doing this :
SELECT (SELECT COUNT(qna_id) AS Expr1
FROM QnAs) AS Count
FROM QnAs AS QnAs_1 CROSS JOIN
Courses
GROUP BY Courses.courses_id
It does counts how many QnA's there are but not for each Courses
The output i got is each Courses names and QnAs count number but what i want is the QnA's number for each of the Courses
It seems you merely want to aggregate QNAs by course ID:
select courses_id, count(*)
from qnas
group by courses_id
order by courses_id;
Along with the course names:
select c.course_id, c.name, coalesce(q.cnt, 0) as qna_count
from courses c
left join
(
select courses_id, count(*) as cnt
from qnas
group by courses_id
) q on q.course_id = c.course_id
order by c.course_id;
Why not just use GROUP BY?
SELECT q.courses_id, COUNT(qna_id) as cnt
FROM QnAs q
GROUP BY q.courses_id;
This is not an answer, but just an explanation what your query does.
In your own query you first cross join all QnAs with all courses for no apparent reason, thus getting all possible combinations. So with two courses, each with three QNAs (that makes six QNAs in total), you'd construct 2 x 6 = 12 rows.
For each of these rows you select the total number of rows in the QNA table, which is six in above example. So you'd select 12 rows, all showing the number 6.
But then you group by course ID, thus ending up with two rows only in my example. You should apply an aggregate function on your subquery, e.g. MAX or SUM, but you don't, which makes your query invalid (because you are dealing with many rows, but treat this as if it were a single value). MySQL however silently applies ANY_VALUE, so your query becomes:
SELECT
ANY_VALUE( (SELECT COUNT(*) FROM QnAs) ) AS Count
FROM QnAs AS QnAs_1
CROSS JOIN Courses
GROUP BY Courses.courses_id;
I hope this explanation helps you understand how joins and aggregation work. You may want to set ONLY_FULL_GROUP_BY mode (https://dev.mysql.com/doc/...) in order to have MySQL report the syntax error instead of silently "fixing" the query by applying ANY_VALUE.
SELECT db.people.first_name,
db.people.last_name,
db.people.email
FROM db.convert, db.people
HAVING COUNT(db.convert.year) > 1
WHERE db.convert.email = db.people.email
AND (SUBSTRING(db.convert.gross, 1, 2) != '$-')
GROUP BY db.people.email
This query works fine without the HAVING statement, but I need it to only select emails that are in the table with multiple records for different years. So 2011, 2012, 2013
Also I want it to show that email and last name only once, distinct?
When I add the having statement the query does not run
Here is a better way to write this query:
SELECT p.first_name, p.last_name, p.email
FROM db.convert c join
db.people p
on c.email = p.email
WHERE c.gross not like '$-%'
GROUP BY p.email
HAVING COUNT(c.year) > 1;
In addition to fixing the syntax problem with having, I also:
Introduce table aliases (c and p) to make the query more readable.
Use proper join syntax rather than implicit joins in the where clause.
Use like rather than substring(), so the comparison could take advantage of an index.
Also, COUNT(c.year) probably doesn't do what you want. It is probably the same as COUNT(c.gross), COUNT(c.email), and COUNT(*), because it counts the number of non-NULL values inyear`. I'm not sure what you want, but if you want emails with at least two years of records, then the expression should be:
HAVING COUNT(DISTINCT c.year) > 1
HAVING is based on a GROUP BY condition. So it needs to be after the GROUP BY.
GROUP BY db.people.email HAVING COUNT(db.convert.year) > 1
As Ollie Jones and Gordon Linoff suggested, if you have more than one record per year for each person then you will want to use a DISTINCT on the COUNT so you aren't counting each year more than once:
GROUP BY db.people.email HAVING COUNT(DISTINCT db.convert.year) > 1
You have two requirements, it seems. (I assume an email uniquely identifies a person.)
One, give a list of persons' names and emails.
Two, show the persons who appear in more than one distinct year in your convert table.
The second requirement can be met by this query. It counts distinct conversion years by email and throws out those with only one conversion year. As somebody mentioned in comments, HAVING should come after GROUP BY.
SELECT COUNT(DISTINCT year) AS years, email
FROM db.convert
WHERE SUBSTRING(gross, 1, 2) !- '$-'
GROUP BY email
HAVING COUNT(DISTINCT year) > 1
Then we can use that as a subquery (a virtual table) to go get peoples' names, as follows:
SELECT p.first_name,
p.last_name,
p.email
FROM db.people AS p
JOIN (
SELECT COUNT(DISTINCT year), email
FROM db.convert
WHERE SUBSTRING(gross, 1, 2) !- '$-'
GROUP BY email
HAVING COUNT(DISTINCT year) > 1
) AS years ON p.email = years.email
ORDER BY p.last_name, p.first_name, p.email
This uses the structured part of structured query language precisely to specify what you need from your tables.
Building up your query this way lets you debug things progressively. You have to be careful when using COUNT() or other aggregate functions in table joins, because you may get a lot more rows than you bargained for due to the combinatorial way joins work.
I have two tables, lists and globals_lists. A globals_lists basically associates a list_id with a global_id value. I'd like to get a count of global_ids (ie the number of 'manys' in the globals_lists table) for the associated list type for each user.
something like:
select l.id, l.user_id, count(gl.global_id) as gl_count
from lists l, globals_lists gl
where l.list_type_id=10 and l.id=gl.list_id;
but this is giving me back wrong information.
Add:
GROUP BY l.id, l.user_id
after your where clause.
Without GROUP BY, you are basically just counting up all rows that meet the WHERE and join conditions without any regard to set grouping. GROUP BY will ensure that you are performing the count aggregation per user -> list combination.
select l.id, l.user_id, count(gl.global_id) as gl_count
from lists l, globals_lists gl
where l.list_type_id=10 and l.id=gl.list_id
GROUP BY l.id, l.user_id;
When you use an aggregate function like count, you should specify the group on which the aggregate should operate. MySQL does not require this, but it's always clearer if you do.
In your case, the group you're looking for is (user_id, list_id). You could write the query like:
select l.user_id
, l.id
, count(gl.global_id) as gl_count
from lists l
join globals_lists gl
on l.id = gl.list_id
where l.list_type_id = 10
group by
l.user_id
, l.id