60

Example:

SELECT
   (SELECT SUM(...) FROM ...) as turnover,
   (SELECT SUM(...) FROM ...) as cost,
   turnover - cost as profit

Sure this is invalid (at least in Postgres) but how to achieve the same in a query without rewriting the sub-query twice?

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
Wernight
  • 36,122
  • 25
  • 118
  • 131
  • Depends on details, such as columns & tables involved. – OMG Ponies Nov 18 '10 at 23:13
  • @OMG Ponis: Like? Isn't there a general way for such? – Wernight Nov 18 '10 at 23:20
  • I agree with @OMG. That said, if you can write *one* subquery that returns both `turnover` and `cost` as columns, the query wrapped around that subquery can perform `turnover - cost`. For more details, we'll need some details about your schema. – Dan J Nov 18 '10 at 23:21
  • 2
    Also consider using "common table expressions" aka CTE. See http://stackoverflow.com/questions/2686919/is-possible-to-reuse-subqueries and http://www.postgresql.org/docs/8.4/static/queries-with.html – Vadzim Dec 11 '12 at 09:12
  • Edited title: this question doesn't focus on reusing subqueries (tables) but rather single columns, unlike http://stackoverflow.com/q/2686919/648265 . – ivan_pozdeev Nov 24 '15 at 10:42
  • See also this answer in [how to use alias in calculation](https://stackoverflow.com/a/18020948/2641825) `SELECT 10 AS my_num, (SELECT my_num) * 5 AS another_number FROM table` – Paul Rougieux Mar 20 '19 at 16:07

9 Answers9

50

Like so:

SELECT
   turnover,
   cost,
   turnover - cost as profit
from (
   (SELECT SUM(...) FROM ...) as turnover,
   (SELECT SUM(...) FROM ...) as cost
   ) as partial_sums
Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
  • 1
    Usually when you do `select from table1, table2` you get the CROSS JOIN of the two tables. What does `select from (table1, table2) as bla` (as you have here) do with the two tables? – Charl Botha Jan 21 '15 at 09:15
  • 2
    @CharlBotha: it's not select from (foo, bar). It's [select] (select sum from foo), (select sum from bar) [from nothing]. – Denis de Bernardy Jan 22 '15 at 06:05
  • In your example above, you have `... from ((select ...) as turnover, (select ...) as cost) as partial_sums` -- I'm still wondering how exactly the turnover and cost sub-selects are combined? – Charl Botha Jan 22 '15 at 07:09
  • @CharlBotha: they're two scalars, in very much the same way as you'd go `select 1 as foo, 2 as bar`. – Denis de Bernardy Jan 22 '15 at 10:12
  • Thank you very much! (I did not see that they were scalars, probably because I was struggling with an own example of a correlated subquery from which I was trying to aggregate multiple columns in different ways) – Charl Botha Jan 22 '15 at 13:54
  • 1
    What about if the subselect uses a common FROM as well :S – fatuhoku Feb 09 '17 at 10:27
  • This doesn't work for me on Postgres 10, I'm getting a syntax error on line 6 on the comma after turnover. – kralyk Jan 03 '18 at 12:05
  • @kralyk: You sure you didn't forget parenthesis? Also note that Alex offers an equivalent query that one might consider cleaner. – Denis de Bernardy Jan 03 '18 at 21:05
  • This syntax is invalid. You cannot alias a table list in SQL, only join tables and other table expressions can be aliased. Even if you could, it wouldn't be necessary. Just write `FROM (SELECT ..) t1, (SELECT ..) t2` – Lukas Eder Nov 09 '22 at 09:56
15

You could reuse the query like this:

WITH 
  TURNOVER AS (
    SELECT SUM(...) FROM ...)
  ),
  COST AS(
    SELECT SUM(...) FROM ...
  )

SELECT *
FROM(
 SELECT
   TURNOVER.sum as SUM_TURNOVER
 FROM
 TURNOVER,COST
 WHERE ....
) AS a

This is equivalent to :

SELECT *
FROM(
 SELECT
   TURNOVER.sum as SUM_TURNOVER
 FROM
 (
   SELECT SUM(...) FROM ...)
 )AS TURNOVER,
 (
   SELECT SUM(...) FROM ...
 )AS COST
 WHERE ....
) AS a

There is a point to note here. The first method is more readable and reusable, but the second method might be faster, because the DB might choose a better plan for it.

6

