macmilan
macmilan

Reputation: 75

MySQL IFNULL() doesn't catch null values

I am developing a Yii2 framework application and during my data modelling phase I decided to let DB engine handle simple updates and maintain my counter tables (which are used to return the number of unread messages and other stuff for the user).

I have designed a trigger on message table to increase and decrease number of unread messages for the user.

User can be either client or architect.

All of my id columns in the DB are UNSIGNED INTs (these are also the PK for respective tables).

I have a problem when forward engineering from Workbench. I have made a script with test data for integrity testing and initial population. The script executed fine, before I added the AFTER_INSERT trigger for the message table.

Here is the trigger code:

CREATE DEFINER = CURRENT_USER TRIGGER `visavis`.`message_AFTER_INSERT` AFTER INSERT ON `message` FOR EACH ROW
BEGIN
DECLARE archid INT UNSIGNED;

# Cheks if recevier is an architect    
SELECT IFNULL(`visavis`.`architect`.`id`,0)
INTO archid
FROM `visavis`.`architect`
WHERE `visavis`.`architect`.`user` = NEW.`to`;

# Checks if the new message is set to sent (and not read)
IF NEW.status = 1
THEN
    IF archid = 0 -- if the receiver is client
    THEN
        # Checks if the user receiving exists in the user_counter table
        IF NOT EXISTS (SELECT 1 FROM `visavis`.`user_counter` WHERE `visavis`.`user_counter`.`user` = NEW.`to`)
        THEN
            # Insert new row into user_counter table
            INSERT INTO `visavis`.`user_counter` (`user`,`messages`) VALUES (NEW.`to`,1);
        ELSE
            # Add one to the user followings counter
            UPDATE `visavis`.`user_counter`
            SET `visavis`.`user_counter`.`messages` = `visavis`.`user_counter`.`messages` + 1
            WHERE `visavis`.`user_counter`.`user` = NEW.`to`;
        END IF; -- if user_counter
    ELSE
        # Extra check if archid is null
        #IF ISNULL(archid)
        #THEN
        #   SET archid = 1; -- Testing value
        #END IF;

        # Checks if the architect receiving exists in the architect_counter table
        IF NOT EXISTS (SELECT 1 FROM `visavis`.`architect_counter` WHERE `visavis`.`architect_counter`.`architect` = archid)
        THEN                
            # Insert new row into architect_counter table
            INSERT INTO `visavis`.`architect_counter` (`architect`,`messages`) VALUES (archid,1);
        ELSE                
            # Add one to the user followings counter
            UPDATE `visavis`.`architect_counter`
            SET `visavis`.`architect_counter`.`messages` = `visavis`.`architect_counter`.`messages` + 1
            WHERE `visavis`.`architect_counter`.`architect` = archid;
        END IF; -- if architect_counter
    END IF; -- if receiver is client
END IF; -- if message is sent
END

The problem is that I get this error:

ERROR: Error 1048: Column 'architect' cannot be null

In the code above in the ELSE branch of the client or architect check I inserted the extra check code to assign value to the variable (it is commented out). With this code script passes fine, but all unread messages end up with architect with id=1.

I am also adding my tables' DDLs:

CREATE TABLE IF NOT EXISTS `architect` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'A PK for the table.',
  `user` INT UNSIGNED NOT NULL COMMENT 'Link to the user that is the architect.',
  `office` INT UNSIGNED NULL COMMENT 'Link to the architect\'s office, if any.',
  `short_description` INT UNSIGNED NOT NULL COMMENT 'Link to the text of short description.',
  `description` INT UNSIGNED NOT NULL COMMENT 'Link to the text of description.',
  `specialty_1` INT UNSIGNED NULL COMMENT 'Link to the specialty.',
  `specialty_2` INT UNSIGNED NULL COMMENT 'Link to the specialty.',
  `specialty_3` INT UNSIGNED NULL COMMENT 'Link to the specialty.',
  `order` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `fk_architect_specialty_1`
    FOREIGN KEY (`specialty_1`)
    REFERENCES `specialty` (`id`)
    ON DELETE SET NULL
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_architect_specialty_2`
    FOREIGN KEY (`specialty_2`)
    REFERENCES `specialty` (`id`)
    ON DELETE SET NULL
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_architect_specialty_3`
    FOREIGN KEY (`specialty_3`)
    REFERENCES `specialty` (`id`)
    ON DELETE SET NULL
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_architect_short_description`
    FOREIGN KEY (`short_description`)
    REFERENCES `text` (`id`)
    ON DELETE RESTRICT
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_architect_description`
    FOREIGN KEY (`description`)
    REFERENCES `text` (`id`)
    ON DELETE RESTRICT
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_architect_office`
    FOREIGN KEY (`office`)
    REFERENCES `office` (`id`)
    ON DELETE SET NULL
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_architect_user`
    FOREIGN KEY (`user`)
    REFERENCES `user` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = 'The info about the architect.';

