How to aggregate results in many to many Query - mysql

I have 3 tables in my database:
Student:
id
name
Student_Course:
student_id
course_id
Course:
id
grade
And I want to list all the students and the results of whether they have pass all of the course they have chosen . Assuming that grade <= 'C' is pass.
I tried sql like:
SELECT s.*,
IF('C'>=ALL(SELECT c.grade FROM from Course c WHERE c.id=sc.course_id),1,0) as isPass
FROM Student s LEFT JOIN Student_Course sc on sc.student_id=s.id
This sql works, but if now I want a column 'isGood' which means all the grade='A', do I need to execute the subquery again? How can I get both 'isGood' and 'isPass' by executing subquery only once?

I believe the grade would be better served in the junction table. Using that, I have created a scenario that might help you solve your question:
Scenario
create table student (id int, fullname varchar(50));
insert into student values (1, 'john'), (2, 'mary'), (3, 'matt'), (4, 'donald');
create table course (id int, coursename varchar(50));
insert into course values (1, 'math'), (2, 'science'), (3, 'business');
create table student_course (student_id int, course_id int, grade char(1));
insert into student_course values
(1, 1, 'C'), (1, 2, 'C'), (1, 3, 'C'),
(2, 1, 'A'), (2, 2, 'A'), (2, 3, 'A'),
(3, 1, 'A'), (3, 2, 'C'), (3, 3, 'C'),
(4, 1, 'A'), (4, 2, 'C'), (4, 3, 'F');
Query
select s.*, case when all_a_grades.student_id is not null then 'GOOD' else 'MEH' end as grades
from student s
left join (
-- find students who got A in all classes
select student_id, count(distinct ca.id) as aclasses, count(distinct sc.course_id) as allclasses
from student_course sc
left join (select id, 'A' as agrade from course) ca
on ca.id = sc.course_id and ca.agrade = sc.grade
group by student_id
having aclasses = allclasses
) all_a_grades on all_a_grades.student_id = s.id
where not exists (
-- let's make sure we filter OUT students who have failed
-- at least one course
select 1
from (
-- find students who have failed at least one course
select distinct student_id
from student_course
where grade not in ('A', 'B', 'C')
) t where t.student_id = s.id
)
Result
| id | fullname | grades |
| 1 | john | MEH |
| 2 | mary | GOOD |
| 3 | matt | MEH |

Related

MYSQL - facing difficulty combining two columns from two different tables

