diff --git a/financer-server/src/main/java/database/common/V22_0_1__calculateAccountStatistics.java b/financer-server/src/main/java/database/common/V22_0_1__calculateAccountStatistics.java new file mode 100644 index 0000000..80ff288 --- /dev/null +++ b/financer-server/src/main/java/database/common/V22_0_1__calculateAccountStatistics.java @@ -0,0 +1,111 @@ +package database.common; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class V22_0_1__calculateAccountStatistics extends BaseJavaMigration { + private static class TransactionPeriodAccountsContainer { + public Long transactionAmount; + public Long expensePeriodId; + public Long fromAccountId; + public Long toAccountId; + + public TransactionPeriodAccountsContainer(Long transactionAmount, Long expensePeriodId, Long fromAccountId, Long toAccountId) { + this.transactionAmount = transactionAmount; + this.expensePeriodId = expensePeriodId; + this.fromAccountId = fromAccountId; + this.toAccountId = toAccountId; + } + } + + private static final String GET_CONTAINERS_SQL = "SELECT t.amount, p.id, t.from_account_id, t.to_account_id FROM \"transaction\" t INNER JOIN link_transaction_period ltp ON ltp.transaction_id = t.id INNER JOIN period p ON p.id = ltp.period_id WHERE p.type = 'EXPENSE'"; + + private static final String ACCOUNT_STATISTIC_EXISTS_SQL = "SELECT id FROM account_statistic WHERE account_id = ? AND period_id = ?"; + + private static final String ACCOUNT_STATISTIC_UPDATE_FROM_SQL = "UPDATE account_statistic SET spending_total_from = spending_total_from + ?, transaction_count_from = transaction_count_from + 1 WHERE id = ?"; + + private static final String ACCOUNT_STATISTIC_UPDATE_TO_SQL = "UPDATE account_statistic SET spending_total_to = spending_total_to + ?, transaction_count_to = transaction_count_to + 1 WHERE id = ?"; + + private static final String ACCOUNT_STATISTIC_INSERT_FROM_SQL = "INSERT INTO account_statistic(account_id, period_id, spending_total_from, transaction_count_from, spending_total_to, transaction_count_to) VALUES(?, ?, ?, 1, 0, 0)"; + + private static final String ACCOUNT_STATISTIC_INSERT_TO_SQL = "INSERT INTO account_statistic(account_id, period_id, spending_total_from, transaction_count_from, spending_total_to, transaction_count_to) VALUES(?, ?, 0, 0, ?, 1)"; + + @Override + public void migrate(Context context) throws Exception { + final List cons = getContainers(context); + + for (TransactionPeriodAccountsContainer con : cons) { + handleAccount(con, context, true); + handleAccount(con, context, false); + } + } + + private void handleAccount(TransactionPeriodAccountsContainer con, Context context, boolean from) throws SQLException { + try (PreparedStatement statement = + context + .getConnection() + .prepareStatement(ACCOUNT_STATISTIC_EXISTS_SQL)) { + statement.setLong(1, from ? con.fromAccountId : con.toAccountId); + statement.setLong(2, con.expensePeriodId); + + final ResultSet rs = statement.executeQuery(); + boolean found = false; + + // Only one possible entry because of the unique constraint + while(rs.next()) { + found = true; + + try (PreparedStatement statementUpdate = + context + .getConnection() + .prepareStatement(from ? ACCOUNT_STATISTIC_UPDATE_FROM_SQL : ACCOUNT_STATISTIC_UPDATE_TO_SQL)) { + statementUpdate.setLong(1, con.transactionAmount); + statementUpdate.setLong(2, rs.getLong(1)); + + statementUpdate.executeUpdate(); + } + } + + // We need to create a new account_statistic entry for the tuple {account_id;report_id} + if (!found) { + try (PreparedStatement statementInsert = + context + .getConnection() + .prepareStatement(from ? ACCOUNT_STATISTIC_INSERT_FROM_SQL : ACCOUNT_STATISTIC_INSERT_TO_SQL)) { + statementInsert.setLong(1, from ? con.fromAccountId : con.toAccountId); + statementInsert.setLong(2, con.expensePeriodId); + statementInsert.setLong(3, con.transactionAmount); + + statementInsert.execute(); + } + } + } + } + + private List getContainers(Context context) throws SQLException { + try (PreparedStatement statement = + context + .getConnection() + .prepareStatement(GET_CONTAINERS_SQL)) { + final ResultSet rs = statement.executeQuery(); + final List cons = new ArrayList<>(); + + while(rs.next()) { + cons.add(new TransactionPeriodAccountsContainer( + rs.getLong(1), // transaction amount + rs.getLong(2), // expense period ID + rs.getLong(3), // from account ID + rs.getLong(4) // to account ID + )); + } + + return cons; + } + } +} diff --git a/financer-server/src/main/java/database/package-info.java b/financer-server/src/main/java/database/package-info.java new file mode 100644 index 0000000..db69396 --- /dev/null +++ b/financer-server/src/main/java/database/package-info.java @@ -0,0 +1,4 @@ +/** + * Package for Java based database migrations via Flyway + */ +package database; \ No newline at end of file diff --git a/financer-server/src/main/resources/database/common/V22_0_0__accountStatisticUniqueConstraint.sql b/financer-server/src/main/resources/database/common/V22_0_0__accountStatisticUniqueConstraint.sql new file mode 100644 index 0000000..684663c --- /dev/null +++ b/financer-server/src/main/resources/database/common/V22_0_0__accountStatisticUniqueConstraint.sql @@ -0,0 +1,2 @@ +ALTER TABLE account_statistic +ADD CONSTRAINT un_account_statistic_account_period UNIQUE (account_id,period_id); \ No newline at end of file