【问题标题】:SQL Avoid summing rows multiple times based on LIKE expressionSQL 避免基于 LIKE 表达式多次求和行
【发布时间】:2011-08-27 12:06:47
【问题描述】:

我有一张银行交易表,其中包含 id、tDate、description、cashOut、cashIn 列。我想看看我是如何花钱的,特别是在亚马逊和一家名为 Mazo 的商店,所以我想要这样的结果:

Month   Amazon   Mazo   Total
1       100      200    300

我试过这个:

SELECT
    MONTH(tDate) AS Month,
    SUM(IF(description LIKE '%amazon%',cashOut,0)) AS Amazon,
    SUM(IF(description LIKE '%mazo%',cashOut,0)) AS Mazo,
    SUM(cashOut) AS Total
FROM `transactions` 
GROUP BY Month

但是,我得到了以下信息:

Month   Amazon   Mazo   Total
1       100      300    300

这个 SQL 查询的问题是“mazo”事务的总和是错误的,因为它也将“amazon”事务相加。

我希望交易总和的选择是互斥的或类似的,以便每个交易只是上述 SUM 之一的一部分(不诉诸 PHP 或类似的东西)。 (我的表格包含比这更多的数据,而且我有很多搜索条件,所以使用 '% mazo %' 作为搜索词是不够的。我需要一个通用的解决方案。)

有人有什么建议吗?

表格及其数据的详细信息:

CREATE TABLE `transactions` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`tDate` date NOT NULL,
`description` varchar(200) NOT NULL,
`cashOut` decimal(10,0) NOT NULL,
`cashIn` decimal(10,0) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB 

INSERT INTO `transactions` (`id`, `tDate`, `description`, `cashOut`, `cashIn`) VALUES
(1, '2010-01-05', 'amazon', '100', '0'),
(4, '2010-01-15', 'mazo', '200', '0');

【问题讨论】:

    标签: sql aggregate mutual-exclusion


    【解决方案1】:

    您可以通过标记内部查询中的每一行然后对其进行过滤来创建互斥组:

    SELECT
        MONTH(tDate) AS Month,
        SUM(IF(flag = 'amazon', cashOut, 0)) AS Amazon,
        SUM(IF(flag = 'mazo', cashOut, 0)) AS Mazo,
        SUM(IF(flag = 'other', cashOut, 0)) AS Other,
        SUM(cashOut) AS Total
    FROM (
        SELECT tDate, cashOut,
            CASE
                WHEN description LIKE '%amazon%' THEN 'amazon'
                WHEN description LIKE '%mazo%' THEN 'mazo'
                ELSE 'other'
            END AS flag
        FROM transactions
    ) x
    GROUP BY Month
    

    这样一来,如果某笔交易匹配多个关键字,您将永远不会重复计算同一笔交易。如果您不想在查询中重复相同的关键字两次并且可以使用如下所示的列表:

    Month   What     Sum
    1       amazon   100
    2       mazo     200
    

    那么你可以使用:

    SELECT
        MONTH(tDate) AS Month,
        flag AS What,
        SUM(cashOut) AS Total
    FROM (
        SELECT tDate, cashOut,
            CASE
                WHEN description LIKE '%mazo%' THEN 'mazo'
                WHEN description LIKE '%amazon%' THEN 'amazon'
                ELSE 'other'
            END AS flag
        FROM transactions
    ) x
    GROUP BY Month, flag
    

    【讨论】:

    • 正是我需要的,非常感谢! (至于第二个建议,在我的情况下不会这样做,因为我需要知道每个搜索键每个月的总和。)
    【解决方案2】:
    SELECT
        MONTH(tDate) AS Month,
        SUM(IF(description LIKE '%amazon%',cashOut,0)) AS Amazon,
        SUM(IF(description LIKE '%mazo%' AND description NOT LIKE '%amazon%',cashOut,0)) AS Mazo,
        SUM(cashOut) AS Total
    FROM `transactions` 
    GROUP BY Month
    

    【讨论】:

    • 这行得通。但是,假设我有 50 个搜索词,例如 amazon 和 mazo,SQL 会非常大。有没有更通用或更优雅的方式来做到这一点?
    • 我建议在插入时在列中添加某种冗余标识符。这意味着计算只在插入时完成一次。例如:您为亚马逊添加“AMZ”、“MAZO”等然后您只需要进行相等比较。如果您想更进一步,您可以使用整数标识符,因为这样比较会快得多(1 = Amazon,2 = Mazo 等)
    • 如果您有50个搜索词,那么最好创建一个供应商表,并将供应商代码存储在每个事务中。然后按供应商汇总费用将是微不足道的。
    【解决方案3】:

    您正在搜索包含mazo 的字符串。如果您只想要 Mazo,请更改:

    SUM(IF(description LIKE '%mazo%',cashOut,0)) AS Mazo,
    

    SUM(IF(description = 'mazo',cashOut,0)) AS Mazo,
    

    编辑:在回复您的评论时,您可以使用正则表达式[[:<:]] 搜索word boundaries

    SUM(IF(description REGEXP '[[:<:]]mazo[[:>:]]',cashOut,0)) AS Mazo,
    

    【讨论】:

    • 不,我想要包含 Mazo 的字符串。我看到我没有提到交易描述通常是一些较长的字符串,例如“53,12 New York Mazo blabla”。
    • @Yngvar Kristiansen:也许最好搜索匹配字符串开头、空格、逗号等的单词边界。示例添加到答案。
    • 正则表达式仍然没有产生互斥的结果。我不想多次汇总交易。
    【解决方案4】:

    = 与确切的文本一起使用,而不是like,并且只需丢失IF。试试这个:

    SELECT
        MONTH(tDate) AS Month,
        SUM((description = 'Amazon') * cashOut) AS Amazon,
        SUM((description = 'Mazo') * cashOut) AS Mazo,
        SUM(cashOut) AS Total
    FROM `transactions`
    GROUP BY Month
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-28
      • 1970-01-01
      • 2019-03-26
      相关资源
      最近更新 更多