Select columns other than the one specified in GROUP BY clause - mysql

Is there a way to select columns other the one specified in the group by clause?
Let's say I have the following schema:
Student(id, name, age), Course(id, name, credit), Enrollment(student_id, course_id, grade)
I want to query for each course the following: course's name, student_count.
I came up with workaround, but I was wondering if there's a cleaner way to do this:
SELECT MAX(c.name), COUNT(distinct e.student_id)
FROM Enrollment e
INNER JOIN Course c ON c.id = e.course_id
GROUP BY e.course_id;

You might want to copy this DDL, adjust it to match your schema, and paste it into your question.
create table Student(
student_id integer primary key,
student_name varchar(35) not null,
age int not null default 20
);
create table Course(
course_id integer primary key,
course_name varchar(35) not null,
credit integer not null default 3
);
create table Enrollment(
student_id integer not null references Student (student_id),
course_id integer not null references Course (course_id),
primary key (student_id, course_id),
grade char(1) not null
);
insert into student values
(1, 'a', 20),
(2, 'b', 20),
(3, 'c', 20);
insert into course values
(1, 'course 1', 3),
(2, 'course 2', 3),
(3, 'course 3', 3);
insert into enrollment values
(1, 1, 'b'),
(2, 1, 'b'),
(3, 1, 'b'),
(1, 2, 'b'),
(2, 2, 'b'),
(3, 3, 'b');
Now, you can get the number of students enrolled in each course by querying only the "enrollment" table.
select course_id, count(student_id) num_students
from enrollment
group by course_id
order by course_id;
course_id num_students
--
1 3
2 2
3 1
All that remains is to get the corresponding course name. To do that, you just join the table "Course" with the query we just wrote.
select course.course_name, course_enrollment.num_students
from course
inner join (select course_id, count(student_id) num_students
from enrollment
group by course_id) course_enrollment
on course.course_id = course_enrollment.course_id;
course_name num_students
--
course 1 3
course 3 1
course 2 2

No, you can't. But you can extend GROUP BY with c.name:
SELECT MAX(c.name), COUNT(distinct e.student_id)
FROM Enrollment e
INNER JOIN Course c ON c.id = e.course_id
GROUP BY e.course_id, c.name
Because e.course_id is unique, it won't change results.

Related

Sum sales from different tables

