В Firebird, при использовании FireDAC в Delphi, часто возникает вопрос, почему свойство RowsAffected у TFDQuery возвращает 1, даже если запрос INSERT...SELECT не вставил ни одной записи. Рассмотрим эту проблему и предложим решения, учитывая совместимость с Firebird 3.0.
Проблема:
Код, подобный приведенному ниже, может ввести в заблуждение:
uses
FireDAC.Comp.Client, FireDAC.Phys.FB, FireDAC.Stan.Intf, FireDAC.Stan.Option;
procedure TForm1.Button1Click(Sender: TObject);
var
FDQuery: TFDQuery;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1; // Укажите ваше соединение FireDAC
FDQuery.SQL.Text := 'INSERT INTO t(c1) SELECT c2 FROM t2 WHERE c2 = 0';
FDQuery.ExecSQL;
ShowMessage('RowsAffected: ' + IntToStr(FDQuery.RowsAffected));
finally
FDQuery.Free;
end;
end;
В этом примере, даже если в таблице t2 нет записей, удовлетворяющих условию c2 = 0, RowsAffected все равно вернет 1.
Причина:
FireDAC, вероятно, оптимизирует обработку запросов INSERT...SELECT. Вместо того, чтобы возвращать фактическое количество вставленных строк, он просто сообщает об успешном выполнении команды INSERT. Это может быть связано с тем, что FireDAC изначально проектировался с учетом INSERT ... VALUES ..., который всегда вставляет одну строку (или выдает ошибку).
Решение 1: Раздельный SELECT и INSERT (Совместимо с Firebird 3.0)
Самый надежный и совместимый способ получить точное количество вставленных строк - это выполнить SELECT COUNT(*) перед INSERT и использовать результат для определения, были ли вставлены строки.
procedure TForm1.Button1Click(Sender: TObject);
var
FDQuery: TFDQuery;
RowCount: Integer;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
// Получаем количество строк, которые будут вставлены
FDQuery.SQL.Text := 'SELECT COUNT(*) FROM t2 WHERE c2 = 0';
FDQuery.Open;
RowCount := FDQuery.Fields[0].AsInteger;
FDQuery.Close;
// Выполняем вставку только если есть строки для вставки
if RowCount > 0 then
begin
FDQuery.SQL.Text := 'INSERT INTO t(c1) SELECT c2 FROM t2 WHERE c2 = 0';
FDQuery.ExecSQL;
ShowMessage('RowsAffected: ' + IntToStr(FDQuery.RowsAffected) + ', Rows actually inserted: ' + IntToStr(RowCount));
end
else
begin
ShowMessage('No rows to insert.');
end;
finally
FDQuery.Free;
end;
end;
Преимущества:
Совместимость с Firebird 3.0 и более ранними версиями.
Точное количество вставленных строк.
Недостатки:
Требуется два запроса к базе данных.
Решение 2: Хранимая процедура с циклом и счетчиком (Совместимо с Firebird 3.0)
Можно создать хранимую процедуру, которая выполняет SELECT с использованием FOR ... SELECT ... DO и вставляет записи в цикле, ведя подсчет вставленных строк.
-- Хранимая процедура Firebird
CREATE PROCEDURE InsertAndCount
AS
DECLARE VARIABLE RowCount INTEGER = 0;
DECLARE VARIABLE C2_Value INTEGER;
BEGIN
FOR SELECT c2 FROM t2 WHERE c2 = 0 INTO :C2_Value DO
BEGIN
INSERT INTO t (c1) VALUES (:C2_Value);
RowCount = RowCount + 1;
END
SUSPEND;
END
В Delphi:
procedure TForm1.Button1Click(Sender: TObject);
var
FDQuery: TFDQuery;
RowCount: Integer;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := 'EXECUTE PROCEDURE InsertAndCount';
FDQuery.Open;
// Получаем RowCount из первой записи результирующего набора
if not FDQuery.IsEmpty then
RowCount := FDQuery.Fields[0].AsInteger
else
RowCount := 0;
FDQuery.Close;
ShowMessage('Rows inserted: ' + IntToStr(RowCount));
finally
FDQuery.Free;
end;
end;
Преимущества:
Выполнение логики в базе данных.
Точное количество вставленных строк.
Недостатки:
Более сложная реализация.
Необходимость создания хранимой процедуры.
Решение 3: Использование RETURNING (Не совместимо с Firebird 3.0, работает с Firebird 4.0+ для единичных вставок и с Firebird 5+ для множественных)
Начиная с Firebird 4.0, можно использовать предложение RETURNING для получения значений вставленных строк. Однако, в Firebird 4.0 и старше, RETURNING работает только для единичных вставок. Firebird 5+ позволяет использовать RETURNING для множественных вставок.
Важно: Это решение НЕ подходит для Firebird 3.0.
// Только для Firebird 5+
procedure TForm1.Button1Click(Sender: TObject);
var
FDQuery: TFDQuery;
RowCount: Integer;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := 'INSERT INTO t(c1) SELECT c2 FROM t2 WHERE c2 = 0 RETURNING 1';
FDQuery.Open; // Используем Open вместо ExecSQL
RowCount := FDQuery.RecordCount;
FDQuery.Close;
ShowMessage('Rows inserted: ' + IntToStr(RowCount));
finally
FDQuery.Free;
end;
end;
Преимущества:
Относительно простое решение (для Firebird 5+).
Недостатки:
Не совместимо с Firebird 3.0.
Требует использования Open вместо ExecSQL.
RETURNING в Firebird 4.0 работает только для единичных вставок.
Вывод:
Для Firebird 3.0 рекомендуется использовать решение 1 (раздельный SELECT и INSERT) или решение 2 (хранимая процедура с циклом и счетчиком). Решение 3 с RETURNING не подходит из-за несовместимости с Firebird 3.0. Выбор между решениями 1 и 2 зависит от конкретных требований проекта и предпочтений разработчика. Если важна простота и читаемость кода, решение 1 предпочтительнее. Если требуется оптимизация производительности и перенос логики в базу данных, решение 2 может быть более подходящим.
В Firebird при использовании FireDAC свойство RowsAffected у TFDQuery может возвращать 1 при запросе INSERT...SELECT, даже если записи не были вставлены, что требует использования альтернативных методов для точного подсчета вставленных строк, таких как р
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.