SQL - 按组分组

时间:2013-07-07 20:25:17

标签: mysql sql

在MySQL数据库中,我有一个包含以下列的购买表:

USERID PURCHASE_AMOUNT  
3         20
9         30
3         5
4         5
1         10
1         5

我想生成一个像这样的报告

SUM_OF_PURCHASES_RANGE NUM_OF_USERS  
0-1                    0
1-5                    1
5-20                   1
20-30                  2

这意味着:有0个用户购买最多1个(购买总和)(含),有1个用户购买1到5个等...

我应该使用什么查询来生成它?

6 个答案:

答案 0 :(得分:1)

更简单的语法:

SELECT PURCHASE_RANGE , COUNT(*) as NUM_OF_USERS  
FROM 
(
SELECT 
CASE 
    WHEN PURCHASE_AMOUNT <= 1 THEN 1
    WHEN PURCHASE_AMOUNT > 1 AND PURCHASE_AMOUNT <= 5 THEN 5
    WHEN PURCHASE_AMOUNT > 5 AND PURCHASE_AMOUNT <= 10 THEN 10 
    WHEN PURCHASE_AMOUNT > 10 AND PURCHASE_AMOUNT <= 20 THEN 20
    WHEN PURCHASE_AMOUNT > 20 AND PURCHASE_AMOUNT <= 30 THEN 30 END AS PURCHASE_RANGE  
FROM Table1
) AS A
GROUP BY PURCHASE_RANGE
ORDER BY PURCHASE_RANGE

SqlFiddle

答案 1 :(得分:1)

您可以使用UNION创建范围,只需LEFT JOIN即可获得所有类别; (根据所需结果进行了编辑)

SELECT CONCAT(base.lower,'-',base.upper) PURCHASE_RANGE, COUNT(userid) NUM_OF_USERS
FROM (
  SELECT 0 lower, 1 upper UNION SELECT 2, 5 UNION SELECT 6,20 UNION SELECT 21,30
) base
LEFT JOIN (
  SELECT userid, SUM(purchase_amount) pa FROM purchases GROUP BY userid
) p
  ON p.pa >= base.lower AND p.pa <= base.upper
GROUP BY base.upper

An SQLfiddle to test with

答案 2 :(得分:0)

试试这个

    select PURCHASE_RANGE , NUM_OF_USERS 
 from (
 select 1 as PURCHASE_RANGE ,count(*) as  NUM_OF_USERS  from table1 where PURCHASE_AMOUNT between 0 and 1
  union all
 select 5 ,count(*) from table1 where PURCHASE_AMOUNT between 1 and 5
 union all
 select 20 ,count(*) from table1 where PURCHASE_AMOUNT between 6 and 20
  union all
 select 30 ,count(*) from table1 where PURCHASE_AMOUNT between 21 and 30
)t

DEMO HERE

答案 3 :(得分:0)

如果您需要性能(这将进行全表扫描),有更快的方法可以执行此操作,但请尝试以下操作:

SELECT 
SUM(CASE WHEN purchase_amount BETWEEN 0 AND 1 THEN 1 ELSE 0) bucket_0_to_1,
SUM(CASE WHEN purchase_amount BETWEEN 1 AND 5 THEN 1 ELSE 0) bucket_1_to_5,
SUM(CASE WHEN purchase_amount BETWEEN 5 AND 20 THEN 1 ELSE 0) bucket_5_to_20,
SUM(CASE WHEN purchase_amount BETWEEN 20 AND 30 THEN 1 ELSE 0) bucket_20_to_30,
SUM(CASE WHEN purchase_amount > 30 THEN 1 ELSE 0) bucket_over_30, FROM my_table LIMIT 1;

答案 4 :(得分:0)

如果范围发生变化,这可能会更容易。

with ranges(rstart, rfinish) as (
    select  0,  1 union all
    select  2,  5 union all
    select  6, 20 union all
    select 21, 30
), purchases(amount) as (
    select sum(PURCHASE_AMOUNT)
    from <purchases_basetable> -- <-- your tablename goes here
    group by USERID
)
select
    -- concat(case when r.rstart = 0 then 0 else r.rstart-1 end, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* op's name for the group */,
    concat(r.rstart, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* better name for the group */,
    count(*) as NUM_OF_USERS
from
    purchases as p inner join
    ranges as r
        on p.amount between r.start and r.finish
group by r.rstart, r.rfinish
order by r.rstart, r.rfinish

我不知道mysql查询计划会是什么样子。将查询更改为使用派生表而不是表表达式是微不足道的。 (但无论如何我都把它包括在内。)

您可能还会发现UNPIVOT操作在支持它的平台上很有用。

select
    -- concat(case when r.rstart = 0 then 0 else r.rstart-1 end, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* op's name for the group */,
    concat(r.rstart, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* better name for the group */,

    count(*) as NUM_OF_USERS
from
    (
        select sum(PURCHASE_AMOUNT) as amount
        from <purchases_basetable> -- <-- your tablename goes here
        group by USERID
    ) as p inner join
    (
        select  0 as rstart, 1 as rfinish union all
        select  2,  5 union all
        select  6, 20 union all
        select 21, 30
    ) as r
        on p.amount between r.start and r.finish
group by r.rstart, r.rfinish
order by r.rstart, r.rfinish

答案 5 :(得分:0)

要在行中获取所需的值,您需要从包含您感兴趣的所有值的驱动程序表开始,然后left outer join到数据:

select driver.mina, coalesce(sum(cnt), 0) as Num_Of_Users
from (select 1 as mina, 5 as maxa union all
      select 5, 10 union all
      select 10, 20 union all
      select 20, 30 union all
      select 30, NULL
     ) driver left outer join
     (select purchase_amount, count(*) as cnt
      from purchases
      group by purchase_amount
     ) pa
     on driver.mina >= pa.purchase_amount and
        (pa.purchase_amount < driver.maxa or driver.maxa is null)
group by driver.mina
order by driver.mina

如果没有内部group by,您实际上可以这样做。这可能会在加入之前显着减少数据的大小(特别是在您的示例中)。

我建议你在每一行都包括范围的下限和上限。