简体   繁体   中英

Insert a row that doesn't exist via a procedure

I am trying to select the id of a country from the countries table. If the country doesn't exist, I want to insert that country then get the id of that country. I want to do this a stored function/procedure.

CREATE TABLE countries (
    id                      int
                            PRIMARY KEY
                            AUTO_INCREMENT,

    country                 char(2)
                            NOT NULL,

    CONSTRAINT uq_countries_country UNIQUE (country)
);

My attempt at the procedure, but I am clueless as to where I am going wrong. Essentially I am giving a 2 character string, and I should get an id back from the procedure.

DELIMITER $$

CREATE PROCEDURE GetIPCountryId(
    IN  _country char(2),  
    OUT _id int
)
BEGIN
DECLARE _country_id int;


    SELECT
        id INTO _country_id
    FROM countries
    WHERE lower(country) = lower(_country)
    
    IF _country_id IS NULL THEN
        INSERT INTO countries (country) VALUES (lower(_country));
    END IF;

    SELECT
        id
    FROM countries
    WHERE lower(country) = lower(_country)


END$$

DELIMITER ;

Use into in last select.

SELECT
        id into _id
    FROM countries
    WHERE lower(country) = lower(_country)

Also, you can use insert into table select... where not exists.. instead of count and insert logic.

As far as i know, you can use these 2 methods

  1. use NOT EXISTS
INSERT INTO countries(country)
SELECT * FROM countries c 
WHERE NOT EXISTS 
(SELECT 1 FROM countries c1 WHERE c1.id = c.id)
  1. use ON DUPLICATE KEY UPDATE

     SELECT * FROM countries UNION INSERT INTO countries(country) SELECT country FROM countries ON DUPLICATE KEY UPDATE id = values(id)

BOTH of these queries will insert the country into the table if the country doesnt appear on table countries

You would often use LAST_INSERT_ID() for the purpose of getting the id from an INSERT :

CREATE PROCEDURE GetIPCountryId (
    IN  _country char(2),  
    OUT _id int
)
BEGIN
    DECLARE _country_id int;

    SELECT
        id INTO _country_id
    FROM countries
    WHERE lower(country) = lower(_country);
    
    IF _country_id IS NULL THEN
        INSERT INTO countries (country) VALUES (lower(_country));
        SET _id = LAST_INSERT_ID();
    END IF;
END$$

Your code fails because the last SELECT has no INTO .

However, this is still not the recommended way to handle this logic, because it is subject to race conditions (two users attempting the same insert at the same time). It is better to attempt the insert and then just fetch the value:

CREATE PROCEDURE GetIPCountryId (
    IN  _country char(2),  
    OUT _id int
)
BEGIN
    INSERT INTO countries (country)
        VALUES (lower(_country))
        ON DUPLICATE KEY UPDATE country = VALUES(country);

    SELECT c.id INTO _id
    FROM countries c
    WHERE c.country = lower(_country);
END$$

For this to work, you need a unique index or constraint on countries(country) .

Note: Newer versions of MySQL would use an ON CONFLICT clause instead of ON DUPLICATE KEY .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM