Как сравнить значение, которое пользователь вводит, с данными в базе данных при случайном использовании plpgsql - PullRequest
1 голос
/ 30 марта 2019

Я пользуюсь базой данных Postgres и хочу генерировать случайное имя student_no всякий раз, когда пользователь вставляет какие-либо записи в базу данных. Команда выглядит следующим образом:

NEW.booking_no: = array_to_string (ARRAY (SELECT chr ((48 + round (random () * 9)) :: integer) FROM generate_series (1,10)), '');

Моя структура таблицы выглядит следующим образом:

Name Table : Student
(id Pk, 
 firstName varchar,
 lastName varchar, 
 student_no varchar, 
 location varchar, 
 age integer
)

Для удобства я реализую функции записи и триггеры с помощью plpgsql следующим образом:

//Create function
CREATE OR REPLACE FUNCTION student_no()
RETURNS TRIGGER AS
$$
BEGIN
    NEW.student_no := array_to_string(ARRAY(SELECT chr((48 + round(random() * 9)) :: integer) FROM generate_series(1,10)), '');
RETURN NEW;
END
$$ LANGUAGE plpgsql;

//create trigger

CREATE TRIGGER student_no
BEFORE INSERT
ON public."Student"
FOR EACH ROW
EXECUTE PROCEDURE student_no();

//Data User Insert to database
INSERT INTO public."Student"(
    student_id, "firstName", "lastName", location, age)
    VALUES (2231, 'Join', 'David', 'UK',26);    

Когда я вставляю, оно успешно создается и случайно в моем БД. Это здорово. Но я хочу сравнить, если ученик в одном месте, student_no он не должен дублировать, если другой он может дублировать. Если одно и то же местоположение и функция случайно совпадают с student_no, он должен создать другое случайное имя student_no. Я пишу код выглядит так:

CREATE OR REPLACE FUNCTION student_no()
RETURNS TRIGGER AS
$$
DECLARE
canIUseIt boolean := false;
randomNumber BIGINT;
BEGIN
    //loop when random success
    WHILE ( not ( canIUseIt ) ) LOOP
    randomNumber  := array_to_string(ARRAY(SELECT chr((48 + round(random() * 9)) :: integer) FROM generate_series(1,10)), '');
        //Get data from user input and compare with database. I not sure it true. If it wrong, please help me fix it.
        //New.location : data from user insert. I think
       // location  data from database
    SELECT location FROM Student WHERE location = NEW.location;
            IF NOT FOUND THEN
                canIUseIt = true;
            END IF;                                   
    END LOOP;
$$ LANGUAGE plpgsql;
 //If not duplicate, insert random number to database. And break loop.
 IF ( canIUseIt ) THEN
        RETURN NEW.booking_no: = array_to_string (ARRAY (SELECT chr ((48 + round (random () * 9)) :: integer) FROM generate_series (1,10)), '');
END IF;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER student_no
BEFORE INSERT
ON public."Student"
FOR EACH ROW
EXECUTE PROCEDURE student_no();

Но когда я выполняю команду Вставить

  INSERT INTO public."Student"(
        student_id, "firstName", "lastName", location, age)
        VALUES (2231, 'A', 'Van Nguyen', 'DN',26);  

Это не работает. PostgresSQL выдает мне исключение:

ЗАПРОС: ВЫБЕРИТЕ местоположение ОТ студента, ГДЕ местоположение = NEW.location КОНТЕКСТ: PL / pgSQL функция student_no () строка 8 в SQL-выражении SQL состояние: 42P01.

У меня есть вопрос:

  • Как получить данные от входного пользователя и сравнить с данными из база данных. Если не то же самое, извините команду random. Это же значение от базы данных, он должен вернуться и создать новый случайный. Пожалуйста, помогите мне потому что я работаю в 1 день и не справиться с проблемой.

1 Ответ

1 голос
/ 31 марта 2019

Существует более одной проблемы

  1. SELECT location FROM Student WHERE location = NEW.location; - PLpgSQL не позволяет выполнить запрос без какой-либо цели для результата.Для SELECT требуется условие INTO.Если вам не нужно сохранять результат, используйте оператор PERFORM или лучше (в данном случае), используйте предикат EXISTS Вместо этого:

    -- bad
    SELECT location FROM student WHERE location = NEW.location;
    IF NOT FOUND THEN
       can_i_use_it := true;
    END IF; 
    
    -- can works
    PERFORM location FROM student WHERE location = NEW.location;
    IF NOT FOUND THEN
      can_i_use_it := true;
    END IF;
    
    -- better
    IF NOT EXISTS(SELECT * FROM student WHERE ...) THEN
      can_i_use_it := true;
    END IF;              
    
    -- good
    can_i_use_it := EXISTS(SELECT * FROM Student WHERE location = NEW.location)
    
  2. , но этоТехники недостаточно, чтобы защитить вас от состояния гонки.В любое время база данных может использоваться большим количеством пользователей.Более новые вы видите текущие последние данные.Каждый раз, когда вы видите только некоторый снимок - и без блокировки или индекса UNIQUE запрос, такой как IF EXISTS(some query) THEN, не является хорошей защитой от дублирующихся строк.Невозможно сделать это хорошо без более агрессивной блокировки от триггеров.Ваш пример - хороший пример того, как не использовать триггеры .Используйте эту логику в явно вызванной функции (например, в plpgsql тоже), но не в триггере.Для этого случая это плохое место.

  3. PLpgSQL - это язык без учета регистра - не используйте верблюжьи обозначения.SQL является нечувствительным к регистру языком - не используйте верблюжьи нотации и не используйте чувствительные к регистру идентификаторы SQL, такие как «lastName» - это самый короткий путь в психиатрическую больницу.

...