Zgodnie z obietnicą dzisiaj druga część. Zwiększamy poziom trudności o relację wiele-do-wielu.
Na początek dorzucamy tabelę Ficzer
zawierającą dodatkowe bajery, o które ma być wzbogacony artykuł:
CREATE TABLE IF NOT EXISTS `Ficzer` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`nazwa` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Ficzer` (`id`, `nazwa`) VALUES
(1, 'Podświetlenie'),
(3, 'Pogrubienie'),
(5, 'Pochylenie');
Zaraz za nią tworzymy tabelę pośredniczącą Ogloszenie_Ficzer
:
CREATE TABLE IF NOT EXISTS `Ogloszenie_Ficzer` (
`ogloszenie_id` INT(11) NOT NULL,
`ficzer_id` INT(11) NOT NULL,
PRIMARY KEY (`ogloszenie_id`,`ficzer_id`),
KEY `ficzer_id` (`ficzer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `Ogloszenie_Ficzer`
ADD CONSTRAINT `Ogloszenie_Ficzer_ibfk_4` FOREIGN KEY (`ficzer_id`) REFERENCES `Ficzer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `Ogloszenie_Ficzer_ibfk_3` FOREIGN KEY (`ogloszenie_id`) REFERENCES `Ogloszenie` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
Procedura składowana ogloszenie_history
musi zostać rozbudowana o obsługę dodanych tabel (a raczej tabeli, bo wystarczy nam tabela pośrednicząca). Dokonamy „spłaszczenia” struktury bazy w wierszu historii wymieniając wszystkie ficzery rozdzielone średnikami. Dorzucamy pole ficzers
do OgloszenieHistory
:
ALTER TABLE `OgloszenieHistory` ADD COLUMN `ficzers` text DEFAULT NULL;
Poniżej uaktualniony kod procedury składowanej:
DELIMITER $$
CREATE PROCEDURE `ogloszenie_history`(IN `id` INT, IN `change_type` ENUM('created','modified','deleted'))
MODIFIES SQL DATA
BEGIN
DECLARE p_user_id INT;
DECLARE p_data TEXT;
# Ciag tekstowy zawierajacy ficzer_id;ficzer_id;...
DECLARE p_ficzers TEXT DEFAULT NULL;
# Pojedynczy wiersz kursora.
DECLARE p_ficzer INT;
# Blokada kursora gdy braknie wynikow.
DECLARE p_last_ficzer INT DEFAULT FALSE;
# Deklaracja kursora przechodzacego po wszystkich ficzerach ogloszenia.
DECLARE cur_ficzer CURSOR FOR SELECT ficzer_id FROM Ogloszenie_Ficzer WHERE ogloszenie_id=id;
# Ustawienie blokady kursora.
DECLARE continue handler FOR NOT found SET p_last_ficzer = TRUE;
# Wyciagam dane ze zmienianego wiersza i wrzucam do zadeklarowanych wczesniej zmiennych.
SELECT modified_by, DATA INTO p_user_id, p_data FROM Ogloszenie WHERE id=id LIMIT 1;
OPEN cur_ficzer;
ficzer_loop: LOOP
FETCH cur_ficzer INTO p_ficzer;
IF p_last_ficzer THEN
LEAVE ficzer_loop;
END IF;
# Sklejam kolejne wartosci.
SET p_ficzers = CONCAT_WS(';', p_ficzers, p_ficzer);
END LOOP;
CLOSE cur_ficzer;
# Wrzucam wiersz do historii.
INSERT INTO OgloszenieHistory (ogloszenie_id, change_type, user_id, DATA, ficzers)
VALUES (id, change_type, p_user_id, p_data, p_ficzers);
END$$
DELIMITER ;
Dzięki zastosowaniu kursora zbieramy sobie każdorazowo bieżące wartości tabeli pośredniczącej. Ostatnią rzeczą jest dodanie wyzwalaczy:
DROP TRIGGER IF EXISTS `new_ficzer`;
DELIMITER //
CREATE TRIGGER `new_ficzer` AFTER INSERT ON `Ogloszenie_Ficzer`
FOR EACH ROW BEGIN
CALL ogloszenie_history (NEW.ogloszenie_id, 'modified');
END//
DELIMITER ;
DROP TRIGGER IF EXISTS `delete_ficzer`;
DELIMITER //
CREATE TRIGGER `delete_ficzer` BEFORE DELETE ON `Ogloszenie_Ficzer`
FOR EACH ROW BEGIN
CALL ogloszenie_history (OLD.ogloszenie_id, 'modified');
END//
DELIMITER ;
Stosując takie rozwiązanie każda zmiana jest rejestrowana w dzienniku zmian. Minusem jest to, że następuje gwałtowny przyrost wierszy (dodanie 10 ficzerów do ogłoszenia powoduje dodanie 10 wierszy historii, skasowanie to samo). Jeśli bardzo zależy nam na ograniczeniu liczby wierszy trzeba zastanowić się nad jakimś automatem odpalanym cyklicznie z CRONa kasującym starą historię lub bardziej wyrafinowanym — wyłapującym „stany pośrednie” (co zresztą też jest niezłym pretekstem do napisania na blogu ;-) ).
PS.: Interesujecie się DARTem? Jeszcze trochę ponad 100 defektów i będzie milestone 1.