I have a TComboBox containing a list of names gathered from my database. Next to it is a TEdit that I intend on using for the purposes for displaying the ID number associated to each person.
Since firstName and lastName are separate fields within the table, but displayed together in the TCombobox I have written a small section to split the firstName and lastName into two separate variables:
pos := AnsiPos(' ', cbStudents.Text);
firstName := Copy(cbStudents.Text, 0, pos-1);
lastName := Copy(cbStudents.Text, pos+1, Length(cbStudents.Text));
Then I execute the SQL code:
try
query.Open;
query.SQL.Add('Select studentID');
query.SQL.Add('From student');
query.SQL.Add('Where firstName = ' + StrToQuote(firstName));
query.SQL.Add('And lastName = ' + StrToQuote(lastName));
editID.Text := query
finally
query.Free;
end;
Note: StrToQuote encapsulates the variable firstName and lastName with double quotes (" ")
The error that I am receiving is:
Argument out of range
What am I doing wrong? Thank you in advanced for the help.
Your code can not work. It opens the query first, then it sets the SQL query string. Instead of
try
query.Open;
query.SQL.Add('Select studentID');
query.SQL.Add('From student');
query.SQL.Add('Where firstName = ' + StrToQuote(firstName));
query.SQL.Add('And lastName = ' + StrToQuote(lastName));
finally
query.Free;
end;
use
// create or reset query here
query := ...
try
query.SQL.Add('SELECT studentID');
query.SQL.Add('FROM student');
query.SQL.Add('WHERE firstName = :firstname');
query.SQL.Add('AND lastName = :lastName');
// set parameter values here
query.Open;
// now transfer data from fields to the user interface (TEdit)
finally
query.Free;
end;
Your approach does not seam optimal for me (splitting Displayed Name), but your problem here would be accessing query.Fields[0].AsString after freeing the query.
Related
I have something like this:
var
text: string ;
fid: string;
for a = 1 to 100 do
begin
text:=stringgrid.cells[5,a];
fid:=stringgrid.cells[0,a];
query.SQL.Text := 'Update dbtable Set atext=' + text + ' Where id=' + fid;
query.ExecSQL;
end;
Is there a way to avoid calling ExecSQL() 100 times?
In this particular instance, yes, it is easy to do the updates with a single call to ExecSQL().
If the IDs are sequential, you can do this:
var
text: string ; // data i take from table in my loop.
query.SQL.Text := 'Update dbtable Set atext=' + QuotedStr(text) + ' Where id >= 1 and id <= 100';
query.ExecSQL;
If the IDs are not sequential, you could use an in clause instead, but this is less efficient if you have a large list of IDs:
var
text: string ; // data i take from table in my loop.
query.SQL.Text := 'Update dbtable Set atext=' + QuotedStr(text) + ' Where id in [1, ..., 100]';
query.ExecSQL;
Another option would be to create a stored procedure in the DB. Have it take the text and IDs as paramenters, and then perform any needed looping internally. You can then perform a single SQL statement in your code to execute the stored procedure with parameter values.
With a minimum of information I suggestd you can use the functionality of Delphi.
Do something like this:
var
Text: string ; // data i take from table in my loop.
query.open('select * from dbtable where id between 1 and 100');
query.beginBatch;
for a = 1 to 100 do
begin
if query.locate('id',id) then
begin
query.edit;
query.fieldbyname('atext').asString:=text;
end;
end;
query.endBatch;
query.post;
I can't say anything to the performance of this. Yout have to test it by your own.
procedure TForm1.Button1Click(Sender: TObject);
var
a: Integer;
text: String;
begin
query.SQL.Clear;
for a := 1 to 100 do
begin
query.SQL.Add('Update dbtable Set atext=' + QuotedStr(text) +
' Where id=' + IntToStr(a) + ';');
end;
query.ExecSQL;
end;
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 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) + ')';
I have in my hands an old app on informix server and I'm migrating data into a different database. Both are informix databases. I have a particular problem with one table. Old app used it to support multiline text.
OldTable:
HeaderID int
LineNum int
Descr nvarchar(50,1)
NewTable:
HeaderID int
Descr lvarchar(max)
So, for each HeaderID I have to read the descriptions ordered by line number and put them all together for insert into a new table. There has to be a newline character between each line for conversion to succeed.
Any tips on how to do this?
If you need to do it from SQL then you can use procedure:
CREATE FUNCTION get_text(aHeaderID int)
RETURNING lvarchar;
DEFINE result lvarchar;
DEFINE vcfld lvarchar;
LET result=NULL;
EXECUTE PROCEDURE IFX_ALLOW_NEWLINE('T');
FOREACH cur1
FOR SELECT Descr INTO vcfld FROM OldTable WHERE HeaderID = aHeaderID ORDER BY LineNum
IF result IS NULL THEN
LET result = vcfld;
ELSE
LET result = result || '
' || vcfld;
END IF;
END FOREACH;
RETURN result;
END FUNCTION;
(notice usage of IFX_ALLOW_NEWLINE and line breaking when updating result)
Then you can fill NewTable using:
UPDATE NewTable SET Descr=get_text(HeaderID);
You can use PreparedStatement. This is example in Jython that uses JDBC Informix driver:
db = DriverManager.getConnection(db_url, usr, passwd)
pstm = db.prepareStatement("SELECT vc FROM src ORDER BY id")
rs = pstm.executeQuery()
lines = []
while (rs.next()):
lines.append(rs.getString(1))
pstm = db.prepareStatement("INSERT INTO dest (lvc) VALUES (?)")
pstm.setString(1, '\n'.join(lines))
rs = pstm.execute()
db.close()
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;