MYSQL query using JOIN table - mysql

I have two tables one CUSTOMERS
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| ID | int(11) | NO | PRI | NULL | |
| FIRSTNAME | varchar(50) | YES | | NULL | |
| LASTNAME | varchar(50) | YES | | NULL | |
| ADDRESS | varchar(100) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
and orders
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| ID | int(11) | NO | PRI | NULL | |
| PRODUCT_NAME | varchar(100) | YES | | NULL | |
| PRODUCT_PRICE | double(10,4) | YES | | NULL | |
| DATE_ORDER | date | YES | | NULL | |
| ID_CUSTOMER | int(11) | YES | | NULL | |
| AMOUNT | int(11) | YES | | NULL | |
+---------------+--------------+------+-----+---------+-------+
I need to "Get the first and last names of the customers who made orders in total sum greater than the average sum of all orders. Don’t care about duplicates".
Here what I've tried
select FIRSTNAME, LASTNAME, ID,
AVG(PRODUCT_PRICE * AMOUNT)
from CUSTOMERS C
join ORDERS O
on C.ID = ID_CUSTOMER
GROUP BY FIRSTNAME, LASTNAME
HAVING AVG(PRODUCT_PRICE * AMOUNT) < (
select (PRODUCT_PRICE * AMOUNT)
from ORDERS
where C.ID = O.ID_CUSTOMER;
That does not work. I need some help

You need to find average sum of all orders in a subquery.
select
C.ID,
C.FIRSTNAME,
C.LASTNAME,
SUM(O.PRODUCT_PRICE * O.AMOUNT)
from CUSTOMERS C
join ORDERS O
on C.ID = O.ID_CUSTOMER
GROUP BY C.ID, C.FIRSTNAME, C.LASTNAME
HAVING SUM(PRODUCT_PRICE * AMOUNT) > (
select AVG(PRODUCT_PRICE * AMOUNT)
from ORDERS)

Most part of your code is correct, you want just remove the part where C.ID = O.ID_CUSTOMER;. this is not need to calculate the average value of the order items.
using this following code you can get the required outut
SELECT FIRSTNAME,LASTNAME FROM CUSTOMERS
INNER JOIN ORDERS ON CUSTOMERS.ID=ORDERS.ID_CUSTOMER
GROUP BY CUSTOMERS.ID
HAVING SUM(ORDERS.PRODUCT_PRICE*ORDERS.AMOUNT)>
(SELECT AVG(PRODUCT_PRICE*AMOUNT) FROM ORDERS)

Related

MYSQL (MariaDB) - Invalid use of group function

I have two tables called addresses and house_sales
addresses
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| house_number_name | varchar(150) | NO | | NULL | |
| address_line1 | varchar(150) | NO | MUL | NULL | |
| address_line2 | varchar(150) | YES | | NULL | |
| address_line3 | varchar(150) | YES | MUL | NULL | |
| town_city | varchar(150) | NO | MUL | NULL | |
| district | varchar(150) | YES | MUL | NULL | |
| county | varchar(150) | YES | MUL | NULL | |
| post_code | varchar(8) | NO | MUL | NULL | |
| updated_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
+-------------------+------------------+------+-----+---------+----------------+
house_sales
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| address_id | int(11) unsigned | NO | MUL | NULL | |
| price | int(11) unsigned | NO | MUL | NULL | |
| date | datetime | NO | MUL | NULL | |
| updated_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
I'm trying to select all the addresses grouped by address_line1 and then getting the average price for that street. The query works but I want to only select where there is more than one house on the same street. However when I add the AND count(*) > 1 I get the error "Invalid use of group function". Below is the query
SELECT count(*) as total_sales, avg(price) as average_price, `address_line1`, `town_city`
FROM `house_sales` `hs`
LEFT JOIN `addresses` `a` ON `hs`.`address_id` = `a`.`id`
WHERE `town_city` = 'London'
AND count(*) > 1
GROUP BY `address_line1`
ORDER BY `average_price` desc
I'm not sure why I'm getting this error. I've tried a sub query so I can use HAVING but haven't got this to work. Any help or pointers would be appreciated
You need a having clause to filter on the aggregate expression:
SELECT count(*) as total_sales, avg(price) as average_price, `address_line1`, `town_city`
FROM `house_sales` `hs`
LEFT JOIN `addresses` `a` ON `hs`.`address_id` = `a`.`id`
WHERE `town_city` = 'London'
GROUP BY `address_line1`, `town_city`
HAVING count(*) > 1
ORDER BY `average_price` desc
MySQL extends the SQL standard by allowing the use of aliases in the having clause, so you can also do:
having total_sales > 1
Side notes:
as commented by jarlh, it is a good practice to qualify (prefix) all column names with the table they belong to
it is also a good practice to put all non-aggregated columns in the group by clause (I added town_city, which was missing in your original query) - newer versions of MySQL do not allow this by default
quoting all identifiers is usually not necessary (unless they contain special characters)
There are two ways to go here. One would be to add town_city to the GROUP BY list:
SELECT
address_line1,
town_city,
COUNT(*) AS total_sales,
AVG(price) AS average_price
FROM house_sales hs
LEFT JOIN addresses a ON hs.address_id = a.id
WHERE town_city = 'London'
GROUP BY address_line1, town_city
HAVING COUNT(*) > 1
ORDER BY average_price DESC;
The other would be to just keep your current query but remove town_city from the select list, since you are restricting to just London anyway.
SELECT
address_line1,
COUNT(*) AS total_sales,
AVG(price) AS average_price
FROM house_sales hs
LEFT JOIN addresses a ON hs.address_id = a.id
WHERE town_city = 'London'
GROUP BY address_line1
HAVING COUNT(*) > 1
ORDER BY average_price DESC;

Cant figure out self join query

I have a table with the following structure.
+-----------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+--------------+------+-----+---------+-------+
| linq_order_num | char(32) | NO | PRI | NULL | |
| order_status_id | int(11) | YES | MUL | NULL | |
| order_id | varchar(100) | YES | | NULL | |
| item_name | varchar(120) | YES | | NULL | |
| item_cost | float | YES | | NULL | |
| custmer_id | int(11) | YES | MUL | NULL | |
| order_date_time | datetime | YES | | NULL | |
| order_category | varchar(120) | YES | | NULL | |
| ordered_by | int(11) | YES | MUL | NULL | |
| linq_shipping_cost | float | YES | | NULL | |
| website_shipping_cost | float | YES | | NULL | |
| total_cost | float | YES | | NULL | |
| advance_amount | float | YES | | NULL | |
| website | varchar(120) | YES | | NULL | |
| other | varchar(120) | YES | | NULL | |
| rvn | int(11) | YES | | NULL | |
| received_date | datetime | YES | | NULL | |
| delivered_date | datetime | YES | | NULL | |
| store_id | int(11) | YES | MUL | NULL | |
+-----------------------+--------------+------+-----+---------+-------+
So for every day I need to find the total order cost.I can get it by using this query.
select sum(total_cost), date_format(order_date_time,"%Y-%m-%d") from
order_item group by date_format(order_date_time,"%Y-%m-%d")
Also I need the total remaining amount paid on the delivered dates.
select sum(total_cost-advance_amount),date_format(delivered_date,"%Y-%m-%d")
from order_item group by date_format(delivered_date,"%Y-%m-%d")
Not all the days, orders will happen and not all the days deliveries will happen.If there is day with no orders the total cost for that day should be shown as zero and the total remaining amount shown should be sum of (total_cost-advance_amount) for the day.
Is there a way I could combine the above two queries in one query and get the result?
So to summarise for a particular day d:
I need sum(total_cost) where ordered_date_time = d ,
I need sum(total_cost -advance_amount) where delivered_date = d
Essentially looking for a table like this:
Date Total Cost Total Delivery Amounts
d 500 2000
d1 0 900
d2 900 0
I tried using a subquery. The problem is it doesn't display the cases for d1, where is total cost for that day is 0.
Query:
select
date_format(order_date_time,"%Y-%m-%d") date,
sum(total_cost) total,
sum(advance_amount) advance_amount,
IFNULL( (select sum(total_cost-advance_amount)
from order_item a
where date_format(a.delivered_date,"%Y-%m-%d") = date_format(d.order_date_time,"%Y-%m-%d") ),0 ) delivery_amount
from order_item d
group by date_format(order_date_time,"%Y-%m-%d"), delivery_amount
You can use your two queries as derived tables and join them on date. The problem is, that you would need a FULL OUTER JOIN, which is not supported by MySQL. So you first need to extract all the dates from both columns
select date(order_date_time) as d from order_item
union
select date(delivered_date) as d from order_item
und use a left join with your queries
select
dates.dt,
coalesce(tc.total_cost, 0),
coalesce(tm.total_remaining, 0)
from (
select date(order_date_time) as dt from order_item
union
select date(delivered_date) as dt from order_item
) dates
left join (
select sum(total_cost) as total_cost, date(order_date_time) as dt
from order_item
group by dt
) tc using(dt)
left join (
select sum(total_cost-advance_amount) as total_remaining, date(delivered_date)
from order_item
group by dt
) tm using(dt)
I also replaced date_format(..) with date(..). You can format the dates in the outer select or in your application.

How to return NULL values when joining multiple tables

here are the contents of the tables.
mysql> desc student;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| name | varchar(20) | NO | | NULL | |
| sex | enum('F','M') | NO | | NULL | |
| student_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+------------+------------------+------+-----+---------+----------------+
mysql> desc grade_event;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| date | date | NO | | NULL | |
| category | enum('T','Q') | NO | | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+----------+------------------+------+-----+---------+----------------+
mysql> desc score;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| student_id | int(10) unsigned | NO | PRI | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | |
| score | int(11) | NO | | NULL | |
+------------+------------------+------+-----+---------+-------+
What I'm trying to accomplish is to display which students missed a quiz/test, found under 'category' in the grade_event table.
Here's what I've come up with, but am not generating any results;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score is NULL
group by name, event_id;
I've also gone the route of attempting a subquery;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score not in (select score from score)
group by name, event_id;
Any help would be appreciated.
I think you should just replace your join by left join, join is an inner join in MySQL : http://dev.mysql.com/doc/refman/5.7/en/join.html
And take care with group by event_id, it can be useful to precise group by sc.event_id. I don't know in MySQL but in sql server it wouldn't work.
Your second query is necessarily empty as you ask a column to have its values not in its values :)
You need to use an outer join instead of an inner join to get list of students that do not have a corresponding record in the events after creating a carthesian join of students and events:
select name, category, ge.event_id
from (student s
join grade_event ge) --no join condition creates a carthesian join
left join score sc on s.student_id=sc.student_id and sc.event_id=ge.event_id
where sc.score is NULL

