I want to create a new column from this example table but with one more condition that so far I couldn't figure out, I want to create an average holdings column that's specific to each city.
Name | City | Holdings
Tom Jones | London | 42
Rick James| Paris | 83
Mike Tim | NY | 83
Milo James| London | 83
So in this example table London has more than one instance and accordingly it will have a unique value of '62.5' indicating an average of holdings that's specific to the value in the city column.
Name | City | Holdings | City Avg. Holdings
Tom Jones | London | 42 | 62.5
Rick James| Paris | 36 | 36
Mike Tim | NY | 70 | 70
Milo James| London | 83 | 62.5
In MySQL 8.0, this is straight-forward with window functions:
select t.*, avg(holdings) over(partition by city) avg_city_holdings
from mytable t
In earlier versions, you can join:
select t.*, a.avg_city_holdings
from mytable t
left join (select city, avg(holdings) avg_city_holdings from mytable group by city) a
on a.city = t.city
Related
My table in MySQL :
mysql> select * from student;
+-----+----------+--------+------+---------+
| ano | name | gender | age | place |
+-----+----------+--------+------+---------+
| 114 | ron | m | 18 | cbe |
| 115 | dhruv | m | 18 | cbe |
| 116 | mini | f | 23 | chennai |
| 117 | yash | m | 20 | chennai |
| 118 | aathmika | f | 19 | delhi |
| 119 | aadhi | m | 9 | pune |
+-----+----------+--------+------+---------+
There was a question called :
Create a query to display the student table with students of age more than 18 with unique
city.
According to me, required output :
+-----+----------+--------+------+---------+
| ano | name | gender | age | place |
+-----+----------+--------+------+---------+
| 116 | mini | f | 23 | chennai |
| 118 | aathmika | f | 19 | delhi |
+-----+----------+--------+------+---------+
Or
+-----+----------+--------+------+---------+
| ano | name | gender | age | place |
+-----+----------+--------+------+---------+
| 117 | yash | m | 20 | chennai |
| 118 | aathmika | f | 19 | delhi |
+-----+----------+--------+------+---------+
I've tried the following :
mysql> select distinct place from student where age>18;
+---------+
| place |
+---------+
| chennai |
| delhi |
+---------+
2 rows in set (0.05 sec)
I tried to add unique key to place field to delete the second record with cbe, whereas my assumption was wrong.
mysql> alter table student add constraint unique(place);
ERROR 1062 (23000): Duplicate entry 'cbe' for key 'place'
mysql> alter table student modify place char(10) unique;
ERROR 1062 (23000): Duplicate entry 'cbe' for key 'place'
mysql> alter table student change place place char(10) unique;
ERROR 1062 (23000): Duplicate entry 'cbe' for key 'place'
mysql> select place from student where age>18 group by place having count(place)
=1;
+-------+
| place |
+-------+
| delhi |
+-------+
Also,
mysql> select distinct place,name,ano,age from student where age>18;
+---------+----------+-----+------+
| place | name | ano | age |
+---------+----------+-----+------+
| chennai | mini | 116 | 23 |
| chennai | yash | 117 | 20 |
| delhi | aathmika | 118 | 19 |
+---------+----------+-----+------+
3 rows in set (0.00 sec)
When I use many fields along with distinct place, it's distinct characteristic is lost!!!
What changes shall I make in any of the above queries to get the desired output???
Thanks in advance.
Create a query to display the student table with students of age more than 18 with unique city
I understand this as: the student should be more than 18, and their place should appear only once in the table. Only one row meets this criteria, that is ano 118 (Aathmika is 19 years old, and no other student lives in Delhi).
You could phrase this as:
select s.*
from student s
where
age > 18
and not exists(select 1 from student s1 where s1.place = s.place and s1.ano <> s.ano)
Important note : Check your sql_mode before executing this query.
You can try with the following query:
select * from student where age > 18 group by place;
You filter the age with the where statement and then you make the place unique with a group by.
SQL
SELECT *
FROM student s1
WHERE age > 18
AND NOT EXISTS
(SELECT * FROM student s2
WHERE s2.ano < s1.ano /* Or could alternatively use > here */
AND s2.age > 18
AND s2.place = s1.place);
Demo
dbfiddle.uk demo
Have added a couple of extra rows in the demo for testing purposes - please note that some of the other answers fail with this data.
For each distinct place you want only 1 row returned under the condition age > 18.
Since you don't care which row will be returned, if there are more than 1 rows that satisfy the conditions you set, you can GROUP BY place and for each place get only 1 of ano with this query:
SELECT ANY_VALUE(ano) ano
FROM student
WHERE age > 18
GROUP by place
The aggregate function ANY_VALUE() will choose 1 value of ano for each place.
The above query can be joined to the table student on the column ano and return your expected results:
SELECT s.*
FROM student s
INNER JOIN (
SELECT ANY_VALUE(ano) ano
FROM student
WHERE age > 18
GROUP by place
) t ON t.ano = s.ano
See the demo.
Note that instead of ANY_VALUE() you could also use MIN() or MAX(), since you don't care which row will be returned for each place.
From the question, I think the result should have students with age>18 and the city should be only once in the table.
select * from student group by place having age>18 and count(*) = 1;
This query groups by place, checks age and return records for which there is only one row in the group.
if you are not bothered of ambiguity in results - as you expected (mini and yash are from chennai and are above 18), i think following queries may help you
SELECT ano,name, age from student where ano in(SELECT MIN(ano) FROM student WHERE age>18 GROUP BY place)
RESULTS
117 MINI 23 chennai
118 AATHMIKA 29 delhi
OR YOU CAN USE
SELECT ano,name, age from student where ano in(SELECT MIN(ano) FROM student WHERE age>18 GROUP BY place)
RESULTS
4 yash 20 chennai
5 aathmika 19 delhi
In Mysql:
I have customer name (primary key), cities and an amount in table:
Table Cities:
customer location amount
Cust1 New York, USA 200
Cust2 New York, USA 300
Cust3 Chicago, USA 100
Cust4 Paris, France 400
Cust5 Nice, France 500
Cust6 Milan, Italy 600
Cust7 Mumbai, India 0
The format of location name in this table is:
<city>, <country>
Same as:
<city><comma><space><country>
Table Country (Primary key):
Name
USA
France
Italy
India
Thailand
I want to get how many cities each country has, and the average amount of each country. Like:
Country Count Average
USA 3 200 // (200 + 300 + 100) / 3
France 2 450 // (400 + 500) / 2
Italy 1 600 // (600) / 1
India 1 0 // (0) / 1
Thailand 0 0 // 0
So, my query is:
SELECT t1.name Country, count(distinct t2.location) Count
FROM Country t1 LEFT JOIN Cities t2
ON t2.location LIKE concat('%, ', t1.name)
GROUP BY t1.name ORDER BY Count DESC
But it does not give Average data, it only gives Country name and Count
Here is one way to do it:
select co.name, count(*) cnt, coalesce(avg(amount), 0) avg
from countries co
left join cities ci
on ci.location like concat('%, ', co.name)
group by co.name
order by co.name
Note that the way you store your data is inefficient. You should:
separate the city name from the country in two different columns
have a primary key in the countries table, and reference it in the cities table
For your dataset, this would be:
Countries
id | name
-- | ---------
1 | USA
2 | France
3 | Italy
4 | India
5 | Thailand
Cities
id | customer | location | country_id | amount
-- | -------- | -------- | ---------- | ------
1 | Cust1 | New York | 1 | 200
2 | Cust2 | New York | 1 | 300
3 | Cust3 | Chicago | 1 | 100
4 | Cust4 | Paris | 2 | 400
5 | Cust5 | Nice | 2 | 500
6 | Cust6 | Milan | 3 | 600
7 | Cust7 | Mumbai | 4 | 0
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 3 years ago.
I have a single database table 'Accomodation' that lists information of hotel/B&B suites with the city they are located in, the price and an ID for each suite that functions as the primary key.
It looks similar to this:
id | city | Price
ams001 | Amsterdam | 160
ams011 | Amsterdam | 120
par004 | Paris | 90
par006 | Paris | 120
rom005 | Rome | 130
rom015 | Rome | 130
I want to list all information of the cheapest accomodation for each city, however if two records both share the same lowest price I want to display both of these.
The result should look something like this:
ams011 | Amsterdam | 120
par004 | Paris | 90
rom005 | Rome | 130
rom015 | Rome | 130
I have tried using
SELECT * FROM accomodation
WHERE price IN (SELECT MIN(price) FROM accomodation GROUP BY city);
However this will produce a table like this
ams011 | Amsterdam | 120
par004 | Paris | 90
par006 | Paris | 120
rom005 | Rome | 130
rom015 | Rome | 130
Since the 120 price is the cheapest for Amsterdam it will show up at Paris too.
If I add a group by statement at the end, outside of the subquery like this:
SELECT * FROM accomodation
WHERE price IN (SELECT MIN(price) FROM accomodation GROUP BY city)
GROUP BY city;
It will fail to display lowest values that are identical and I'm left with a table like this:
ams011 | Amsterdam | 120
par004 | Paris | 90
rom015 | Rome | 130
First GROUP BY city to get the min price for each city and then join to the table:
SELECT a.*
FROM accomodation a INNER JOIN (
SELECT city, MIN(price) minprice
FROM accomodation
GROUP BY city
) g ON g.city = a.city AND g.minprice = a.price
Suppose we have this table mytable:
+--------+-------+-----+
| City | User | Amt |
+--------+-------+-----+
| London | John | 100 |
| London | John | 200 |
| London | James | 300 |
| London | James | 50 |
| Paris | Jean | 100 |
+--------+-------+-----+
I want to write a query that would produce the following result:
+--------+-------+------------+------------+
| City | User | AmtPerUser | AmtPerCity |
+--------+-------+------------+------------+
| London | John | 300 | 650 |
| London | James | 350 | 650 |
| Paris | Jean | 100 | 100 |
+--------+-------+------------+------------+
This can be done with the following query:
SELECT t1.City, User, AmtPerUser, AmtPerCity
FROM
(SELECT City, User, SUM(Amt) as AmtPerUser FROM mytable GROUP BY City, User) t1
JOIN
(SELECT City, SUM(Amt) as AmtPerCity FROM mytable GROUP BY City ) t2
USING (City);
But this query runs too slow, because the derived tables do not have indexes.
So I wonder if there is a more efficient way to accomplish this task.
03/25/2019 UPDATE
Thank you for the provided solutions. I am currently still using old MySQL 5.1.
Good to know about window functions in MySQL 8.
I've tested the queries on a larger set (the data listed here, copied 3600 times to make 18K rows). This one is the best so far:
SELECT City, User, SUM(Amt) as AmtPerUser,
SUM(SUM(Amt)) OVER (PARTITION BY City) as AmtPerCity
FROM mytable
GROUP BY City, User;
Demo on DB Fiddle
#GordonLinoff: 43ms
My original query: 70ms
#GMB: 135ms
It also turned out that presence or absence of indexes does not contribute.
Use window functions:
SELECT City, User, SUM(Amt) as AmtPerUser,
SUM(SUM(Amt)) OVER (PARTITION BY City) as AmtPerCity
FROM mytable
GROUP BY City, User;
Note: This assumes MySQL 8+.
In earlier versions, your version with the JOIN and two GROUP BYs is probably the best approach.
In MySQL 8.0, you can use window functions along with SELECT DISTINCT to obtain the same result:
SELECT DISTINCT
city,
user,
SUM(amt) OVER(PARTITION BY City, User) AmtPerUser,
SUM(amt) OVER(PARTITION BY City) AmtPerCity
FROM mytable
Window functions usually perform better than the equivalent aggregated queries. But if you really worry about performance, then you do want to create indexes anyway (possibly a compound index on (city, user)).
Demo on DB Fiddle:
| city | user | AmtPerUser | AmtPerCity |
| ------ | ----- | ---------- | ---------- |
| London | James | 350 | 650 |
| London | John | 300 | 650 |
| Paris | Jean | 100 | 100 |
You could use a GROUP BY ... WITH ROLLUP query to get the City totals on another row in your output data (and a grand total row as well):
SELECT City, User, SUM(Amt)
FROM mytable
GROUP BY City, User WITH ROLLUP
Output:
City User SUM(Amt)
London James 350
London John 300
London null 650
Paris Jean 100
Paris null 100
null null 750
You can recognise the ROLLUP rows by the NULL values - the total for each city has the city name for City and NULL for User, while the grand total row has NULL for both City and User. Although this isn't exactly the format you are looking for, it will be more efficient.
Demo on dbfiddle
I am working on a project where every user has an id and can have multiple regions as follows:
Regions Table
ID | State | etc..
-----------
122 | MD
122 | FL
122 | NY
122 | NJ
122 | CA
11 | NC
11 | SC
11 | GA
I would like to essentially write a query that will create a result set where every user ID only appears once and if the user ID is listed multiple times, it concatenates the column as follows...
ID | State
----------
122 | MD, FL, NY, NJ, CA
11 | NC, SC, GA
Is this possible? I appreciate any suggestions.
Thanks in advance!
You can use GROUP_CONCAT:
SELECT id, GROUP_CONCAT(state SEPARATOR '|')
FROM regions
GROUP BY id