Filling a dataset from a sql select string in Delphi - sql

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!

Related

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.

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

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.

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;

Variable structure for database results

A lot of times when we query the database, we just need one column with varchar.
So I've made a nice function for querying the database and putting the results in a stringlist:
function Getdatatostringlist(sqlcomponent, sqlquery: string): TStringlist;
What I'm looking for now is basically the same function but for results with multiple columns where you don't know in advance what type the data is, be it varchar, int, datetime.
What kind of datastructure would be good to use here.
The reason I want this is that I try not to work on open datasets. I like much more to fetch all results into a temporary structure, close the dataset and work on the results.
After Kobiks reply about using in Memory datasets I came up with the following, it's fast put together to test the concept:
procedure TForm1.Button2Click(Sender: TObject);
var
MyDataSet : TAdoDataSet;
begin
MyDataSet := GetDataToDataSet('SELECT naam FROM user WHERE userid = 1', ADOConnection1);
try
Form1.Caption := MyDataSet.FieldByName('naam').AsString;
finally
MyDataSet.free;
end;
end;
function TForm1.GetDataToDataSet(sSql: string; AdoConnection: TADOConnection): TAdoDataSet;
begin
Result := TAdoDataSet.Create(nil);
Result.LockType := ltBatchOptimistic;
Result.Connection := AdoConnection;
Result.CommandText := sSql;
Result.Open;
Result.Connection := nil;
end;
I think this is something to build on.
You should use any disconnected in-memory TDataSet descendant, such as TClientDataSet.
Do not attempt to re-invent the wheel by storing a record-set in some new "Variant" structure. A TClientDataSet already contains all features you need to manipulate a "temporary" data structure.
Here is how you create a TClientDataSet structure:
cds.FieldDefs.Add('id', ftInteger);
cds.FieldDefs.Add('name', ftString, 100);
// ...
// create it
cds.CreateDataSet;
// add some data records
cds.AppendRecord([1, 'Foo']);
cds.AppendRecord([2, 'Bar']);
Many TDataSets has an ability to be used as an in-memory (client) datasets depending on the provider and LockType, for example a TADODataSet with LockType=ltBatchOptimistic could fetch results-set from the server, and then remain disconnected.
For exchanging Data with Excel this structure is usefull, might be useful for other purposes.
Function GetDatasetasDynArray(Ads: TDataset; WithHeader: Boolean = true): Variant;
// 20130118 by Thomas Wassermann
var
i, x, y: Integer;
Fields: Array of Integer;
begin
x := 0;
y := Ads.RecordCount;
if WithHeader then
inc(y);
SetLength(Fields, Ads.FieldCount);
for i := 0 to Ads.FieldCount - 1 do
if Ads.Fields[i].Visible then
begin
Fields[x] := i;
inc(x);
end;
SetLength(Fields, x);
Result := VarArrayCreate([0, y - 1 , 0, length(Fields) - 1], VarVariant);
y := 0;
if WithHeader then
begin
for i := Low(Fields) to High(Fields) do
begin
Result[y, i] := Ads.Fields[Fields[i]].DisplayLabel;
end;
inc(y);
end;
try
Ads.DisableControls;
Ads.First;
while not Ads.EOF do
begin
for i := Low(Fields) to High(Fields) do
begin
Result[y, i] := Ads.Fields[Fields[i]].Value;
end;
Ads.Next;
inc(y);
end;
finally
Ads.EnableControls;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
DynArray:Variant;
begin
DynArray := GetDatasetasDynArray(Adodataset1,true);
//DynArray[0,x] Header or First row
//DynArray[1,x] First row or SecondRow
Excel.Range.Value := DynArray;
end;
Why don't you like working with open datasets? They usually do not block the server. Copying the data from the dataset to whatever you want is extra overhead which is most likely not necessary.
A dataset provides exactly the functionality you want: A matrix with variable columns and rows.
EDIT: However, if you have iterate through the dataset often, you should consider creating a class holding the relevant information and then copy the data into a generic list, dictionary, tree or whatever you need as fast lookup structure.
Of course you could think of building something smart which can be as flexible as a dataset but: The more general things get, the poorer the performance (usually).

