cannot perform this operation on a closed dataset and incorrect data entered into table - sql

I have been getting this error and I am struggling to find what I have done wrong. What doesn't make sense is that the result is still posted to the database with all fields being correct except the name field. In the name field it enters 'dmInfo' for some reason.
with dmQuery_u.dmInfo do
begin
dsInfo.Edit;
qryData.SQL.Clear;
qryData.SQL.Add('SELECT * FROM eventinfo ORDER BY eventnumber');
qryData.Open;
qryData.Last;
autonum := qryData['eventnumber'] + 1;
qryData.SQL.Clear;
qryData.SQL.Add(
'INSERT INTO eventinfo (eventnumber, bandname, venue, dateofevent, ticketcost, openingact, amountbooked)');
qryData.SQL.Add(
'VALUES (:eventnumber, :bandname, :venue, :dateofevent, :ticketcost, :openingact, :amountbooked)');
qryData.Parameters.ParamByName('eventnumber').Value := autonum;
qryData.Parameters.ParamByName('bandname').Value := name;
qryData.Parameters.ParamByName('venue').Value := venue;
qryData.Parameters.ParamByName('dateofevent').Value := date;
qryData.Parameters.ParamByName('ticketcost').Value := ticketcost;
qryData.Parameters.ParamByName('openingact').Value := openingact;
qryData.Parameters.ParamByName('amountbooked').Value := amountbooked;
qryData.ExecSQL;
qryData.SQL.Clear;
qryData.SQL.Add('SELECT * FROM eventinfo');
qryData.Last;
qryData.Open;

Your error is being caused here :
qryData.ExecSQL;
qryData.SQL.Clear;
qryData.SQL.Add('SELECT * FROM eventinfo');
qryData.Last; { !! should be after ---v }
qryData.Open; { should be first ---^ }
Calling .ExecSQL executes a non-query (ie : returns no recordset) command. Since there is no valid dataset here, calling .Last will throw an error. You have to call .Open first.
For the second issue of your name field populating with dmInfo, you're being caught out by use of the dangerous with statement here:
with dmQuery_u.dmInfo do
{...}
qryData.Parameters.ParamByName('bandname').Value := name;
Here the with statement is hiding the name variable you want to use and is instead interpreting it as dmQuery_u.dmInfo.Name. Either get rid of the with statement and use explicit qualifiers for everything or change the name of your name variable.

Related

Filling a dataset from a sql select string in Delphi

I am currently VERY new to delphi. It is older code, but I need to know if why my code is failing is what I think it is. all the data I need to read is in the tables I'm reading from, I know that much. but I'm getting the following error back on my SOAP call:
There is no row at position 0.
This tells me that although the data is in the DB, I know I'm not able to read it out. am I using the correct way to read sql data from a db table, in delphi, using the following code:
FXMLDocument := XMLDocument.Create;
FXMLDocument.LoadXML(strXML);
// Get WebService URL - needed for saving COG Assessments
strUrl := GetURL;
// Fill Applicant and Configuration DataSets
FApplicant := TApplicant.Create;
FConfiguration := TConfiguration.Create;
FApplicant.Username := FXMLDocument.GetElementsByTagName('appid').item(0).FirstChild.Value;
strFormID := FXMLDocument.GetElementsByTagName('formid').item(0).FirstChild.Value;
SqlConDB.Close;
selApp.CommandText := 'SELECT AppID, FirstName, LastName, Age, Gender, UserPassword, ProgToRun, ReportLang, CompanyID FROM Users WHERE AppID = '''+ FApplicant.Username +'''';
selConfig.CommandText := 'SELECT * FROM Preferences';
SqlConDB.Open;
daApp.Fill(dsApp, 'Applicant');
daConfig.Fill(dsConfig, 'Configuration');
SqlConDB.Close;
FConfiguration := CASConfigRoutines.FillConfigurationFromDB(dsConfig);
// Get Program to Run from Data
FApplicant.ProgToRun := dsApp.Tables[0].Rows[0].Item['ProgToRun'].ToString;
FApplicant.Gender := dsApp.Tables[0].Rows[0].Item['Gender'].ToString;
FApplicant.ReportLang := dsApp.Tables[0].Rows[0].Item['ReportLang'].ToString;
// Get Company ID from Data for FNB check
FApplicant.Company := dsApp.Tables[0].Rows[0].Item['CompanyID'].ToString;
Should these two lines:
daApp.Fill(dsApp, 'Applicant');
daConfig.Fill(dsConfig, 'Configuration');
Not rather be:
daApp.Fill(selApp, 'Applicant');
daConfig.Fill(selConfig, 'Configuration');
as it is selApp that is the "select" statement? I don't know enough about Delphi to say if it is right or wrong, but can someone maybe assist in telling me why I'm getting a record count of 0 if I know for a fact, my data is in the DB?
Thanks!

How to execute function with parameter in PostgreSQL using anonymous code block in FireDAC?

My database (PostgreSQL 11) has a function that must be called to execute an action. The return is not important, as long as there's no error. This function has arguments that must be passed as parameters.
I cannot directly use TFDConnection.ExecSQL because I use parameters of type array that is not supported by the method (to my knowledge). So, I use TFDQuery.ExecSQL like this:
msql1: = 'DO $$ BEGIN PERFORM DoIt (:id,:items); END $$; ';
{... create FDQuery (fq), set connection, set SQL.Text to msql1}
fq.Params.ParamByName ('id'). AsInteger: = 1;
{$ REGION 'items'}
fq.ParamByName ('items'). DataType: = ftArray;
fq.ParamByName ('items'). ArrayType: = atTable; // Must be atTable, not atArray
if length (items)> 0 then
begin
fq.ParamByName ('items'). ArraySize: = length (items);
for it: = 0 to length (items) -1 do
fq.ParamByName ('items'). AsIntegers [it]: = items [it];
end;
fq.ExecSQL;
{$ ENDREGION}
When executing the above code, the error above message raises
"Parameter 'id' not found".
After some research that suggested using fq.Params.ParamByName I was also unsuccessful.
However, if you change the way the function is called to
select DoIt (:id,:items); and obviously replacing the execution with fq.Open works perfectly.
Is it possible to execute a PL / pgSQL block that contains parameters in the function called by this block using TFDConnection / TFDQuery?
PS: I'm using Delphi Rio 10.3.3
When you assign a value to TFDQuery.SQL, FireDAC does some pre-processing of the SQL based on various options. ResourceOptions.CreateParams option controls whether parameters should be processed. This is enabled by default.
The preprocessor recognizes string literals in your SQL and doesn't try to look for the parameters in them. You used dollar quoted string constant and that's why FireDAC doesn't recognize parameters in it. Even if you add parameter manually I think that FireDAC would not bind the value.
With that said, the proper way to execute stored procedures/function is to use TFDStoredProc. You just assign StoredProcName and call its Prepare method which will retrieve procedure's metadata (parameters) from database so you don't need to set ArrayType or DataType of the parameter.
In your code you set the DataType to ftArray which is wrong, because in case of array parameter it should be set to array's element type. Anyway, by setting fq.ParamByName ('items').AsIntegers you effectively set parameter's DataType to ftInteger. All you need to do is to set ArraySize
Here's what you should do instead:
procedure DoIt(Connection: TFDConnection; ID: Integer; const Items: TArray<Integer>);
var
StoredProc: TFDStoredProc;
ParamItems: TFDParam;
Index: Integer;
begin
StoredProc := TFDStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.StoredProcName := 'DoIt';
StoredProc.Prepare;
StoredProc.Params.ParamByName('id').AsInteger := ID;
if Length(Items) > 0 then
begin
ParamItems := StoredProc.Params.ParamByName('items');
ParamItems.ArraySize := Length(Items);
for Index := Low(Items) to High(Items) do
ParamItems.AsIntegers[Index] := Items[Index];
end;
StoredProc.ExecProc;
finally
StoredProc.Free;
end;
end;
Alternatively you can use ExecFunc to get the result of stored function.

I keep getting "Data type mismatch in criteria expression" error in delphi

Whenever this code runs the I get the above error. The code is supposed to insert a record into a table and then delete records from another table.
for I := iMax - K to iMax do
begin
Inc(a);
with dmMenu.qryMcDonalds do
begin
SQL.Text :=
'SELECT ID, ItemID, ItemPrice, ItemCategory FROM tblCheckout WHERE ID = ' + IntToStr(I);
Open;
sItemID := Fields[1].AsString;
rItemPrice := Fields[2].AsFloat;
sItemCategory := Fields[3].AsString;
ShowMessage(IntToStr(I));
// I get the error here
SQL.Text :=
'INSERT INTO tblOrderItems (OrderItemID, OrderID, ItemID, ItemCategory, ItemPrice) VALUES ("' + sOrderID + '_' + IntToStr(a) + '"' + ', "' + sOrderID + '", "' + sItemID + '", "' + sItemCategory + '", "' + FloatToStrF(rItemPrice, ffCurrency, 10, 2) + '")';
ExecSQL;
SQL.Text := 'DELETE FROM tblCheckout WHERE ID = ' + IntToStr(I);
ExecSQL;
end; // with SQL
end; // for I
edit: I think i the problem is in the 'INSERT INTO' part. All the columns are short text except the last one, ItemPrice is a currency. I am also using Access
Please start a new project with just the following items on the main form:
A TAdoConnection configured to connect to your database;
A TAdoQuery configured to use the TAdoConnection
A TDataSource and TDBGrid configured to display the contents of the TAdoQuery.
Then, add the following code to the form
const
sSelect = 'select * from tblOrderItems';
sInsert = 'insert into tblOrderItems(OrderItemID, OrderID, ItemID, ItemCategory, ItemPrice) '#13#10
+ 'values(:OrderItemID, :OrderID, :ItemID, :ItemCategory, :ItemPrice)';
sOrderItemID = '0999';
sOrderID = '1999';
sItemID = '2999';
sItemCategory = 'Bolt';
fItemPrice = 5.99;
procedure TForm2.FormCreate(Sender: TObject);
begin
AdoQuery1.SQL.Text := sInsert;
AdoQuery1.Prepared := True;
AdoQuery1.Parameters.ParamByName('OrderItemID').Value:= sOrderItemID;
AdoQuery1.Parameters.ParamByName('OrderID').Value := sOrderID;
AdoQuery1.Parameters.ParamByName('ItemID').Value := sItemID;
AdoQuery1.Parameters.ParamByName('ItemCategory').Value := sItemCategory;
AdoQuery1.Parameters.ParamByName('ItemPrice').Value := fItemPrice;
AdoQuery1.ExecSQL;
AdoQuery1.SQL.Text := sSelect;
AdoQuery1.Open;
end;
Before compiling and running the program, review and change the values of the constants
so that they don't conflict with any rows already in your table.
The constant sInsert defines a so-called "parameterised SQL statement" Inside the Values
list, the items :OrderItemID, :OrderID, etc, are placeholders which the AdoQuery will turn into
parameters whose values you can supply at run-time, as in the block after AdoQuery1.Parameters.ParamByName(...
You should always construct SQL statements this way and not by concatenating strings
as your code attempts to do. It is far less error-prone, because it's easy to get into a
syntax muddle if you use DIY code and it also makes sure your queries are not prone
to Sql-Injection exploits, which they would be if you
give the user the opportunity to edit the SQL.
If you are using a FireDAC FDQuery, this has Params (an FD data-type) rather thn Parameters
but they work in pretty much the same way - see the Online Help.
In future, please be a bit more careful to ensure you include important details, especially things like the exact line where the error occurs - which you can check by single-stepping through your code using the debugger - and things like the exact db-access components you are using. Also try to get details right like whether a column is a "short text" type or an autonumber.
For a problem that needs to be debugged, like this one, you should also provide a complete, minimal, reproducible example, not just a snippet of code. Quite often, you will realise what the problem is while you are preparing the mre, so it helps you as well as us.
I bet the problem is in incompatible floating point format.
Use parameters to avoid it.

Using ParseSQL Command for ADO Parameters Cause Invalid Parameter DataType

I have some SQL Command that contains a parameter such:(Note that myID has "int" type in SQL)
vSqlString :='Select * From myTable Where myID= :paramID';
and Use ParseSQL Command to execute this command:
myADOQuery.Parameters.ParseSQL(vSqlString , True);
Now myADOQuery.Parameters.ParamByName('paramID').DataType is smallint Type and it can't accept negative integer values.
I can exactly show to compiler that my Parameter[0].DataType is ftIneteger and it works properly, but what is a good solution for this problem?
My question is asked by Mr. imanShadabi Here: Using TAdoQuery.ParseSql and has resolved by user1008646
Hope this will help.
If you want to create in runtime parameters, you can use something like this:
ADOQuery1.Close;
ADOQuery1.SQL.Text := vSqlString;
ADOQuery1.Parameters.Clear;
ADOQuery1.Parameters.CreateParameter('paramID', ftInteger, pdInput, 10, vIntegerValue);
ADOQuery1.Open;
Or you can concatenate values to the query.
For example:
//For Integer values:
vSqlString: = 'Select * From myTable Where myID =' + IntToStr (vIntegerValue);
//For String values:
vSqlString: = 'Select * From myTable Where myID =' + QuotedStr (vStringValue);
//For float values:
//Be careful with this, usually in a query, the comma is separator values,
//so make sure that the decimal separator is '.'
vDS := DecimalSeparator; //I keep the value it had
DecimalSeparator := '.';
try
ADOQuery1.close;
ADOQuery1.SQL.Text := 'Select * From myTable Where myID='+FloatToStr(vFloatValue);
ADOQuery1.Open;
finally
DecimalSeparator := vDS; //Restore the value that had
end;
The third option is to set the parameters at design time.
But I think this is not what you want.

SQL Query fails on empty result

I have a function that performs a query on a SQL database with an ADO connection, it is simply designed to provide a single result for a database entry that can only have one match for a SELECT type of query (i.e. get me the x value from ID 45, where there is and can only be one ID 45 entry).
The function works fine, until I hit a query that returns no results. The query just hangs, and the application cannot continue. Here is an example query string:
'SELECT Cost FROM MaterialCost ' +
'WHERE MaterialType = ''' + 'MS' +
''' AND Thickness = ''' + '0.250' + '''';
Again this exact string will work fine until I maybe query for something that I know before hand doesn't exist, which should return null or an empty string. Here is the function:
function SelectOneQuery(AQueryString : String; AADOConnectionString : String) : String;
var
ADOQuery: TADOQuery;
begin
//Create empty ADO Query, then connect to connection string
ADOQuery := TADOQuery.Create(nil);
ADOQuery.ConnectionString:=AADOConnectionString;
ADOQuery.SQL.Clear;
ADOQuery.SQL.Add(AQueryString);
ADOQuery.ExecSQL;
ADOQuery.Open;
//Set first query result and return first result
ADOQuery.First;
if(ADOQuery.Fields.Count > 0) then begin
result:=ADOQuery.Fields[0].Value;
end
else begin
result := '';
end;
end;
I added the fields count thing, but I'm not sure if that's helping at all. Basically, if there are no result, i want result := ''
You have a few problems in your code snippet:
The main problem is that you are checking FieldCount. FieldCount will always be nonzero because it contains the number of columns your query returns, regardless of the fact your query returned records or not. One option is to check RecordCount which represents the number of rows returned but a better option is to check EOF flag.
you are leaking ADOQuery. Always use try/finally blocks to create and cleanup objects.
ExecSQL is used for queries that don't return recordsets (like INSERT and DELETE),
use Open instead
No need to use First after Open
If you use the same query over and over you are better off using parameters, as a bonus your code will be more readable.
Example:
ADOQuery.SQL.Text := 'SELECT Cost FROM MaterialCost WHERE MaterialType = :MaterialType AND Thickness = :Thickness';
ADOQuery.Parameters.ParamByname('MaterialType').Value := 'MS';
ADOQuery.Parameters.ParamByname('Thickness').Value := 0.25;
Your function code should be something like this:
function SelectOneQuery(const AQueryString, AADOConnectionString: string): string;
var
ADOQuery: TADOQuery;
begin
Result := '';
ADOQuery := TADOQuery.Create(nil);
try
ADOQuery.ConnectionString := AADOConnectionString;
ADOQuery.SQL.Text := AQueryString;
ADOQuery.Open;
if not ADOQuery.EOF then
Result := ADOQuery.Fields[0].AsString;
finally
ADOQuery.Free;
end;
end;