Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

208
Visualizações
Cómo almacenar la lógica de funciones en la base de datos

Estoy haciendo una aplicación de gestión financiera. Tengo una base de datos que contiene todos los lugares donde el usuario tiene su dinero, que incluye bancos. Así es como está estructurada la tabla.

 CREATE TABLE IF NOT EXISTS reserves ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(31) NOT NULL, balance DECIMAL(10, 2) NOT NULL ) CREATE TABLE IF NOT EXISTS banks ( reserve_id SMALLINT UNSIGNED UNIQUE NOT NULL, apy DECIMAL(4, 2) NOT NULL, accrued_interest DECIMAL(10, 4) NOT NULL, last_transaction DATE, FOREIGN KEY(reserve_id) REFERENCES reserves(id) )

En este modelo puedo tener un APY fijo que se establecerá durante la inserción. Pero en el mundo real, los bancos tienen tasas de interés variables basadas en el saldo. Y los detalles son diferentes para cada banco en la tabla de bancos.

En la clase JAVA puedo capturar esto muy fácilmente con APY definido como una Function<BigDecimal, Big Decimal> APY donde puedo almacenar la lógica APY específica y usar APY.apply(balance) para recuperar la tasa de interés en cualquier punto.

Pero no sé cómo almacenar esta lógica en la base de datos mySQL.

Sé que puedo crear una tabla separada como bank_balance_interest donde puedo almacenar la tasa de interés hasta el saldo mínimo en la identificación del banco específico y luego referirme a eso.

Pero simplemente no se siente bien. Por un lado, es muy engorroso y tedioso. Además, todavía no habrá ninguna solución si no hay límites claros para el equilibrio del interés y, más bien, es una función continua.

¿Hay un enfoque más elegante?

Editar Aquí hay algo de mi código:

 public class Reserve { short id; final String name; BigDecimal balance; ReservesData reservesData; public Reserve(short id, String name, BigDecimal balance) { this.id = id; this.name = name; this.balance = balance; reservesData = ReservesData.instance; } public Reserve(String name) { this((short) -1, name, new BigDecimal("0.0")); } @Override public String toString() { return name; } public short getId() { return id; } public String getName() { return name; } public BigDecimal getBalance() { return balance; } public boolean transact(BigDecimal amount) { if(balance.add(amount).compareTo(new BigDecimal("0.0")) < 0) return false; balance = balance.add(amount); return true; } public boolean save() { if(id == -1) return (id = reservesData.addReserve(this)) != -1; return reservesData.updateReserve(this); } }

