3

This is very weird, so I could use a pair of spare eyes to understand what's happening.

So I have this query:

WITH x as (
            SELECT num_aula, tipo_aula, min(abs(capienza-1)) score
            FROM aula
            JOIN (
                    select num_aula, tipo_aula
                    from aula
                    where tipo_aula = 'Laboratorio'
                    minus
                    select num_aula, tipo_aula
                    from occr_lezione
                    where to_char(Data_inizio_occr_lezione,'hh24:mi') = '12:30'
                    and Nome_sede = 'Centro Direzionale'
                    and Giorno_lezione = 2
                 )
            USING(num_aula,tipo_aula)
            GROUP BY num_aula, tipo_aula
            ORDER BY score asc
)
SELECT *
FROM x

which return this result set:

NUM TIPO_AULA                 SCORE
--- -------------------- ----------
1   Laboratorio                  35
2   Laboratorio                  35

Which is the desired result.

Now, if I add this line to the query:

WHERE rownum = 1;

which should return the first row of the table, I get this:

NUM TIPO_AULA                 SCORE
--- -------------------- ----------
2   Laboratorio                  35

How is that possible?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
darkpirate
  • 712
  • 3
  • 10
  • 27
  • https://docs.oracle.com/cd/E11882_01/server.112/e41084/pseudocolumns009.htm#SQLRF00255 –  Mar 20 '15 at 19:11
  • 2
    Where are you putting that extra line? Inside or outside the CTE? – Alex Poole Mar 20 '15 at 19:12
  • What's your Oracle version number? In 12+ you can use the row_limiting_clause to get top-n query results without analytic functions or a combination of in-line views and round: http://docs.oracle.com/database/121/SQLRF/statements_10002.htm#BABHFGAA – David Aldridge Mar 20 '15 at 21:56

5 Answers5

3

I think the query you really want is

WITH x as (
    SELECT num_aula, 
           tipo_aula, min(abs(capienza-1)) score, 
           row_number() over(partition by num_aula, tipo_aula order by score asc ) as seq
    FROM aula
    JOIN (
        select num_aula, tipo_aula
          from aula
          where tipo_aula = 'Laboratorio'
          minus
          select num_aula, tipo_aula
            from occr_lezione
            where to_char(Data_inizio_occr_lezione,'hh24:mi') = '12:30'
            and Nome_sede = 'Centro Direzionale'
            and Giorno_lezione = 2
        )
        USING(num_aula,tipo_aula)
)
SELECT *
FROM x
WHERE x.seq = 1;

The ROWNUM keyword does not behave as you think, see this article about rownum.

To give more details, the ROWNUM are assigned before any order is given to the result set.

If you really want to get the correct result using the ROWNUM keyword, then you could achieve this with a subquery that would first order, then generate the rownum to the actual ordered result set. However, I would prefer the first approach as it is more readable in my opinion, but you are free to prefer this one.

SELECT *
FROM (SELECT num_aula, 
           tipo_aula, min(abs(capienza-1)) score
        FROM aula
        JOIN (
            select num_aula, tipo_aula
            from aula
            where tipo_aula = 'Laboratorio'
            minus
                select num_aula, tipo_aula
                from occr_lezione
                where to_char(Data_inizio_occr_lezione,'hh24:mi') = '12:30'
                  and Nome_sede = 'Centro Direzionale'
                  and Giorno_lezione = 2
            ) USING(num_aula,tipo_aula)
        GROUP BY num_aula, tipo_aula
        ORDER BY score asc) x