SELECT statement with an case statement

I have the following table for Customer:
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| first | varchar(45) | YES | | NULL | |
| last | varchar(45) | YES | | NULL | |
| password | varchar(45) | NO | | NULL | |
| contact_id | int(11) | NO | PRI | NULL | |
| address_id | int(11) | NO | PRI | NULL | |
+------------+-------------+------+-----+---------+----------------+
And the following structure for Appointment:
+-------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| time | datetime | NO | | NULL | |
| cancelled | tinyint(1) | YES | | 0 | |
| confirmed | tinyint(1) | YES | | 0 | |
| customer_id | int(11) | NO | PRI | NULL | |
+-------------+------------+------+-----+---------+----------------+
I want to use a single query to get the customer information, if they have appointment information, then it'll query it, otherwise it won't.
I am trying to use the following:
CASE
WHEN (SELECT count(a.id) FROM appointment
INNER JOIN customer c ON a.customer_id = c.id)
THEN (SELECT c.first, c.last, c.id, a.id FROM appointent
INNER JOIN customer c ON a.customer_id = c.id)
ELSE
(SELECT c.first, c.last, c.id FROM customer)
END;
Do you have any suggestions?
how about
SELECT * FROM Customer c LEFT JOIN Appointment a ON a.CustomerId = c.Id
You could make two queries and UNION them.
SELECT c.first, c.last, c.id, a.id FROM appointent a
INNER JOIN customer c ON a.customer_id = c.id
UNION
SELECT c.first, c.last, c.id, null FROM customer c
Or an outer join, where the a.id would be populated with null if there was no match during the join.
SELECT c.first, c.last, c.id, a.id FROM customer c
OUTER JOIN appointent a ON a.customer_id = c.id
As per my comment on Zdravko's answer, you could have also used:
select * from customer where id in (select customer_id from appointment where cancelled = 0)
Which would allow you to filter in a nice way.
You can also filter Zdravko's answer like this:
SELECT * FROM Customer c LEFT JOIN Appointment a ON a.CustomerId = c.Id
WHERE a.cancelled 0 and a.confirmed = 1

