Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

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

01.01.2007

Очевидно что данная ошибка происходит в вашем триггере или хранимой процедуре. Обычный 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 
...

Borland Interbase / Firebird FAQ

Borland Interbase / Firebird Q&A, версия 2.02 от 31 мая 1999

последняя редакция от 17 ноября 1999 года.

Часто задаваемые вопросы и ответы по Borland Interbase / Firebird

Материал подготовлен в Демо-центре клиент-серверных технологий. (Epsylon Technologies)

Материал не является официальной информацией компании Borland.

E-mail mailto:delphi@demo.ru

www: http://www.ibase.ru/

Телефоны: 953-13-34

источники: Borland International, Борланд АО, релиз Interbase 4.0, 4.1, 4.2, 5.0, 5.1, 5.5, 5.6, различные источники на WWW-серверах, текущая переписка, московский семинар по Delphi и конференции, листсервер ESUNIX1, листсервер mers.com.

Cоставитель: Дмитрий Кузьменко