Table alias is unknown in nested subquery - mysql

The following query works just fine. I am using a value from the outer select to filter inside the inner select.
SELECT
bk.ID,
(SELECT COUNT(*) FROM guests WHERE BookingID = bk.ID) as count
FROM
bookings bk;
However, the following select will not work:
SELECT
bk.ID,
(SELECT SUM(count) FROM (SELECT COUNT(*) AS count FROM guests WHERE BookingID = bk.ID GROUP BY RoomID) sub) as sumcount
FROM
bookings bk;
The error message is: Error Code: 1054. Unknown column 'bk.ID' in 'where clause'
Why is my alias bk known in the subselect, but not in the subselect of the subselect?
For the record, I am using MySQL 5.6.

This is called "scoping". I know that Oracle (for instance) only looks out one level for resolving table aliases. SQL Server is also consistent: it looks out more than one level.
Based on this example, MySQL clearly limits the scope of the identifier bk to the immediate subquery. There is a small hint in the documentation (emphasis mine):
A correlated subquery is a subquery that contains a reference to a
table that also appears in the outer query.
However, I have not found any other specific reference to the scoping rules in the documentation. There are other answers (here and here) that specify that the scope of a table alias is limited to one level of subquery.
You already know how to fix the problem (your two queries should produce identical results). Re-arranging the query to have joins and aggregations can also resolve this problem.

Correlated Scalar Subqueries in the SELECT list can usually be rewritten to a LEFT JOIN on a Derived Table (and in many cases they might perform better then):
SELECT
bk.ID,
dt.sumcount
FROM
bookings bk
LEFT JOIN
(SELECT BookingID,SUM(COUNT) AS sumcount
FROM
(
SELECT BookingID, RoomId, COUNT(*) AS COUNT
FROM guests
GROUP BY BookingID, RoomID
) sub
) AS dt
ON bk.BookingID = dt.BookingID

Related

Can SQL queries have subqueries in the from clause?

I've written the following query that is supposed to find the max sum of all wages of every staff member. For some reason mysql doesn't seem to like the the sub query in the from clause.
select max(sumwages)
from
(select staff.name, sum(wages) as sumwages
from staff, schedule
where staff.ssn = schedule.ssn
group by staff.ssn)
I also wrote this simpler query to test my theory. This also gives a syntax error. Is what I'm trying to do even possible or do I have to find another way?
select *
from (select ssn from staff)
Yes. In MySQL, these are referred to as derived tables. And every derived table requires an alias:
select max(sumwages)
from (select staff.name, sum(wages) as sumwages
from staff join
schedule
on staff.ssn = schedule.ssn
group by staff.ssn
) ss;
Also note the use of proper, explicit, standard, readable JOIN syntax. Never use commas in the FROM clause.
Additionally - you may avoid the subquery by
select sum(wages) as sumwages
from staff, schedule
where staff.ssn = schedule.ssn
group by staff.ssn
ORDER BY sumwages DESC LIMIT 1
This will give one row with maximal sum too...

Left join not returning all results

I am attempting to join the two tables below to show all the columns for the incident table and just a count of the corresponding records from the tickets table with the incident_id as the same in the incidents table.
As you can see below, none of the tickets have an incident_id assigned yet. The goal of my query is to show all of the records in the incident table with a count of the ticket_ids assigned to that ticket. I thought that this would work but it's returning only one row:
SELECT inc.incident_id, inc.title, inc.date_opened, inc.date_closed, inc.status, inc.description, issue_type, COUNT(ticket_id) as example_count
FROM fin_incidents AS inc
LEFT OUTER JOIN fin_tickets ON inc.incident_id = fin_tickets.incident_id;
What query can I use to return all of the incidents and their count of tickets, even if that count is 0?
Images:
Incident Table
Tickets Table
Result of my query
Your query should not work at all -- and would fail in the more recent versions of MySQL. The reason is that it is missing a GROUP BY clause:
SELECT inc.incident_id, inc.title, inc.date_opened,
inc.date_closed, inc.status, inc.description, inc.issue_type,
COUNT(t.ticket_id) as example_count
FROM fin_incidents inc LEFT OUTER JOIN
fin_tickets t
ON inc.incident_id = t.incident_id
GROUP BY inc.incident_id, inc.title, inc.date_opened,
inc.date_closed, inc.status, inc.description, inc.issue_type
You have an aggregation query with no GROUP BY. Such a query returns exactly one row, even if the tables referred to are empty.
Your code is not a valid aggregation query. You have an aggregate function in the SELECT clause (the COUNT()), but no GROUP BY clause. When executed this with sql mode ONLY_FULL_GROUP_BY disabled, MySQL gives you a single row with an overall count of tickets that are related to an incident, and any value from incident row. If that SQL mode was enabled, you would a compilation error instead.
I find that the logic you want is simpler expressed with a correlated subquery:
select i.*
(select count(*) from fin_tickets t where t.incident_id = i.incident_id) as example_count
from fin_incidents i
This query will take advantage of an index on fin_tickets(incident_id) - if you have defined a foreign key (as you should have), that index is already there.

Remove Duplicate record from Mysql Table using Group By

I have a table structure and data below.
I need to remove duplicate record from the table list. My confusion is that when I am firing query
SELECT * FROM `table` GROUP BY CONCAT(`name`,department)
then giving me correct list(12 records).
Same query when I am using the subquery:
SELECT *
FROM `table` WHERE id IN (SELECT id FROM `table` GROUP BY CONCAT(`name`,department))
It returning all record which is wrong.
So, My question is why group by in subquery is not woking.
Actually as Tim mentioned in his answer that it to get first unique record by group by clause is not a standard feature of sql but mysql allows it till mysql5.6.16 version but from 5.6.21 it has been changed.
Just change mysql version in your sql fiddle and check that you will get what you want.
In the query
SELECT * FROM `table` GROUP BY CONCAT(`name`,department)
You are selecting the id column, which is a non-aggregate column. Many RDBMS would give you an error, but MySQL allows this for performance reasons. This means MySQL has to choose which record to retain in the result set. Based on the result set in your original problem, it appears that MySQL is retaining the id of the first duplicate record, in cases where a group has more than one member.
In the query
SELECT *
FROM `table`
WHERE id IN
(
SELECT id FROM `table` GROUP BY CONCAT(`name`,department)
)
you are also selecting a non-aggregate column in the subquery. It appears that MySQL actually decides which id value to be retained in the subquery based on the id value in the outer query. That is, for each id value in table, MySQL performs the subquery and then selectively chooses to retain a record in the group if two id values match.
You should avoid using a non-aggregate column in a query with GROUP BY, because it is a violation of the ANSI standard, and as you have seen here it can result in unexpected results. If you give us more information about what result set you want, we can give you a correct query which will avoid this problem.
I welcome anyone who has documentation to support these observations to either edit my question or post a new one.
You can JOIN the grouped ids with that of table ids, so that you can get desired results.
Example:
SELECT t.* FROM so_q32175332 t
JOIN ( SELECT id FROM so_q32175332
GROUP BY CONCAT( name, department ) ) f
ON t.id = f.id
ORDER BY CONCAT( name, department );
Here order by was added just to compare directly the * results on group.
Demo on SQL Fiddle: http://sqlfiddle.com/#!9/d715a/1

wrapping inside aggregate function in SQL query

