MySQL gruop by when there is no aggregation - mysql

I have a table called booking_details.
id | tour_id | tour_fee| booking_id
1 | 1 | 200 | 1
2 | 2 | 350 | 1
3 | 1 | 200 | 2
4 | 2 | 350 | 3
tour_id refers to the Tours table and the booking_id refers Bookings table.
I want to get a report like this
tour_id 1 refers to New york tour
tour_id 2 refers to Paris tour
I need a generate a report something like this
tour name | total_income | number_of_bookings
New york tour| 400 | 2
Paris tour | 700 | 2
Here basicaly tour name, total income from that tour and number of bookings for that tour.
What I have done upto now is this. But this gives me a syntax error. It seems I can't group by results.
SELECT booking_details.*,Tours.name as name, count(Tours.id) FROM booking_details
inner join Tours on
booking_details.tour_id = Tours.id group by Tours.name;
How do I achive this using MySQL?

you have used aggregation count() in your query and from your requirement, it shows you need aggregation. when you used aggregation you have to put selection column in group by also
SELECT Tours.name as name,sum(tour_fee) income, count(Tours.id)
FROM booking_details
inner join Tours on
booking_details.tour_id = Tours.id group by Tours.name
As you used in selection booking_details.* which means every column of booking table but you have not put those column in group by so it thrown error

You are trying to select non aggregated columns which are not part of your GROUP BY clause.
Change your query like following.
SELECT t.NAME AS NAME,
Sum(bd.tour_fee) total_income,
Count(t.id) number_of_bookings
FROM booking_details bd
INNER JOIN tours t
ON bd.tour_id = t.id
GROUP BY t.NAME;
Small suggestion, as a good practice you should use alias names for tables when joining.

You need to add all other columns in group by except aggregated fields
SELECT
booking_details.tour_id,
Tours.name AS name,
SUM(tourfee) AS total_income,
COUNT(Tours.id)
FROM
booking_details
INNER JOIN
Tours ON booking_details.tour_id = Tours.id
GROUP BY
booking_details.tour_id, Tours.name

Related

SQL Predicates per aggregate function

I am struggling with a SQL query,
Query:
I want to find a list of hospitals with a count of dentists (is_denitist=true) and all doctors (including dentists) having monthly_income > 100 000
I have 2 tables Hospitals and Doctors with the following schema,
-------------
| Hospital |
|-----------|
| id | name |
|-----------|
---------------------------------------------------------
| Doctor |
|--------------------------------------------------------
| id | name | monthly_income | is_dentist | hospital_id |
|--------------------------------------------------------
The query I came up with is,
select h.name, count(d.is_dentist), sum(d.monthly_income)
from Hospital h inner join Doctor d
on h.id = d.hospital_id
where d.monthly_income > 100000 and d.is_dentist=true
group by h.name;
If I am a dentist and having income less than 100 000 then the hospital should still count me as a dentist.
But the caveat in the above query is it filters out all doctors having monthly_income above 100 000 and are dentists. I want an independent count of these conditions like predicates over each count() column. How can we achieve this in a single query?
You can do conditional aggregation.
Since is_dentist (presumably) contains 0/1 values, you can just sum() this column to count how many doctors belong to the group.
On the other hand, you can use another conditional sum() to count how many doctors have an income above the threshold.
select
h.name,
sum(d.is_dentist) no_dentists,
sum(d.monthly_income > 100000) no_doctors_above_100000_income
from Hospital h
inner join Doctor d on h.id = d.hospital_id
group by h.name;
You have two independent conditions (monthly_income > 100000, and is_dentist=true) which means there are two different data sets. You can't be used two different data set in the same group query.
So you need to divide it into two subqueries. You can check the following query whether the result is you wanted:
select temp3.name, temp1.dentist_count, temp2.income_count from
(select d1.hospital_id, count(*) as dentist_count from Doctor d1 where d1.monthly_income>100000 group by d1.hospital_id) as temp1
join
(select d2.hospital_id, count(*) as income_count from Doctor d2 where d2.is_dentist=true group by d2.hospital_id) as temp2
on temp1.hospital_id=temp2.hospital_id
join
(select h.id, h.name from Hospital h) as temp3
on temp2.hospital_id=temp3.id;

MySQL count number of occurrences of field value with join data

