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;
Related
I am pretty confused and absolutely not sure if this is the right way.
In the example below I am trying to check if the promotion type is 1 ( percentage e.g. 10% ) or 2 ( hard price e.g 10 EUR ) and compute the price after it and that only if main_product_id IS NOT NULL. Otherwise the price stays the same.
SELECT p.price
FROM product as p
LEFT JOIN promotion_product as pp ON p.id=pp.main_product_id
LEFT JOIN promotion as pr ON pp.promo_id=pr.id
(
CASE
WHEN pp.main_product_id IS NOT NULL THEN
CASE
WHEN pr.type=1 THEN p.price = p.price - (p.price * pr.value/100)
WHEN pr.type=2 THEN p.price = p.price - pr.value
END
END
)
What I get as error is:
#1305 - FUNCTION pr.id does not exist
This is pretty clear I know. But how to compute the new price and is it possible with CASE syntax ?
product table :
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| category_id | int(11) | YES | MUL | NULL | |
| brand_id | int(11) | YES | MUL | NULL | |
| sort | int(11) | NO | | 999 | |
| enable | tinyint(1) | NO | MUL | 2 | |
| product_number | varchar(255) | YES | MUL | NULL | |
| price | float | YES | | NULL | |
| quantity | float | YES | | NULL | |
| rating | tinyint(4) | NO | | 0 | |
+----------------+--------------+------+-----+---------+----------------+
promotion table:
+------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| start_date | timestamp | YES | | NULL | |
| end_date | timestamp | YES | | NULL | |
| type | tinyint(4) | NO | | NULL | |
| value | float | NO | | NULL | |
| enable | tinyint(4) | NO | | 2 | |
+------------+------------+------+-----+---------+----------------+
promotion_product table:
+-----------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| promo_id | int(11) | NO | MUL | NULL | |
| product_id | int(11) | YES | MUL | NULL | |
| main_product_id | int(11) | YES | MUL | NULL | |
+-----------------+---------+------+-----+---------+----------------+
CASE...WHEN...THEN expressions are for use in SELECT clauses. You have yours wrapped in parentheses at the end of your query. Because your query looks like pr.id(yadda yadda) MySQL thinks pr.id should be a function. It isn't, so MySQL throws your error.
Try something like this:
SELECT p.price,
CASE
WHEN pr.type=1 THEN p.price = p.price - (p.price * pr.value/100)
WHEN pr.type=2 THEN p.price = p.price - pr.value
ELSE p.price
END promoted_price
FROM product as p
LEFT JOIN promotion_product as pp ON p.id=pp.main_product_id
LEFT JOIN promotion as pr ON pp.promo_id=pr.id
I refactored your case expressions so it's not nested. The ELSE clause deals with the default cases where pr.type isn't 1 or 2, and where the ON clauses of your left joins don't match anything.
This is the way I will approach your goal:
SELECT
IF(pp.main_product_id IS NOT NULL,
CASE
WHEN pr.type = 1 THEN p.price - (p.price * pr.value / 100)
WHEN pr.type = 2 THEN p.price - pr.value
ELSE p.price -- Added a default case.
END,
p.price) AS finalPrice
FROM
product AS p
LEFT JOIN
promotion_product AS pp ON p.id = pp.main_product_id
LEFT JOIN
promotion AS pr ON pp.promo_id = pr.id
Note also, that you have start_date and end_date on your promotion table that you are currently ignoring.
I have 3 tables as follows:
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | 0 | |
| full_name | varchar(200) | YES | | NULL | |
| gender | varchar(1) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
+----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+-------+
| actor_id | int(11) | YES | MUL | NULL | |
| movie_id | int(11) | YES | MUL | NULL | |
| salary | int(11) | YES | | NULL | |
+----------+---------+------+-----+---------+-------+
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | 0 | |
| title | varchar(100) | YES | | NULL | |
| year | int(11) | YES | | NULL | |
| genre | varchar(100) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
I'm trying to figure out which actor has the longest time between movies.
I'm trying to use local variables to reference the previous row value in the case where the actor was the same (or else it defaults to 0). However for some reason the #previousName variable returns a random list of names.
Here is the code:
SELECT
a.full_name,
m.year,
m.title,
#PreviousName,
#PreviousYear,
if(#PreviousName = a.full_name, m.year - #PreviousYear, 0) AS Delta,
#PreviousName := a.full_name,
#PreviousYear := m.year
FROM
actors AS a
INNER JOIN
cast AS c ON a.id = c.actor_id
INNER JOIN
movies AS m ON c.movie_id = m.id,
(SELECT #PreviousName := null, #PreviousYear := 999) as SQLVars
ORDER BY full_name;
And here is what I get:
Picture of result table
Note I am using mySQL V5.7 so window functions are not an option.
You can do this with a correlated subquery:
select mc.*, (year - prev_year) as diff
from (select c.*, m.year,
(select m2.year
from movies m2 join
cast c2
on c2.movie_id = m2.id
where c2.actor_id = c.actor_id and
m2.year < m.year
order by m2.year desc
limit 1
) prev_year
from movies m join
cast c
on c.movie_id = m.id
) mc
order by diff desc;
This assumes that an actor is not in two movies in the same year. If you had a release date or something, that would be more effective for ordering the times.
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.
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
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' )