How to union data from different databases?

I came across the necessity to union two selects from different databases, namely paradox (in bde) and ms sql server.
Currently bde (through TQuery) is used only in this part of the programm (i.e. dbgrid). Now I need to add some data stored in ms sql server database (with which I usually use TADOQuery) to the same grid.
Although queries are executed over completely different tables, the result set of columns is named and typed similarly (I mean, if I had these tables, say, in ms sql server database, I could use a trivial union for that).
Is there any way to unite recordsets selected from these in delphi7 that I could use the result as a data source for a dbgrid?
You could use a clientdataset, created by the definitions of eg. the dataset of your SQL-Server dataset and add data of your paradox dataset. TFieldDefArray can be empty in your case.
type
TMyFieldDef = Record
Name: String;
Size: Integer;
DataType: TFieldType;
end;
TFieldDefArray = array of TMyFieldDef;
function GetClientDSForDS(ADataSet: TDataSet; AFieldDefArray: TFieldDefArray; AClientDataSet: TClientDataSet = nil; WithRecords: Boolean = true)
: TClientDataSet;
var
i: Integer;
Function NoAutoInc(ft: TFieldType): TFieldType;
begin
if ft = ftAutoInc then
Result := ftInteger
else
Result := ft;
end;
begin
if Assigned(AClientDataSet) then
Result := AClientDataSet
else
Result := TClientDataSet.Create(nil);
Result.Close;
Result.FieldDefs.Clear;
for i := 0 to ADataSet.FieldCount - 1 do
begin
Result.FieldDefs.Add(ADataSet.Fields[i].FieldName, NoAutoInc(ADataSet.Fields[i].DataType), ADataSet.Fields[i].Size);
end;
for i := 0 to High(AFieldDefArray) do
Result.FieldDefs.Add(AFieldDefArray[i].Name, AFieldDefArray[i].DataType, AFieldDefArray[i].Size);
Result.CreateDataSet;
for i := 0 to ADataSet.FieldCount - 1 do
begin
Result.FieldByName(ADataSet.Fields[i].FieldName).DisplayLabel := ADataSet.Fields[i].DisplayLabel;
Result.FieldByName(ADataSet.Fields[i].FieldName).Visible := ADataSet.Fields[i].Visible;
end;
if WithRecords then
begin
ADataSet.First;
while not ADataSet.Eof do
begin
Result.Append;
for i := 0 to ADataSet.FieldCount - 1 do
begin
Result.FieldByName(ADataSet.Fields[i].FieldName).Assign(ADataSet.Fields[i]);
end;
Result.Post;
ADataSet.Next;
end;
end;
end;
another attempt might be creating a linked server for paradox, I didn't try that...
http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/SQL_Server_2008/Q_24067488.html
No problem with AnyDAC LocalSQL. You can execute SQL's with any DataSet, not only select SQL, insert, update, delete SQL too.
You can use the Built-in TClientDataSet functionality to union the data by appending the data from the second dataset to the data of the first one.
There are different ways to do it, my preferred one because the simple code would be to add two DataSetProviders and link it to each of your DataSets, for example
dspBDE.DataSet := MyTQuery;
dspADO.DataSet := MyAdoQuery;
Then, to open your DataSets, you can just do:
MyClientDataSet.Data := dspBDE.Data;
MyClientDataSet.AppendData(dspADO.Data, True);
To make this to work, both DataSets have to match the field number and data types. Since your structures are similar, you can work by typecasting in your SQL if this does not happen automatically.
BDE supports (or supported) heterogeneous queries
This allows queries to span more than one dataset, but with a limited SQL syntax.
IIRC I used a few over a decade ago for some quick'n'dirty datamerges, but I can't remember the specifics - I haven't touched BDE for years.
Several years ago (Delphi 7) i used TxQuery, but i'dont know if it is still in development
I have found this link