WHERE x.rownum = 1;
Jean-François Savard
  • 20,626
  • 7
  • 49
  • 76
  • well the seq keyword give me the invalid identifier error, don't know why frankly. Besides, can you tell me what's the difference ? the documentation seems quite articulated, it's quite time investing to find the actual difference in there :S – darkpirate Mar 20 '15 at 19:19
  • *The first row fetched is assigned a ROWNUM of 1 and makes the condition false. The second row to be fetched is now the first row and is also assigned a ROWNUM of 1 and makes the condition false.* – Jean-François Savard Mar 20 '15 at 19:20
  • @darkpirate Note that this answer changes the query so that `seq` is generated on the inside (so if the entire thing is not copied/used it won't work). However I don't believe it's required. The documentation explains how to use rownumber *when* an order by is involved, and if this transfers over the CTE as well then 'done'. – user2864740 Mar 20 '15 at 19:20
  • @user2864740 i don't get the difference from mine – darkpirate Mar 20 '15 at 19:20
  • @darkpirate It is a rewrite with `row_number() over(partition by num_aula, tipo_aula order by score asc ) as seq` (look inside). This is why I am challenging this answer - it misses out on the explaining bit. – user2864740 Mar 20 '15 at 19:21
  • @Jean-FrançoisSavard Uhh, herm, that's not how rownum works, even with order by involved. – user2864740 Mar 20 '15 at 19:22
  • @Jean-FrançoisSavard But *if you read the documentation* you'd see that this *is guaranteed by Oracle* when the ORDER BY is contained in a sub-query (I don't know if/how this extends over CTEs). – user2864740 Mar 20 '15 at 19:22
  • The `row_number()` function will also allow you to directly know what is the partition and order without having to look at the bottom of the query. – Jean-François Savard Mar 20 '15 at 19:22
  • @Jean-FrançoisSavard While a different way to write the query (and as stated, I would write it like this, largely because I use SQL Server) it is irrelevant to the behavior of rownum. – user2864740 Mar 20 '15 at 19:22
  • Oh well, if you really want to use the `rownum` then simply do a subquery that will order first, then select from that subquery using rownum. However, I still think that this way is more readable. Feel free to downvote my alternative if you disagree with it. – Jean-François Savard Mar 20 '15 at 19:26
  • "The row_number() function will also allow you to directly know what is the partition and order without having to look at the bottom of the query" i don't fully understand what that means @Jean-FrançoisSavard – darkpirate Mar 20 '15 at 19:29
  • @Jean-FrançoisSavard I'm not going to downvote (and am in fact the only current upvote), but make sure to expand upon the actual problem(s) and include relevant details (in the answer itself) instead of just brushing it all aside for an alternate. – user2864740 Mar 20 '15 at 19:29
  • @darkpirate this simply means that the order and by partition will be defined aside the row_number() call in the query instead of at the bottom. – Jean-François Savard Mar 20 '15 at 19:30
  • Is it possible to see some code based on your ideas, it's not very clear to understand your opinion in the comment section – darkpirate Mar 20 '15 at 19:40
  • the second approach give me the missing keyword error – darkpirate Mar 20 '15 at 19:42
  • 16 GROUP BY num_aula, tipo_aula ERROR at line 16: ORA-00905: missing keyword @Jean-FrançoisSavard – darkpirate Mar 20 '15 at 19:53
  • the same unfortunally. – darkpirate Mar 20 '15 at 20:09
  • @darkpirate edited, the order was incorrect ... sorry about that. – Jean-François Savard Mar 20 '15 at 20:15
  • now it give error : ORA-01747: invalid user.table.column, table.column, or column specification on the last line – darkpirate Mar 20 '15 at 20:27
  • http://stackoverflow.com/questions/9459204/ora-01747-invalid-user-table-column-table-column-or-column-specification – Jean-François Savard Mar 20 '15 at 20:30
1

The rownum is assigned to the row before any sorting takes place, so I suspect that removing your order by and running without the where rownum = 1 will return:

NUM TIPO_AULA                 SCORE
--- -------------------- ----------
2   Laboratorio                  35
1   Laboratorio                  35

There is a good article on rownum that can be found here: http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html

Mark
  • 76
  • 5
1

I'm not sure the problem here is with the rownum. You are using order by score, but this does not uniquely define the order, because there are duplicate values of score.

Try using order by score, num in order to get a stable sort and see if that fixes your problem.

Note that the lack of a stable sort also affects row_number(), so just switching to row_number() may not fix the problem -- unless you include an addition field to make the sort stable.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • I think that order by still might need to be moved to a subquery with relation to the rownum, and it is unclear if the OP is doing this .. but this is very true observation. – user2864740 Mar 20 '15 at 19:27
  • your suggestion actually works on my dataset, but that's not a bulletproof evidence of course. By the way i would try to avoid to add a subquery if possible, don't want to make the code more complex – darkpirate Mar 20 '15 at 19:37
  • 2
    @darkpirate . . . Oracle has documentation that shows `order by` in subqueries and `rownum` in the outer query. I don't recall seeing examples in the documentation with a CTE. Happily, this issue will gradually fade, because Oracle now supports `fetch first 1 row only`, which fixes this problem. And in an ANSI-compatible way. – Gordon Linoff Mar 20 '15 at 20:14
1
SELECT num_aula, tipo_aula, min(abs(capienza-1)) score
FROM aula
JOIN (
        select num_aula, tipo_aula
        from aula
        where tipo_aula = 'Laboratorio'
        minus
        select num_aula, tipo_aula
        from occr_lezione
        where to_char(Data_inizio_occr_lezione,'hh24:mi') = '12:30'
        and Nome_sede = 'Centro Direzionale'
        and Giorno_lezione = 2
     )
USING(num_aula,tipo_aula)
GROUP BY num_aula, tipo_aula
ORDER BY score asc
FETCH FIRST ROW ONLY;

Using FETCH FIRST ROW ONLY will order the results and then return the first row, this way you do not need to select again and have WHERE ROWNUM = 1 .

-2

If you say something like this:

with t as
(
   select *
   from foo
   order by foo.x
)
select *
from t
where t.rownum = 1
  1. You haven't specified an ordering via order by in your actual query. SQL is free to return the results set in any order (and even to vary the ordering from one execution to another).

  2. The order by in your inner 'select' statement is going to be ignored by the optimizer, unless you're doing something in that inner select with row numbering or partitioning or the like.

  3. You're making an assumption that the sort order is stable with respect to the ordering of duplicate values.

So...you need to write something like this:

with t as
(
   select *
   from foo
)
select *
from t
order by foo.x, foo.id
where t.rownum = 1
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • i'm sorry but i'm drastically tired at this point, can you please post your version according to mine ? – darkpirate Mar 20 '15 at 19:55
  • This is exactly the wrong way round, I believe, and the Oracle documentation contradicts your interpretation of the use of ORDER BY inside a subquery and its relationship with ROWNUM: http://docs.oracle.com/database/121/SQLRF/pseudocolumns009.htm#SQLRF00255 – David Aldridge Mar 20 '15 at 21:54
  • A `WHERE` cannot follow an `ORDER BY`. – David Faber Mar 22 '15 at 00:31