MySQL query to match date and null between two tables

I have two MySQL-tables like this:
desc students;
+---------------------------+---------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+---------------------------+---------------+------+-----+---------+
| student_id | int(11) | NO | PRI | NULL |
| student_firstname | varchar(255) | NO | | NULL |
| student_lasttname | varchar(255) | NO | | NULL |
+---------------------------+---------------+------+-----+---------+
desc studentabsence;
+---------------------------+-------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+---------------------------+-------------+------+-----+---------+
| student_absence_id | int(11) | NO | PRI | NULL |
| student_id | int(11) | YES | | NULL |
| student_absence_startdate | date | YES | | NULL |
| student_absence_enddate | date | YES | | NULL |
| student_absence_type | varchar(45) | YES | | NULL |
+---------------------------+-------------+------+-----+---------+
Then I have this MySQL- query to list students.
Query:
SELECT s.student_id, s.student_firstname, s.student_lastname,
a.student_absence_startdate, a.student_absence_enddate, a.student_absence_type
FROM students s LEFT JOIN studentabsence a ON a.student_id = s.student_id
Whenever a student has absence information this is displayed in the columns
a.student_absence_startdate a.student_absence_enddatea.student_absence_type
Sometimes a student has two or more rows in the table studentabsence then he is listed two times.
My question is if there is any way to be more specific in the query. I would like to list all students from db.students and if there is a row in db.studentabsence with a date between startdate and enddate (for example 2012-07-30) list the student one time with this absence information. Only if there is a match on date.
So something like...
... WHERE (a.student_absence_startdate OR a.student_absence_enddate) IS NULL OR
'2012-07-30' BETWEEN a.student_absence_startdate AND
a.student_absence_enddate ...
It's kinda hard to explain so let me know if you need more information...
I think that you can arrange it with a JOIN on a subselect/subview :
SELECT s.student_id, s.student_firstname, s.student_lastname,
a.student_absence_startdate, a.student_absence_enddate, a.student_absence_type
FROM students s
LEFT JOIN
(SELECT * FROM studentabsence a1 WHERE ('2012-07-30' BETWEEN a1.student_absence_startdate AND a1.student_absence_enddate) ) a
ON a.student_id = s.student_id
I'd use parameters with default values (01/01/1900 00:00:00), like this:
AND ( a.student_absence_startdate >= #P_startdate OR #P_startdate = '01/01/1900 00:00:00' )
AND ( a.student_absence_enddate <= #P_enddate OR #P_enddate = '01/01/1900 00:00:00' )