Perhaps the sql "with" clause could help, as presented here http://orafaq.com/node/1879 (other databases such as Postgres do it as well, not just oracle).

faintsignal
  • 1,828
  • 3
  • 22
  • 30
andrers52
  • 524
  • 6
  • 9
  • 2
    This feature is called "common table expressions" aka CTE. See http://stackoverflow.com/questions/2686919/is-possible-to-reuse-subqueries – Vadzim Dec 11 '12 at 09:11
5
SELECT turnover, cost, turnover - cost
FROM
(
SELECT
(SELECT ...) as turnover,
(SELECT ...) as cost
) as Temp
Eric K Yung
  • 1,754
  • 11
  • 10
4

Actually I did a lot of work on this, and hit many brick walls, but finally figured out an answer - more of a hack - but it worked very well and reduced the read overhead of my queries by 90%....

So rather than duplicating the correlated query many times to retrieve multiple columns from the subquery, I just used concat all the values I want to return into a comma separated varchar, and then unroll them again in the application...

So instead of

select a,b,
(select x from bigcorrelatedsubquery) as x,
(select y from bigcorrelatedsubquery) as y,
(select z from bigcorrelatedsubquery) as z
from outertable

I now do

select a,b,
(select convert(varchar,x)+','+convert(varchar,x)+','+convert(varchar,x)+',' 
from bigcorrelatedsubquery) from bigcorrelatedquery) as xyz
from outertable
group by country

I now have all three correlated 'scalar' values I needed but only had to execute the correlated subquery once instead of three times.

Community
  • 1
  • 1
bhealy
  • 41
  • 1
  • What you _should_ have done is `JOIN` the `bigcorrelatedsubquery` with `outertable` once, instead of repeating it multiple times or resorting to the concat hack. Even in the worst case you could have factored that subquery as a CTE to avoid repeating it. – Amit Naidu Mar 01 '16 at 22:42
3

I think the following will work:

SELECT turnover, cost, turnover-cost as profit FROM
   (SELECT 1 AS FAKE_KEY, SUM(a_field) AS TURNOVER FROM some_table) a
INNER JOIN
   (SELECT 1 AS FAKE_KEY, SUM(a_nother_field) AS COST FROM some_other_table) b
USING (FAKE_KEY);

Not tested on animals - you'll be first! :-)

Share and enjoy.

  • You don't need `INNER JOIN` with a fake key. Just use a `CROSS JOIN`, or list the derived tables in a table list: `FROM (SELECT ..), (SELECT ..)` – Lukas Eder Nov 09 '22 at 09:51
0

Use a cross apply or outer apply.

SELECT
  Calc1.turnover,
  Calc2.cost,
  Calc3.profit
from
   cross apply ((SELECT SUM(...) as turnover FROM ...)) as Calc1
   cross apply ((SELECT SUM(...) as cost FROM ...)) as Calc2

   /*
     Note there is no from Clause in Calc 3 below.
     This is how you can "stack" formulas like in excel.
     You can return any number of columns, not just one.
   */
   cross apply (select Calc1.turnover - Calc2.cost as profit) as Calc3
William Egge
  • 429
  • 4
  • 5
  • That's T-SQL syntax, only supported by SQL Server and Oracle. In PostgreSQL, this approach would require standard SQL `LATERAL` derived tables. – Lukas Eder Nov 09 '22 at 09:50
0

this is pretty old but i ran into this problem and saw this post but didnt manage to solve my problem using the given answers so i eventually arrived at this solution :

if your query is :

SELECT
   (SELECT SUM(...) FROM ...) as turnover,
   (SELECT SUM(...) FROM ...) as cost,
   turnover - cost as profit

you can turn it into a subquery and then use the fields such as :

SELECT *,(myFields.turnover-myFields.cost) as profit 
FROM
(      
SELECT
       (SELECT SUM(...) FROM ...) as turnover,
       (SELECT SUM(...) FROM ...) as cost

) as myFields

i'm not entirely sure if this is a bad way of doing things but performance wise it seems okay for me querying over 224,000 records took 1.5 sec not sure if its later on turned into 2x of the same sub query by DB.

Niklas
  • 955
  • 15
  • 29
-1

You can use user defined variables like this

SELECT
   @turnover := (SELECT SUM(...) FROM ...),
   @cost := (SELECT SUM(...) FROM ...),
   @turnover - @cost as profit

http://dev.mysql.com/doc/refman/5.7/en/user-variables.html

Dinesh
  • 2,194
  • 3
  • 30
  • 52