How can I find the restaurant name and the total number of orders for each in Jan 2021? The issue I'm facing is that the restaurant names and the orders are on separate tables as you can see from the code below.
create table orders (id integer, country text, customer_id integer,
restaurant_id INTEGER, date date, order_value integer);
create table customers (id integer, name text, country text);
create table restaurants (id integer, name text, country text);
INSERT INTO orders (
id,
country,
customer_id,
restaurant_id,
date,
order_value)
VALUES
(1, 'Pakistan', 1, 1, '2021-01-01', 400),
(2, 'Pakistan', 2, 1, '2021-01-01', 500),
(3, 'Pakistan', 4, 2, '2021-01-01', 300),
(4, 'Pakistan', 4, 3, '2021-01-05', 200),
(5, 'Pakistan', 5, 4, '2021-01-01', 250),
(6, 'Pakistan', 4, 1, '2021-01-09', 266),
(7, 'Pakistan', 3, 2, '2021-01-07', 322),
(1, 'Holland', 1, 1, '2021-01-01', 378),
(8, 'Pakistan', 1, 3, '2021-06-01', 289),
(2, 'Holland', 1, 1, '2021-08-01', 480),
(9, 'Pakistan', 1, 1, '2021-03-01', 580),
(10, 'Pakistan', 3, 2, '2021-07-01', 360),
(3, 'Holland', 1, 1, '2021-09-01', 550),
(11, 'Pakistan', 4, 3, '2021-04-01', 991),
(12, 'Pakistan', 5, 1, '2021-04-01', 875),
(4, 'Holland', 1, 1, '2021-03-02', 250),
(13, 'Pakistan', 1, 1, '2021-08-01', 150),
(14, 'Pakistan', 1, 2, '2021-09-01', 290),
(5, 'Holland', 1, 1, '2021-07-01', 240),
(15, 'Pakistan', 1, 3, '2021-03-01', 780),
(16, 'Pakistan', 1, 4, '2021-06-01', 987),
(6, 'Holland', 1, 1, '2021-05-03', 457),
(17, 'Pakistan', 1, 4, '2021-05-04', 258);
INSERT INTO customers (
id,
name,
country)
VALUES
(1, 'Steven Smith', 'Pakistan'),
(2, 'Arthur Chen', 'Holland'),
(3, 'Michael Wren', 'Pakistan'),
(4, 'John Almagro', 'Pakistan'),
(5, 'Luke Pablo', 'Pakistan'),
(6, 'Monty Tron', 'Pakistan');
INSERT INTO restaurants (
id,
name,
country)
VALUES
(1, 'KFC', 'Pakistan'),
(2, "McDonald's", 'Holland'),
(3, 'Howdy', 'Pakistan'),
(4, 'Kitchen Cuisine', 'Pakistan'),
(5, 'JFC', 'Pakistan'),
(6,'Hardees','Pakistan');
I learned about JOIN functions but I'm not able to join the dots.
Joining two table, is made by telling the database, which rows belong together. this is defined in the ON clause, where the joning columns are mentioned.
the WHERE clause is the same as in the last query it removes all rows that have not the right year and month.
The Group By has here three columns, because the restaurant_id has always the same value. We could also had added a aggregation function to the columns, which would have the same effect
SELECT
r.name,
r.country,
COUNT(*) Total_orders
FROM
orders o JOIN restaurants r ON o.restaurant_id = r.id
WHERE YEAR(`date`)= 2021 AND MONTH(`date`)= 1
GROUP BY restaurant_id,r.name,r.country
name | country | Total_orders
:-------------- | :------- | -----------:
KFC | Pakistan | 4
McDonald's | Holland | 2
Howdy | Pakistan | 1
Kitchen Cuisine | Pakistan | 1
SELECT
MAX(r.name) name,
MAX(r.country) country,
COUNT(*) Total_orders
FROM
orders o JOIN restaurants r ON o.restaurant_id = r.id
WHERE YEAR(`date`)= 2021 AND MONTH(`date`)= 1
GROUP BY restaurant_id
name | country | Total_orders
:-------------- | :------- | -----------:
KFC | Pakistan | 4
McDonald's | Holland | 2
Howdy | Pakistan | 1
Kitchen Cuisine | Pakistan | 1
db<>fiddle here
SELECT -- MySQL SELECT statement
MAX(r.name) `Hotel Name`, -- column name as Hotel Name from table restaurants, alias as r
COUNT(*) `Number Of Orders`, -- count all the records for table orders, alias o
SUM(o.order_value) `Total Order Value`, -- SUM all the order_values for match records
MAX(r.country) `Country` -- column country
FROM
orders o -- Running operations on table orders and set alias o
INNER JOIN restaurants r ON o.restaurant_id = r.id -- INNER JOIN second table restaurants as r and joining
-- two tables using field r.id (restaurants primary key id)
-- and o.restaurant_id (foreign key of restaurant's primary key id)
WHERE -- setting condition
YEAR(o. `date`) = 2021 -- Year must be 2021
AND MONTH(o. `date`) = 1 -- and month must be JAN or 1
GROUP BY -- group all same hotels id
o.restaurant_id;
| Hotel Name | Number Of Orders | Total Order Value | Country |
|-----------------|------------------|-------------------|----------|
| KFC | 4 | 1544 | Pakistan |
| McDonald's | 2 | 622 | Holland |
| Howdy | 1 | 200 | Pakistan |
| Kitchen Cuisine | 1 | 250 | Pakistan |
HINT -
To join the two different tables we'd need two columns which has same values, common or has some linking.
Here table orders has restaurant_id which is a foreign key of table restaurants (id). In other word, we can use those id to identify the restaurant details by querying table restaurants.
hence to join table orders and restaurants, we should use column id from table restaurants and column restaurant_id from table orders.
Now since orders table has multiple rows with the same restaurant_id; it's better group them together to make as buckets.
Once we use GROUP BY a column; MySQL group them in a bucket which has same values or given conditions.
Any aggregated statement like SUM, AVG, COUNT, MAX, MIN, etc. would take those individual buckets as logical table and perform the operations.

Select products and join categories hierarchical

I have two tables in my database:
create table category (id integer, name text, parent_id integer);
create table product (id integer, name text, category integer, description text);
insert into category
values
(1, 'Category A', null),
(2, 'Category B', null),
(3, 'Category C', null),
(4, 'Category D', null),
(5, 'Subcategory Of 1', 1),
(6, 'Subcategory Of 5', 5),
(7, 'Subcategory Of 5', 5),
(8, 'Subcategory of D', 4)
;
insert into product
values
(1, 'Product One', 5, 'Our first product'),
(2, 'Product Two', 6, 'Our second product'),
(3, 'Product Three', 8, 'The even better one');
How can I return like this:
product_id | product_name | root_category | category_path
-----------+--------------+---------------+-----------------------------
1 | Product One | 1 | /Category A/Subcategory Of 1
2 | Product Two | 1 | /Category A/Subcategory of 5/Subcategory of 6
I use "WITH RECURSIVE" in categories table but can't find the way to combine product table with 1 time query.
I use example from here
What's the best way to do this ?
Here you go, assumming you have MariaDB 10.2 or newer:
with recursive pt (root_id, id, path) as (
select id, id, concat('/', name) from category where parent_id is null
union all
select pt.root_id, c.id, concat(pt.path, '/', c.name)
from pt join category c on c.parent_id = pt.id
)
select p.id, p.name, pt.root_id, pt.path
from pt
join product p on pt.id = p.category;
Result:
id name root_id path
-- -------------- ------- ---------------------------------------------
1 Product One 1 /Category A/Subcategory Of 1
2 Product Two 1 /Category A/Subcategory Of 1/Subcategory Of 5
3 Product Three 4 /Category D/Subcategory of D

Joining 2 tables with Group By

poll_opts table for storing options and poll_voted for storing vote result, pid stands for poll id(unique) and oid stands for option id(unique for individual poll only)
poll_voted [Primary Key: pid.oid.emp]
+-----+-----+-------+
| pid | oid | emp |
+-----+-----+-------+
poll_opts [Primary Key: pid.oid]
+-----+-----+---------+
| pid | oid | opt |
+-----+-----+---------+
pid & oid type: int , opt type: text
If you need the "not existent" results as well you need a left outer join preserves all results from poll_opts even if no match in poll_votes is found.
MySql 5.7 Join Syntax
Query:
select opt, count(vo.oid)
from poll_opts po
left outer join poll_voted vo on vo.oid = po.oid and po.pid=vo.pid
where po.pid = 3 -- 3
group by opt
Output:
opt count(vo.oid)
Chrome 0
Firefox 0
IE 0
MS Edge 0
Opera 1
Testdata:
CREATE TABLE poll_voted (`pid` int, `oid` int, `emp` int);
INSERT INTO poll_voted (`pid`, `oid`, `emp`)
VALUES
(1, 0, 1989),
(1, 2, 1989),
(1, 4, 1989),
(1, 6, 1989),
(3, 2, 1989)
;
CREATE TABLE poll_opts (`pid` int, `oid` int, `opt` varchar(15));
INSERT INTO poll_opts (`pid`, `oid`, `opt`)
VALUES
(1, 0, 'WinXP'),
(1, 2, 'wIN7'),
(1, 4, 'wIN 10'),
(1, 6, 'Ubuntu'),
(3, 0, 'IE'),
(3, 1, 'MS Edge'),
(3, 2, 'Opera'),
(3, 3, 'Chrome'),
(3, 4, 'Firefox')
;

SQL join multiple criteria

I have a difficult task to build up an array retrieved from a table similar to the one below:
table_a
id | scenario_id | entity_id
1 1;2;3;4;5 1;3
2 4;5;8;10 2;3
3 1;5;8;11 1;2;4;
4 3;5;8;9 4;5;
Now, if one user selects from one entity_id, let's say 3, the SQL query should return something similiar to:
scenario_id
1;2;3;4;5;8;10
Or, if he selects 5, the returned array should look like:
scenario_id
3;5;8;9
Could that be done using only SQL statements?
For SQL Server you can use this to get desired output:
DECLARE #xml xml, #entity_id int = 3
--Here I generate data similar to yours
;WITH cte AS (
SELECT *
FROM (VALUES
(1, '1;2;3;4;5', '1;3'),
(2, '4;5;8;10', '2;3'),
(3, '1;5;8;11', '1;2;4;'),
(4, '3;5;8;9', '4;5;')
) as t(id, scenario_id, [entity_id])
)
--create xml
SELECT #xml = (
SELECT CAST('<i id="'+ CAST(id as nvarchar(10)) +'"><s>' + REPLACE(scenario_id,';','</s><s>') + '</s><e>' + REPLACE([entity_id],';','</e><e>') + '</e></i>' as xml)
FROM cte
FOR XML PATH('')
)
--Normalizing the table and getting result
SELECT STUFF((
SELECT ';' + CAST(scenario_id as nvarchar(10))
FROM (
SELECT DISTINCT t.v.value('.','int') as scenario_id
FROM #xml.nodes('/i/s') as t(v)
INNER JOIN #xml.nodes('/i/e') as s(r)
ON t.v.value('../#id','int') = s.r.value('../#id','int')
WHERE s.r.value('.','int') = #entity_id
) as p
FOR XML PATH('')),1,1,'') as scenario_id
Output for entity_id = 3:
scenario_id
1;2;3;4;5;8;10
For entity_id = 5
scenario_id
3;5;8;9
you can use something like this to find a id in the scenario_id, but its always a FULL TABLE scan.
SELECT *
FROM table_a
WHERE
FIND_IN_SET('3', REPLACE(scenario_id,';',',')) > 0;
Simple. NORMALISE your schema... At it's crudest, that might be as follows...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,scenario_id INT NOT NULL
,entity_id INT NOT NULL
,PRIMARY KEY (id,scenario_id,entity_id)
);
INSERT INTO my_table VALUES
(1, 1,1),
(1, 1,3),
(1, 2,1),
(1, 2,3),
(1, 3,1),
(1, 3,3),
(1, 4,1),
(1, 4,3),
(1, 5,1),
(1, 5,3),
(2, 4,2),
(2, 4,3),
(2, 5,2),
(2, 5,3),
(2, 8,2),
(2, 8,3),
(2,10,2),
(2,10,3),
(3, 1,1),
(3, 1,2),
(3, 1,4),
(3, 5,1),
(3, 5,2),
(3, 5,4),
(3, 8,1),
(3, 8,2),
(3, 8,4),
(3,11,1),
(3,11,2),
(3,11,4),
(4, 3,4),
(4, 3,5),
(4, 5,4),
(4, 5,5),
(4, 8,4),
(4, 8,5),
(4, 9,4),
(4, 9,5);
SELECT DISTINCT scenario_id FROM my_table WHERE entity_id = 3 ORDER BY scenario_id;
+-------------+
| scenario_id |
+-------------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 8 |
| 10 |
+-------------+
split the scenario_id by ';' and copy to temporary table to use that for your query use instr and substring functions
this link may help you but you need a loop function to call your procedure as the ';' is repeated