I have 2 tables in my database, see below:
Events Table
title | venue_id
event 1 | 1
event 2 | 1
event 3 | 1
event 4 | 1
event 5 | 2
event 6 | 2
event 7 | 2
event 8 | 2
event 9 | 3
event 10 | 3
Venues Table
id | title
1 | Venue 1
2 | Venue 2
3 | Venue 3
The desired results I am looking for would be
venue_title | event_count
Venue 1 | 4
Venue 2 | 4
Venue 3 | 2
I have been trying to do this with joins but for some reason I am getting the count 1 back for every venue.
This is what my SQL looks like just now.
SELECT
count(DISTINCT events.venue_id),
venues.title,
FROM
events
INNER JOIN
venues
ON venues.id = events.venue_id
I have looked around various over SO posts but unable to find anything that has been able to help. I am really a beginner at SQL like this.
Thanks
You're missing the group by clause:
SELECT
count(DISTINCT events.venue_id),
venues.title,
FROM
events
INNER JOIN
venues
ON venues.id = events.venue_id
GROUP BY -- Here!
venues.id, venutes.title
You can do a group by on events table, get the results and do an inner join of this obtained result with venues table. This can also improve performance of inner join since it will have to map fewer number of rows.
SQL:
select v.title,derived.event_count
from venues v
inner join (select venue_id,count(venue_id) as event_count
from events
group by venue_id) derived
on v.id = derived.venue_id
you can also use GROUP BY and HAVING clause
SELECT
count(events.venue_id),venues.title,
FROM events
INNER JOIN venues
ON venues.id = events.venue_id group by events.venue_id having count(events.venue_id) > 0
The GROUP BY Clause is used to group rows with same values and HAVING clause is used to restrict the results returned by the GROUP BY clause.
so > 0 return the duplicate values having count > 0

mysql using limit in a left join not working properly

I have two tables looking like this
Patient (table 1)
id | name
------------
1 | robel
2 | dave
Patient_followup (table 2)
id | Patient_id | date_created
-----------------------
1 | ---- 1 -- | 01/01/2015
2 | -----1 -- | 01/07/2016
I want to display all the patients with their perspective latest followup data. so i tried using this query
Select * from patient
left join Patient_followup pf on pf.Patient_id = patient.id
order by pf.date_created
Limit 1
but this is giving me only the first patient robel. i tryed removing the limit and its giving me two records of robel and one record of dave because robel has two followup data. so what should i do to get only one record of each patient ?
Try this:
Select
*
from
patient
left join
(SELECT
id as pf_id,
MAX(date_created) as latest_followup_date,
Patient_id
FROM
Patient_followup
GROUP BY
Patient_id) as pf
ON pf.Patient_id = patient.id
As mentioned by anton in the first comment, you need to use aggregation to get one record per patient.
Select patient.*,MAX(pf.date_created) as followupdate,group_concat(pf.date_created) from patient
left join Patient_followup pf on pf.Patient_id = p.patient.id
group by patient.id
order by pf.date_created
Here, you will get your values comma separated.
1) "Limit 1" will only return the first result. Typically this is used if the query will result in a very large result set and you only want the first few results.
Ex:
"LIMIT 30" will show the first 30 rows of the query.
2) I would change to setup of the tables so the query is smoother. Right now, you create a new line for each follow-up date even if the patient is already created. You could add another column in the table named "FollowUpDate". That way each patient record has the table id, patient id, creation date and followup date in the same row. That way, each patient has only one row.
EX:
Patient (table 1)
id | name | created_date | next_followup_date |
1 | Robel | 01/01/2015 | 01/01/2016 |
2 | Dave |[created_date]| [next_follup_date] |
Patient_followup (table 2)
id | Patient_id | date_created | followUpDate |
1 | 1 | 01/01/2015 | 06/01/2016 | // example date
2 | 1 | 01/01/2015 | 01/01/2016 |
3 | 2 |[date created]| [FollowUpDate] |
3) Change query to:
Use this select statement to get all patient records.
Select * from patient
left join Patient_followup pf on pf.Patient_id = patient.id
order by pf.Patient_id
Use this select statement to get the specific patient record information.
Select * from patient
inner join Patient_followup pf on pf.Patient_id = patient.id
where patient.id = 1 //to get robel. Edit this line as necessary, perhaps by user input...
order by pf.followUpDate
NOTE: When you insert a new record in Patient_followup, make sure you update Patient.next_followup_date.
I hope this helps!

MySQL double group

