1

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;
2
  • 1
    You should use parameters in your insert statement to prevent SQL injection attacks - see here Commented Aug 26, 2015 at 19:28
  • Ooh I really need to take some time and make a dedicated project out of this one Commented Aug 27, 2015 at 11:29

2 Answers 2

1

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.
Sign up to request clarification or add additional context in comments.

Comments

0

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) + ')';

3 Comments

Wrong. Do not concatenate SQL. Do not instruct people to concatenate SQL. Search for SQL injection to learn why you should not concatenate SQL.
I didn't. I simply reformatted his insert statement and pointed to the error. He asked "why" it's not working and I explained. This is an answer. Of course SQL injection is a big topic. But this question is not about a general refactoring or securing the code. It's a beginner question: Why doesn't it work.
Thank you to both of you. This is for a school project and I have had zero experience working with SQL in Delphi, so "help for beginners" is perfect.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.