Question

I have a table with bank transactions with columns id, tDate, description, cashOut, cashIn. I want to see how I spend my money, specifically at Amazon and a shop called Mazo, so I want a result like this:

Month   Amazon   Mazo   Total
1       100      200    300

I attempted this:

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

However, I got the following:

Month   Amazon   Mazo   Total
1       100      300    300

The problem with this SQL query is that the sum of "mazo"-transactions is wrong, because it also adds up the "amazon" transactions.

I want the selection of the transactions to sum to be mutually exclusive or something like that, so that each transaction is part of only one of the SUMs above (without resorting to PHP or similar). (My tables contain much more data than this, and I have lots of search criteria, so it doesn't suffice to use '% mazo %' as search word. I need a general solution to the problem.)

Does anybody have a suggestion?

Details of the table and its data:

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');
Was it helpful?

Solution

You can create mutually exclusive groups by flagging each row in an inner query and then filtering on that:

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

That way you will never count the same transaction twice if some transaction will match more than one keyword. If you don't want to repeat the same keyword twice in query and can live with a list looking like that:

Month   What     Sum
1       amazon   100
2       mazo     200

then you can use:

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

OTHER TIPS

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

You're searching for strings that contain mazo. If you just want Mazo, change:

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

to

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

EDIT: In reply to your comment, you can use regex [[:<:]] to search for word boundaries:

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

Use = with the exact text, not like and simply by losing the IF. Try this:

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top