I'm glad the explanation helped. I figured that the solution alone wouldn't
be very useful if it wasn't explained since it is not obvious to most people
how correlated queries work :-)

I really wasn't trying to center you out with my "rant" about MySQL version.
It's been a long-running frustration for me; I've tried to urge people to
give this information in their notes to the list but an awful lot of people
never think to mention their version. Then, an already lengthy reply gets
even longer as you try to explain: "If you have Version 3.x, the solution is
this.... If you have Version 4.x the solution is this..... If you have
Version 5.x, the solution is this....." You get the picture.

Okay, let me explain the temp tables approach now.

Most subqueries are not correlated so the subquery gets done once and then
its result gets plugged into the outer query. For example, given a table
called Employee in the Sample database which has one row for each employee
and each row contains an employee number, last name, and salary, this query
will return the employee number and last name of every employee who makes
more than the average salary for all employees in the table:

---
select empno, lastname
from Sample.Employee
where salary >
(select avg(salary)
from Sample.Employee);
---

If subqueries are supported, as in Version 4.1.x or above of MySQL, you just
run that exact query and you will get a list of all of the people who make
more than the average salary.

However, if you are using an older version of MySQL which doesn't support
subqueries, you need to use a temporary table approach. Basically, you
create a temporary table that contains the same results that the subquery
would have given you, then join it to the outer query. [Many, but not all,
subqueries can be rewritten as joins.] For example:
---
#Create the temporary table that will contain the average salary for all
employees.
create table if not exists temp_average_salary (average_salary
decimal(9,2));

#Populate the temporary table with the average salary for all employees.
insert into temp_average_salary(select avg(salary) from Sample.Employee);

#Do a join of the employee table and the temporary table to obtain only
employees who make
#more than the average salary.
select empno, lastname, salary
from Sample.Employee e inner join temp_average_salary t
where e.salary > t.average_salary;
---

This would give the same result as the original solution that used
subqueries although there is obviously more work to do in accomplishing the
desired result.

Initially, I was almost certain that this approach could not be made to work
for your problem due to its correlated nature but a couple of experiments
persuaded me that it was possible after all. It's actually quite similar to
the example I gave above, except that this time the temporary table is not
just a single value on a single row but a table containing one row for each
VBS_id, showing the max (latest) date for any row containing that VBS_id.
Here's the definition of the table and the correct method of populating the
table:

---
create table if not exists temp_table
(VBS_id int not null,
max_date date not null);

insert into temp_table (select VBS_id, max(date) from VBS_table group by
VBS_id);
---

If you display the contents of the temporary table, you get:

---
VBS_id    max_date
111111    2005-10-08
111112    2005-10-08

---
Now, you simply join the VBS_table and the temp_table, as follows:
---
select v.VBS_id, v.date, v.weight
from VBS_table v inner join temp_table t
on v.VBS_id = t.VBS_id
and v.date = t.max_date;
---

The result is exactly what you wanted:
---
VBS_id    date                weight
111111    2005-10-08    11.52
111112    2005-10-08    10.50
---

You could apply this solution to versions of MySQL that don't have subquery
support; just remember that you need to re-create the temp_table immediately
before you do the join each time; otherwise, you are going to determine
whatever weights were in effect whenever you first populated the temp_table,
not the current weights.

In your case though, you can make life better for yourself. Since you have
view support, you can simply create a view that is the equivalent to the
temp_table and then join the view to the VBS_table to get the data you want.
Since the view is always up-to-date, you eliminate the need to have the
temp_table at all. Something like this:

---
create view VBS_max_dates
(VBS_id, max_date)
as select VBS_id, max(date)
from VBS_table
group by VBS_id;

select v.VBS_id, v.date, v.weight
from VBS_table v inner join VBS_max_dates z
on v.VBS_id = z.VBS_id
and v.date = z.max_date;
---

Note that the view is only created once, not each time you are about to do
the join!!

One other solution jumps to mind that should be just as good. Instead of
creating temp tables or views, why not just put the original correlated
subquery that I gave you into a stored procedure? Then, all you'd need to do
is call that stored procedure every time you need that result.

Rhino


----- Original Message ----- 
From: "Gobi" <[EMAIL PROTECTED]>
To: <mysql@lists.mysql.com>
Sent: Saturday, November 05, 2005 8:05 AM
Subject: Re: Help with an SQL query


