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

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;

Related

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

SQL Product Group Size & Colors Together

I have following 3 tables, which I want to use to group 2 rows together to make it as single row, because they belong to one product.
Order
--------
order_id
Order_product
-------------
order_id
order_product_id
order_product_attribute
------------------------------
orders_products_attributes_id
order_id
product_id
products_options
options_id
=== Data ===
|1|2|7|Size |1|270
|2|2|7|Colour(s) |3|99202
|3|2|8|Size |1|270
|4|2|8|Colour(s) |3|47768
My desire result needs to be following:
order_id, product_id, size_options_id (option_id for size), colour_options_id (option_id for color)
size_options_id is optional, which means it might not even exist for some products.
I'm not sure what join I need to use to get this result in one go, rather then doing it from PHP checks.
Assuming that your tables are as follows:
create table order (order_id int);
insert into order values(2);
create table order_product (order_id int, order_product_id int);
insert into order_product values (2,7);
insert into order_product values (2,8);
create table order_product_attribute (orders_products_attributes_id int, order_id int, product_id int, products_options int, options_id int);
insert into order_product_attribute values (1, 2, 7, 1, 270);
insert into order_product_attribute values (2, 2, 7, 3, 99202);
insert into order_product_attribute values (3, 2, 8, 1, 270);
insert into order_product_attribute values (4, 2, 8, 3, 47768);
desired output:
2 7 270 99202
2 8 270 47768
You should use two left joins. It should be something like:
select
o.order_id,
op.product_id,
opas.options_id as size_options_id,
opac.options_id as colour_options_id
from my_order o
join order_product op on op.order_id=o.order_id
left join order_product_attribute opas
on opas.order_id=o.order_id
and opas.product_id=op.product_id
and opas.product_options=1
left join order_product_attribute opac
on opac.order_id=o.order_id
and opac.product_id=op.product_id
and opac.product_options=3
First left join is joining only with these rows which contain sizes.
Second left join is joining with rows containing colors.
You can see it in SQLFiddle

Equations in SQL (MySQL)

I have a table with a cost_maintence column that has cost for the entire year(52) weeks. I also have a table of renters, and a table of renter_units where there is a week_owned column that has the week number the renter rented. I am trying to figure out how I could calculate the cost for each renter. The equation I came up with is:
what each person owes = (cost_maintence/52) * #weeks each renter
rented
Is there any way I could get the value from a query?
create table renters(
id,
lname,
fname,
phone_num);
create table unit(
id,
unit_name,
unit_num,
cost_maintence);
create table renters_unit(
renters_id,
unit_id,
week_owned);
This is the query I came up with but I have no way of testing it out
select r.lname, r.fname, count(ru.week_owned),
sum(u.cost_maintence/52*count(ru.week_owned))
from renters r, renters_units ru, units u
where r.id = ru.renter_id
and ru.unit_id = u.id
and u.unit_name =unitname
and u.unit_num = unitnum
group by lname
order by lname,fname asc;
Here's an example. The inner query will get you amount owed per item, and the outer query sums that to find the total owed per person.
SELECT fname, SUM(owes) AS total_due
FROM (
SELECT r.fname,
r.id,
u.unit_name,
u.cost_maintence/52*COUNT(ru.week_owned) AS owes
FROM renters AS r
INNER JOIN renters_unit AS ru ON r.id = ru.renters_id
INNER JOIN unit AS u ON u.id = ru.unit_id
GROUP BY r.id, u.id
) AS t
GROUP BY id
Try it out with a SQLFiddle demo
Example Schema:
create table renters(
id int,
lname varchar(20),
fname varchar(20),
phone_num varchar(20));
create table unit(
id int,
unit_name varchar(30),
unit_num int,
cost_maintence int);
create table renters_unit(
renters_id int,
unit_id int,
week_owned int);
INSERT INTO renters VALUES (1, 'Peterson', 'Chaz', '8675309');
INSERT INTO unit VALUES (1, 'Skateboard', 1337, 52);
INSERT INTO unit VALUES (2, 'Flamethrower', 5432, 104);
INSERT INTO renters_unit VALUES (1, 1, 1);
INSERT INTO renters_unit VALUES (1, 1, 2);
INSERT INTO renters_unit VALUES (1, 1, 4);
INSERT INTO renters_unit VALUES (1, 2, 4);
INSERT INTO renters_unit VALUES (1, 2, 5);
By this, we can see that Chaz should owe $7 for the year (had a skateboard for 3 weeks at $1 per week, and a flamethrower for 2 weeks at $2 per week).
The inner query gives the following:
FNAME UNIT_NAME OWES
Chaz Skateboard 3
Chaz Flamethrower 4
And the outer:
FNAME TOTAL_DUE
Chaz 7
SELECT t.renters_id, SUM(u.cost_maintence)/52
FROM unit u JOIN renters_unit t ON t.unit_id = u.id
GROUP BY t.renters_id

