简体   繁体   中英

Oracle How to simplify a select count(*) from (subquery)?

I was asked to try to simplify a count() query but I do not know where to begin, the query is something like this:

SELECT COUNT( 1 )
FROM (
        SELECT DISTINCT a.col,b.colx,c.coly
        FROM  a
            JOIN b on a.id = b.id
            JOIN c on b.id = c.id
        WHERE a.xyz = 'something'
        AND   b.hijk = 'something else'
        AND c.id IN (
                SELECT cid
                FROM cwa
                WHERE csid = 22921
            )
        ORDER BY
            e.create_timestamp DESC
    );

I was told that the SELECT COUNT(1) FROM (subquery) can be simplified, how can this be done ?

I've tried a couple of things but the result is different to the query above.

An order-by in a subquery isn't useful unless you're filtering the result on rownum (and sometimes will error, depending on context). And you can replace the inner subquery with a join:

SELECT COUNT(*)
FROM (
    SELECT DISTINCT a.col,b.colx,c.coly
    FROM  a
    JOIN b on a.id = b.id
    JOIN c on b.id = c.id
    JOIN cwa on c.id cwa.cid
    WHERE a.xyz = 'something'
    AND   b.hijk = 'something else'
    AND   cwa.csid = 22921
);

You could even do it without a subquery, if you can identify a character that does not appear in any of the three columns you're selecting, so you can use it as a delimiter; eg if you'll never have a tilde you could do:

SELECT COUNT(DISTINCT a.col ||'~'|| b.colx ||'~'|| c.coly)
FROM  a
JOIN b on a.id = b.id
JOIN c on b.id = c.id
JOIN cwa on c.id cwa.cid
WHERE a.xyz = 'something'
AND   b.hijk = 'something else'
AND   cwa.csid = 22921;

though whether that's simpler or clearer is a matter of opinion.


As count() only takes a single argument, and you want to count the (distinct) combinations of those three columns, this mechanism concatenates all three into a single string and then counts appearances of that string. The delimiter is added so you can distinguish between ambiguous column values, for instance with a contrived example in a CTE:

with cte (col1, col2) as (
  select 'The', 'search' from dual
  union all select 'These', 'arch' from dual
)
select col1, col2,
  col1 || col2 as bad,
  col1 ||'~'|| col2 as good
from cte;

COL1  COL2   BAD         GOOD        
----- ------ ----------- ------------
The   search Thesearch   The~search  
These arch   Thesearch   These~arch  

With simple 'bad' concatenation both rows appear the same; by adding the delimiter to make the 'good' version you can still distinguish between them, so counting distinct concatenate values gets the right answer:

with cte (col1, col2) as (
  select 'The', 'search' from dual
  union all select 'These', 'arch' from dual
)
select count(distinct col1 || col2) as bad_count,
  count (distinct col1 ||'~'|| col2) as good_count
from cte;

 BAD_COUNT GOOD_COUNT
---------- ----------
         1          2

If col1 ended with a tilde, or col2 started with a tilde, you'd be back to ambiguity:

with cte (col1, col2) as (
  select 'The~', 'search' from dual
  union all select 'The', '~search' from dual
)
select col1, col2,
  col1 || col2 as bad,
  col1 ||'~'|| col2 as still_bad
from cte;

COL1 COL2    BAD         STILL_BAD   
---- ------- ----------- ------------
The~ search  The~search  The~~search 
The  ~search The~search  The~~search 

so the delimiter needs to be something you won't find in any values.

Try to use

    SELECT count(DISTINCT a.col)
    FROM  a
        JOIN b on a.id = b.id
        JOIN c on b.id = c.id
    WHERE a.xyz = 'something'
    AND   b.hijk = 'something else'
    AND c.id IN (
            SELECT cid
            FROM cwa
            WHERE csid = 22921
        );

Because order by will increase your execution time unnecessary

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM