I am currently coding a Delphi 7 program that utilises SQL and an Access 2003 database.
The unit receives a 5 digit code from a previous unit, via a public variable (this is frmLogin.sCode). On form activation, the program will execute an SQL query to display the record from tblStudents that matches sCode. This statement uses a ParamByName line and works perfectly.
If no match is found, a message is displayed and the user is left with no option, but to click on the add user button. The user is then prompted to enter all of his details into the program, which are then passed to a class which sets out the SQL Insert Statement. A problem occurs now, however, as a message is displayed stating that Parameter Username is not found. I cannot understand why, as it is found when the Select statement is run. Please could someone help with this?
procedure TfrmProfilePage.FormActivate(Sender: TObject);
begin
//Instantiates the object.
objProfilePage := TProfilePage.Create;
sSQL := objProfilePage.SelectSQL;
ExecuteSQL(sSQl);
end;
procedure TfrmProfilePage.ExecuteSQL(sSQL : String);
begin
With dmTextbookSales do
Begin
dbgrdDisplay.DataSource := dsProfilePage;
qryProfilePage.SQL.Clear;
qryProfilePage.SQL.Add(sSQL);
qryProfilePage.Parameters.ParamByName('Username').Value := frmLogin.sCode;
qryProfilePage.Open;
If qryProfilePage.RecordCount = 0
Then
Begin
ShowMessage('Please click on the "Add Details" button to get started.');
btnChange.Enabled := False;
btnSelling.Enabled := False;
btnBuying.Enabled := False;
End;
End;
end;
procedure TfrmProfilePage.GetValues(VAR sStudentName, sStudentSurname, sCellNumber, sEmailAddress : String; VAR iCurrentGrade : Integer);
begin
ShowMessage('Fields may be left blank, but users wishing to sell textbooks should enter at least one contact field.');
sStudentName := InputBox('Name','Please enter your first name:','');
sStudentSurname := InputBox('Surame','Please enter your surname:','');
iCurrentGrade := StrToInt(InputBox('Current Grade','Please enter your current grade:',''));
sCellNumber := InputBox('Cellphone Number','Please enter your cellphone number:','');
sEmailAddress := InputBox('Email Address','Please enter your email address:','#dainferncollege.co.za');
end;
procedure TfrmProfilePage.btnAddClick(Sender: TObject);
begin
GetValues(sStudentName, sStudentSurname, sCellNumber, sEmailAddress, iCurrentGrade);
sSQL := objProfilePage.InsertSQL;
ExecuteSQL(sSQL);
btnChange.Enabled := True;
btnSelling.Enabled := True;
btnBuying.Enabled := True;
end;
The following code is obtained from the linked class, clsProfilePage:
function TProfilePage.InsertSQL: String;
begin
Result := 'INSERT INTO tblStudents (' + '[StudentID]' + ',' + '[StudentName]' + ',' + '[StudentSurname]' + ',' + '[CurrentGrade]' + ',' + '[CellNumber]' + ',' + '[EmailAddress]' + ') VALUES (' + 'Username' + ',' + QuotedStr(fStudentName) + ',' + QuotedStr(fStudentSurname) + ',' + IntToStr(fCurrentGrade) + ',' + QuotedStr(fCellNumber) + ',' + QuotedStr(fEmailAddress) + ')';
end;
function TProfilePage.SelectSQL: String;
begin
Result := 'SELECT * FROM tblStudents Where StudentID = Username';
end;
Your INSERT statement is wrong. You need to add parameters before you can set parameter values. In Delphi, you do that using : before the parameter name in your SQL statement.
In addition, Open is only used when performing a SELECT. INSERT, UPDATE, and DELETE don't return a rowset, and therefore you must use ExecSQL (the dataset method, not your function with the conflicting name) instead.
(While we're at it, never use RecordCount on a SQL dataset - it requires all rows to be retrieved in order to obtain a count, and it's not relevant when performing anything other than a SELECT anyway. Operations performed by ExecSQL should use RowsAffected instead, which tells you the number of rows that were affected by the operation.
(If you need a count of the number of rows, perform a SELECT COUNT(*) AS NumRecs FROM YourTable WHERE <some condition> instead, and access the NumRecs field using FieldByName.)
Change your function that returns the INSERT statement to something like this (the #13 in Result is a carriage return, which prevents having to manually insert spaces at the end of each line to separate SQL words):
function TProfilePage.InsertSQL: String;
begin
Result := 'INSERT INTO tblStudents ([StudentID],'#13 +
'[StudentName], [StudentSurname],'#13 +
'[CurrentGrade], [CellNumber], [EmailAddress])'#13 +
'VALUES (:Username, :StudentName,'#13 +
':StudentSurname, :CurrentGrade,'#13 +
':CellNumber, :EMailAddress)';
end;
You can then use it with ParamByName, without jumping through all of the hoops with QuotedStr and concatenation:
procedure TfrmProfilePage.AddUser;
begin
with dmTextbookSales do
begin
qryProfilePage.SQL.Text := InsertSQL;
qryProfilePage.Parameters.ParamByName('Username').Value := frmLogin.sCode;
qryProfilePage.Parameters.ParamByName('StudentName').Value := frmLogin.sUserName;
// Repeat for other parameters and values - you have to set every
// single parameter. To skip, set them to a null variant.
// Execute the INSERT statement
qryProfilePage.ExecSQL;
// See if row was inserted, and do whatever.
If qryProfilePage.RowsAffected > 0 then
ShowMessage('User added successfully');
// Perform a SELECT to populate the grid contents with the new
// rows after the update
end;
end;
I'd highly suggest you rethink this code. It's extremely convoluted, and it requires too much to accomplish a simple task (adding a new user).
If it were me, I'd use a separate query that was dedicated only to performing the INSERT operation, where you can set the SQL at design-time, and set the proper types for the parameters in the Object Inspector. Then at runtime, you simply set the parameter values and call ExecSQL on that insert query, and refresh your SELECT query to reflect the new row. It avoids all of the noise and clutter (as well as some unnecessary function calls and convoluted SQL building, opening and closing your SELECT query, etc.).
(It would also allow you to remove that horrific with statement, which leads to hard-to-find bugs and difficult to maintain code.)
You also have some bad linkages between forms, where you're referencing frmLogin (a specific form instance) from a second form. This means that you can never have more than one instance of either form in use at the same time, because you've hard-coded in that reference. I'd rethink that to use either parameters passed in when you create the form, or as properties that are set by the login form when creating the profile page form (or whatever TProfilePage is - your post doesn't say).
The best solution would be to move all of the non-UI related code into a separate unit (such as a TDataModule, which is designed to be used to work with non-visual components such as ADO queries) and remove it from the user-interface related forms anyway, which
Eliminates the coupling between forms, allowing the code to be reused.
Removes the non-visual data related components from cluttering the form and form code.
Separates the business logic (the part not related to interacting with the user) in a separate, distinct location, making it (and the code that uses it) easier to maintain.
Your insert statement is wrong. You need to replace the value for [StudentID].
'INSERT INTO tblStudents ('
+ '[StudentID]' + ','
+ '[StudentName]' + ','
+ '[StudentSurname]' + ','
+ '[CurrentGrade]' + ','
+ '[CellNumber]' + ','
+ '[EmailAddress]' + ')
VALUES ('
+ 'Username' + ',' // <-- UserName will never be replaced by a QuotedStr
// Access is looking for a column with the name
// 'UserName' which can not be found
+ QuotedStr(fStudentName) + ','
+ QuotedStr(fStudentSurname) + ','
+ IntToStr(fCurrentGrade) + ','
+ QuotedStr(fCellNumber) + ','
+ QuotedStr(fEmailAddress) + ')';
Related
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.
I want to calculate all fields and add a total field. How can I do this? I did this with sql command, but I don't know how to update dbgrid every time.
My code:
procedure TForm1.Button1Click(Sender: TObject);
begin
ADOQuery1.Close;
ADOQuery1.SQL.Text := 'select names,big1,small1,black1,big2,small2,big3,'+
'((big1+small1+black1+big2+small2+big3)*0.35) as total from adlar7v';
ADOQuery1.Open;
end;
when I run this code the first time it works, but the second time the changes don't appear in the grid.
Second method:
I did this with the TAdoQuery.OnCalcField event but there is a problem.
I know how to calculate a field but I don't know how to multiply all field after addition
my code:
ADOQuery1.FieldValues['total'] :=
ADOQuery1.FieldValues['big1'] + ADOQuery1.FieldValues['small1'] +
ADOQuery1.FieldValues['black1'] + ADOQuery1.FieldValues['big2'] +
ADOQuery1.FieldValues['small2'] + ADOQuery1.FieldValues['big3'];
ADOQuery1.FieldValues['cemi']:=((ADOQuery1.FieldValues['boyuk1'] + ADOQuery1.FieldValues['boyuk2'] + ADOQuery1.FieldValues['boyuk3'])*0.35)+((ADOQuery1.FieldValues['kicik1'] + ADOQuery1.FieldValues['kicik2'])*0.25)+(ADOQuery1.FieldValues['qara1']*0.30);
Hi I want to post calc field(cemi) to table (sql). when I calc all field the last field doesn't post on sql table. because last field (cemi) type fkcalc how can I post fkcalc type field Thanks in advance!
when I run this code first time working second time changes doesn't appear db grid
Use the Object Inspector to set up an AfterPost handler on your AdoQuery1:
procedure TForm1.ADOQuery1AfterPost(DataSet: TDataSet);
begin
AdoQuery1.Refresh;
end;
Then, after you save a change to one of the numeric columns in the grid, the AdoQuery1.Refresh will cause the data to be retrieved again from the server, and that will cause the server to recalculate the "Total" column. The fact that it isn't doing that at the moment is why the Total column isn't updating.
Btw, if I've understood what you are asking about that correctly, then your second point
I know how to calculate field but don't know how to multiplication(*) all field after (+)
Use the following code for this
ADOQuery1.FieldValues['total'] := (
ADOQuery1.FieldValues['big1'] + ADOQuery1.FieldValues['small1'] +
ADOQuery1.FieldValues['black1'] + ADOQuery1.FieldValues['big2'] +
ADOQuery1.FieldValues['small2'] + ADOQuery1.FieldValues['big3'] )
* 0.35;
Im reading one Access database with ADO and copying the data to another with the same structure. My problem is that sometime, it dont insert at the end of the file.
DM1.ADOConnectionReadAccess.ConnectionString :=
'Provider=Microsoft.Jet.OLEDB.4.0;' + 'User ID=Admin;' +
'Data Source=' + Directory + 'Courbe\Courbe Actuel.mdb' + ';' +
'Mode=Share Deny None;Extended Properties="";' + 'Jet OLEDB:Engine Type=5;';
DM1.ADOConnectionReadAccess.Connected := true;
DM1.ADODataSetReadAccess.CommandText := 'select * from Courbe_Reservoir order by DateHeure';
DM1.ADODataSetReadAccess.Active := true;
So i read my database and order it by DateTime. Now, i have to do some change on the data when i copy it to the new database.
DM1.ADOConnectionCopyExcel.ConnectionString :=
'Provider=Microsoft.Jet.OLEDB.4.0;' +
'User ID=Admin;' +
'Data Source=' + Directory + 'Courbe\Part of curve.mdb' + ';' +
'Mode=Share Deny None;Extended Properties="";' +
'Jet OLEDB:Engine Type=5;';
DM1.ADOConnectionCopyExcel.Connected := true;
while not DM1.ADODataSetReadAccess.Eof do
begin
Date2 := DM1.ADODataSetReadAccess.FieldByName('DateHeure').AsDateTime;
DernierDebit := DernierDebit + DM1.ADODataSetReadAccess.FieldByName('DebitRes').AsFloat / 1000;
DM1.ADOCommandCopyExcel.CommandText :=
'INSERT INTO Courbe_Reservoir' + ' VALUES (:DateHeure1, ' +
':Niveau, ' + //niveau
':DebitRes, ' + //débit entrée
':PH, ' + //ph
':Chlore, ' + //Chlore
':Turbidite, ' + //Turbidité
':Temp, ' + //température
'0, ' + //Consommation l/min
'0, ' + //Log
':DernierDebit, ' + //Consommation journalière
'0, 0, 0, '''', '''', ''''' + ')';
DM1.ADOCommandCopyExcel.Parameters.ParamByName('DateHeure1').Value := Date1;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('Niveau').Value := DM1.ADODataSetReadAccess.FieldByName('Niveau').AsFloat;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('DebitRes').Value := DM1.ADODataSetReadAccess.FieldByName('DebitRes').AsFloat;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('PH').Value := DM1.ADODataSetReadAccess.FieldByName('PH').AsFloat;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('Chlore').Value := DM1.ADODataSetReadAccess.FieldByName('Chlore').AsFloat;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('Turbidite').Value := DM1.ADODataSetReadAccess.FieldByName('Turbidite').AsFloat;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('Temp').Value := DM1.ADODataSetReadAccess.FieldByName('Temp').AsFloat;
DM1.ADOCommandCopyExcel.Parameters.ParamByName('DernierDebit').Value := DernierDebit;
DM1.ADOCommandCopyExcel.Execute;
end;
When i check the new database, i got the following result in the image :
As you can see, i got a gap in time because i got a record for each minute. The missing record in the image are located elsewhere in the database.
In the end, i want to know why its not show in the same order i insert them. The bottom line is i want to export the new access database (.mdb) to an excel file using an export command but the export command seem to take the access file and copy it to excel as is, so no way to order it because the export. I dont want to go throught all the record again with a select and insert it in excel one after the other.
Right now, i got a file with almost 180 000 record and i need to split it per month, so the new database will have around 44 000 record.
Thanks for your help
Insertion location is not defined in most databases — the database engine has the liberty to store data wherever it deems most appropriate. It might choose to add records to the end of the table, but it might also choose to reuse space vacated by previously deleted records.
If you want results in a particular order, then indicate that in your selection query, typically by including an ORDER BY clause.
I believe the records should appear in the "order" you inserted them due to the nature of .mdb files (in a multi-user db this would be chaos to be anything else!). Try adding an autonumber if you don't already have one -- you can then double check after what the order the autonum's were assigned in.
I put order in quotes because order is implied by some field (eg autonum). If there is no autonum, we cannot expect the results in any particular way
I have the following code which is constructs an SQL statement based on a number of variables:
SQL.Add('SELECT CDbl(Answer) as An FROM v_Outcomes_FirstLastOneYear ' +
'WHERE ' + GetTool(ReportGrid.Cells[col, 0]) + ' ' +
'AND (Collector = ' + QuotedStr('Patient') + ') ' +
'AND (Question=' + GetQuestion(ReportGrid.Cells[col, 0]) + ') ' +
'AND (Answer is not null) ' +
'AND (Answer <> ''null '') ');
if Copy(ReportGrid.Cells[col, 0], Length(ReportGrid.Cells[col, 0]), 1) = '1' then
SQL.Add('AND ('+GetTPoint(first)+') ')
else
SQL.Add('AND ('+GetTPoint(second)+') ');
SQL.Add('ORDER BY CDbl(Answer)');
The top row of ReportGrid (a TStringGrid) has already been populated with a series of strings such as 'Peds phys 1', or 'Peds phys 2'. The last number is either 1 or 2.
The variables 'first' and 'second' a strings defined further up.
col is an integer - this statement is constructed as part of a for loop which goes through each of the columns and populates each row with data corresponding to the header.
GetTool(), GetQuestion() and GetTPoint() are functions I have defined. The code for GetTPoint() is as follows:
function GetTPoint(timepoint: string): string;
begin
if (timepoint = '0') or (timepoint = 'discharge') then
begin
if timepoint = '0' then
Result := 'FirstAxData=''TRUE'' and DischargeData=''FALSE''';
if timepoint = 'discharge' then
Result := 'DischargeData=''TRUE'' and FirstAxData=''FALSE''';
end
else
begin
timepoint := FormatFloat('0', StrToInt(timepoint)*30.4368);
Result := '[Date] BETWEEN ([Date of First CPC]+' + timepoint +
')-61 AND ([Date of First CPC]+'+timepoint+')+61';
end;
end;
Each of the custom functions are similar and simply returns a string. GetTool() and GetQuestion() both work fine but GetTPoint() throws the following compiler error:
E2010 Incompatible types: 'string' and 'procedure, untyped pointer or
untyped parameter'
Does anyone know why this is being thrown?
The code you've shown appears inside a with statement for the query object you're operating on. Something like this:
with SomeQuery do begin
SQL.Add(...);
...
end;
You're able to refer to the SQL property alone like that because the with statement brings the query object's members into the current scope. That means all its members, including the First method (used for selecting the first query result). Anything introduced in the scope of the with statement will hide things of the same name introduced in earlier scopes. That includes the first variable you say was declared elsewhere.
When you have the expression GetTPoint(first), the compiler interprets the name first as referring to the query method, not the variable. The method is not a string, and calling it doesn't return a string, so the compiler rightfully complains.
The best solution is to stop using with. It just introduces brittleness to code. If you simply must continue using with, then rename your first variable so it doesn't interfere with names brought into scope later. There is no way to refer to local variables that have been hidden by another scope. (If first were global, you could qualify it with the name of the unit, but global variables should be avoided almost as much as with statements.)
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;