tags:

views:

61

answers:

6
+4  Q: 

SQL: query help

My table had 3 fields: id and unit. I want to count how many ids have <10, 10-49, 50-100 etc units. The final result should look like:

Category | countIds  
<10      | 1516  
10 - 49  | 710  
50 - 99  | 632  
etc.

This is the query that returns each id and how many units it has:

select id, count(unit) as numUnits
from myTable
group by id

How can I build on that query to give me the category, countIds result?

Note: I couldn't figure out how to title this question. Please feel free to edit it/ offer suggestions

+1  A: 

Giving an example for one range: (10 - 49)

select count(id) from 
(select id, count(unit) as numUnits from myTable group by id) 
where numUnits >= '10' && numUnits <= '49' 
Mahesh Velaga
If I'm understanding correctly, this answer would mean that I would have to write a separate query for each category. I want one query to divide it into categories.
dmr
@dmr It would be easier if you could make the categories have the same size. You have one category with 10 items and others with 40 items. If you must have different-sized categories, you're going to have a complicated solution.
DOK
+1  A: 

It's not precisely what you want, but you could use fixed ranges, like so:

select ' < ' || floor(id / 50) * 50, count(unit) as numUnits
  from myTable
group by floor(id / 50) * 50
order by 1
Brandon Horsley
Four spaces - no pre tags pls ;)
OMG Ponies
+2  A: 
SELECT id, countIds
FROM (
SELECT id
     , 'LESS_THAN_TEN' CATEGORY
     , COUNT(unit) countIds
  FROM table1
 GROUP BY ID
 HAVING COUNT(UNIT) < 10
UNION ALL
SELECT id
     , 'BETWEEN_10_AND_49' category
     , COUNT(unit) countIds
  FROM table1
 GROUP BY ID
 HAVING COUNT(UNIT) BETWEEN 10 AND 49
UNION ALL
SELECT id
     , 'BETWEEN_50_AND_99' category
     , COUNT(unit) countIds
  FROM table1
 GROUP BY id
 HAVING COUNT(UNIT) BETWEEN 50 AND 99
) x
dcp
+1: Just need to pivot it to match the OP expected output
OMG Ponies
Yep, thanks, I corrected it.
dcp
Actually, I think Bill Karwin has a really nice solution since it's table driven, I think it's the best one I've seen to this question.
dcp
+3  A: 
create temporary table ranges (
  seq         int primary key,
  range_label varchar(10),
  lower       int,
  upper       int
);

insert into ranges values
(1, '<10',     0, 9), 
(2, '10 - 49', 10, 49),  
(3, '50 - 99', 50, 99)
etc.

select r.range_label, count(c.numUnits) as countIds
from ranges as r 
join (
  select id, count(unit) as numUnits
  from myTable
  group by id) as c
on c.numUnits between r.lower and r.upper
group by r.range_label
order by r.seq;

edit: changed sum() to count() above.

Bill Karwin
Very nice, only r.seq has to be in the select doesn't it, because you have it in the order by (it doesn't work on Oracle because anything you order by has to be in the select, and I think that's true for most DB's). Anyway, I really think this is a great answer and better than mine!
dcp
@dcp: Thanks! Yes, it's possible that Oracle or some other implementation requires that a column in ORDER BY must appear in the select. But FWIW, this would be a vendor idiosyncrasy; it isn't required by the ANSI SQL standard. I most often use MySQL, and it works fine there.
Bill Karwin
An expression being ordered by must only appear in the select list if using DISTINCT or GROUP BY, at least in Oracle.
Adam Musch
Thanks for the info @Adam Musch!
Bill Karwin
+3  A: 
select category_bucket, count(*) 
  from (select case when category < 10 then "<10"
                    when category >= 10 and category <= 49 then "10 - 49"
                    when category >= 50 and category <= 99 then "50 - 99"
                    else "100+"
               end category_bucket, num_units
          from my_table)
  group by category_bucket 

A dynamically grouped solution is much harder.

Adam Musch
See Bill Karwin's solution, it's table driven and dynamic.
dcp
Adding a temporary table and populating it with ranges, in my opinion, qualifies it as "harder." Certainly a more intrusive solution, and one may not be able to add tables to the databases for vendor-supplied products.
Adam Musch
+1  A: 

Try this working sample in SQL Server TSQL

SET NOCOUNT ON
GO
WITH MyTable AS
(
SELECT 00 as Id, 1 Value UNION ALL
SELECT 05 , 2 UNION ALL
SELECT 10 , 3 UNION ALL
SELECT 15 , 1 UNION ALL
SELECT 20 , 2 UNION ALL
SELECT 25 , 3 UNION ALL
SELECT 30 , 1 UNION ALL
SELECT 35 , 2 UNION ALL
SELECT 40 , 3 UNION ALL
SELECT 45 , 1 UNION ALL
SELECT 40 , 3 UNION ALL
SELECT 45 , 1 UNION ALL
SELECT 50 , 3 UNION ALL
SELECT 55 , 1 UNION ALL
SELECT 60 , 3 UNION ALL
SELECT 65 , 1 UNION ALL
SELECT 70 , 3 UNION ALL
SELECT 75 , 1 UNION ALL
SELECT 80 , 3 UNION ALL
SELECT 85 , 1 UNION ALL
SELECT 90 , 3 UNION ALL
SELECT 95 , 1 UNION ALL
SELECT 100 , 3 UNION ALL
SELECT 105 , 1 UNION ALL
SELECT 110 , 3 UNION ALL
SELECT 115 , 1 Value
)
SELECT Category, COUNT (*) CountIds
FROM 
(
    SELECT
        CASE 
            WHEN Id BETWEEN 0 and 9 then '<10' 
            WHEN Id BETWEEN 10 and 49   then '10-49' 
            WHEN Id BETWEEN 50 and 99   then '50-99' 
            WHEN Id > 99                then '>99' 
        ELSE '0' END as Category    
    FROM MyTable
) as A
GROUP BY Category

This will give you the following result

Category CountIds
-------- -----------
<10      2
>99      4
10-49    10
50-99    10
Raj More