Select primary photo if exist else pick first uploaded photo

I am trying to retrieve a single picture set by the user as the primary picture from table as below:
SELECT p.*, ph.* FROM place AS p
INNER JOIN photo as ph
ON p.place_id = ph.place_id
WHERE ph.primary_pic = 'X';
But not all user has set their primary picture, resulting in the query does not return anything.
IF(query is empty)
//perform SQL again with primary_pic = ''
Is there any ways or syntax that could be use to query this with one single SQL statement?
I was working in MS SQL but I don't see anything MS-specific except for the table variables. So if you change that to existing tables, it should run on MySQL too. (I am not sure but I guess MySQL should have EXCEPT set operation.)
-- sample data start
declare #place as table (plid int, plname nvarchar(100))
declare #photo as table (phid int, phname nvarchar(100), plid int, primary_pic nvarchar(1))
insert into #place values (1, 'aaa')
insert into #place values (2, 'bbb')
insert into #photo values (1, 'aaa_1.jpg', 1, '')
insert into #photo values (2, 'aaa_2.jpg', 1, 'X')
insert into #photo values (3, 'aaa_3.jpg', 1, '')
insert into #photo values (4, 'aaa_4.jpg', 1, '')
insert into #photo values (5, 'bbb_1.jpg', 2, '')
insert into #photo values (6, 'bbb_2.jpg', 2, '')
insert into #photo values (7, 'bbb_3.jpg', 2, '')
insert into #photo values (8, 'bbb_4.jpg', 2, '')
-- sample data end
-- note: #place and #photo are table variables in MS SQL
select p.*, ph2.*
from #place p inner join #photo ph2 on p.plid = ph2.plid
inner join (
select ph.plid, ph.primary_pic, min(ph.phid) phid
from #photo ph inner join
(select distinct plid from #photo where primary_pic <> 'X'
except
select distinct plid from #photo where primary_pic = 'X') hasnoprimary
on hasnoprimary.plid = ph.plid
group by ph.plid, ph.primary_pic
union
select ph.plid, ph.primary_pic, min(ph.phid) phid
from #photo ph inner join
(select distinct plid from #photo where primary_pic = 'X') hasprimary
on hasprimary.plid = ph.plid
where primary_pic = 'X'
group by ph.plid, ph.primary_pic
) trickypart on trickypart.phid = ph2.phid
Method: (1) get two lists of place IDs. One for which there is a primary photo and another for which there isn't (this is where I used EXCEPT). (2) join the photos table to both of them separately to get the photo IDs. For the first list, it is what was marked with X, for the second it is the minimum of all photo IDs. (3) make a union of the two. (4) join it back to places and photos.
In MS SQL it works ang gives the following for the sample data above:
plid plname phid phname plid primary_pic
----------- -------------- ----------- -------------- ----------- -----------
1 aaa 2 aaa_2.jpg 1 X
2 bbb 5 bbb_1.jpg 2

Select columns other than the one specified in GROUP BY clause

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.