Необходимо отменить столбец на основе другого столбца, как часть совокупного итога - PullRequest
0 голосов
/ 04 мая 2020

У меня возникают проблемы при создании отчетов о прибылях и убытках в системе учета.

Каждая запись общего журнала имеет сумму, исходную учетную запись и конечную учетную запись. Вот упрощенная схема и некоторые примеры данных:

CREATE TABLE `sa_general_journal` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `Date` timestamp NOT NULL DEFAULT current_timestamp(),
  `Item` varchar(1024) NOT NULL DEFAULT '',
  `Amount` decimal(9,2) NOT NULL DEFAULT 0.00,
  `Source` int(10) unsigned NOT NULL,
  `Destination` int(10) unsigned NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `Date` (`Date`),
  KEY `Source` (`Source`),
  KEY `Destination` (`Destination`),
  CONSTRAINT `sa_credit-account` FOREIGN KEY (`Destination`) REFERENCES `sa_accounts` (`ID`),
  CONSTRAINT `sa_debit-account` FOREIGN KEY (`Source`) REFERENCES `sa_accounts` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=21561 DEFAULT CHARSET=utf8;

CREATE TABLE `sa_accounts` (
  `ID` int(10) unsigned NOT NULL,
  `Name` varchar(255) NOT NULL,
  `Type` enum('Asset','Liability','Income','Expense'),
  `Report` enum('BS','PL'), -- for "Balance Sheet" or "Profit & Loss"
  PRIMARY KEY (`ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO sa_account (`ID`, `Name`, `Type`, `Report`)
  VALUES (1009999, "Test chequing account", "Asset", "BS"),
         (4059999, "Test Income account", "Income", "PL"),
         (5059999, "Test Expense account", "Expense", "PL");
INSERT INTO sa_general_journal (`ID`, `Date`, `Item`, `Amount`, `Source`, `Destination`)
  VALUES (NULL, "2020-05-03", "Test income transaction", 10.10, 4059999, 1009999),
 (NULL, "2020-05-03", "Test expense transaction", 1.01, 1009999, 5059999);

Это доход в размере 10,10 долл. США, зачисляемый на счет проверки, и расход в размере 1,01 долл. США, который поступает со счета проверки проверки.

Чтобы получить баланс для отчета о прибылях и убытках, необходимо сложить все записи Amount для каждого вхождения счета в столбце Source, а затем вычесть все записи Amount, где этот счет находится в Destination столбец.

Ожидаемый результат будет следующим:

<table>
<th>ID</th><th>Name</th><th>Debits</th><th>Credits</th><th>Net</th></tr>
<tr><td>1009999</td><td>Test chequing account</td><td>-1.01</td><td>10.10</td><td>9.09</td></tr>
<tr><td>4059999</td><td>Test income transaction</td><td>-10.10</td><td><i>NULL</i></td><td>-10.10</td></tr>
<tr><td>5059999</td><td>Test expense transaction</td><td><i>NULL</i></td><td>1.01</td><td>1.01</td></tr>
</table>

Мой первый подход был несколько наивным, чтобы запросить таблицу sa_general_journal, объединенную с таблицей sa_accounts, выбранной столбцами Source и Destination, использование функции IF для отмены Amount, если Destination содержало учетную запись интереса. Я делаю это успешно, когда запрашиваю отдельную учетную запись, используя подготовленную выписку:

SELECT
  DATE_FORMAT(exp.Date, '%Y') AS `Year`,
  AVG(exp.Amount) AS `Avg`,
  SUM(IF(Source = ?, 0 - exp.Amount, NULL)) AS `Debits`,
  SUM(IF(Destination = ?, exp.Amount, NULL)) AS `Credits`,
  SUM(IF(Source = ?, 0 - exp.Amount, exp.Amount)) AS `Net`
FROM sa_general_journal exp
  LEFT JOIN sa_accounts Destination ON exp.Destination = Destination.ID
WHERE Destination = ?
  OR Source = ?
GROUP BY `Year`

, где все четыре заполнителя имеют одинаковый идентификатор учетной записи.

Но это не удается при группировании по идентификатору учетной записи без WHERE пункт, чтобы получить список всех остатков на счетах - замена динамического c идентификатора счета вместо stati c идентификатора аккаунта в заполнителе никогда не кажется, что попадет в код «0 - exp.Amount»… Дух! Запрос выбирается как набор, а не как шаги в процедуре. Я понял это.

Создание подзапросов операторов SUM работает, но это ужасно медленно, по-видимому, выполняется три подзапроса для каждой из десятков тысяч записей!

Итак, я подумал, что мог бы учесть подзапросы с парой общих табличных выражений, но, похоже, это тоже не работает должным образом, так как кажется, что они просто возвращают SUM (Amount), не вычитая Amount, которые скорее равны Destination s чем Source с.

WITH
 source1 AS (SELECT
  src.ID,
  src.Name,
  SUM(Amount) Amount
FROM sa_general_journal gj
  LEFT JOIN sa_accounts src ON gj.`Source` = src.ID
WHERE src.Report = "PL" -- AND YEAR(`Date`) = 2019
GROUP BY src.ID),
 destination1 AS (SELECT
  dst.ID,
  dst.Name,
  SUM(0-Amount) Amount
FROM sa_general_journal gj
  LEFT JOIN sa_accounts dst ON gj.`Destination` = dst.ID
WHERE dst.Report = "PL" --  AND YEAR(`Date`) = 2019
GROUP BY dst.ID)
SELECT ID, Name, sum(Amount)
FROM source1
UNION ALL
SELECT ID, Name, sum(Amount)
FROM destination1
GROUP BY ID

Полагаю, я делаю какие-то глупые предположения или делаю что-то глупое, поэтому любой совет приветствуется!

1 Ответ

1 голос
/ 04 мая 2020

Без примеров данных трудно быть уверенным на 100%, но этот запрос должен дать желаемые результаты. Он использует запрос UNION для разделения транзакций на их Source и Destination учетные записи, а затем использует условное агрегирование для SUM транзакций для каждой учетной записи.

WITH CTE AS (
  SELECT Source AS account, 'debits' AS type, -Amount AS Amount
  FROM sa_general_journal
  UNION ALL 
  SELECT Destination, 'credits', Amount
  FROM sa_general_journal
)
SELECT acc.ID, acc.Name, 
       SUM(CASE WHEN CTE.type = 'debits'  THEN Amount END) AS Debits,
       SUM(CASE WHEN CTE.type = 'credits' THEN Amount END) AS Credits,
       SUM(Amount) AS Net
FROM CTE
JOIN sa_accounts acc ON CTE.account = acc.ID
GROUP BY acc.ID, acc.Name

Вывод (для вашего примера данные):

ID          Name                    Debits  Credits Net
4059999     Test Income account     -10.1   null    -10.1
1009999     Test chequing account   -1.01   10.1    9.09
5059999     Test Income account     null    1.01    1.01

Демонстрация на dbfiddle

...