CREATE TABLE IF NOT EXISTS `visavis`.`message` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'A PK of the table.',
  `from` INT UNSIGNED NOT NULL COMMENT 'User that sent the message.',
  `to` INT UNSIGNED NOT NULL COMMENT 'User that recieves the message.',
  `text` VARCHAR(2000) NOT NULL COMMENT 'Text of the message. Length constrained in the frontend.',
  `status` INT UNSIGNED NOT NULL DEFAULT 6 COMMENT 'Status of the message.',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date and time when the message was created. Automaticaly recieves status 6 (draft).',
  `viewed_at` DATETIME NULL COMMENT 'Date and time when the message was viewed by the reciever. Set when the status changes to 1.',
  `sent_at` DATETIME NULL COMMENT 'Date and time when the message was sent. Set when the status changes to 2.',
  `replied_at` DATETIME NULL COMMENT 'Date and time when the message was replied, if any. Set when the status changes to 3.',
  `shared_at` DATETIME NULL COMMENT 'Date and time when the message was shared to external board. Set when the status changes to 4.',
  `deleted_at` DATETIME NULL COMMENT 'Date and time of message deletion (from the view). Set when the status changes to 5.',
  `message_type` INT UNSIGNED NOT NULL COMMENT 'Link to the type of the message.',
  `attachment` INT UNSIGNED NULL COMMENT 'Link to the attachment.',
  `template` INT UNSIGNED NULL COMMENT 'Link to the template the message implements.',
  PRIMARY KEY (`id`),
  INDEX `fk_user_from_idx` (`from` ASC),
  INDEX `fk_user_to_idx` (`to` ASC),
  INDEX `fk_message_type_type_idx` (`message_type` ASC),
  INDEX `fk_message_status_status_idx` (`status` ASC),
  INDEX `fk_message_attachment_idx` (`attachment` ASC),
  INDEX `fk_message_template_idx` (`template` ASC),
  CONSTRAINT `fk_user_from`
    FOREIGN KEY (`from`)
    REFERENCES `visavis`.`user` (`id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_user_to`
    FOREIGN KEY (`to`)
    REFERENCES `visavis`.`user` (`id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_message_type_type`
    FOREIGN KEY (`message_type`)
    REFERENCES `visavis`.`message_type` (`id`)
    ON DELETE RESTRICT
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_message_status_status`
    FOREIGN KEY (`status`)
    REFERENCES `visavis`.`message_status` (`id`)
    ON DELETE RESTRICT
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_message_attachment`
    FOREIGN KEY (`attachment`)
    REFERENCES `visavis`.`attachment` (`id`)
    ON DELETE SET NULL
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_message_template`
    FOREIGN KEY (`template`)
    REFERENCES `visavis`.`message_template` (`id`)
    ON DELETE SET NULL
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = 'Internal messaging system.'

CREATE TABLE IF NOT EXISTS `visavis`.`architect_counter` (
  `architect` INT UNSIGNED NOT NULL COMMENT 'A PK of the table.',
  `houses` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of houses in the system.',
  `followers` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of followers.',
  `liked_houses` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of houses that the users liked.',
  `sold` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of purchased items of the architect.',
  `messages` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of unread messages.',
  `customizings` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of customize request an architect has received.',
  `workings` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'The number of customizing work an archotect has begun work on. (accepted and assigned quotes)',
  PRIMARY KEY (`architect`),
  CONSTRAINT `fk_architect_counter_architect`
    FOREIGN KEY (`architect`)
    REFERENCES `visavis`.`architect` (`id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = 'Counter table for summed values, of the logged in achitect, ' /* comment truncated */ /*needed for the front end.*/

I also tried COALESCE(), CASE, read that IFNULL() returns SIGNED values so I CAST() the entire IFNULL clause.

I don't have any problem with delimiters (Workbench handles them)

THIS IS THE FIX:

IF EXISTS (SELECT 1 FROM `visavis`.`architect` WHERE `visavis`.`architect`.`user` = NEW.`to`)
THEN
    SELECT `visavis`.`architect`.`id`
    INTO archid
    FROM `visavis`.`architect`
    WHERE `visavis`.`architect`.`user` = NEW.`to`;
ELSE
    SET archid = 0;
END IF;

Upvotes: 2

Views: 347

Answers (1)

Rhumborl
Rhumborl

Reputation: 16609

I may be off track here, but this query

SELECT IFNULL(`visavis`.`architect`.`id`,0)
INTO archid
FROM `visavis`.`architect`
WHERE `visavis`.`architect`.`user` = NEW.`to`;

will set archid to the value of visavis.architect.id in the result row of the select statement or 0 if that value in the result row is null - i.e. there is a null value for that id in visavis.architect.

So what happens if the select statement doesn't match any rows? Well there is no result row, so the IFNULL statement is never run and archid remains at it's initial value of null.

I would remove the ifnull and let archid be set to null. Then just chnage your client/architect check to test if archid is null:

SELECT `visavis`.`architect`.`id`
INTO archid
FROM `visavis`.`architect`
WHERE `visavis`.`architect`.`user` = NEW.`to`;

IF NEW.status = 1
THEN
    IF ISNULL(archid) THEN -- if the receiver is client
...

Upvotes: 1

Related Questions