I'm puzzled at the behavior of the TADOQuery, let's just call Q. When I use Q.Edit, populate some fields, then Post, it ends up actually inserting a new record.
The code is simple, and reading the ID from an object:
Q.SQL.Text := 'select * from SomeTable where ID = :id';
Q.Parameters.ParamValues['id'] := MyObject.ID;
Q.Open;
try
Q.Edit;
try
Q['SomeField']:= MyObject.SomeField;
finally
Q.Post;
end;
finally
Q.Close;
end;
To my surprise, rather than updating the intended record, it decided to insert a new record. Stepping through the code, immediately after Q.Edit, the query is actually in Insert mode.
What could I be doing wrong here?
I think the comments that this behaviour is documented are off the point. What the docs don't make clear (possibly because the point never occurred to the author) is that this behaviour is not guaranteed to be deterministic.
The innards of TDataSet.Edit have scarcely changed in decades. Here is the Seattle version:
procedure TDataSet.Edit;
begin
if not (State in [dsEdit, dsInsert]) then
if FRecordCount = 0 then Insert else
begin
CheckBrowseMode;
CheckCanModify;
DoBeforeEdit;
CheckParentState;
CheckOperation(InternalEdit, FOnEditError);
GetCalcFields(ActiveBuffer);
SetState(dsEdit);
DataEvent(deRecordChange, 0);
DoAfterEdit;
end;
end;
Now, notice that the if .. then .. is predicated on the value of FRecordCount, which at various points in the TDataSet code is forced to have a given assumed value (variously 1, 0 or something else) by code such as in SetBufferCount and that behaviour isn't documented at all. So on reflection I think Jerry was probably right to expect that attempting to edit a non-existent record should be treated as an error condition, and not be fudged around by silently calling Insert whether or not it is documented.
I'm posting both a question and an answer, because the cause of the problem was totally unexpected behavior, and surely someone else had the same bewildering thing happen.
This happens in the event that the dataset you're trying to edit doesn't have any records. Personally, I would think it should produce an exception that you can't edit when there's no records. But the TADOQuery decides to append a new record instead.
The very root cause of this issue was that the object where I supplied the ID actually had a value of 0, and therefore since there's no record in the database with ID 0, it returned nothing.
Related
This is an extension of an old question of mine where the answer wasn't quite what I was asking. What I'm doing is executing SQL Script on an MS SQL Server database. This script may or may not return any recordsets. The problem is that the way that ADO components work, at least to my knowledge, I can only explicitly request one or the other.
If I know a query will return data, I use TADOQuery.Open
If I know a query will not return data, I use TADOConnection.Execute
If I don't know whether query will return data or not... ???
How can I execute any query and read the response to determine whether it has any recordsets or not so I can read that recordset?
What I've tried:
Calling TADOQuery.Open, but raises exception if there's no recordset
Calling TADOQuery.ExecSql, but never returns any data
Calling TADOConnection.Execute, but never returns any data
Using Option 3 and reverting to Option 1 on exceptions, but this is double the work (script files over 38,000 lines) and kinda nasty.
Using TADOCommand.Execute, but keeps raising "Parameter object is improperly defined. Inconsistent or incomplete information was provided" on creating some stored procedures (which otherwise don't happen when using TADOConnection.Execute).
Calling TADOConnection.Execute overload which returns _Recordset, but then the TADOConnection.Errors returns empty (and I depend on this).
Just as some background, the purpose is to implement something like the SQL Query tool in the SQL Server Management Studio. It allows you to execute any SQL script, and if it returns any recordsets, it displays them, otherwise it just displays output messages. The tool I'm working on automatically breaks SQL Script at GO statements, and executes each individual block separately. Some of those blocks might return data, and others might not. It's already obvious that I cannot make this determination prior to execution, so I'm looking for a way to go ahead with the execution and observe the result. TADOConnection.Execute provides some useful information, including the Errors (or output messages).
As of now, the only option I have is to supply an option in the user interface to allow the user to choose which type of execution to use - but this is what I'm trying to eliminate.
EDIT
The TADOCommand.Execute method is the closest to what I want. However, it fails on some bits of script which otherwise work perfectly fine using TADOConnection.Execute. See #5 above in "What I've tried". I almost wrote that as my answer, until I found this error happens on almost everything.
EDIT
After posting my answer below, I then came to learn that the Errors property no longer returns anything when I use this other overload of Execute. See #6 above in "What I've tried".
Calling...
ADOConnection1.Execute('select * from something', cmdText, []);
...does not return anything in ADOConnection1.Errors, whereas...
var
R: Integer;
begin
ADOConnection1.Execute('select * from something', R);
...does return messages in ADOConnection1.Errors, which is what I need, but yet, doesn't return any recordsets.
EDIT: Not the right solution
I discovered my solution finally after digging even deeper. The answer is to use the TADOConnection.Execute() overload which supports returning the recordset:
function TADOConnection.Execute(const CommandText: WideString;
const CommandType: TCommandType = cmdText;
const ExecuteOptions: TExecuteOptions = []): _Recordset;
Then, just assign the resulting _Recordset to the Recordset property of supported dataset components.
var
RS: _Recordset;
begin
RS := ADOConnection1.Execute('select * from something', cmdText, []);
if Assigned(RS) then begin
ADODataset1.Recordset:= RS;
...
end;
end;
The downside is that you cannot use the other overload which supports returning the RowsAffected. Also, nothing is returned in the Errors property of the TADOConnection when using this overload version of Execute, whereas the other one does. But that other doesn't return a recordset.
I have recently started to learn how to code in Delphi since moving from Python to do a project. I can't seem to find a fix to this issue. I have searched online for a couple of hours now and none of the 'fixes' seem to be working. I have tried using 'Query.ExecSQL', I have tried breaking the code up into segments, I have tried to assign edtSubject.Text to a variable and doing it that way, among a handful of other 'solutions' that haven't fixed the issue. I am really struggling with this and need a way to fix it that works quick, here is my code:
Query := TADOQuery.Create(Self);
Query.Connection := ADOConnection;
Query.SQL.Add('INSERT INTO tbl_RFI (Subject) VALUES (:Subject)');
Query.Parameters.ParamByName('Subject').Value := edtSubject.Text;
Query.ExecSQL;
I really hope that someone can help me with this,
Thanks.
Just add Query.ParamCheck := true before setting the SQl Text and it should be fine
Despite what others say I have had this same thing happen from time to time over the years in my production code.
Parameter 'ParameterName' not found
Always with ADO objects that were created in code. The best fix that I have found was here http://edn.embarcadero.com/article/20420
Basically just force the ADO object to reparse the parameters before you reference them.
EDIT: Thanks to Ken's feedback I have added a test first to check if the parameter exists before calling ParseSQL, thereby conserving the execution plan for the 99.9% of the time.
with TADODataSet.Create(nil) do
try
Connection := MyADOConnection;
CommandText := 'SELECT Foo ' +
'FROM FooBar ' +
'WHERE Bar = :Bar ';
if Parameters.FindParam('Bar') = nil then
Parameters.ParseSQL(CommandText, True); {<- THIS IS THE FIX }
Parameters.ParamByName('Bar').Value := 'value';
Open;
finally
Free;
end;
Since adding the ParseSQL I haven't had the problem.
Late reply I know, but I just went through the same situation, so in case this helps someone else. And it happens at random, "parameter not found" during in my case a tadocommand. Program errors out, restart doing exactly same sequence of events and works fine. Load up compiler, step through and works just fine. Set a breakpoint, and I can see the parameter not defined. But again, it's not every time, it's kind of random.
So it feels like some type of timing issue. I tried the ParseSQL command, and that actually makes it error out every time with a different error. I think the command text was getting cropped but did not investigate this. (it's a fairly long command)
Anyway, it seems I was able to fix this by adding in an application.processmessages after my tadocommand is created and sql text assigned.
I'm always looking to improve and applying best practices. I read quite a bit about refactoring in the last weeks. I have to work with a lot of awful code and I produced some not so nice stuff too but I'm trying to change that. Thats no problem for most languages but I'm pretty new to PL/SQL so I just copied the style of the already written code.
After reading some tutorials I realized that a lot of our code is pretty much more C style code using retval instead exceptions etc.
We have a lot of functions like open cursor, loop through it, validate the data, trim it or make some string manipulation and insert it into another table, update the status etc. I wonder what a best practice solution would look like on something like this. Atm most functions look like this:
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
retval := plausibilty_check(r_ABC);
IF (retval = STATUS_OK) THEN
retval := convert_ABC_TO_XYZ(r_ABC, r_XYZ);
END IF;
IF (retval = STATUS_OK) THEN
retval := insert_XYZ(r_XYZ);
END IF;
retval := update_ABC(r_ABC.PK_Id, retval);
END LOOP;
If i was using exceptions I guess I had to raise them inside the functions so I can handle them in the main function, if not everyone would have to crawl to every sub function to understand the program and where the updates happen etc. So I guess I would have to use another PL/SQL block inside the loop? Like:
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
BEGIN
plausibilty_check(r_ABC);
convert_ABC_TO_XYZ(r_ABC, r_XYZ);
insert_XYZ(r_XYZ);
update_ABC(r_ABC.PK_Id, STATUS_OK);
EXCEPTION
WHEN ERROR_CODE_XYZ THEN
update_ABC(r_ABC.PK_Id, ERROR_CODE_XYZ);
END
END LOOP;
I guess that function handles a pretty common problem but I still havn't found any tutorial covering something like this. Maybe someone more experienced with PL/SQL might gimme a hint what a best practice on a task like that would look like.
I like to use exceptions to jump out of a block of code if an exceptional event (e.g. an error) occurs.
The problem with the "retval" method of detecting error conditions is that it introduces a second layer of semantics on what a function is and for.
In principle, a function should be used to perform a calculation and return a result; in this sense, a function doesn't do anything, i.e. it makes no changes to any state - it merely returns a value.
If it cannot for some reason calculate that value, that would be an exceptional circumstance, so I'd want the function to raise an exception so that the calling program will not blindly continue on its merry way, thinking it got a valid value from the function.
On the other hand, a procedure is a method by which action is done - something is changed, something is validated, or something is sent. The normal expected path is that the procedure is executed, it does its thing, then it finishes. If an error occurs, I want it to raise an exception so that the calling program will not blindly continue thinking that the procedure has successfully done its thing.
Thus, the original intent of the fundamental difference between "procedures" and "functions" is preserved.
In languages like C, there are no procedures - everything is a function in a sense (even functions that return "void") - but in addition, there is no real concept of an "exception" - so these semantics don't apply. It's for this reason that the C style of returning an error/success flag don't translate well into languages like PL/SQL.
In your example, I'd consider doing it something like this:
BEGIN
LOOP
FETCH c_ABC INTO r_ABC;
EXIT WHEN c_ABC%NOTFOUND;
IF record_is_plausible(r_ABC) THEN
r_XYZ := convert_ABC_TO_XYZ(r_ABC);
insert_or_update_XYZ(r_XYZ);
ELSE
update_as_implausible(r_ABC);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
-- log the error or something, then:
RAISE;
END;
So where the semantics of the operation is doing some validation or something, and returning a result, I converted plausibilty_check into a function record_is_plausible that returns a boolean.
I'd pull the call to update_ABC out of the BEGIN block and make it common at the bottom of the loop, as in:
DECLARE
nFinal_status NUMBER;
BEGIN
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
nFinal_status := nSTATUS_OK;
BEGIN
plausibilty_check(r_ABC);
convert_ABC_TO_XYZ(r_ABC, r_XYZ);
insert_XYZ(r_XYZ);
EXCEPTION
WHEN excpERROR_CODE_XYZ THEN
nFinal_status := nERROR_CODE_XYZ;
END;
update_ABC(r_ABC.PK_Id, nFinal_status);
END LOOP;
END;
You might want to have each of the procedures throw its own exception so you could more easily identify where the issue(s) are coming from - or use different exceptions/error codes for each possible issue (for example, the plausibility check might raise different exceptions depending on what implausible condition it found). However, in my experience plausibility checks will often uncover multiple conditions (if the data's bad it's often really bad :-), so it might be nice to tabularize the errors and relate them to the base ABC data through a foreign key, thus allowing each individual error to be identified with just one pass through the data. Then the 'status' field on ABC becomes moot; you either have errors associated with the ABC row or you don't. If errors exist, do whatever is needed. If no errors, proceed with 'normal' processing.
Just a thought.
Share and enjoy.
I am trying to check the input length, to see if it's less than 7. It should show an error message, but the code below doesn't work. What's wrong with it?
CREATE OR REPLACE PROCEDURE prc_staffContact(IN_staffID IN CHAR, IN_staffContact IN VARCHAR) IS
v_staffName VARCHAR(50);
v_staffID CHAR(6);
v_staffContact VARCHAR(11);
BEGIN
SELECT s.staffName, s.staffID, s.staffContact
INTO v_staffName, v_staffID, v_staffContact
FROM staff s
WHERE staffID = IN_staffID;
IF (LENGTH(IN_staffContact) < 7 )
THEN
DBMS_OUTPUT.PUT_LINE('Error. Contact number at least 7 digits.');
ELSE
UPDATE staff
SET staffContact = IN_staffContact
WHERE staffID = IN_staffID;
DBMS_OUTPUT.PUT_LINE('=============================================================================');
DBMS_OUTPUT.PUT_LINE('The contact number of [ ' ||v_staffName || ' ] has been updated successfully.');
DBMS_OUTPUT.PUT_LINE('New contact number: [ ' ||v_staffContact || ' ].');
DBMS_OUTPUT.PUT_LINE('=============================================================================');
END IF;
END;
/
You said something that appears to be contradictory:
it shows [PL/SQL procedure successfully completed.] but doesn't checking even my input is less than 7 characters. and the data doesn't update also.
You described that you're doing as:
i save it under procedure1.sql and i start it in sql plus. that is my 1st call. after that i call the exec prc_staffContact('100001', '0000')
Together those suggest that when you say the data isn't updated, what you really mean is you don't get the contact number/new contact number message from the else branch, and I think you're assuming that means the update doesn't happen either, so it didn't execute either branch. But you must have gone into either the if or the else, by definition.
So if you didn't get either message, then you haven't done:
set serveroutput on
in SQL*Plus before calling exec. That setting is off by default, unless you have it turned on in your login.sql or glogin.sql, so you have to turn it on explicitly if you want to see dbms_output messages.
In this case, for the validation, (a) you probably want the select inside the elsef` too, partly because (b) if the passed values doesn't exist you'll get a no_data_found exception, and (c) you might want to consider throwing an exception if the length is less than 7 rather than (only) displaying a message. Someone else calling this might not have serverout on either, or might be using a different client that doesn't have that option.
You've also got v_staffID defined as char(6). Apart from wondering why that isn't a varchar2, the length you've given it means that if IN_staffID is 7 chars or more, the select into will get a 'character string buffer too small' error. I'd declare that as:
v_staffID staff.staffID%TYPE;
... to avoid issues like that, and the same for the other fields that relate to table columns.
And your 'success' message is showing the old contact number, not the new one. Not sure you need v_staffContact at all.
Look at your code carefully. May be your variable types not compatible or stored procedure parameters not compatible with other variables (or table columns, column's types). I tested your code in my database, all success. but may be error not data found in your select statement, or may be buffer too small error. Hope this help you. thanks
I'm using qt 4.8 and psql driver to connect to a postgres 9.1 database.
I'm doing a generic library to connect and insert into the database; almost all methods are ready but in the last one, I need to do a select from a table to insert values into another. When I try the select statement it behaves different. According to the code in turn but no one of the tests I made have resulted in a correct solution.
Here's my code:
struct enfriadores enf;
enf.horaE=time(&enf.horaE);
enf.horaS=time(&enf.horaS)+1900;
//base1.insertaEvento(db,enf);
QString consulta = "Select id_evento from eventos where extract(epoch from horae)=:hora_bus";
QDateTime hora_bus(QDateTime::fromTime_t(enf.horaE));
//qDebug()<< enf.horaE;
QSqlQuery query(db);
query.prepare(consulta);
query.bindValue(":hora_bus",hora_bus);
query.exec();
query.first();
while(query.next())
{
int valor = query.value(0).toInt();
qDebug() << valor << endl;
}
The base1.insertaEvento is a call from a class I did to insert data on the table where afterwards I'll need to extract the id. The
qDebug() << enf.horaE;
I put it to know if the time was in the right form before I attached it to the query, which by the way, was correct.
horaE is taken from a struct I have declaed in the previously mentioned class.
When I run the query as it is with the while(query.next()) it runs good but returns no results and if I delete the while loop but still maintain the query.next() compiler returns
QSqlQuery::value: not positioned on a valid record
0
I tried using the query.first() method and the query.setForwardOnly(true) but with same results.
Also if I try the value of hora_bus with qDebug() and replace it directly in the psql console I get a positive match so the problem is not in the way data is inserted or formatted, it's in the way the query is retrieved I believe but do not know how to resolve this
Any ideas?
Thanks
The SQL expression extract(epoch from horae) produces a number of seconds since 1/1/1970 so so that's what should be passed to the parameter :hora_bus.
The QDateTime::fromTime_t(enf.horaE) indicates that enf.horaE has this value, however instead of passing ot to the query, it's passing a QDateTime object whose text representation is probably going to be a string with year,month,etc... that can't be compared to a number of seconds.
So try this instead:
query.bindValue(":hora_bus",enf.horaE);
Also the code shouldn't ignore the boolean return values of prepare() and exec(). You don't want to try looping within results when the execution of the query has failed.
EDIT1:
indeed when passing a QDateTime set to today to a prepared query similar to yours, QSqlQuery::exec() returns false with an SQL error invalid input syntax for type double precision.
EDIT2: it appears QVariant doesn't support being initialized with a long so an explicit cast to a different supported type is necessary. I've chosen qlonglong for a safe larger value:
query.bindValue(":hora_bus",(qlonglong)enf.horaE);
Tested, it worked for me.
http://qt-project.org/doc/qt-4.8/qsqlquery.html#details
At the very end of the documentation it mentions the following:
Warning: You must load the SQL driver and open the connection before a QSqlQuery is created. Also, the connection must remain open while the query exists; otherwise, the behavior of QSqlQuery is undefined.
If the connection to the database is timing out, or one of the variables you are using goes out of scope, you might disconnect early and get undefined results.
You can also check the return values on most of your function calls to see if they were successful or not.
Hope that helps.