Reputation: 523
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.
Upvotes: 3
Views: 15779
Reputation: 101
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
Upvotes: 0
Reputation: 191235
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; e.g. 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.
Upvotes: 7