SQL Avoid summing rows multiple times based on LIKE expression
-
14-01-2021 - |
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');
La 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
Autres conseils
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