For å jobbe med lagrede programmer, så kan det være nødvendig å endre "standard skilletegn". Det gjøres med kommandoen DELIMITER og jeg har da valgt $$ som alternativt skilletegn. Les mer om dette under Defining Stored Programs i MySQL-manualen.
Lagret funksjon som returnerer navnet på en vare.
DELIMITER $$
DROP FUNCTION IF EXISTS hent_varenavn $$
CREATE FUNCTION hent_varenavn
(
p_vnr CHAR(5)
)
RETURNS VARCHAR(40)
READS SQL DATA
BEGIN
DECLARE v_betegnelse VARCHAR(40) DEFAULT NULL;
SELECT Betegnelse INTO v_betegnelse
FROM Vare
WHERE VNr = p_vnr;
RETURN v_betegnelse;
END $$
DELIMITER ;
-- Test med eksisterende varenummer
SELECT hent_varenavn('10830') FROM Dual;
-- Test med ukjent varenummer
SELECT hent_varenavn('9999') FROM Dual;
-- Test med spørring mot Ordrelinje
SELECT *, hent_varenavn(VNr) AS Betegnelse
FROM Ordrelinje;
Alle data om en gitt vare i form av ut-parametre.
DELIMITER $$
DROP PROCEDURE IF EXISTS hent_vare $$
CREATE PROCEDURE hent_varedata
(
IN p_vnr CHAR(5),
OUT p_betegnelse VARCHAR(40),
OUT p_pris DECIMAL(8, 2),
OUT p_antall INT(11)
)
BEGIN
SELECT Betegnelse, Pris, Antall
INTO p_betegnelse, p_pris, p_antall
FROM Vare
WHERE VNr = p_vnr;
END $$
DELIMITER ;
-- Tester prosedyren
CALL hent_varedata('10820', @betegnelse, @pris, @antall);
SELECT @betegnelse AS Betegnelse, @pris AS Pris, @antall AS Antall
FROM Dual;
Setter inn en ny vare - uten feilsjekking.
DELIMITER $$
DROP PROCEDURE IF EXISTS ny_vare $$
CREATE PROCEDURE ny_vare
(
IN p_vnr CHAR(5),
IN p_betegnelse VARCHAR(30),
IN p_pris DECIMAL(8,2),
IN p_antall INT(11)
)
BEGIN
INSERT INTO Vare(VNr, Betegnelse, pris, antall)
VALUES (p_vnr, p_betegnelse, p_pris, p_antall);
END $$
DELIMITER ;
-- Tester prosedyren (med lovlige inndata, se oppgave 6)
CALL ny_vare('99999', 'Strøsand', 20.50, 3);
Slett vare.
DELIMITER $$
DROP PROCEDURE IF EXISTS slett_vare $$
CREATE PROCEDURE slett_vare
(
IN p_vnr CHAR(5),
OUT ut_status VARCHAR(30)
)
BEGIN
DECLARE v_ant INT;
SELECT COUNT(*) INTO v_ant
FROM Vare
WHERE VNr = p_vnr;
IF v_ant = 1 THEN
DELETE FROM Vare
WHERE VNr = p_vnr;
COMMIT;
SET ut_status = 'Varen er slettet.';
ELSE
SET ut_status = 'Ukjent vare!';
END IF;
END $$
DELIMITER ;
-- Tester prosedyren (legg inn 99999 først)
CALL slett_vare('99999', @kvittering);
SELECT @kvittering;
Endre prisen på en vare.
DELIMITER $$
DROP PROCEDURE IF EXISTS endre_varepris $$
CREATE PROCEDURE endre_varepris
(
IN p_vnr CHAR(5),
IN p_ny_pris DECIMAL(8, 2),
OUT ut_status VARCHAR(30)
)
BEGIN
DECLARE v_ant INT;
SELECT COUNT(*) INTO v_ant
FROM Vare
WHERE VNr = p_vnr;
IF v_ant = 1 THEN
UPDATE Vare
SET pris = p_ny_pris
WHERE VNr = p_vnr;
COMMIT;
SET ut_status = 'Prisen er endret.';
ELSE
SET ut_status = 'Ukjent vare!';
END IF;
END $$
DELIMITER ;
-- Tester prosedyren (sett inn 99999 først)
CALL endre_varepris('99999', 50, @kvittering);
SELECT @kvittering;
Sett inn ny vare - med feilsjekking.
DELIMITER $$
DROP PROCEDURE IF EXISTS ny_vare $$
CREATE PROCEDURE ny_vare
(
IN p_vnr CHAR(5),
IN p_betegnelse VARCHAR(30),
IN p_pris DECIMAL(8,2),
IN p_antall INT(11)
)
BEGIN
DECLARE v_antall INT;
DECLARE v_utdata TEXT;
SET v_utdata = '';
-- Sjekker om varen finnes fra før
SELECT COUNT(*) INTO v_antall
FROM Vare
WHERE VNr = p_vnr;
IF v_antall > 0 THEN
SET v_utdata = 'Varen finnes allerede!';
ELSEIF p_antall < 0 THEN
SET v_utdata = 'Antall på lager må være større enn 0!';
ELSEIF p_pris <= 0 OR p_pris > 100000 THEN
SET v_utdata = 'Pris må være positiv og mindre enn kr 100 000!';
ELSE
SET v_utdata = 'Varen er registrert.';
INSERT INTO Vare(VNr, Betegnelse, pris, antall)
VALUES (p_vnr, p_betegnelse, p_pris, p_antall);
COMMIT;
END IF;
-- Kvittering til bruker
SELECT v_utdata;
END $$
DELIMITER ;
-- Tester prosedyren med lovlige inndata
CALL ny_vare('99999', 'Strøsand', 20.50, 3);
-- Tester prosedyren med eksisterende varenummer
CALL ny_vare('10830', 'Strøsand', 20.50, 3);
-- Tester prosedyren med negativt antall
CALL ny_vare('99999', 'Strøsand', 20.50, -3);
-- Tester prosedyren med for høy pris
CALL ny_vare('99999', 'Strøsand', 200000, 3);
-- Tester prosedyren med negativ pris
CALL ny_vare('99999', 'Strøsand', -1, 3);
Viser kun kall på ny_vare (oppgave 3). Antar vi får inn data via URL-parametre (GET).
$vnr = $_GET['vnr'];
$betegnelse = $_GET['betegnelse'];
$pris = $_GET['pris'];
$antall = $_GET['antall'];
// Oppkoblingsparametre må tilpasses
$tjener = 'localhost';
$bruker = 'root';
$pass = '';
$db = 'hobbyhuset';
// Koble opp databasen
$link = mysqli_connect($tjener, $bruker, $pass, $db);
mysqli_set_charset($link, 'utf8');
// Utfør prosedyrekall
$sql = "CALL ny_vare('$vnr', '$betegnelse', $pris, $antall);";
$resultat = mysqli_query($link, $sql);
$rad = mysqli_fetch_array($resultat);
if ($rad) {
$melding = $rad[0];
print("$melding
");
}
else {
print('Prosedyrekallet feilet!
');
}
mysqli_close($link);
Lagret prosedyre som viser alle varer - foreløpig uten PHP-skript.
DELIMITER $$
DROP PROCEDURE IF EXISTS vis_varer $$
CREATE PROCEDURE vis_varer()
BEGIN
SELECT VNr, Betegnelse, Pris
FROM Vare
WHERE Pris < 100;
END $$
DELIMITER ;
-- Test
CALL vis_varer();
Ny ordre - bør utvides med diverse sjekker, f.eks. at varen eksisterer.
DELIMITER $$
DROP PROCEDURE IF EXISTS ny_ordre $$
CREATE PROCEDURE ny_ordre
(
IN p_ordrenr INT(11),
IN p_knr INT(11),
IN p_ansnr INT(5),
IN p_vnr CHAR(6),
IN p_antall INT(4)
)
BEGIN
DECLARE v_pris DECIMAL(8, 2);
INSERT INTO Ordre(OrdreNr, OrdreDato, KNr, AnsNr)
VALUES (p_ordrenr, CURDATE(), p_knr, p_ansnr);
SELECT Pris INTO v_pris
FROM Vare
WHERE VNr = p_vnr;
INSERT INTO Ordrelinje(OrdreNr, VNr, PrisPrEnhet, Antall)
VALUES (p_ordrenr, p_vnr, v_pris, p_antall);
COMMIT;
END $$
DELIMITER ;
-- Test
CALL ny_ordre(99999, 5002, 1, '10830', 33);
Trigger som lagrer navn med stor forbokstav.
DELIMITER $$
DROP TRIGGER IF EXISTS ansatt_b_ins_trg $$
DROP TRIGGER IF EXISTS ansatt_b_upd_trg $$
DROP FUNCTION IF EXISTS INITCAP $$
-- Denne er litt for enkel: 'anders' blir korrekt til 'Anders',
-- men 'karl martin' blir til 'Karl martin'.
CREATE FUNCTION INITCAP(str VARCHAR(255))
RETURNS VARCHAR(255)
DETERMINISTIC
BEGIN
DECLARE ut VARCHAR(255);
SET ut = CONCAT(UPPER(SUBSTR(str, 1,1)), LOWER(SUBSTR(str, 2)));
RETURN ut;
END $$
CREATE TRIGGER ansatt_b_ins_trg
BEFORE INSERT ON Ansatt
FOR EACH ROW
BEGIN
SET NEW.Fornavn = INITCAP(NEW.Fornavn);
SET NEW.Etternavn = INITCAP(NEW.Etternavn);
END $$
CREATE TRIGGER ansatt_b_upd_trg
BEFORE UPDATE ON Ansatt
FOR EACH ROW
BEGIN
SET NEW.Fornavn = INITCAP(NEW.Fornavn);
SET NEW.Etternavn = INITCAP(NEW.Etternavn);
END $$
DELIMITER ;
-- Tester hjelpefunksjonen
SELECT INITCAP('oLe') FROM DUAL;
-- Tester innsetting
INSERT INTO Ansatt(AnsNr, Fornavn, Etternavn, PostNr)
VALUES (999, 'PeR', 'hansen', 3800);
-- Tester oppdatering
UPDATE Ansatt
SET Fornavn='ola', Etternavn='li'
WHERE AnsNr = 999;
Trigger for loggføring.
-- Lager logg-tabellen først
DROP TABLE IF EXISTS logg;
CREATE TABLE logg
(
id INT(11) AUTO_INCREMENT,
bruker VARCHAR(40),
vnr CHAR(5),
antall_gml INT(5),
antall_ny INT(5),
tidspunkt DATE,
PRIMARY KEY (id)
);
-- Lager triggeren:
DELIMITER $$
DROP TRIGGER IF EXISTS vare_a_upd_trg $$
CREATE TRIGGER vare_a_upd_trg
AFTER UPDATE ON Vare
FOR EACH ROW
BEGIN
INSERT INTO logg(bruker, vnr, antall_gml, antall_ny, tidspunkt)
VALUES(USER(), NEW.VNr, OLD.Antall, NEW.Antall, NOW());
END $$
DELIMITER ;
-- Tester
UPDATE Vare
SET Antall = Antall+1;
COMMIT;
Oppdater SendtDato.
DELIMITER $$
DROP TRIGGER IF EXISTS ordre_b_ins_trg $$
CREATE TRIGGER ordre_b_ins_trg
BEFORE INSERT ON Ordre
FOR EACH ROW
BEGIN
SET NEW.SendtDato = NEW.OrdreDato;
END $$
DELIMITER ;
-- Tester
INSERT INTO Ordre(OrdreNr, OrdreDato, KNr, AnsNr)
VALUES(99999, CURDATE(), 5002, 1);
Sjekk at varepriser er mellom 0 og 10 000.
Dette lar seg generelt formulere som en CHECK contraint, og MySQL godtar syntaksen:
ALTER TABLE Vare
ADD CHECK (Pris < 10000);
Men det ser ikke ut til at MySQL kontrollerer regelen, så da må vi bruke triggere i stedet. Viser bare UPDATE-triggeren.
DELIMITER $$
DROP TRIGGER IF EXISTS vare_b_upd_trg $$
CREATE TRIGGER vare_b_upd_trg
BEFORE UPDATE ON Vare
FOR EACH ROW
BEGIN
IF NOT (NEW.Pris BETWEEN 0 AND 10000) THEN
SIGNAL SQLSTATE 'U0001' -- Egendefinert feilkode
SET MESSAGE_TEXT = 'Pris må være mellom 0 og 10 000!';
END IF;
END $$
DELIMITER ;
-- Tester
UPDATE Vare
SET Pris = -1
WHERE VNr = '10820';
UPDATE Vare
SET Pris = 20000
WHERE VNr = '10820';
Forhindre endring av PrisPrEnhet.
DELIMITER $$
DROP TRIGGER IF EXISTS ordrelinje_b_upd_trg $$
CREATE TRIGGER ordrelinje_b_upd_trg
BEFORE UPDATE ON Ordrelinje
FOR EACH ROW
BEGIN
IF NEW.PrisPrEnhet <> OLD.PrisPrEnhet THEN
SIGNAL SQLSTATE 'U0002' -- Brukerdefinert feilkode
SET MESSAGE_TEXT = 'Ikke lov å endre pris på eksisterende ordrelinje!';
END IF;
END $$
DELIMITER ;
-- Tester
UPDATE Ordrelinje
SET PrisPrEnhet = PrisPrEnhet + 1;
Tester triggeren fra PHP:
// Oppkoblingsparametre må tilpasses
$tjener = 'localhost';
$bruker = 'root';
$pass = '';
$db = 'hobbyhuset';
// Koble opp databasen
$link = mysqli_connect($tjener, $bruker, $pass, $db);
mysqli_set_charset($link, 'utf8');
$sql = "UPDATE Ordrelinje " .
"SET PrisPrEnhet = PrisPrEnhet + 1";
$resultat = mysqli_query($link, $sql);
if (mysqli_errno($link) <> 0) {
printf("Feil %d %s: %s",
mysqli_errno($link), mysqli_sqlstate($link), mysqli_error($link));
}
else {
print("Prisen er oppdatert!");
}
mysqli_close($link);