> Rhino wrote:
>
> >I can't test this in MySQL- I'm using an older version of MySQL that
doesn't
> >support subqueries - but it works in DB2 and it should do the trick for
> >getting the current weight of each VBS_id value:
> >
> >select VBS_id, date, weight
> >from VBS_table x
> >where date =
> >(select max(date) from VBS_table
> >where VBS_id = x.VBS_id);
> >
> >I'm assuming you are using a more version of MySQL which DOES support
> >subqueries! If not, you may be able to get the same result with temp
tables
> >but I haven't tried that. Make sure to say something if you don't know
how
> >to use temp tables to simulate subqueries. I'm dubious that this query
can
> >be simulated with temp tables though....
> >
> >(Rant: I REALLY wish people (all people, not just you) posting questions
to
> >this mailing list would get in the habit of specifying which version of
> >MySQL they are using!! The answers to questions FREQUENTLY depend on the
> >MySQL version so it would really help reduce the size of answers if
people
> >volunteered this information in the original question.)
> >
> >Also, I'm assuming that that MySQL supports correlated subqueries; I'm
> >really not sure so I'll let you try the actual query and see if it works
for
> >you.
> >
> >Here's an explanation of how this query works, in case you've never seen
one
> >like this before.
> >
> >This is called a correlated subquery; the key thing that makes it obvious
> >that this is a correlated subquery (in case you have to recognize one) is
> >that a correlation name, in this case 'x', appears in the FROM clause of
the
> >outer query and again in the WHERE clause of the subquery. The subquery
> >appears in brackets in case you are not familiar with subqueries.
> >
> >A correlated subquery works backwards to a normal subquery. In a normal
> >subquery, the subquery is executed first and is only executed once: its
> >result is plugged into the outer query which then executes just once as
> >well. In a correlated subquery, both the outer query and the subquery are
> >executed _repeatedly_, possibly MANY times each.
> >
> >The outer query is executed in order to obtain one row, THEN the subquery
is
> >executed to see if the row found by the outer query can be kept. In this
> >case, let's say that the outer query returned the first row of the table,
> >which has a VBS_id of 111111 and a date of '10/3/2005': the subquery
> >determines the maximum (most recent) date for any row that has same
VBS_id
> >as the one just found by the outer query; if the maximum date differs
from
> >the date found by the outer query, the outer query row is discarded and
does
> >not appear in the final result set. In this case, the maximum date for
> >VBS_ID is 10/8/2005 which is not equal to the value found by the outer
> >query, so that row is discarded.
> >
> >Having dealt with the first row of the outer query, the outer query
executes
> >again and gets another row. Again, the subquery is executed to see if the
> >date is the same as maximum date for that VBS_id and again, the outer row
is
> >only kept if its date matches the maximum date found by the subquery. And
so
> >it goes, one row at a time, until the outer query has read every row of
the
> >table; a single row of the outer query is obtained, then the subquery
> >determines if that row contains the maximum date for the VBS_id that was
> >just found by the outer query. The final result will contain only rows
that
> >have the maximum dates for each VBS_id and will show the VBS_id, the
maximum
> >date, and the weight at that date. That is the result you requested.
> >
> >Bear in mind that a correlated query can perform much more poorly than a
> >regular subquery, although the optimizer sometimes has tricks that make
the
> >performance quite tolerable.
> >
> >There may be a solution that doesn't involve a correlated subquery but
it's
> >not coming to me. However, others who are better at SQL may think of
> >something that does the job.
> >
> >Rhino
> >
>
> Wow, excellent explanation.  By the way, my apologies... I am using
> MySQL 5.0.15.  I would be interested in knowing how to use temp tables
> to simulate subqueries though.  I found out that the sql statement,
> although it returns the correct result, does not work in a view because
> MySQL Views does not allow subqueries.
>
> -- 
> MySQL General Mailing List
> For list archives: http://lists.mysql.com/mysql
> To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]
>
>
> -- 
> No virus found in this incoming message.
> Checked by AVG Free Edition.
> Version: 7.1.362 / Virus Database: 267.12.8/161 - Release Date: 03/11/2005
>
>



-- 
No virus found in this outgoing message.
Checked by AVG Free Edition.
Version: 7.1.362 / Virus Database: 267.12.8/161 - Release Date: 03/11/2005


-- 
MySQL General Mailing List
For list archives: http://lists.mysql.com/mysql
To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]

Reply via email to