【问题标题】:SQL: How to find top customers that pay 80% of revenue?SQL:如何找到支付 80% 收入的顶级客户?
【发布时间】:2018-09-10 17:32:38
【问题描述】:

假设我有一个表 TRANSACTIONS:

desc customer_transactions; 
+------------------------------+--------------+------+-----+---------+----------------+
| Field                        | Type         | Null | Key | Default | Extra          |
+------------------------------+--------------+------+-----+---------+----------------+
| id                           | int(11)      | NO   | PRI | NULL    | auto_increment |
| transactionID                | varchar(128) | YES  |     | NULL    |                |
| customerID                   | varchar(128) | YES  |     | NULL    |                |
| amountAuthorized             | DECIMAL(5,2) | YES  |     | NULL    |                |
| createdDatetime              | datetime     | YES  |     | NULL    |                |
+------------------------------+--------------+------+-----+---------+----------------+

此表包含过去 5 年 SAAS 业务的信用卡交易记录。该公司有一个典型的每月订阅模式,客户根据他们的计划自动收费。

我需要找到对所有收入(每个时间段)负责 80% 的顶级客户。 SAAS 业务非常不平衡,因为有些客户每月支付 10 美元,而另一些客户可能每月支付数千美元。

稍后我将添加一个“时间段”过滤器,只需要聚合方面的帮助。

我想生成一份报告,其中我只选择以这种格式产生 80% 收入的客户:

+------------+-------+
| customerID | Total | 
+------------+-------+

不知道为什么这个问题被“搁置”。我只需要帮助编写查询并且没有足够的 SQL 经验。基本上,问题的标题说明了这里需要什么:

我需要列出客户及其相应的总数,但是,只需要选择那些占总收入 80% 的客户。该报告需要汇总每个客户的总数。

使用 MariaDB 10.3.9 版

【问题讨论】:

  • 如何使用这些数据计算“收入百分比”?除了id,我在表格上看不到任何数字类型。还有80%的收入是什么?总收入的80%?根据定义,这只能是 0 个或 1 个客户,因为如果有一个客户产生了 80% 或更多,那么任何其他客户的产生量都将不到总数的 20%。
  • 为客户计算收入背后的逻辑是什么?
  • 编辑问题添加一些关于当前问题的更多信息,你卡在哪里,向我们展示你当前的尝试。
  • 另外,您使用的是什么 DBMS(mysql、sql server、oracle、db2、postgres 等)?
  • 为什么数量是 varchar !

标签: mysql sql mariadb cumulative-sum subtotal


【解决方案1】:

这就是你需要使用窗口函数来做的事情。

WITH 
    -- define some sample data, 
    -- where the sum total of amountAuthorized is 10,000
    customer_transactions(  `id`, transactionID, customerID, 
                            amountAuthorized, createdDatetime) AS
    (
                    SELECT 1, 1, 1, 5000, '2018-08-01'
        UNION ALL   SELECT 2, 2, 2, 2000, '2018-08-01'
        UNION ALL   SELECT 3, 3, 3, 1000, '2018-08-01'
        UNION ALL   SELECT 4, 4, 4, 1000, '2018-08-01'
        UNION ALL   SELECT 5, 5, 5, 1000, '2018-08-01'
    )
    -- a query that gives us the running total, sorted to give us the biggest customers first.
    -- note that the additional sorts affect what customers might be returned.
    ,running_totals AS
    (
        SELECT *, SUM(amountAuthorized) OVER (ORDER BY amountAuthorized DESC, createdDatetime DESC, `id`)  AS runningTotal
        FROM customer_transactions
    )
SELECT *
FROM running_totals
WHERE runningTotal <= ( SELECT 0.8 * SUM(amountAuthorized) 
                        FROM customer_transactions)

请注意,这会考虑(不是双关语)表中的所有数据。当您只想查看特定时间段时,您可能需要创建一个中间 CTE 来过滤掉您想要的日期。

【讨论】:

    【解决方案2】:

    你会发现惊人的接近 20% 的客户占了 80%。见the 80/20 rule

    但是,如果您不想朝那个方向发展,您有两个选择:

    • 切换到 MySQL 8.0 或 MariaDB 10.1 以使用“窗口化”功能;或
    • 使用@variables 生成运行总计,然后(在外部查询中)获取所需的行。

    由于您使用的是 MariaDB 10.3.9,因此窗口化似乎是要走的路。但首先,您需要一个单独的查询(或派生表)来计算总收入,以便获得其中的 80%。

    建议

     SELECT @revenue80 := 0.8 * SUM(amountAuthorized) 
         FROM customer_transactions
    

    然后在 Zack 建议的 WHERE 中使用 @revenue80

    我看到每个金额不能超过999.99。真的吗?这是一家咖啡店吗?

    【讨论】:

    • 感谢您的建议,我对窗口函数不熟悉。 999.99 的金额是巧合。这是针对 SaaS 业务的,它可以进行更大的交易。除了 999.99 似乎有点太晚了,对吧?大声笑
    • 也许是拿铁咖啡,上面漂浮着金片?
    【解决方案3】:

    使用以下内容:

    SELECT 
      ct1.customerID, 
      SUM(ct1.amountAuthorized) as Total, 
      100 * (SUM(ct1.amountAuthorized) / ct3.total_revenue) as percent_revenue 
    FROM 
      customer_transactions ct1
    CROSS JOIN (SELECT SUM(amountAuthorized) AS total_revenue 
                FROM customer_transactions ct2) AS ct3 
    GROUP BY 
      ct1.customerID 
    HAVING percent_revenue >= 80
    

    【讨论】:

    • amountAuthorized 定义为 VARCHAR(128)。你如何对字符串进行求和和除法?此外,即使该列是数字,您的逻辑也是错误的。只有一个客户占收入的 80% 以上才是正确的。
    • 我无法想象 OP 真的在寻找这样一个客户,该客户本身就产生了该表中所有收入的 80%。只有一个或没有客户返回,这将是一个愚蠢的查询。
    • @KenWhite 添加了 Cast 以将 varchar 转换为 double
    • 抱歉打错了,伙计们。我将金额更改为 DECIMAL。
    • @ipolevoy 您是否在我的回答中运行了查询?它仍然应该工作
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多