I am making queries to extract data from database which holds customer order. There's one table which holds customer id's and the customer's name. Another table which has the order id, customer id of who placed the order, a quantity of the item bought, and an item id. The last table holds the item id's and item names. I am trying to sort these to show an individual's most popular purchase, but am having issues properly grouping and ordering to produce the correct result, below is an example of what is intended.
customers
1 | John
---+-----
2 | Jane
orders
1 | 2 | 4 | 1
---+---+---+---
2 | 2 | 5 | 2
---+---+---+---
3 | 2 | 2 | 1
---+---+---+---
4 | 1 | 1 | 2
items
1 | Chair
---+-------
2 | Sofa
After properly sorting and grouping, the output table should like:
John | Sofa
------+------
Jane | Chair
Currently I can connect the item names to the purchaser and return a random item bought, but not the most popular by quantity. I have tried entering multiple fields into group by and managed to properly group the items by name and sort by quantity, but in doing so the customer id's became ungrouped. Been trying to solve this for days so any help would be appreciated. Please note that this is a very simplified version of the actual problem where many more tables are involved, including multiple items table which are being joined together to one.
You should use group by on joined tables
select
b.name
, c.name
, sum(quantity) as tot
from orders as a
inner join Customers as b on a.customer_id = b.id
inner join Items as c on a.item_id = c.id
group by b.name, c.name
order by tot
Selecting the sum of the quantities per customer-item group is easy, but selecting the top seller is a bit harder.
The first step is the query to get all the groups with the sums of the quantities for each customer-item:
SELECT
customer_name,item_name,SUM(quantity)
FROM
orders o
JOIN customers c ON o.customer_id=c.id
JOIN items i ON o.item_id=i.id
GROUP BY customer_name,item_name;
Then to only select the groups with the maximum quantity sums we use some trickery:
SELECT
customer_name,item_name,SUM(quantity),
(SELECT SUM(quantity) AS qmax
FROM
orders o2
JOIN customers c2 ON o2.customer_id=c2.id
JOIN items i2 ON o2.item_id=i2.id
WHERE c2.id=c.id
GROUP BY c2.customer_name,i2.item_name
ORDER BY qmax DESC LIMIT 1) AS qmax
FROM
orders o
JOIN customers c ON o.customer_id=c.id
JOIN items i ON o.item_id=i.id
GROUP BY customer_name,item_name
HAVING SUM(quantity)=qmax;
Edit:
Here's a link to a fiddle: SQLFiddle

select min value of a field from joins table

CREATE VIEW products_view
AS
Hi guys ! I've tree tables:
Products
Categories
Prices
A product belongs to one category and may has more prices.
consider this set of data:
Product :
id title featured category_id
1 | bread | yes | 99
2 | milk | yes | 99
3 | honey | yes | 99
Price :
id product_id price quantity
1 | 1 | 99.99 | 10
2 | 1 | 150.00 | 50
3 | 2 | 33.10 | 20
4 | 2 | 10.00 | 11
I need to create a view, a full list of products that for each product select the min price and its own category.
eg.
id title featured cat.name price quantity
1 | bread | yes | food | 99.99 | 10
I tried the following query but in this way I select only the min Price.price value but Price.quantity, for example, came from another row. I should find the min Price.price value and so use the Price.quantity of this row as correct data.
CREATE VIEW products_view
AS
SELECT `Prod`.`id`, `Prod`.`title`, `Prod`.`featured`, `Cat`.`name`, MIN(`Price`.`price`) as price,`Price`.`quantity`
FROM `products` AS `Prod`
LEFT JOIN `prices` AS `Price` ON (`Price`.`product_id` = `Prod`.`id`)
LEFT JOIN `categories` AS `Cat` ON (`Prod`.`category_id` = `Cat`.`id`)
GROUP BY `Prod`.`id`
ORDER BY `Prod`.`id` ASC
My result is:
id title featured cat.name price quantity
1 | bread | yes | food | 99.99 | **50** <-- wrong
Can you help me ? Thx in advance !
As documented under MySQL Extensions to GROUP BY (emphasis added):
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause. For example, this query is illegal in standard SQL because the name column in the select list does not appear in the GROUP BY:
SELECT o.custid, c.name, MAX(o.payment)
FROM orders AS o, customers AS c
WHERE o.custid = c.custid
GROUP BY o.custid;
For the query to be legal, the name column must be omitted from the select list or named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. This means that the preceding query is legal in MySQL. You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values within each group the server chooses.
What you are looking for is the group-wise minimum, which can be obtained by joining the grouped results back to the table:
SELECT Prod.id, Prod.title, Prod.featured, Cat.name, Price.price, Price.quantity
FROM products AS Prod
LEFT JOIN categories AS Cat ON Prod.category_id = Cat.id
LEFT JOIN (
prices AS Price NATURAL JOIN (
SELECT product_id, MIN(price) AS price
FROM prices
GROUP BY product_id
) t
) ON Price.product_id = Prod.id
ORDER BY Prod.id