I have 2 tables called Orders and Salesperson shown below:
And I want to retrieve the names of all salespeople that have more than 1 order from the tables above.
Then firing following query shows an error:
SELECT Name
FROM Orders, Salesperson
WHERE Orders.salesperson_id = Salesperson.ID
GROUP BY salesperson_id
HAVING COUNT( salesperson_id ) >1
The error is:
Column 'Name' is invalid in the select list because it is
not contained in either an aggregate function or
the GROUP BY clause.
From the error and searching it on google, I could understand that the error is because of Name column must be either a part of the group by statement or aggregate function.
Also I tried to understand why does the selected column have to be in the group by clause or art of an aggregate function? But didn't understand clearly.
So, how to fix this error?
SELECT max(Name) as Name
FROM Orders, Salesperson
WHERE Orders.salesperson_id = Salesperson.ID
GROUP BY salesperson_id
HAVING COUNT( salesperson_id ) >1
The basic idea is that columns that are not in the group by clause need to be in an aggregate function now here due to the fact that the name is probably the same for every salesperson_id min or max make no real difference (the result is the same)
example
Looking at your data you have 3 entry's for Dan(7) now when a join is created the with row Dan (Name) gets multiplied by 3 (For every number 1 Dan) and then the server does not now witch "Dan" to pick cos to the server that are 3 lines even doh they are semantically the same
also try this so that you see what I am talking about:
SELECT Orders.Number, Salesperson.Name
FROM Orders, Salesperson
WHERE Orders.salesperson_id = Salesperson.ID
As far as the query goes INNER JOIN is a better solution since its kinda the standard for this simple query it should not matter but in some cases can happen that INNER JOIN produces better results but as far as I know this is more of a legacy thing since this days the server should pretty much produce the same execution plan.
For code clarity I would stick with INNER JOIN
Assuming the name is unique to the salesperson.id then simply add it to your group by clause
GROUP BY salesperson_id, salesperson.Name
Otherwise use any Agg function
Select Min(Name)
The reason for this is that SQL doesn't know whether there are multiple name per salesperson.id
For readability and correctness, I usually split aggregate queries into two parts:
The aggregate query
Any additional queries to support fields not contained in aggregate functions
So:
1.Aggregate query - salespeople with more than 1 order
SELECT salesperson_id
FROM ORDERS
GROUP BY salespersonId
HAVING COUNT(Number) > 1
2.Use aggregate as subquery (basically a select joining onto another select) to join on any additional fields:
SELECT *
FROM Salesperson SP
INNER JOIN
(
SELECT salesperson_id
FROM ORDERS
GROUP BY salespersonId
HAVING COUNT(Number) > 1
) AGG_QUERY
ON AGG_QUERY.salesperson_id = SP.ID
There are other approaches, such as selecting the additional fields via aggregation functions (as shown by the other answers). These get the code written quickly so if you are writing the query under time pressure you may prefer that approach. If the query needs to be maintained (and hence readable) I would favour subqueries.

About sql query

SELECT E.flmavailable_date,E.flmavailable_num_licenses, flmavailable_product AS SERVER
FROM (SELECT flmavailable_num_licenses,flmavailable_date
FROM licenses_available
ORDER BY flmavailable_num_licenses ASC) E
WHERE flmavailable_product <= 4;
Error:---Unknown column 'flmavailable_product' in 'field list'
Even though there is field called by that name I am getting error.
I need help to resolve this
Your outer select is trying to select a flmavailable_product from your inner select, yet there is no such field in that inner query.
Can you include the schema of the licenses_available table and a description of what your query is intended to return? This would make it easier to help you. Based on your query as it stands, it seems like you might not need that inner subquery.
You need to include the flmavailable_product field in the inner sub query if it is a column in the licences_available table, or join to the appropriate table which contains this field in the inner query :
SELECT E.flmavailable_date,E.flmavailable_num_licenses,flmavailable_product AS SERVER
FROM (SELECT flmavailable_num_licenses,flmavailable_date, flmavailable_product
FROM licenses_available ORDER BY flmavailable_num_licenses ASC)E
WHERE flmavailable_product <= 4;
Also, not sure why you are using the inner subquery as all it does is a straight select
This is the query you wanted to write.
SELECT flmavailable_num_licenses,flmavailable_date, flmavailable_product AS SERVER
FROM licenses_available
WHERE flmavailable_product <= 4
ORDER BY flmavailable_num_licenses ASC
General select query structure is:
SELECT a,b,c FROM `table`
WHERE x<=y
Here your inner query is acting as a table. However your inner query is returning only two columns: flmavailable_num_licenses & flmavailable_date.
In your outer query, you are getting an error as you are trying to select column 'flmavailable_product' which is not available in the table (Inner query).
This is clearly indicated by the error (Unknown column 'flmavailable_product' in 'field list') you are getting.
To solve this, you need to either select flmavailable_product in inner query or if it is not present there, use join to join the tables where required column is present.
Please provide table structure for exact query.