Как избавиться от ошибки multiple rows in singleton select?

Материал из DRKB

Как избавиться от ошибки multiple rows in singleton select?[править | править код]

Очевидно что данная ошибка происходит в вашем триггере или хранимой процедуре. Обычный SELECT внутри триггера или процедуры должен возвращать одну строку (row), т.к. при двух и более строках IB не знает куда поместить значения полей этих строк. Если ваш SELECT возвращает несколько записей, то нужно пользоваться конструкцией FOR SELECT ... INTO ... DO ... которая производит обработку возвращаемого набора записей в цикле.

Если-же вы уверены, что ваш SELECT должен вернуть только одну запись, а ошибка все-таки возникает, то давайте рассмотрим следующую ситуацию:

  • существуют таблицы ORDERS (заказы) и CLIENTS (клиенты).
  • обе эти таблицы имеют поле связи CLIENT_ID INTEGER.

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


SELECT CLIENT_ID, CLIENT_NAME
FROM CLIENTS
WHERE CLIENT_ID = ?


где ? - либо значение либо переменная.

Теперь представим себе, что этот запрос должен выполняться в триггере при вставке записи в таблицу ORDERS

CREATE TRIGGER TI_ORDERS FOR ORDERS
ACTIVE AFTER INSERT POSITION 0
AS
DECLARE VARIABLE CID INTEGER;
DECLARE VARIABLE CNAME CHAR(30);
BEGIN
    SELECT C.CLIENT_ID, C.CLIENT_NAME
    FROM CLIENTS C
    WHERE C.CLIENT_ID = CLIENT_ID
    INTO :CID, :CNAME;
    ...


Итак, поскольку в запросе использован псевдоним C (FROM CLIENTS C), то якобы существует гарантия что в предложении WHERE будут сравниваться поле C.CLIENT_ID из таблицы CLIENTS и поле CLIENT_ID из таблицы ORDERS (в триггере доступны имена полей собственной таблицы). На самом деле даже использование псевдонимов не дает гарантии что переменные будут разичаться, и получается что в предложении WHERE сравнивается само с собой поле таблицы CLIENTS.CLIENT_ID, и в запросе возвращается ВСЯ таблица CLIENTS.

Вот почему возникает вышеупомянутое сообщение об ошибке.

Избавиться от него можно несколькими путями:

  • Использовать разные имена полей для связи между CLIENTS и ORDERS. например OCLIENT_ID и CCLIENT_ID.
  • Использовать уточнитель new.CLIENT_ID, несмотря на то что в документации указано что для триггеров последействия (AFTER) он не имеет смысла.
SELECT C.CLIENT_ID, C.CLIENT_NAME
FROM CLIENTS C
WHERE C.CLIENT_ID = new.CLIENT_ID
-- ...


Перед запросом поместить CLIENT_ID в локальную переменную, и в запросе использовать сравнение не с полем, а с этой локальной переменной.

CID=CLIENT_ID;
SELECT C.CLIENT_ID, C.CLIENT_NAME
FROM CLIENTS C
WHERE C.CLIENT_ID = :CID
-- ...


Author: Дмитрий Кузьменко
Source: http://www.ibase.ru/
ID: 02887