I have a MySQL transfers
table with the following structure:
CREATE TABLE `transfers` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`id_account_destination` bigint(20) unsigned NOT NULL,
`id_account_origin` bigint(20) unsigned NOT NULL,
`amount` decimal(10, 2) signed NOT NULL DEFAULT 0,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `id_account_destination` (`id_account_destination`),
KEY `id_account_origin` (`id_account_origin`),
KEY `created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
This table stores transfers, obviously. It records the transfer amount and the two accounts involved: the origin and the destination.
What I need is to create a SELECT
statement that takes all the given transfers from a period (filtered by created_at
) and shows me how much income and outcome do all the present accounts have in that segment. Something like:
+------------+---------------+-----------------|
| id_account | income_amount | outcome_ammount |
+------------+---------------+-----------------|
| 19 | 27690.87 | 57204.80 |
| 112 | 1000.00 | 2349.00 |
| 1011 | 575877.56 | 17454.50 |
| 17 | 135002.61 | 204.30 |
+------------+---------------+-----------------|
The difficulty here is that the id_account
column will take a id_account_origin
or id_account_destination
from any transfer and find all its other occurrences through the rest of the transfers. If the account is found in the "origin" position, the transfer’s amount will be added to the outcome_ammount
column; but if the account is found in the "destination" position, the transfer’s amount should instead be added to the income_ammount
column.
So the output table is completely different from the transfers table, although all the needed information is already there.
What I’ve acchieved so far was to get that information only in one way:
SELECT
id_account_origin,
SUM(t.amount) AS outcome_amount
FROM transfers t
GROUP BY t.id_account_origin;
Which returns:
+-------------------+----------------+
| id_account_origin | outcome_amount |
+-------------------+----------------+
| 10009 | 2761390.87 |
| 10012 | 1000.00 |
| 10011 | 575877.56 |
| 10007 | 135002.61 |
+-------------------+----------------+
And is far easier than crossing columns as expected above.
2
Answers
One approach unpivots the data, then aggregates.
In recent MySQL versions, we can use a lateral join, which avoids scanning the table twice with
union all
:Reference: MySQL – How to unpivot columns to rows?
Write another query for
id_account_destination
. Then combine them withUNION
to unpivot.