Нужна помощь в разработке схемы MySQL - текущая схема требует динамического SQL внутри триггера - PullRequest
2 голосов
/ 14 ноября 2010

Я предполагаю, что я плохо спроектировал свою базу данных, но в настоящее время я озадачен тем фактом, что мне нужно использовать динамический sql в триггере, и это делает mysql несчастным.

Ситуация в том, что у меня естьсоздал базу данных о членстве с несколькими десятками таблиц, главной из которых является таблица 'member' с уникальным первичным ключом 'id'.Существует ряд других таблиц, которые имеют внешние ключи, относящиеся к полю member.id.

Поскольку данные собирались в течение многих лет и с небольшим контролем дублирования, в 'member' есть еще одно полетаблица с именем 'superseded_by', которая содержит идентификатор члена, который заменяет этот.По умолчанию superseded_by установлен как member_id.Любой, чей id superseded_by <> считается дублирующим.

Теперь сложная часть ... когда мы идентифицируем дубли, мы хотим установить поле superseded_by так, чтобы оно указывало на новый первичный элемент и обновите все таблицы с помощью внешних ключей, указывающих на теперь избыточный идентификатор элемента .Я пытался сделать это, используя триггер после обновления ... и затем я попытался быть умным, запрашивая внешние ключи из information_schema и используя динамический sql для их обновления.

Это явно неработа (Код ошибки: 1336 Динамический SQL не разрешен в хранимой функции или триггере).

Я предполагаю, что есть лучший способ для создания схемы / дескриптора, о которых я не думал.

Помогите, пожалуйста ...

КОДЕКС SNIPPET:

-- ---
-- Table 'member'
-- ---
DROP TABLE IF EXISTS member;
CREATE TABLE member (
    id INTEGER AUTO_INCREMENT,
    superseded_by INTEGER DEFAULT NULL,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    date_of_birth DATE DEFAULT NULL,
    gender ENUM('M', 'F') DEFAULT NULL,
    mailing_address_id INTEGER DEFAULT NULL,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    FOREIGN KEY (mailing_address_id) REFERENCES mailing_address (id),
    FOREIGN KEY (superseded_by) REFERENCES member (id)
);
DELIMITER $$
    CREATE TRIGGER set_superseded_by_on_insert BEFORE INSERT ON member FOR EACH ROW
    BEGIN
        SET NEW.superseded_by = NEW.id;
    END$$

    -- Trigger to update other tables (volunteers, donations, presenters, etc.) when member's superseded_by record is updated
    -- Assumes the new superseding person exists (they should also not be superseded by anyone themselves)
    CREATE TRIGGER adjust_foreign_member_keys_on_superseded_by_update AFTER UPDATE ON member FOR EACH ROW
    BEGIN
        DECLARE db, tbl, col VARCHAR(64);
        DECLARE fk_update_statement VARCHAR(200);
        DECLARE no_more_rows BOOLEAN;
        DECLARE fks CURSOR FOR  SELECT kcu.TABLE_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME
                                FROM information_schema.TABLE_CONSTRAINTS tc
                                JOIN information_schema.KEY_COLUMN_USAGE kcu ON
                                        tc.table_schema = kcu.table_schema AND tc.constraint_name = kcu.constraint_name
                                WHERE   tc.constraint_type='FOREIGN KEY' AND
                                        kcu.REFERENCED_TABLE_NAME = 'member' AND
                                        kcu.REFERENCED_COLUMN_NAME = 'id';
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;

        IF NEW.superseded_by <> OLD.superseded_by THEN

            OPEN fks;
            SET no_more_rows = FALSE;
            update_loop: LOOP
                FETCH fks INTO db, tbl, col;
                IF no_more_rows THEN
                    LEAVE update_loop;
                END IF;
                SET @fk_update_statement = CONCAT("UPDATE ", db, ".", tbl, " SET ", col, " = NEW.superseded_by WHERE ", col, " = NEW.id;");
                PREPARE stmt FROM @fk_update_statement;
                EXECUTE stmt;
                DEALLOCATE PREPARE stmt;
            END LOOP;
            CLOSE fks;

        END IF;
    END$$
DELIMITER ;

Ответы [ 3 ]

1 голос
/ 14 ноября 2010

Почему вы пытаетесь сохранить дубликаты в основной таблице? Похоже, вам лучше использовать таблицу участников и таблицу member_history для отслеживания предыдущих изменений. Это можно сделать, изменив таблицу, в которой хранятся поля, даты и старые и новые значения. Или вы можете просто сохранить предыдущий снимок таблицы элементов перед обновлением. Например:

INSERT INTO member_history SELECT NULL, * FROM member WHERE id = ?
UPDATE member SET [...] WHERE id = ?

Схема для member_history будет почти идентична, за исключением того, что вы сохраните member.id как member_id и будете иметь отдельный первичный ключ для каждой записи истории. (Примечание: я немного приукрашиваю синтаксис, часть NULL, * может не сработать, в этом случае вам может понадобиться явно назвать все поля. У вас не было времени, чтобы проверить это).

CREATE TABLE member (
    id INTEGER AUTO_INCREMENT,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    date_of_birth DATE DEFAULT NULL,
    gender ENUM('M', 'F') DEFAULT NULL,
    mailing_address_id INTEGER DEFAULT NULL,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    FOREIGN KEY (mailing_address_id) REFERENCES mailing_address (id),
);

CREATE TABLE member_history (
    id INTEGER AUTO_INCREMENT,
    member_id INTEGER NOT NULL,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    date_of_birth DATE DEFAULT NULL,
    gender ENUM('M', 'F') DEFAULT NULL,
    mailing_address_id INTEGER DEFAULT NULL,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    FOREIGN KEY (member_id) REFERENCES member (id),
);

Обратите внимание, что я удалил поле superseded_by в таблице-члене и внешний ключ mailing_address в таблице member_history. Вам больше не нужно использовать superseded_by, и хранить внешний ключ в таблице member_history на самом деле не нужно, если только вы не беспокоитесь о свисающих ссылках в своей истории.

0 голосов
/ 20 декабря 2013

Триггер и хранимая функция в MySQL имеют ограничения, которые мы не можем использовать динамический SQL в обоих из них. Надеюсь, это поможет.

0 голосов
/ 14 ноября 2010

Хорошо, просто пара мыслей по этому поводу:

superseded_by ссылается на id в той же таблице и в целом совпадает с последней - не в тех случаях, когда вы смогли идентифицироватьобман, но в этом случае он будет указывать на id.

другого уже существующего члена. Учитывая, что мы можем с уверенностью предположить, что никакое поле superseded_by никогда не повредит ограничению внешнего ключа.1010 * Далее я предполагаю, что поля id и superseded_by дупсов, которые еще не были идентифицированы, равны.

Итак, если все вышесказанное верно, вы можете согнуть внешний ключ другогосвязанные таблицы для ссылки superseded_by вместо id.Таким образом, вы могли бы каскадно вносить изменения, внесенные в дублирование, в другие таблицы и при этом иметь те же ограничения, что и раньше.

Что вы думаете?Я что-то упустил?

Обратите внимание, что это вариант, только если вы используете InnoDB, а не MyISAM.

С уважением, aefxx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...