DAM - DAW - MP 0484 Bases de Dades
Exercici proposat – Procediments i Funcions
La proposta és anar creant procediments i funcions per tal que els pugui utilitzar el treballador de l’oficina (userbanc). La proposta de procediments i funcions a realitzar és la següent:
Si voleu tenir ja algunes dades inserides per a fer proves...
Podeu agafar el fitxer adjunt o utilitzar les següents instruccions
DELETE FROM moviment;
DELETE FROM compte;
DELETE FROM oficina;
INSERT INTO `oficina` VALUES
(1,'Una','Avda. Montilivi',1,30.00),
(2,'Dues','Pl. Catalunya',2,450.00),
(3,'Tres','C. Amunt',0,0.00);
INSERT INTO `compte` VALUES
(1,1,1,2,30.00),
(2,1,3,3,250.00),
(2,2,2,1,200.00);
INSERT INTO `moviment` VALUES
(1,1,1,200.00,CURRENT_DATE() - INTERVAL 12 DAY,'Ingrés'),
(1,1,2,-70.00,CURRENT_DATE() - INTERVAL 7 DAY,'Reintegre'),
(1,1,3,-100.00,CURRENT_DATE() - INTERVAL 1 DAY,'Traspas a 1234 0002 15 0000000001'),
(2,1,1,100.00,CURRENT_DATE() - INTERVAL 13 DAY,'Ingrés'),
(2,1,2,350.00,CURRENT_DATE() - INTERVAL 8 DAY,'Ingrés'),
(2,1,3,-50.00,CURRENT_DATE() - INTERVAL 6 DAY,'Reintegre'),
(2,1,4,-125.00,CURRENT_DATE() - INTERVAL 5 DAY,'Reintegre'),
(2,1,5,100.00,CURRENT_DATE() - INTERVAL 1 DAY,'Traspas de 1234 0001 75 0000000001'),
(2,1,6,-125.00,CURRENT_DATE(),'Traspas a 1234 0002 11 0000000002'),
(2,2,1,90.00,CURRENT_DATE() - INTERVAL 8 DAY,'Ingrés'),
(2,2,2,-15.00,CURRENT_DATE() - INTERVAL 1 DAY,'Reintegre'),
(2,2,3,125.00,CURRENT_DATE(),'Traspas de 1234 0002 15 0000000001');
LlistaOficines - Procediment
-
Mostra el codi, nom i adreça de les oficines ordenat segons el paràmetre: 1:
Codi, Altre:NomSELECT
Exemple d'ús i execucióCALL LlistaOficines(0); +------+-------+-----------------+ | Codi | Nom ^ | Adreça | +------+-------+-----------------+ | 2 | Dues | Pl. Catalunya | | 3 | Tres | C. Amunt | | 1 | Una | Avda. Montilivi | +------+-------+-----------------+ CALL LlistaOficines(1); +--------+------+-----------------+ | Codi ^ | Nom | Adreça | +--------+------+-----------------+ | 1 | Una | Avda. Montilivi | | 2 | Dues | Pl. Catalunya | | 3 | Tres | C. Amunt | +--------+------+-----------------+Possible solució
DELIMITER // CREATE OR REPLACE PROCEDURE LlistaOficines(ordre INT) SQL SECURITY DEFINER BEGIN IF ordre = 1 THEN SELECT cOficina "Codi ^", dOficina "Nom", adrOficina "Adreça" FROM oficina ORDER BY cOficina; ELSE SELECT cOficina "Codi", dOficina "Nom ^", adrOficina "Adreça" FROM oficina ORDER BY dOficina; END IF; END // DELIMITER ; CALL LlistaOficines(0); CALL LlistaOficines(1);
MostraOficina - Procediment
-
Mostra el codi, nom i adreça de la oficina que passem com a paràmetre.
SELECT
Exemple d'ús i execucióCALL MostraOficina(2); +------+------+---------------+ | Codi | Nom | Adreça | +------+------+---------------+ | 2 | Dues | Pl. Catalunya | +------+------+---------------+Possible solució
DELIMITER // CREATE OR REPLACE PROCEDURE MostraOficina( p_cOficina TYPE OF Oficina.cOficina ) BEGIN DECLARE v_dOficina TYPE OF Oficina.dOficina; DECLARE v_adrOficina TYPE OF Oficina.adrOficina; DECLARE EXIT HANDLER FOR NOT FOUND SELECT 'Oficina Inexistent' AS "Missatge"; SELECT dOficina, adrOficina INTO v_dOficina, v_adrOficina FROM oficina WHERE cOficina = p_cOficina; SELECT p_cOficina "Codi", v_dOficina "Nom", v_adrOficina "Adreça"; END // DELIMITER ; CALL MostraOficina(2); CALL MostraOficina(12);
DigitControlBO - Funció
-
Donat el número de banc i el d’oficina, retorna el dígit de control. El del nostre Banc ens imaginarem que és l'1234.
Càlcul dígits de Control (La Sexta)
Crida a una altra funció.
Exemple d'ús i execucióSELECT DigitControlBO(1234, 1111); -- Ha de donar 1 SELECT DigitControlBO(1234, 2222); -- Ha de donar 7Possible solució
No utilitzarem
FOR, ho deixarem per la funcióDigitControlCte.Treballant amb CHAR - LPADDELIMITER // CREATE OR REPLACE FUNCTION DigitControlBO( p_cBanc TYPE OF Oficina.cOficina, p_cOficina TYPE OF Oficina.cOficina ) RETURNS INT BEGIN -- DECLARACIÓ DE VARIABLES LOCALS DECLARE v_digit INT; DECLARE v_cBanc CHAR(4); DECLARE v_cOficina CHAR(4); DECLARE v_A INT DEFAULT 0; DECLARE v_B INT DEFAULT 0; DECLARE v_C INT; -- CODI DE LA FUNCIÓ SET v_cBanc = LPAD(p_cBanc, 4, '0'); SET v_digit = substring(v_cBanc, 1, 1); SET v_A = v_digit * 4; SET v_digit = substring(v_cBanc, 2, 1); SET v_a = v_A + v_digit * 8; SET v_digit = substring(v_cBanc, 3, 1); SET v_a = v_A + v_digit * 5; SET v_digit = substring(v_cBanc, 4, 1); SET v_a = v_A + v_digit * 10; SET v_cOficina = LPAD(p_cOficina, 4, '0'); SET v_digit = substring(v_cOficina, 1, 1); SET v_B = v_digit * 9; SET v_digit = substring(v_cOficina, 2, 1); SET v_B = v_B + v_digit * 7; SET v_digit = substring(v_cOficina, 3, 1); SET v_B = v_B + v_digit * 3; SET v_digit = substring(v_cOficina, 4, 1); SET v_B = v_B + v_digit * 6; SET v_C = v_A + v_B; SET v_C = 11 - (v_C % 11); IF v_C = 10 THEN SET v_C = 1; ELSEIF v_C = 11 THEN SET v_C = 0; END IF; RETURN v_C; END // DELIMITER ; SELECT DigitControlBO(1234, 1111); -- Ha de donar 1 SELECT DigitControlBO(1234, 2222); -- Ha de donar 7Treballant amb INT - TRUNCATEDELIMITER // CREATE OR REPLACE FUNCTION DigitControlBO(pBanc INT, pOficina INT) RETURNS INT DETERMINISTIC BEGIN -- DECLARACIÓ DE VARIABLES LOCALS DECLARE vDigit INT; DECLARE vA, vB INT DEFAULT 0; DECLARE vC INT; -- CODI DE LA FUNCIÓ SET vA = TRUNCATE(pBanc / 1000, 0) * 4; -- Primer Digit SET pBanc = pBanc % 1000; SET vA = vA + TRUNCATE(pBanc / 100, 0) * 8; -- Segon Digit SET pBanc = pBanc % 100; SET vA = vA + TRUNCATE(pBanc / 10, 0) * 5; -- Segon Digit SET pBanc = pBanc % 10; SET vA = vA + TRUNCATE(pBanc / 1, 0) * 10; -- Segon Digit SET vB = TRUNCATE( pOficina / 1000, 0) * 9; -- Primer Digit SET pOficina = pOficina % 1000; SET vB = vB + TRUNCATE(pOficina / 100, 0) * 7; -- Segon Digit SET pOficina = pOficina % 100; SET vB = vB + TRUNCATE(pOficina / 10, 0) * 3; -- Segon Digit SET pOficina = pOficina % 10; SET vB = vB + TRUNCATE(pOficina / 1, 0) * 6; -- Segon Digit SET vC = (vA + vB) % 11; SET vDigit = 11 - vC; IF vDigit = 10 THEN SET vDigit = 1; ELSEIF vDigit = 11 THEN SET vDigit = 0; END IF; -- RETORN DEL VALOR RETURN vDigit; END // DELIMITER ; SELECT DigitControlBO(1234, 1111); -- Ha de donar 1 SELECT DigitControlBO(1234, 2222); -- Ha de donar 7
DigitControlCte - Funció
-
Donat el número de compte, retorna el dígit de control del Cte.
Càlcul dígits de Control (La Sexta)
Utilitzeu com a tipus de paràmetre
BIGINTDETERMINISTIC - SET - IF / ELSEIF - CASE - FOR - TRUNCATE - MOD
Exemple d'ús i execucióSELECT DigitControlCte(0123456789); -- Ha de donar 1 SELECT DigitControlCte(1234567890); -- Ha de donar 6Possible solució
Utilitzarem
FOR, sabent que les posicions es multipliquen per 2n%11.Treballant amb CHAR - LPADDELIMITER // CREATE OR REPLACE function DigitControlCte( p_cCompte TYPE OF Compte.cCompte ) RETURNS INT BEGIN DECLARE v_cCompte CHAR(10); DECLARE v_A INT DEFAULT 0; DECLARE v_C INT; SET v_cCompte = LPAD(p_cCompte, 10, '0'); FOR v_i IN 0..9 DO SET v_A = v_A + SUBSTRING(v_cCompte, v_i + 1, 1) * (POW(2, v_i) % 11); END FOR; SET v_C = 11 - (v_A % 11); IF v_C = 10 THEN SET v_C = 1; ELSEIF v_C = 11 THEN SET v_C = 0; END IF; RETURN v_C; END // DELIMITER ;Treballant amb BIGINT - TRUNCATEDELIMITER // CREATE OR REPLACE FUNCTION DigitControlCte(cCompte BIGINT) RETURNS INT DETERMINISTIC BEGIN DECLARE I, vDigit INT; DECLARE vSuma INT DEFAULT 0; SET vDigit = 0; FOR I IN REVERSE 0..9 DO SET vDigit = cCompte % 10; SET cCompte = TRUNCATE(cCompte / 10, 0); SET vSuma = vSuma + (vDigit * (POW(2, i) % 11)); END FOR; SET vDigit = 11 - MOD(vSuma, 11); IF vDigit = 10 THEN SET vDigit = 1; ELSEIF vDigit = 11 THEN SET vDigit = 0; END IF; -- RETORN DEL VALOR RETURN vDigit; END // DELIMITER ; SELECT DigitControlCte(0123456789); -- Ha de donar 1 SELECT DigitControlCte(1234567890); -- Ha de donar 6
Cte2Char - Funció
-
Donat el codi de banc, oficina i número de compte, retorna un string formatant el compte de la següent forma:
BBBB OOOO DD CCCCCCCCCCCàlcul dígits de Control (La Sexta)
SET - CONCAT - CONVERT - LPAD - Crida a Funcions
Exemple d'ús i execucióSELECT Cte2Char(1234,1111,0123456789); -- Retorna '1234 1111 11 0123456789' SELECT Cte2Char(1234,2222,1234567890); -- Retorna '1234 2222 76 1234567890'Possible solució
DELIMITER // CREATE OR REPLACE FUNCTION Cte2Char(cBanc INt, cOficina INT, cCompte BIGINT) RETURNS VARCHAR(25) DETERMINISTIC BEGIN RETURN CONCAT(LPAD(cBanc,4,'0'), ' ', LPAD(cOficina,4,'0'), ' ', CONVERT(DigitControlBO(cBanc, cOficina), CHAR), CONVERT(DigitControlCte(cCompte), CHAR), ' ', LPAD(cCompte, 10, '0')); END // DELIMITER ; SELECT Cte2Char(1234,1111,0123456789); -- Retorna '1234 1111 11 0123456789' SELECT Cte2Char(1234,2222,1234567890); -- Retorna '1234 2222 76 1234567890'
DarrersMoviments - Procediment
-
Donat un número d’oficina, un número de compte i una data ens mostra els moviments des d’aquella data en ordre de més nou a més antic. Cal mostar al final de cada línia el saldo després d'aquell moviment.
SELECT - CURSOR ( FOR ) - SIGNAL
Exemple d'ús i execucióCALL darrersMoviments(2,1, CURRENT_DATE - INTERVAL 1 WEEK); Mov Data Descripció Import Saldo 004 2023-02-21 Reintegre -125.00 275.00 003 2023-02-20 Reintegre -50.00 400.00 CALL darrersMoviments(2,1, CURRENT_DATE - INTERVAL 1 MONTH); Mov Data Descripció Import Saldo 004 2023-02-21 Reintegre -125.00 275.00 003 2023-02-20 Reintegre -50.00 400.00 002 2023-02-14 Ingrés 350.00 450.00 001 2023-02-09 Ingrés 100.00 100.00 CALL darrersMoviments(2,2, CURRENT_DATE + INTERVAL 1 DAY); Mov Data Descripció Import Saldo No hi ha moviments CALL darrersMoviments(1,2, CURRENT_DATE - INTERVAL 1 DAY); ERROR 1643 (02000): ERROR: Compte InexistentPossible solució
DELIMITER // CREATE OR REPLACE PROCEDURE DarrersMoviments( p_cOficina TYPE OF moviment.cOficina, p_cCompte TYPE OF moviment.cCompte, p_Dia DATE) BEGIN DECLARE v_q INT DEFAULT 0; DECLARE v_saldoCompte TYPE OF compte.saldoCompte; DECLARE v_numeroMoviment TYPE OF moviment.numeroMoviment; DECLARE c1 CURSOR FOR SELECT * FROM Moviment WHERE cOficina = p_cOficina AND cCompte = p_cCompte AND moment >= p_dia ORDER BY numeroMoviment DESC; -- Anem a buscar el Saldo actual, aprofitarem per mirar si existeix el Compte SELECT saldoCOmpte INTO v_saldoCompte FROM Compte WHERE cOficina = p_cOficina AND cCompte = p_cCompte; IF v_saldoCompte IS NULL THEN SIGNAL SQLSTATE '02000' SET MESSAGE_TEXT = 'ERROR: Compte Inexistent'; END IF; SELECT CONCAT_WS(' ', "Mov", "Data ", RPAD('Descripció', 35), LPAD('Import',10), LPAD('Saldo',10)) AS ""; FOR r_moviment IN c1 DO SELECT CONCAT_WS(' ', LPAD(r_moviment.numeroMoviment, 3, '0'), r_moviment.moment, RPAD(r_moviment.tipusMoviment, 35), LPAD(FORMAT(r_moviment.import, 2),10), LPAD(FORMAT(v_saldoCompte, 2),10)) AS ""; SET v_saldoCompte = v_saldoCompte - r_moviment.import; SET v_q = v_q + 1; END FOR; IF v_q = 0 THEN SELECT "No hi ha moviments" AS ""; END IF; END // DELIMITER ; CALL darrersMoviments(2,1, CURRENT_DATE - INTERVAL 1 WEEK); -- Surt algun moviment CALL darrersMoviments(2,1, CURRENT_DATE - INTERVAL 1 MONTH); -- Surt algun moviment CALL darrersMoviments(2,2, CURRENT_DATE + INTERVAL 1 DAY); -- No hi ha moviments CALL darrersMoviments(1,2, CURRENT_DATE - INTERVAL 1 DAY); -- ERROR 1643 (02000): ERROR: Compte Inexistent
Cte2IBAN - Funció opcional
Problemes en el càlcul al treballar amb nombres amb tants dígits
-
Donat el codi de banc, oficina i número de compte, retorna un string amb el codi IBAN del compte de la següent forma:
ESxx BBBB OOOO DD CCCCCCCCCC -
Suposarem que TOTS els comptes són de l’estat espanyol (ES).
Càlcul dígits de Control (La Sexta)
El compte no es pot tractar tot sencer com un número ja que no podem representar números amb tants dígits
BBBBOOOODDCCCCCCCCCC142800.Cal anar dividint, ens quedem el residu, de mica en mica, com si ho féssim a mà en paper, agafant els dígits des de l'esquerra de mica en mica fins agafar-los tots.
Exemple d'ús i execucióSELECT Cte2IBAN(1234,1111,0123456789); -- Ha de donar 53 ES53 1234 1111 11 0123456789 SELECT Cte2IBAN(1234,2222,1234567890); -- Ha de donar 90 ES90 1234 2222 76 1234567890Possible solució
DELIMITER // CREATE OR REPLACE FUNCTION Cte2IBAN(pcBanc INT, pcOficina INT, pcCompte BIGINT) RETURNS VARCHAR(30) DETERMINISTIC BEGIN DECLARE v_treball BIGINT; DECLARE v_afegit BIGINT; DECLARE v_CompteChar VARCHAR(26); -- Generem el CHAR amb tots els dígits més 142800 del ES00 al final SET v_CompteChar = CONCAT(lpad(pcBanc,4,'0'), lpad(pcOficina,4,'0'), FORMAT(DigitControlBO(pcBanc, pcOficina),'0'), FORMAT(DigitControlCte(pcCompte),'0'), lpad(pcCompte,10,'0'), '142800'); -- Agafem els primers 7 dígits. Podriem agafar-ne fins a 9 però millor quedar-nos curts SET v_treball = LEFT(v_CompteChar, 7); -- El dividim entre 97 i ens quedem el residu... SET v_treball = MOD(v_treball, 97); -- Ara hi anirem afegint la resta de dígits i en calularem el residu de dividir-ho per 97. -- Semblant a com dividiriem si ho fessim manualment en paper FOR v_i IN 8..LENGTH(v_CompteChar) DO -- Agafem els dígits que queden d'un en un -- Obtenim e dígit a afegir SET v_afegit = SUBSTR(v_CompteChar, v_i, 1); -- Podriem agafar-los de 5 en 5 -- Afegim el dígit al final SET v_treball = v_treball * 10 + v_afegit; -- Multipliquem per 10^numDigits que agafem -- Calculem el residu de dividir entre 97 SET v_treball = MOD(v_treball, 97); END FOR; -- Calculem els dígits de control SET v_treball = 98 - v_treball; -- Ja tenim a v_treball els 2 dígits del IBAN; RETURN CONCAT_WS(' ', CONCAT('ES', LPAD(v_treball,2,'0')), -- ha de tenir 2 dígits lpad(pcBanc,4,'0'), lpad(pcOficina,4,'0'), CONCAT(FORMAT(DigitControlBO(pcBanc, pcOficina),'0'), FORMAT(DigitControlCte(pcCompte),'0')), lpad(pcCompte,10,'0')); END // DELIMITER ; SELECT Cte2IBAN(1234,1111,0123456789); -- Ha de donar 53 SELECT Cte2IBAN(1234,2222,1234567890); -- Ha de donar 90
ComprovaOficines - Procediment
-
Mostra les oficines que no tenen el saldo correcte. Es comprova, oficina a oficina, que la suma dels imports dels seus comptes sigui el saldo de la oficina. Si no ho son, mostra les dades de la Oficina.
Exemple d'execucióCALL ComprovaOficines(); START TRANSACTION; UPDATE oficina SET saldoOficina = saldoOficina - 10; CALL ComprovaOficines(); 1 Una 20.00 30.00 2 Dues 440.00 450.00 3 Tres -10.00 NULL ROLLBACK; CALL ComprovaOficines();Possible solució
DELIMITER // CREATE OR REPLACE PROCEDURE ComprovaOficines( ) BEGIN DECLARE v_saldoOficinaEsperat TYPE OF oficina.saldoOficina; DECLARE c_oficina CURSOR FOR SELECT * FROM oficina; FOR r_oficina IN c_oficina DO SELECT SUM(saldoCompte) INTO v_saldoOficinaEsperat FROM compte WHERE cOficina = r_oficina.cOficina; IF IFNULL(r_oficina.saldoOficina, 0) <> IFNULL(v_saldoOficinaEsperat, 0) THEN SELECT r_oficina.cOficina, r_oficina.dOficina, r_oficina.saldoOficina, v_saldoOficinaEsperat; END IF; END FOR; END // DELIMITER ; CALL ComprovaOficines(); START TRANSACTION; UPDATE oficina SET saldoOficina = saldoOficina - 10; CALL ComprovaOficines(); ROLLBACK; CALL ComprovaOficines();
ComprovaMoviments - Procediment
-
Mostra els comptes que no tenen el saldo correcte. Es comprova, compte a compte, que la suma dels imports dels seus moviments sigui el saldo del compte. Si no ho son, mostra el número de compte.
Exemple d'execucióCALL ComprovaMoviments(); START TRANSACTION; UPDATE compte SET saldoCompte = saldoCompte - 10; CALL ComprovaMoviments(); 1 1 20.00 30.00 2 1 240.00 250.00 2 2 190.00 200.00 ROLLBACK; CALL ComprovaMoviments();Possible solució
DELIMITER // CREATE OR REPLACE PROCEDURE ComprovaMoviments( ) BEGIN DECLARE v_saldoCompteEsperat TYPE OF compte.saldoCompte; DECLARE c_compte CURSOR FOR SELECT * FROM compte; FOR r_compte IN c_compte DO SELECT SUM(import) INTO v_saldoCompteEsperat FROM moviment WHERE cOficina = r_compte.cOficina AND cCompte = r_compte.cCompte; IF IFNULL(r_compte.saldoCompte, 0) <> IFNULL(v_saldoCompteEsperat, 0) THEN SELECT r_compte.cOficina, r_compte.cCompte, r_compte.saldoCompte, v_saldoCompteEsperat; END IF; END FOR; END // DELIMITER ; CALL ComprovaMoviments(); START TRANSACTION; UPDATE compte SET saldoCompte = saldoCompte - 10; CALL ComprovaMoviments(); ROLLBACK; CALL ComprovaMoviments();
ComprovaSaldosNegatius - Procediment
-
Mostra els comptes que en algun moment han tingut el saldo en negatiu. Per comprovar-ho, cal anar, per cada compte, moviment a moviment calculant el saldo que tenia a cada moment. Si trobem un saldo negatiu, cal mostrar el compte.
Exemple d'execucióCALL ComprovaSaldosNegatius(); START TRANSACTION; UPDATE moviment SET import = import * 2 WHERE import < 0; CALL ComprovaSaldosNegatius(); 1 1 3 -200.00 -140.00 2 1 6 -250.00 -50.00 ROLLBACK; CALL ComprovaSaldosNegatius();Possible solució
DELIMITER // CREATE OR REPLACE PROCEDURE ComprovaSaldosNegatius( ) BEGIN DECLARE v_saldo TYPE OF compte.saldoCompte; DECLARE c_compte CURSOR FOR SELECT * FROM compte; DECLARE c_moviment CURSOR (p_cOficina TYPE OF moviment.cOficina, p_cCompte TYPE OF moviment.cCompte) FOR SELECT * FROM moviment WHERE cOficina = p_cOficina AND cCompte = p_cCompte ORDER BY numeroMoviment; FOR r_compte IN c_compte DO SET v_saldo = 0; FOR r_moviment IN c_moviment(r_compte.cOficina, r_compte.cCompte) DO SET v_saldo = v_saldo + r_moviment.import; IF v_saldo < 0 THEN SELECT r_compte.cOficina, r_compte.cCompte, r_moviment.numeroMoviment, r_moviment.import, v_saldo; END IF; END FOR; END FOR; END // DELIMITER ; CALL ComprovaSaldosNegatius(); START TRANSACTION; UPDATE moviment SET import = import * 2 WHERE import < 0; CALL ComprovaSaldosNegatius(); ROLLBACK; CALL ComprovaSaldosNegatius();