I have 5 tables related to sales. Three of them are like this:
Product_table_image
They are called product_a, product_b, product_c
The other tables are the time_id table which contains the reference for the date_id and the customers' table, which contains the details of customers.
time_id table
and
customer_table
The 3 tables refer to the sales of different products, but the products are not important in this context, because what I need is to sum up all the values per month per customer. There are cases when one or more customers might not have made a purchase of a certain product, which means not all customers ids will be in all products tables and that's what I can't figure out how to solve. It seems that my code is only able to fetch and sum when the clients have made purchases in all 3 tables.
So this is what I was able to come up with:
SELECT C.customer_id, ROUND((A.pa + B.pa + C.pc)* 1, 2) AS total,C.month_id
FROM (SELECT customer.customer_id, SUM(product_a.amount) AS pa , time_id.month_id FROM customer
INNER JOIN product_a on customer.customer_id = product_a.customer_id
INNER JOIN time_id on product_a.date_id = time_id.date_id
GROUP BY customer.customer_id, time_id.month_id) AS A
CROSS JOIN
(SELECT customer.customer_id, SUM(product_a.amount) AS pb , time_id.month_id FROM customer
INNER JOIN product_b on customer.customer_id = product_b.customer_id
INNER JOIN time_id on product_b.date_id = time_id.date_id
GROUP BY customer.customer_id, time_id.month_id) AS B
CROSS JOIN
(SELECT customer.customer_id, SUM(product_a.amount) AS pc , time_id.month_id FROM customer
INNER JOIN product_c on customer.customer_id = product_c.customer_id
INNER JOIN time_id on product_c.date_id = time_id.date_id
GROUP BY customer.customer_id, time_id.month_id) AS C
GROUP BY C.month_id, C.customer_id
ORDER BY C.month_id;
I've been stuck in it for a while, so any help is appreciated!
I have setup the tables and some sample data to make it more real.
create table month(id int, name varchar(20), primary key (id));
insert into month (id, name) values (1, 'January'),(2,'February'),(3,'March'),(4,'April');
create table year(id int, name varchar(4), primary key (id));
insert into year(id, name) values (2019, '2019'),(2020,'2020'),(2021,'2021');
create table time (id int, month_id int, year_id int, primary key (id));
alter table time add constraint fk_month FOREIGN KEY (month_id) REFERENCES month (id);
alter table time add constraint fk_year FOREIGN KEY (year_id) REFERENCES year (id);
insert into time (id, year_id, month_id) values
(1, 2019, 1),(2, 2019, 2),(3,2019,3),(4,2019,4),
(5, 2020, 1),(6, 2020, 2),(7,2020,3),(8,2020,4),
(9, 2021, 1),(10, 2021, 2),(11,2021,3),(12,2021,4);
create table customers (id int, name varchar(100), city varchar(100), country varchar(100), primary key (id));
insert into customers (id, name, city, country) values
(1, 'Google', 'San Francisco', 'US'),
(2, 'Ambev', 'São Paulo', 'BR'),
(3, 'Merck', 'Darmstadt', 'GE');
create table sales_of_product_a (id int, customer_id int, date_id int, amount decimal(10,2), primary key (id));
alter table sales_of_product_a add constraint fk_pa_time FOREIGN KEY (date_id) REFERENCES time (id);
-- only customer 1 - Google and 3 - Merck purchased product A
insert into sales_of_product_a (id, customer_id, date_id, amount) values
(1, 1, 1, 100.10),(2,1,2,200.20),(3,1,3,300.30),(4,1,4,400.40),
(5, 1, 5, 500.50),(6,1,6,600.60),(7,1,7,700.70),(8,1,8,800.80),
(9, 3, 1, 130.10),(10,3,2,230.20),(11,3,3,330.30),(12,3,4,430.40),
(13, 3, 5, 530.50),(14,3,6,630.60),(15,3,7,730.70),(16,3,8,830.80);
create table sales_of_product_b (id int, customer_id int, date_id int, amount decimal(10,2), primary key (id));
alter table sales_of_product_b add constraint fk_pb_time FOREIGN KEY (date_id) REFERENCES time (id);
-- only customer 1 - Google purchased product B
insert into sales_of_product_b (id, customer_id, date_id, amount) values
(1, 1, 1, 100.10),(2,1,2,200.20),(3,1,3,300.30),(4,1,4,400.40),
(5, 1, 5, 500.50),(6,1,6,600.60),(7,1,7,700.70),(8,1,8,800.80),
(9, 1, 9, 900.90),(10,1,10,1000.01),(11,1,11,1100.11),(12,1,12,1200.12);
create table sales_of_product_c (id int, customer_id int, date_id int, amount decimal(10,2), primary key (id));
alter table sales_of_product_c add constraint fk_pc_time FOREIGN KEY (date_id) REFERENCES time (id);
-- only customer 3 - Merck purchased product C
insert into sales_of_product_c (id, customer_id, date_id, amount) values
(1, 3, 1, 130.10),(2,3,2,230.20),(3,3,3,330.30),(4,3,4,430.40),
(5, 3, 5, 530.50),(6,3,6,630.60),(7,3,7,730.70),(8,3,8,830.80),
(9, 3, 9, 930.90),(10,3,10,1030.01),(11,3,11,1130.11),(12,3,12,1230.12);
The SQL you might be looking for would be something like.
with all_sales as (
select pa.customer_id, tt.month_id, sum(pa.amount) as amount from sales_of_product_a pa inner join time tt on (pa.date_id = tt.id) where tt.year_id = 2019 group by pa.customer_id, tt.month_id
union all
select pb.customer_id, tt.month_id, sum(pb.amount) as amount from sales_of_product_b pb inner join time tt on (pb.date_id = tt.id) where tt.year_id = 2019 group by pb.customer_id, tt.month_id
union all
select pc.customer_id, tt.month_id, sum(pc.amount) as amount from sales_of_product_c pc inner join time tt on (pc.date_id = tt.id) where tt.year_id = 2019 group by pc.customer_id, tt.month_id
),
sales_per_customer_per_month as (
-- summary of all sales of all products per customer per month
select customer_id, month_id, sum(amount) as amount from all_sales group by customer_id, month_id
),
customers_month as (
select c.id, c.name, c.city, c.country, m.id as month_id, m.name as month_name from customers c inner join month m on true
)
select c.id,c.name,c.city,c.country,c.month_id, coalesce(s.amount,0) as amount, sum(coalesce(s.amount,0)) over (partition by c.id order by c.id,c.month_id) as total
from customers_month c
left join sales_per_customer_per_month s on (s.customer_id = c.id and s.month_id = c.month_id)
order by c.id,c.month_id;
The result of query above is following.
The concepts used are linked below.
Window Functions:
https://dev.mysql.com/doc/refman/8.0/en/window-functions.html
Common Table Expressions (CTE):
https://www.mysqltutorial.org/mysql-cte/

Write a query to calculate the total value of each product ordered. Columns: ProductName, TotalAmountOrdered

# Product table contains 2 columns - id, name
create table Product
(id INT
,name varchar(100)
);
insert into Product(id, name) values
(1, 'Pen'),
(2, 'Paper'),
(3, 'Printer'),
(4, 'Sharpner'),
(5, 'Eraser'),
(6, 'Clip');
OrderInfo table contains 4 columns - id, customer_id, product_id, amount
customer_id is foreignkey to table Customer
product_id is foreignkey to table Product
amount is the value of order placed by a customer for a particular product
create table OrderInfo
(id INT
, customer_id INT
, product_id INT
, amount decimal);
insert into OrderInfo(id, customer_id, product_id, amount) values
(1, 3, 4, 565),
(2, 5, 4, 346),
(3, 1, 1, 365),
(4, 6, 3, 765),
(5, 1, 4, 245),
(6, 6, 2, 876);
The output should look like this
name amount
Pen 365
Paper 876
Printer 765
Shapner 1156
Fast and easy to understand. Just join the two tables by the id of the product and group by the product. The result cann be summarized with SUM()
Select
P.name
, SUM(O.amount)
FROM Product P
Inner Join OrderInfo O on O.product_id = P.id
Group by P.name

How to combine and get sum from three different related tables in MySQL?

There are three related tables:
operations (id, name)
pricelists (id, operations_id (link to operations table), cost)
accounting (id, pricelists_id (link to pricelists table), quantity)
How to get table, like
NAME SUMMARY_COST SUMMARY_QUANTITY
milling result of 2*750 2
threading result of 1*444 1
... ... ...
overall 2*750+1*444+... 2+1+...
I trying to group for two tables at a start:
select operations.name, sum(pricelists.cost) total
from operations
left join pricelists on pricelists.operations_id*accounting_quantity = operations.id*accounting.quantity
group by operations.id
but it is not worked yet
A little database:
CREATE TABLE operations (id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR (100));
CREATE TABLE pricelists (id INT PRIMARY KEY, operations_id INT NOT NULL, cost DECIMAL(10,2),
FOREIGN KEY (operations_id) REFERENCES operations (id));
CREATE TABLE accounting (id INT PRIMARY KEY, pricelists_id INT NOT NULL, quantity INT,
FOREIGN KEY (pricelists_id) REFERENCES pricelists (id));
INSERT INTO operations (name) VALUES
('milling'),
('threading'),
('grinding'),
('welding'),
('brazing'),
('soldering'),
('riveting');
INSERT INTO pricelists (id, operations_id, cost) VALUES
(1, 2, 750),
(2, 1, 444),
(3, 3, 123),
(4, 4, 450),
(5, 5, 375),
(6, 6, 250),
(7, 7, 232);
INSERT INTO accounting (id, pricelists_id, quantity) VALUES
(1, 7, 2),
(2, 2, 5),
(3, 4, 2),
(4, 1, 1),
(5, 3, 4);
Consder:
select o.name, sum(a.quantity * pl.cost) total, sum(a.quantity) quantity
from operations o
left join pricelists pl on pl.operations_id = o.id
left join accounting a on a.pricelists_id = pl.id
group by o.name
You can generate the summary row by just adding with rollup at the very end of the query.

MYSQL - Updating count in one tabled based on two other tables joined

I have 3 related tables. Adults, Children and AC. Adults contains an INT column to count high school seniors. Children contains a column with year of highs school graduation. AC links the adult.id to the children.id.
CREATE TABLE adults (
id INT,
name VARCHAR(10),
seniors INT DEFAULT 0
) ;
INSERT INTO adults (id, name) VALUES
(1, 'adam'),
(2, 'bob');
CREATE TABLE children (
id INT,
name VARCHAR(10),
grad VARCHAR(4)
) ;
INSERT INTO children (id, name, grad) VALUES
(1, 'sally', '2016'),
(2, 'johnny', '2017'),
(3, 'eric', '2016'),
(4, 'billy', '2016'),
(5, 'rachel', '2016');
CREATE TABLE pc (
id INT,
a_id INT,
c_id INT
) ;
INSERT INTO pc (id, a_id, c_id) VALUES
(1, 1, 1),
(2, 1, 2),
(3, 1, 3),
(4, 2, 3),
(5, 2, 2);
SQLFiddle: http://sqlfiddle.com/#!2/89281e
So I want to update adults.seniors to the count of '2016' children they're linked to. So adult #1 would be "2" (sally and eric), and adult #2 "1" (eric).
The real data will be run across 25,000+ children being matched up to 40,000+ parents with a row count on the "pc" table above 3,000,000 rows - so looking for efficiency. I started working down this path but a) it's not working for obvious reasons and b) I doubt it would be efficient...
UPDATE adults a SET
seniors = (
SELECT p.a_id, count(*)
FROM pc p
INNER JOIN children c ON c.id = p.c_id
WHERE c.grad = '2016'
GROUP BY p.c_id)
WHERE p.a_id = a.id;
I'm thinking there has to be a better way of doing this with joins but can't seem to wrap my head around it.
You should be looking for this update statement:
UPDATE adults a
JOIN
(SELECT
p.a_id, COUNT(*) childrencount
FROM
pc p
INNER JOIN children c ON c.id = p.c_id
WHERE
c.grad = '2016'
GROUP BY p.a_id) c ON (a.id = c.a_id)
SET
seniors = c.childrencount;

Joining two tables with reference to foreign key

I have some problems with joining two tables when foreign key is no set directly.
I have two tables:
TABLE A
{A.ID} {A.NAME} {Parentid} {A_FK} (foreign key)
A.ID1 A.NAME1 NULL A_FK1
A.ID2 A.NAME2 NULL A_FK2
A.ID3 A.NAME3 A.ID2 NULL
A.ID4 A.NAME4 NULL A.FK4
OtherA OtherId Other Other
Table B
{B.ID} {B.Code}
A.FK1 some_text1
A.FK2 some_text2
A.FK4 some_text3
B.ID1 some_text4
In table A. A.ID3 does not have FK but it has ParentID that point to A.ID2 which has ForeingKey.
I would like to have expected:
{A.ID} {A.NAME} {B.Code}
A.ID1 A.NAME1 some_text1
A.ID2 A.NAME2 some_text2
A.ID3 A.NAME3 some_text2
A.ID4 A.NAME4 some_text3
Can anyone help me with this join?
If your parent-child relationship can be of multiple levels, then you need to write a recursive query something like this:
CREATE TABLE TableA(ID INT, Name VARCHAR(10), ParentID INT NULL, FK INT NULL );
CREATE TABLE TableB( ID INT, Code VARCHAR(50));
INSERT INTO TableA VALUES (1, 'Name1', NULL, 1);
INSERT INTO TableA VALUES (2, 'Name2', NULL, 2);
INSERT INTO TableA VALUES (3, 'Name3', 2, NULL);
INSERT INTO TableA VALUES (4, 'Name4', NULL, 4);
INSERT INTO TableA VALUES (5, 'Name4', 3, NULL);
INSERT INTO TableB VALUES (1, 'Some Text 1');
INSERT INTO TableB VALUES (2, 'Some Text 2');
INSERT INTO TableB VALUES (4, 'Some Text 3');
WITH X (ID, NAME, FK) AS (
SELECT ID, NAME, FK
FROM TABLEA
WHERE PARENTID IS NULL
UNION ALL
SELECT T.ID, T.NAME, X.FK
FROM TABLEA T
INNER JOIN X ON (T.PARENTID = X.ID)
)
SELECT X.ID, X.NAME , TABLEB.CODE
FROM X INNER JOIN TABLEB ON (X.FK = TABLEB.ID);
You could first get all rows to be joined on TableB using FK and UNION ALL them with rows to be joined on TableA using ParentID:
SAMPLE DATA
CREATE TABLE TableA(
ID INT,
Name VARCHAR(10),
ParentID INT NULL,
FK INT NULL
)
CREATE TABLE TableB(
ID INT,
Code VARCHAR(50)
)
INSERT INTO TableA VALUES
(1, 'Name1', NULL, 1),
(2, 'Name2', NULL, 2),
(3, 'Name3', 2, NULL),
(4, 'Name4', NULL, 4);
INSERT INTO TableB VALUES
(1, 'Some Text 1'),
(2, 'Some Text 2'),
(4, 'Some Text 3');
QUERY
SELECT
a.ID, a.Name, b.Code
FROM TableA a
INNER JOIN TableB b ON a.FK = b.ID
WHERE a.ParentID IS NULL
UNION ALL
SELECT
a1.ID, a1.Name, b.Code
FROM TableA a1
INNER JOIN TableA a2 ON a2.ID = a1.ParentID
INNER JOIN TableB b ON a2.FK = b.ID
WHERE a1.FK IS NULL
ORDER BY ID
RESULT
ID Name Code
----------- ---------- -------------------
1 Name1 Some Text 1
2 Name2 Some Text 2
3 Name3 Some Text 2
4 Name4 Some Text 3
Try this code , this is working correctly
SELECT TABLEA.ID,TABLEA.NAME,TABLEB.Code
FROM TABLEA
INNER JOIN TABLEB
ON TABLEA.ID=TABLEB.ID OR TABLEA.Parentid=TABLEB.ID;