.

 public class Bank extends Reserve{ private final Function<BigDecimal, BigDecimal> APY; private BigDecimal accruedInterest; private Date lastTransactionDate; private final BanksData banksData; public Bank(short id, String name, BigDecimal balance, Function<BigDecimal, BigDecimal> APY) { super(id, name, balance); this.APY = APY; accruedInterest = new BigDecimal("0.0"); banksData = BanksData.instance; } public Bank(String name, Function<BigDecimal, BigDecimal> APY) { this((short) -1, name, new BigDecimal("0.0"), APY); } @Override public BigDecimal getBalance() { return balance.add(accruedInterest); } public Function<BigDecimal, BigDecimal> getAPY() { return APY; } public BigDecimal getAccruedInterest() { return accruedInterest; } public void setAccruedInterest(BigDecimal accruedInterest) { this.accruedInterest = accruedInterest; }

.

 public class ReservesDAO implements ReservesData { public ReservesDAO() { try(Statement stmt = MyConnection.getMySQLconnection().createStatement()) { stmt.executeUpdate(""" CREATE TABLE IF NOT EXISTS reserves ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(31) NOT NULL, balance DECIMAL(10, 2) NOT NULL )""" ); } catch (SQLException sqlException) { System.out.println("Failed to create reserves table on the database!"); sqlException.printStackTrace(); } } @Override public short addReserve(Reserve reserve) { try ( PreparedStatement pstmt = MyConnection.getMySQLconnection().prepareStatement(""" INSERT INTO reserves (name, balance) VALUES (?, ?)""", Statement.RETURN_GENERATED_KEYS ) ) { pstmt.setString(1, reserve.getName()); pstmt.setBigDecimal(2, reserve.getBalance()); pstmt.executeUpdate(); ResultSet rs = pstmt.getGeneratedKeys(); if (rs.next()) return rs.getShort(1); else throw new RuntimeException("Auto-Generated ID was not returned from reserves!"); } catch (SQLException sqlException) { System.out.println("Failed to insert " + reserve.getName() + " info in the database!"); sqlException.printStackTrace(); return -1; } } public Reserve getReserve(short id) { try( PreparedStatement pstmt = MyConnection.getMySQLconnection().prepareStatement(""" SELECT * FROM reserves WHERE id = ?""") ) { pstmt.setShort(1, id); ResultSet rs = pstmt.executeQuery(); if(rs.next()) return new Reserve(rs.getShort(1), rs.getString(2), rs.getBigDecimal(3)); else throw new RuntimeException("No reserve found on the database with the id " + id); } catch (SQLException sqlException) { System.out.println("Failed to fetch reserve from the database!"); sqlException.printStackTrace(); return null; } } public List<Reserve> getAllReserves() { List<Reserve> reserves = new ArrayList<>(); try(Statement stmt = MyConnection.getMySQLconnection().createStatement()) { ResultSet rs = stmt.executeQuery("SELECT * FROM reserves"); while(rs.next()) reserves.add(new Reserve(rs.getShort(1), rs.getString(2), rs.getBigDecimal(3))); } catch (SQLException sqlException) { System.out.println("Failed to fetch reserves from the database!"); sqlException.printStackTrace(); } return reserves; } @Override public BigDecimal getTotalReserveBalance() { try(Statement stmt = MyConnection.getMySQLconnection().createStatement()) { ResultSet rs = stmt.executeQuery(""" SELECT SUM(balance) FROM reserves"""); if(rs.next()) return rs.getBigDecimal(1); return new BigDecimal("0.0"); } catch (SQLException sqlException) { System.out.println("Could not get total reserve balance from database!"); sqlException.printStackTrace(); return null; } } @Override public List<Reserve> getAllWallets() { List<Reserve> reserves = new ArrayList<>(); try(Statement stmt = MyConnection.getMySQLconnection().createStatement()) { ResultSet rs = stmt.executeQuery(""" SELECT reserves.* FROM reserves LEFT JOIN banks ON reserves.id = banks.id WHERE banks.id IS NULL """); while(rs.next()) reserves.add(new Reserve(rs.getShort(1), rs.getString(2), rs.getBigDecimal(3))); } catch (SQLException sqlException) { System.out.println("Failed to fetch reserves from the database!"); sqlException.printStackTrace(); } return reserves; } @Override public BigDecimal getTotalWalletBalance() { try(Statement stmt = MyConnection.getMySQLconnection().createStatement()) { ResultSet rs = stmt.executeQuery(""" SELECT SUM(balance) FROM reserves LEFT JOIN banks ON reserves.id = banks.id WHERE banks.id IS NULL """); if(rs.next()) return rs.getBigDecimal(1) == null ? new BigDecimal("0.0") : rs.getBigDecimal(1); return new BigDecimal("0.0"); } catch (SQLException sqlException) { System.out.println("Could not get total wallet balance from database!"); sqlException.printStackTrace(); return null; } } @Override public boolean updateReserve(Reserve reserve) { try(PreparedStatement pstmt = MyConnection.getMySQLconnection().prepareStatement(""" UPDATE reserves SET name = ?, balance = ? WHERE id = ?""") ) { pstmt.setString(1, reserve.getName()); pstmt.setBigDecimal(2, reserve.getBalance()); pstmt.setShort(3, reserve.getId()); pstmt.executeUpdate(); return true; } catch(SQLException sqlException) { System.out.println("Failed to update reserves with new data!"); sqlException.printStackTrace(); return false; } } }

...

 public class BanksDAO extends ReservesDAO implements BanksData { public BanksDAO() { try( Statement stmt = MyConnection.getMySQLconnection().createStatement() ) { stmt.executeUpdate(""" CREATE TABLE IF NOT EXISTS banks ( id SMALLINT UNSIGNED UNIQUE NOT NULL, apy DECIMAL(4, 2) NOT NULL, // I have no way to store a logic here, so currently it only stores fixed value. accrued_interest DECIMAL(10, 4) NOT NULL, last_transaction_date DATE, FOREIGN KEY(id) REFERENCES reserves(id) )""" ); } catch (SQLException sqlException) { System.out.println("Failed to create banks table on the database!"); sqlException.printStackTrace(); } } @Override public short addBank(Bank bank) { try ( PreparedStatement pstmt = MyConnection.getMySQLconnection().prepareStatement(""" INSERT INTO banks(id, apy, accrued_interest, last_transaction_date) VALUES (?, ?, ?, ?)""" ) ) { short id = addReserve(bank); pstmt.setShort(1, id); pstmt.setBigDecimal(2, bank.getAPY()); pstmt.setBigDecimal(3, bank.getAccruedInterest()); pstmt.setDate(4, bank.getLastTransactionDate()); pstmt.executeUpdate(); return id; } catch (SQLException sqlException) { System.out.println("Failed to insert " + bank.getName() + " info in the database!"); sqlException.printStackTrace(); return -1; } } @Override public Bank getBank(short reserve_id) { try( PreparedStatement pstmt = MyConnection.getMySQLconnection().prepareStatement(""" SELECT * FROM reserves NATURAL JOIN banks WHERE id = ?""") ) { pstmt.setShort(1, reserve_id); ResultSet rs = pstmt.executeQuery(); if(!rs.next()) return null; Bank requestedBank = new Bank(rs.getShort(1), rs.getString(2), rs.getBigDecimal(3), rs.getBigDecimal(4)); requestedBank.setAccruedInterest(rs.getBigDecimal(5)); requestedBank.setLastTransactionDate(rs.getDate(6)); return requestedBank; } catch (SQLException sqlException) { System.out.println("Failed to fetch bank data from the database!"); sqlException.printStackTrace(); return null; } } @Override public List<Bank> getAllBanks() { List<Bank> allBanks = new ArrayList<>(); try( Statement stmt = MyConnection.getMySQLconnection().createStatement() ) { ResultSet rs = stmt.executeQuery("SELECT * FROM reserves NATURAL JOIN banks"); while(rs.next()) { Bank bank = new Bank(rs.getShort(1), rs.getString(2), rs.getBigDecimal(3), rs.getBigDecimal(4)); bank.setAccruedInterest(rs.getBigDecimal(5)); bank.setLastTransactionDate(rs.getDate(6)); allBanks.add(bank); } return allBanks; } catch (SQLException sqlException) { System.out.println("Failed to fetch bank data from the database!"); sqlException.printStackTrace(); return null; } } @Override public BigDecimal getTotalBankBalance() { try(Statement stmt = MyConnection.getMySQLconnection().createStatement()) { ResultSet rs = stmt.executeQuery(""" SELECT SUM(balance) FROM reserves NATURAL JOIN banks"""); if(rs.next()) return rs.getBigDecimal(1) == null ? new BigDecimal("0.0") : rs.getBigDecimal(1); return new BigDecimal("0.0"); } catch (SQLException sqlException) { System.out.println("Could not get total bank balance from database!"); sqlException.printStackTrace(); return null; } } }

Ahora puedo inicializar un banco como:

 Bank bank1 = new Bank("TestBank1", balance -> balance.compareTo(new BigDecimal("10000")) == -1 ? new BigDecimal("4") : new BigDecimal("5"));

Si bien puedo crear otro banco como:

 Bank bank2 = new Bank("TestBank2", balance -> balance.compareTo(new BigDecimal("8000")) == -1 ? new BigDecimal("3.5") : new BigDecimal("5.3"));

Ahora ambos bancos se crean en la memoria y funcionarán perfectamente mientras la aplicación se esté ejecutando. Pero cuando necesito persistir esto para uso a largo plazo, no puedo almacenar directamente una variable de tipo Funtion<BigDecimal, BigDecimal> en la base de datos mysql.

Muchas personas sugirieron procedimientos almacenados y eso habría funcionado si fuera solo 1 lógica como balance -> balance.compareTo(new BigDecimal("10000")) == -1 ? new BigDecimal("4") : new BigDecimal("5") para cada banco en la tabla de bancos, pero esta información cambiará cada vez. Lo que significa que si tengo 50 entradas en mi tabla de bancos, necesito crear 50 procedimientos almacenados diferentes con 50 tipos de lógica para cada entrada en mi tabla de bancos, para seguir actualizando el campo APY a medida que cambia el saldo. Creo que podría haber una mejor manera, ¿no?

over 4 years ago · Santiago Trujillo
1 Respostas
Responde à pergunta

0

Consejo/opinión

Una base de datos es para el almacenamiento persistente de números y texto.

Una aplicación es para computar y decidir cosas.

Es decir, la lógica comercial compleja que describe puede codificarse de manera más adecuada en Java (u otro lenguaje de aplicación). Mientras tanto, los puntos de corte, etc. para las tasas de interés deben almacenarse en la base de datos.

Expresado de otra manera: la "lógica comercial" pertenece a la aplicación, no a la base de datos. (Por supuesto, hay líneas grises entre ellos. Por ejemplo, SQL es excelente para resumir todos los datos en una tabla; entonces, lo haría en SQL, no en Java).

Decimal

Los bancos tienen reglas exigentes que pueden no ser las mismas que proporcionan DECIMAL o DOUBLE , en Java o MySQL o cualquier otro lenguaje informático. Tenga cuidado con las reglas que pueden ser requeridas. En particular, es poco probable que DECIMAL(10, 4) sea adecuado en su aplicación. Por otro lado, apy DECIMAL(4, 2) puede ser adecuado si eso es lo que se presenta al cliente. Pero, nuevamente, tenga cuidado con las reglas de redondeo al generar ese número. Es posible que se vea obligado a ingresar ese número manualmente.

Tenga en cuenta la diferencia en las características de redondeo entre DECIMAL y DOUBLE .

Responder

Si elige implementar un algoritmo en la base de datos, consulte CREATE STORED PROCEDURE y CREATE FUNCTION .

Los procesos almacenados se pueden usar para encapsular un conjunto de declaraciones SQL. Puede recibir y enviar cadenas y números, pero no matrices. Es capaz de leer tablas (por lo tanto, "matrices" de algún tipo).

Las funciones se pueden llamar en cualquier lugar donde pueda ocurrir una expresión. Puede recibir algunos números/cadenas y producir un número o cadena.

Formación

No tengo claro lo que se necesita, pero imagino una tabla de unas pocas filas o unas pocas docenas de filas con cada fila diciendo "para valores de hasta $ xxx, use la tasa de interés y.yy%".

Los procesos almacenados tienen "cursores" y "bucles" pero son torpes; es probable que el lenguaje de la aplicación tenga un mejor código.

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda