tags:

views:

88

answers:

2

I'd like to calculate the ratio of items in a group that fulfil certain criteria out of the total number of items in that group. I've already solved this but am curious to know if my solution is optimal, as the query takes a problematically long time over my large (10m+) dataset.

Here is what I have in its simplest form:

create table #tableA 
(
 id int IDENTITY(1,1),
 groupid int,
 flag bit,
 CONSTRAINT [PK_TableA] PRIMARY KEY CLUSTERED 
 (
  [id] ASC
 )
)

insert into #tableA (groupid,flag) values (1,0),(1,0),(1,1),(2,0),(2,1)

select 
 a.groupid ,
 cast(totalCount as float)/count(*) as ratio
from 
 #tableA a 
 join 
 (
  select 
   groupid,
   COUNT(*) totalCount 
  from 
   #tableA 
  where 
   flag=1 
  group by 
   groupid
 ) b on a.groupid=b.groupid 
group by 
 a.groupid,
 b.totalCount

drop table #tableA

Is there a more efficient way to write this query?

+2  A: 

Assuming flag is 0 or 1, this should work:

select groupid ,
       cast(sum(flag) as float)/count(*) as ratio
from tableA
group by groupid

If flag can take other values, a CASE or IF() should help make SUM work anyway.

Alex Martelli
Of course! Thanks
spender
if it works the way you want, you should probably mark this accepted
Jonathan Fingland
I'm still testing and may need to append to the question before closing
spender
If [flag] is really a bit then you'll need to cast it to something larger before you SUM it.
RBarryYoung
@RBarryYoung, you're right, TSQL (differing from mysql here) does say "except for the bit data type" in the docs for SUM, so it will need a cast (or CASE or IF as I mentioned for more general cases in the answer).
Alex Martelli
+1  A: 

select groupid , AVG(cast(flag as float)) as ratio from tableA group by groupid

AlexKuznetsov
Really, flag is a null or non-null match from a joined column, so combined with a case statement giving 0 for null and 1 for non-null (from Alex Martelli's answer), this makes for a elegant solution. Thanks.
spender