SELECT columns FROM t1, join with COUNT of columnX in t2, COUNT of columnY in t3

I am trying to combine data from 3 tables as follows :
SELECT u.USER_ID,u.USERNAME,u.FIRST_NAME,u.LAST_NAME,u.ISACTIVE,
u.ISADMIN, COUNT(m.PURCHASED_ID) AS MOVIES_PURCHASED,
COUNT(r.RENTED_ID) AS MOVIES_RENTED
FROM TBL_USERS AS u
LEFT JOIN TBL_MOVIE_PURCHASED AS m
ON u.USER_ID = m.USER_ID
LEFT JOIN TBL_RENTED_MOVIES AS r
ON u.USER_ID = r.USER_ID
GROUP BY u.USER_ID,u.USERNAME,u.FIRST_NAME,u.LAST_NAME,u.ISACTIVE,u.ISADMIN
ORDER BY MOVIES_PURCHASED, MOVIES_RENTED;
The tables have the following columns :
TBL_USERS :
USER_ID, USERNAME, FIRSTNAME, LASTNAME, ISACTIVE, ISADMIN
TBL_MOVIE_PURCHASED :
USER_ID, MOVIE_ID, PURCHASE_ID, PURCHASED_ON (purchase ID is unique)
TBL_RENTED_MOVIES :
USER_ID, MOVIE_ID, RENTED_ID, RENTED_ON (rented ID is unique )
I am trying to display :
1. all contents of TBL_USERS
2. the count of the rented movies for every user from TBL_RENTED_MOVIES,
3. the count of the purchased movies for every user from TBL_MOVIES_PURCHASED.
4. ORDER BY the results based on both COUNTS.
If say user1 has rented 5 movies and purchased 10 movies, then the query is supposed to return :
(MOVIES_PURCHASED, MOVIES_RENTED) = (5,10).
Instead, the query returns 5*10 = 50 for both columns :
(MOVIES_PURCHASED, MOVIES_RENTED) = (50,50)
I know I have gone wrong in joining the results. I tried using UNION to combine results as well but didnt work. Any ideas?
Hence, The output should Ideally be :
USER_ID, USERNAME, FIRST_NAME, LAST_NAME, ISACTIVE, ISADMIN, MOVIES_PURCHASED, MOVIES-RENTED :
1, user1, userFirst, userLast, Active, NotAdmin, 5, 10
. Any help is appreciated.
Here's one way...
CREATE table USERS(USER_ID integer primary key,
USERNAME varchar(50), FIRST_NAME varchar(50),
LAST_NAME varchar(50), ISACTIVE VARCHAR(10)
DEFAULT '0',
ISADMIN VARCHAR(10) DEFAULT '0');
CREATE table MOVIES_PURCHASED(PURCHASE_ID integer primary key,
USER_ID integer,
FOREIGN KEY(USER_ID) REFERENCES USERS(USER_ID),
PURCHASED_ON VARCHAR(50));
CREATE table MOVIES_RENTED(RENTED_ID integer primary key,
USER_ID integer,
FOREIGN KEY(USER_ID) REFERENCES USERS(USER_ID),
RENTED_ON VARCHAR(50));
INSERT INTO USERS (USER_ID, USERNAME, FIRST_NAME, LAST_NAME, ISACTIVE, ISADMIN)
VALUES
(1, 'user1', 'user1FN', 'user1LN', '1', '0'),
(2, 'user2', 'user2FN', 'user2LN', '1', '0'),
(3, 'user3', 'user3FN', 'user3LN', '1', '0'),
(4, 'user4', 'user4FN', 'user4LN', '1', '0'),
(5, 'user5', 'user5FN', 'user5LN', '1', '0');
insert into MOVIES_RENTED (RENTED_ID, USER_ID, RENTED_ON)
values
(1, 5 ,'2014-07-05'),
(2, 4 ,'2014-07-05'),
(3, 5 ,'2014-07-05'),
(4, 4 ,'2014-07-05'),
(5, 5 ,'2014-07-05'),
(6, 5 ,'2014-07-05'),
(7, 3 ,'2014-07-05'),
(8, 2 ,'2014-07-05'),
(9, 2 ,'2014-07-05'),
(10, 1 ,'2014-07-05');
insert into MOVIES_PURCHASED (PURCHASE_ID, USER_ID, PURCHASED_ON)
values
(1, 1 ,'2014-07-05'),
(2, 3 ,'2014-07-05'),
(3, 3 ,'2014-07-05'),
(4, 3 ,'2014-07-05'),
(5, 4 ,'2014-07-05'),
(6, 4 ,'2014-07-05'),
(7, 5 ,'2014-07-05'),
(8, 5 ,'2014-07-05'),
(9, 1 ,'2014-07-05'),
(10, 2 ,'2014-07-05'),
(11, 2 ,'2014-07-05'),
(12, 2 ,'2014-07-05');
SELECT u.*
, SUM(x.source = 'rented') rented
, SUM(x.source = 'purchased') purchased
, COUNT(x.source) total
FROM users u
LEFT
JOIN
( SELECT 'rented' source, user_id FROM movies_rented
UNION ALL
SELECT 'purchased', user_id FROM movies_purchased
) x
ON x.user_id = u.user_id
GROUP
BY user_id
ORDER
BY total DESC;
USER_ID USERNAME FIRST_NAME LAST_NAME ISACTIVE ISADMIN RENTED PURCHASED TOTAL
5 user5 user5FN user5LN 1 0 4 2 6
2 user2 user2FN user2LN 1 0 2 3 5
4 user4 user4FN user4LN 1 0 2 2 4
3 user3 user3FN user3LN 1 0 1 3 4
1 user1 user1FN user1LN 1 0 1 2 3
http://sqlfiddle.com/#!2/8b3c8/10
A simple DISTINCT keyword for m.PURCHASE_ID and r.RENTED_ID solves the problem :
the query that worked is :
SELECT u.USER_ID,u.USERNAME,u.FIRST_NAME,u.LAST_NAME,u.ISACTIVE, u.ISADMIN, COUNT(DISTINCT m.PURCHASED_ID) AS MOVIES_PURCHASED, COUNT(DISTINCT r.RENTED_ID) AS MOVIES_RENTED,
(COUNT(DISTINCT m.PURCHASED_ID) + COUNT(DISTINCT r.RENTED_ID)) AS MAX_ACTIVITY
FROM TBL_USERS AS u
LEFT JOIN TBL_MOVIE_PURCHASED AS m
ON u.USER_ID = m.USER_ID
LEFT JOIN TBL_RENTED_MOVIES AS r
ON u.USER_ID = r.USER_ID
GROUP BY u.USER_ID,u.USERNAME,u.FIRST_NAME,u.LAST_NAME,u.ISACTIVE,u.ISADMIN
ORDER BY MAX_ACTIVITY DESC;
I added an extra MAX_ACTIVITY column and ordered the results by max_activity.
the link to an example for creating multiple tables, performing left joins on them, counting values for columns, ordering and grouping them and avoiding redundant data from being displayed while performing the joins :
http://sqlfiddle.com/#!2/7d44a2/2/0