patternsqlModerate
Using outer alias in a subquery
Viewed 0 times
subqueryaliasusingouter
Problem
| payments | | transactions | | transaction_items |
|:--------------:| |:------------:| |:-----------------:|
| id | | id | | id |
| date | | number | | transaction_id |
| amount | | date | | description |
| transaction_id | | val | | price |
| discount |
| quantity |I'm trying to display a list of payments made on transactions and show the current balance after each payment. Below is an example of expected result
| number | DATE(p.date) | total | paid | balance |
| 1355 | 2016-10-31 | 899.00 | 450.00 | 449.00 |
| 1355 | 2016-12-06 | 899.00 | 449.00 | 0.00 |
| 1359 | 2016-09-28 | 4045.00 | 1515.00 | 2530 |
| 1359 | 2016-10-24 | 4045.00 | 35.00 | 2495.00 |
| 1361 | 2016-09-28 | 1548.00 | 1548.00 | 0.00 |and here is my query so far, but have an error in the where clause
select
t.number,
DATE(p.date),
ti.total 'total',
SUM(p.amount) 'paid',
ti.total - paid.total 'balance'
from payments p
left join transactions t
on p.transaction_id = t.id
left join (
select inner_ti.transaction_id, sum((inner_ti.price - inner_ti.discount) * inner_ti.quantity) 'total'
from transaction_items inner_ti
group by inner_ti.transaction_id
) ti on t.id = ti.transaction_id
left join (
select inner_p.transaction_id, sum(inner_p.amount) 'total'
from payments inner_p
where inner_p.date <= p.date -- error unknown column p.date
group by inner_p.transaction_id
) paid on t.id = paid.transaction_id
group by t.number, DATE(p.date), ti.total, paid.total
order by DATE(p.date) ASCPlease note that I'm grouping by
p.date since our concern is total payments made within the day.Can someone please enlighten me why I'm getting that error? And is there any workaround to achieve
Solution
The two nested selects in your query are called derived tables. A derived table is not meant to be correlated with other datasets participating in the query, hence outer references to them in the nested query are not allowed.
One way to resolve the issue is to rewrite your query so as to move the offending select to the context where correlation is allowed. In your case you can move the offending subquery to the SELECT clause:
rextester here
For the sake of completeness, the SQL standard actually has syntax that allows correlation for derived tables. It is called lateral join. From the syntactical point of view, it looks almost exactly like a normal join, you just need to add the
The added keyword makes all the difference, as only with that keyword a nested query is permitted to reference other datasets in the same FROM clause (to the left of the most recent JOIN keyword).
Lateral joins are currently supported by PostgreSQL and Oracle. A similar concept with a slightly different (and less flexible) syntax is also supported by SQL Server. As you may have guessed, MySQL does not currently support anything of the sort.
One way to resolve the issue is to rewrite your query so as to move the offending select to the context where correlation is allowed. In your case you can move the offending subquery to the SELECT clause:
select t.number,
DATE(p.date),
ti.total 'total',
SUM(p.amount) 'paid',
ti.total - (select sum(inner_p.amount)
from payments inner_p
where inner_p.transaction_id = p.transaction_id
and inner_p.date <= p.date
) 'balance'
from payments p
left join transactions t
on p.transaction_id = t.id
left join (
select inner_ti.transaction_id,
sum((inner_ti.price - inner_ti.discount) * inner_ti.quantity) 'total'
from transaction_items inner_ti
group by inner_ti.transaction_id
) ti
on t.id = ti.transaction_id
group by t.number, DATE(p.date), ti.total, 'balance'
order by DATE(p.date) ASC;rextester here
For the sake of completeness, the SQL standard actually has syntax that allows correlation for derived tables. It is called lateral join. From the syntactical point of view, it looks almost exactly like a normal join, you just need to add the
LATERAL keyword after JOIN:…
left join lateral (
select inner_p.transaction_id, sum(inner_p.amount) 'total'
from payments inner_p
where inner_p.date <= p.date -- this outer reference would be valid
group by inner_p.transaction_id
) paid on t.id = paid.transaction_id
…The added keyword makes all the difference, as only with that keyword a nested query is permitted to reference other datasets in the same FROM clause (to the left of the most recent JOIN keyword).
Lateral joins are currently supported by PostgreSQL and Oracle. A similar concept with a slightly different (and less flexible) syntax is also supported by SQL Server. As you may have guessed, MySQL does not currently support anything of the sort.
Code Snippets
select t.number,
DATE(p.date),
ti.total 'total',
SUM(p.amount) 'paid',
ti.total - (select sum(inner_p.amount)
from payments inner_p
where inner_p.transaction_id = p.transaction_id
and inner_p.date <= p.date
) 'balance'
from payments p
left join transactions t
on p.transaction_id = t.id
left join (
select inner_ti.transaction_id,
sum((inner_ti.price - inner_ti.discount) * inner_ti.quantity) 'total'
from transaction_items inner_ti
group by inner_ti.transaction_id
) ti
on t.id = ti.transaction_id
group by t.number, DATE(p.date), ti.total, 'balance'
order by DATE(p.date) ASC;…
left join lateral (
select inner_p.transaction_id, sum(inner_p.amount) 'total'
from payments inner_p
where inner_p.date <= p.date -- this outer reference would be valid
group by inner_p.transaction_id
) paid on t.id = paid.transaction_id
…Context
StackExchange Database Administrators Q#195868, answer score: 14
Revisions (0)
No revisions yet.