2

The database is running on Oracle Database 11g using SQL *Plus 11.2. Are aggregate methods not allowed in a WITH clause or is WITH doing something magical? This code tells me "most_expensive" is an invalid identifier. Yet, a sub-query works with no issue.

WITH most_expensive AS (SELECT MAX (enrollment_cost) FROM Enrollments)
SELECT e.member_id
FROM Enrollments e
WHERE e.enrollment_cost = most_expensive;
1
  • 1
    You don't have a column name most_expensive in your query - only a sub-query. But you refer to a column of that name in your where clause Commented Oct 7, 2015 at 6:01

3 Answers 3

5

Query factoring (with clauses) allows you to define temporary table aliases. In your example most_expensive is going to reference a table object containing a single row with a single column. You can use it anywhere in the query where you can use a table. Now, if you create a table called t1 (with create table statment), give it one column and insert 1 row, you still won't be able to do "WHERE x = t1".
In other words, a subquery is not always the same as a table, and WITH clauses gives you something that behaves like tables, not like subqueries.

The following works though:

WITH most_expensive AS (SELECT MAX (enrollment_cost) FROM Enrollments)
SELECT member_id
FROM Enrollments e
WHERE e.enrollment_cost = (select * from most_expensive);

http://sqlfiddle.com/#!4/9eecb7/6340

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you, your answer is helpful. Though, what is a subquery returning then? I assumed it was also just giving back a table with 1 row and 1. It still helps in large queries, but I was hoping there was a way to keep a temporary variable to keep the actual query a bit cleaner (not so much for this SQL snippet, but for the future).
Unfortunately SQL has lots of silly non-intuitive corners like that. Technically subquery shouldn't work either -- a result of subquery is a collection of tuples, not a single number, but it is so convenient to support col = (subquery) that they, I guess, couldn't resist adding support for that. Doing the same for the tables would be terrible -- at least when looking at a subquery you can say "yes, this will return a single number". There is no way to force a table to always be a single column single row. Except when you have subquery factoring. But that's kind of too much.
Was joining the only alternative to a subquery before they added subquerying in? They seem to be part of the standard now.
Looks like scalar subquery is there in SQL'92 contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
1

I don't see any benefit of using sub-query factoring(WITH clause) here. The query could simply be written as:

SELECT member_id
FROM Enrollments e
WHERE e.enrollment_cost =
  (SELECT MAX (enrollment_cost) FROM Enrollments
  );

Compare the explain plans:

Without sub-query factoring:

SQL> set autot on explain
SQL> SELECT empno FROM emp e WHERE e.sal =
  2    (SELECT MAX (sal) FROM emp
  3    );

     EMPNO
----------
      7839


Execution Plan
----------------------------------------------------------
Plan hash value: 1876299339

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     1 |     8 |     8   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL  | EMP  |     1 |     8 |     4   (0)| 00:00:01 |
|   2 |   SORT AGGREGATE    |      |     1 |     4 |            |          |
|   3 |    TABLE ACCESS FULL| EMP  |    14 |    56 |     4   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("E"."SAL"= (SELECT MAX("SAL") FROM "EMP" "EMP"))

With sub-query factoring:

SQL> WITH max_sal AS
  2    ( SELECT MAX (sal) sal FROM emp
  3    )
  4  SELECT empno FROM emp e WHERE e.sal =
  5    (SELECT sal FROM max_sal
  6    );

     EMPNO
----------
      7839


Execution Plan
----------------------------------------------------------
Plan hash value: 73843676

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |     8 |     8   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL   | EMP  |     1 |     8 |     4   (0)| 00:00:01 |
|   2 |   VIEW               |      |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    SORT AGGREGATE    |      |     1 |     4 |            |          |
|   4 |     TABLE ACCESS FULL| EMP  |    14 |    56 |     4   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("E"."SAL"= (SELECT "SAL" FROM  (SELECT MAX("SAL") "SAL"
              FROM "EMP" "EMP") "MAX_SAL"))

See the filter applied, all you are doing is making it nested query and going one level deep without actually adding any benefit.

1 Comment

Was hoping subquery factoring would help make the code a bit more readable by getting fluff out of the main query. I guess I was hoping for a local variable for the query.
0

Your factored query(table) is named most_expensive but you use it as column name. In this case you should rather use keep first.

SELECT max(member_id) KEEP (DENSE_RANK FIRST ORDER BY enrollment_cost desc nulls last) "Best"
FROM Enrollments e
WHERE e.enrollment_cost = most_expensive;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.