How to select into table as string builder instead of using cursor in SQL - sql

I am wondering if there is a faster way to handle the following code in SQL. Currently I am using SQL cursor to do a select and build a string of delimiter values, as a dynamic value of suggestion items?
Here is the snippet of SQL:
begin
set #cursor = cursor for
select top 5 Manufacturer,ManufacturerPartNumber,Description as ManufacturerDescription, CONVERT(money,Price) as Price,fms.Score
from Products_OurProducts_Products_View
open #cursor
fetch next from #cursor
into #CURSOR_Mfr,#CURSOR_Model,#CURSOR_Desc,#CURSOR_Price,#CURSOR_Score
while ##FETCH_STATUS = 0
begin
set #suggestionsStringBuilder += #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + '^'
fetch next from #SuggestionsListCursor
into #CURSOR_Mfr,#CURSOR_Model,#CURSOR_Desc,#CURSOR_Price,#CURSOR_Score
end
insert into BASE (Manufacturer, ManufacturerOrig, ManufacturerPartNumber,ManufacturerPArtNumberOrig,ManufacturerDescription, QWDescription, Serial,AssetID,Price,Score,ItemType,MfrFound,ModelFound,trained, SuggestionList,LineNumberIn)
values(#objectORIGMfr,#objectORIGMfr, #objectORIGModel, #objectORIGModel, #objectDescription, #objectDescription, '',#objectAssetID,'0.00',#topMaxScore,'NA','1','0',#trained,#suggestionsStringBuilder,#objectLineNumber)
close #cursor
deallocate #cursor
end
The code above is trying to build a dynamic column of delimiter values such as shown below:
Object Example:
Mfr,
Model,
Price,
Score,
Description,
Suggestions = 'Mfr,Model,Desc,Price^Mfr,Model,Description,Price^
A return model would truly be as follows:
BaseMfr:Fluke,
BaseModel:Tb1,
BaseDescription:'Multi meter item',
BasePrice:120.00,
Suggestions: "Fluke, Tc1, 'Desc', '120.00' ^ 'Fluke', 'T11', 'Desc', 220.00"
Can I do the string builder / cursor section without having to use a looping cursor? The idea behind this is we send in items to be priced. If the item is not found, we then build a list of suggestions to bring back to the user of what they may use in the system or so they can see if there is a typo in the data.
The suggestion list is just the rows found, separating the columns by a "," and separating entities by a "^".
Thanks very much in advance!

Thanks you all for the feedback and I appreciate the help even though I know I had a rough time explaining the question correctly. Thanks to the suggestion from Sean Lange, I was able to be directed in the correct direction and came up with this. Now I will test the performance of it to see if it is better or not. Here is the code:
select
SUBSTRING(
(select top 5
Manufacturer + ',' + ManufacturerPartNumber + ',' + Description +',' + CONVERT(VARCHAR,Price) +',' + CONVERT(varchar,fms.Score) +'^' as [text()]
from Products_OurProducts_Products_View
CROSS APPLY (
select
dbo.FuzzyControlMatch('Flooke', Manufacturer) AS score
) AS fms
order by fms.score desc
FOR XML PATH ('')
), 2, 1000) [Suggestions]
The above code produces the following string:
ARD BROOKE,WB808 10UNF,TORQUE SCREWDRIVER,70.00,50^WARD BROOKE,WB808 1146,TORQUE SCREWDRIVER,70.00,50^WARD BROOKE,WB808 1246,TORQUE SCREWDRIVER,70.00,50^WARD BROOKE,WB808 6UNC,TORQUE SCREWDRIVER,70.00,50^ROKEM TECHNOLOGIES,FIRESET,RC STANDARD,105.00,50^
Now I am not sure if I am handling this the best way, but this is what I was searching for. I will post a comment update to let the feed know if the performance is better or worse.
-Thanks-

Related

How to get rid of Cursor and use UPDATE with SELECT

I believe that the cursor used in this code is the reason for some major performance issues, however I am new to TSQL.
Following script runs on SQL SERVER 2008. I am trying to redo it so I use JOIN statements instead, however I have not been able to do so successfully.
DECLARE AIRAMSDET CURSOR FOR
SELECT BILL, RECIEPT, NAME
FROM Client_Table
WHERE IsProcessed = 1
AND TYPE IN ('Sub','First_Time','Old') AND LEN(BILL) > 1
OPEN AIRAMSDET
FETCH AIRAMSDET into #VARBILL, #VARRECIEPT, #VARNAME
WHILE ##Fetch_Status = 0
BEGIN
UPDATE archieve
SET entry = left(#VARBILL + '- '+ #VARNAME)
WHERE archiveID = #VARBILL
END
It should be something like following
UPDATE ARCHIEVE
SET ENTRY = CT.BILL + '-' + CT.NAME
FROM CLIENT_TABLE CT
WHERE
ARCHIEVE.ARCHIVEID = CT.BILL
AND CT.ISPROCESSED = 1
AND CT.TYPE IN ('Sub','First_Time','Old') AND LEN(BILL) > 1
I have not included LEFT() as its use in your query wasn't very clear. Left takes an integer_expression as its second parameter while you are passing ##VARNAME which most likely is a VARCHAR. Please add that as you deem fit.

Trouble replacing abbreviated text correctly in SQL

I am trying to replace a bunch of difficult to decipher abbreviations with corresponding detailed descriptions. I have a table called Abbreviations that simply holds a list of abbreviations to look for and the corresponding descriptions they should be changed to. Additionally the "Replaced" table holds a list of unaltered abbreviated descriptions that I would like to change in a single column called "DescriptionCodes"
The data I am trying to change is a list of different teas. For instance the entry
"TADIN H-B GR" would be the abbreviation for "TADIN HERBAL BAG GREEN"
The SQL Code I am currently using looks like this:
BEGIN TRANSACTION
DECLARE #Desc varchar(500)
DECLARE #Abbr varchar(500)
DECLARE contact_cursor CURSOR FOR
SELECT Description, Abbrv FROM dbo.Abbreviations
OPEN contact_cursor
FETCH NEXT FROM contact_cursor
INTO #Desc, #Abbr
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Changing ' + #Abbr + ' to ' + #Desc
UPDATE Replaced
SET DescriptionCodes = REPLACE(DescriptionCodes, #Abbr, #Desc)
WHERE DescriptionCodes LIKE CONCAT('% ', #Abbr, ' %')
FETCH NEXT FROM contact_cursor
INTO #Desc, #Abbr
END
CLOSE contact_cursor
DEALLOCATE contact_cursor
COMMIT
Of course the problem I am running into is that once an abbreviation is applied the detailed description may contain a substring that matches another abbreviation that is then applied. For instance PKG might be changed to PACKAGE but PA is the abbreviation for PINEAPPLE as well, meaning that once that change is applied the final result would be PINEAPPLECKAGE. Every abbreviation has a single blank space on either side so I thought to use that fact to not update any abbreviation without a blank space on either side, hence the
CONCAT('% ', #Abbr, ' %') in my code. However, when I try this method basically nothing gets changed at all. I've been able to see limited success by removing that constraint but the other issue then happens. Any ideas as to how I could make sure that only occurrences of abbreviations with a blank space on either side are considered and updated?
UPDATE:
After trying several of the solutions posted here, I still haven't been able to get it to work and I'm not sure why. By all appearances this is correct. Here is a sample of some of the data I'm working with:
JUSTEA HBL PKG CMCL CHM LG LR 1.5OZ
PRIDE OF INDIA BG ASM B BKFST 25 CT
CTL BR H-B 7BLSM PP 1 CT
POSTI H-B HRH CRN 20 CT
DRS H-B EPGP LPLDS PTVP TGN 20 CT
ULTLC BG CHG 100 CT
PG TIPS BG D B 40 CT
RPBL R-B B HLDY FT BLD 6 CT
This is fine and looks like it should convert with no issues. Yet when I run the code with spaces indicated (As with "LIKE '% ' + #Abbr + ' %'", which was my first inclination) the data remains completely unchanged. If I remove them the data becomes completely unintelligible. For example the line beginning with PRIDE OF INDIA becomes
PRIDE OF INDIA(N) IRISH AFTERNOON BLACK AG ASIA PLUM RICOT SPICE(D) EARMINT BLACK BLACK KFST 25 CURRANT AN AID N T
I feel I should note that this data was imported from Excel Spreadsheets originally. Is there any chance that has anything to do with the spaces not being recognized?
Why use "LIKE" if you're going to add the spaces? just do:
WHERE DescriptionCodes = #Abbr
You can do this and it will achieve what you are trying to do:
LIKE '% ' + #Abbr + ' %'
I think you need to update each record of the Replaced table multiple times, and you need to consider the 4 locations of the abbreviations (Alone, First, In the middle, Last). Something like this:
DECLARE #Replaced TABLE ([DescriptionCodes] varchar(50))
DECLARE #Abbreviations TABLE ([Abbrv] varchar(50), [Description] varchar(50))
INSERT INTO #Replaced([DescriptionCodes]) VALUES ('TADIN H-B GR')
INSERT INTO #Replaced([DescriptionCodes]) VALUES ('PKG')
INSERT INTO #Replaced([DescriptionCodes]) VALUES ('PKG PA')
INSERT INTO #Abbreviations([Abbrv], [Description]) VALUES ('H-B', 'HERBAL BAG')
INSERT INTO #Abbreviations([Abbrv], [Description]) VALUES ('GR', 'GREEN')
INSERT INTO #Abbreviations([Abbrv], [Description]) VALUES ('PKG', 'PACKAGE')
INSERT INTO #Abbreviations([Abbrv], [Description]) VALUES ('PA', 'PINAPPLE')
DECLARE #RowCount int;
WHILE 1 = 1
BEGIN
SET #RowCount = 0;
UPDATE r
SET r.[DescriptionCodes] = REPLACE(r.[DescriptionCodes], a.[Abbrv], a.[Description])
FROM #Replaced r join #Abbreviations a ON r.[DescriptionCodes] = a.[Abbrv];
SET #RowCount = #RowCount + ##RowCount;
UPDATE r
SET r.[DescriptionCodes] = REPLACE(r.[DescriptionCodes], ' ' + a.[Abbrv] + ' ', ' ' + a.[Description] + ' ')
FROM #Replaced r join #Abbreviations a ON r.[DescriptionCodes] like '% ' + a.[Abbrv] + ' %';
SET #RowCount = #RowCount + ##RowCount;
UPDATE r
SET r.[DescriptionCodes] = REPLACE(r.[DescriptionCodes], ' ' + a.[Abbrv],' ' + a.[Description])
FROM #Replaced r join #Abbreviations a ON r.[DescriptionCodes] like '% ' + a.[Abbrv];
SET #RowCount = #RowCount + ##RowCount;
UPDATE r
SET r.[DescriptionCodes] = REPLACE(r.[DescriptionCodes], a.[Abbrv] + ' ', a.[Description] + ' ')
FROM #Replaced r join #Abbreviations a ON r.[DescriptionCodes] like a.[Abbrv] + ' %';
SET #RowCount = #RowCount + ##RowCount;
IF #ROWCOUNT = 0 BREAK;
END
SELECT * FROM #Replaced

Logic to prepare SQL statements dynamically

So i have a requirement where I need to read through records of all records of a file and insert them into another file if they meet a set of rules which are described in another table as shown below..
A record after it has been read from the first file has to meet all the sequences of at least one Rule to make it eligible to be written into the Second table.
For example once a record is read from CAR file, the rules below have to be checked till all sequences of atleast one rule set is satisfied. For this I was planning to Create a dynamic SQL program something of this sort. But this does not work as Prepared SQL does not support host variables.
If any body can suggest or provide any guidance on how to create SQL statemtns dynamically and check if records satisfy the required rules for them to be entered into the second file, it would be great
So basically what I am looking for is once I select a field from a table, how do I store it somehere to do further validation and checking.
Update
:
Based on the intelligent advice from Danny117, I have come up with the below code:
H Option(*NoDebugIO:*SrcStmt)
D RULEDS E DS EXTNAME(RULESTABLE)
D MAXRUL S 1 0
D MAXSEQ S 1 0
D STMT S 512
D WHERESTMT S 512 INZ('')
D FullSqlStmt S 512 INZ('')
D RULINDEX S 1 0 INZ(1)
D SEQINDEX S 1 0 INZ(1)
D APOS C CONST('''')
/Free
Exec SQL SELECT MAX(RULENO)INTO :MAXRUL FROM RULESTABLE;
Exec SQL DECLARE RULCRS CURSOR FOR SELECT * FROM RULESTABLE;
Exec SQL OPEN RULCRS;
Exec SQL FETCH RULCRS INTO :RULEDS;
DoW (Sqlcod = 0 AND RULINDEX <= MAXRUL);
Exec SQL SELECT MAX(SEQNO) INTO :MAXSEQ FROM RULESTABLE
WHERE RULENO=:RULINDEX ;
DoW (SEQINDEX <= MAXSEQ);
If (Position <> '');
Field = 'SUBSTR('+%Trim(Field)+','+%Trim(Position)+','
+'1'+')';
EndIf;
WhereStmt = %Trim(WhereStmt) + ' ' + %Trim(field)+ ' ' +
%Trim(condition) + ' ' + APOS + %Trim(Value) + APOS;
If (SeqIndex < MaxSeq);
WhereStmt = %Trim(WhereStmt) + ' AND ';
EndIf;
Exec SQL FETCH NEXT FROM RULCRS INTO :RULEDS;
SeqIndex = SeqIndex + 1;
EndDo;
FullSqlStmt = %Trim('INSERT INTO ITMRVAT SELECT * +
FROM ITMRVA WHERE '+ %Trim(WhereStmt));
Exec SQL Prepare InsertStmt from :FullSqlStmt;
Exec SQL EXECUTE InsertStmt;
RulIndex = RulIndex + 1;
EndDo;
This produces SQL statement as shown below which is what I want. Now let me go ahead and look at the other parts of the code.
> EVAL FullSqlStmt
FULLSQLSTMT =
....5...10...15...20...25...30...35...40...45...50...55...60
1 'INSERT INTO ITMRVAT SELECT * FROM ITMRVA WHERE STID = 'PLD' '
61 'AND ENGNO LIKE '%415015%' AND SUBSTR(ENGNO,1,1) = 'R' AND SU'
121 'BSTR(ENGNO,5,1) = 'Y' '
181 ' '
241 ' '
301 ' '
361 ' '
421 ' '
481 ' '
But the issue is now as I mentioned in my comment to Danny, how to handle if a new rule involving second table is specified..
Embedded SQL does allow for 'dynamic statements' in ILE languages. You are able to have a query within a character field and then pass it into the Embedded SQL.
Dcl-S lQuery Varchar(100);
lQuery = 'SELECT * FROM CUST';
EXEC SQL
PREPARE SCust FROM :lQuery;
EXEC SQL
DECLARE SearchCust CURSOR FOR SCust;
//Continue working with cursor..
You may want to just prepare, execute and return a result set:
lQuery = 'SELECT * FROM CUST WHERE ID = ' + %Char(CustID);
EXEC SQL
PREPARE SCust FROM :lQuery;
DECLARE c1 CURSOR FOR SCust;
OPEN c1;
FETCH c1 INTO :CustDS;
CLOSE c1;
Optional extra: You may also want to use field markers (?) in your query.
//'SELECT * FROM CUST WHERE CUSTID = ?';
EXEC SQL OPEN SearchCust USING :CustID;
//'INSERT INTO CUST VALUES(?,?)';
EXEC SQL EXECUTE CUST USING :CustID;
You have to translate the rules into a join statement or a where clause. The join statement is more complex so go that route.
If you were smart (and you are) consider saving the rules as a SQL clause that you can join or use in a where clause. Its infinitely flexible this way a more modern design.
rule 1 / car.year = 1990 and car.engno like '%43243%' and substring(car.vin,12,1) = 'X'
eval statement =
insert into sometable
Select car.* from car
join sysibm.sysdummy1
on car.year = 1990
and car.engno lile '%43243%'
...etc on to rule 2 starting with "OR"
or car.year = PLD
and car.engno like '%1234%'
...etc other rules starting with "OR"
exec immediate statement

SQL Query Optimization taking 20 - 30 secs to run

Below query takes 20 secs to execute and i need to optimize it as much as i can. Please help me on this.
SELECT Distinct
qh.QuoteHeaderId, [dbo].[mpx2_Get_PhoneGrade](qh.QuoteHeaderId)
FROM
t_QuoteHeader QH
INNER JOIN
t_HandsetQuote h ON Qh.QuoteHeaderId = h.QuoteHeaderId
INNER JOIN
t_phoneAudit P ON ISNULL(h.InspectionPhoneAuditId, h.QuotePhoneAuditId) = p.PhoneAuditId
INNER JOIN
mpx2_vw_customers C ON qh.CustomerId = C.CustomerId
INNER JOIN
#ContactChannels CC ON C.ContactChannelId = CC.ContactChannelId
LEFT OUTER JOIN
t_HandsetQuoteAdditionalInfo_TRNX hqa ON hqa.hqid = h.HandsetQuoteId
WHERE
((#VirtualStatusId = 0 OR #VirtualStatusId = -2 OR
C.ContactChannelId NOT IN (1, 2, 13, 80)))
AND ((#VirtualStatusId = -2) OR
('Q'+ CAST(Qh.QuoteStatusId AS VARCHAR(3)) + 'S' + CAST(h.StockStatusId AS VARCHAR(3)) IN
(SELECT 'Q'+ CAST(QuoteStatusId AS VARCHAR(3)) + 'S' + CAST(StockStatusId AS VARCHAR(3)) FROM t_VirtualStatusMap WHERE (#VirtualStatusId IS NULL OR #VirtualStatusId IN (0,-1) OR VirtualStatusId = #VirtualStatusId))
)
)
AND ((qh.IsCancelled = 0 and #onlyOpenOrders = 1) OR #onlyOpenOrders = 0)
AND ((h.IsCancelled = 0 and #onlyOpenOrders = 1) OR #onlyOpenOrders = 0)
AND (qh.ConfirmedDate <= #CutOff)
Please help me to optimize it. This query is used in a stored procedure.
This is too long for a comment.
ORs in WHERE and ON clauses are very hard to optimize. Often with a query like this, it is better to construct the query based on the components and use dynamic SQL.
For instance, the condition on #OnlyOpenOrders would be included like this:
declare #sql varchar(max);
set #sql = ' . . .';
declare #where varchar(max);
set #where = 'where . . .';
if (#OnlyOpenOrders = 0) begin
set #where = #where + ' and qh.IsCancelled = 0 and h.IsCancelled = 0'
end;
set #sql = #sql + ' ' + #where;
exec sp_executesql #sql;
You need to have similar logic for all the variables you are using.
There are a couple of things, although as others have said without all the required information such as a full execution plan, and schemas of the tables involved it is mostly guidelines/guesswork;
1.) In this part, it would appear you build a string from QuoteStatusId and StockStatusId in order to compare them;
('Q'+ CAST(Qh.QuoteStatusId AS VARCHAR(3)) + 'S' + CAST(h.StockStatusId AS VARCHAR(3))
IN
(SELECT 'Q'+ CAST(QuoteStatusId AS VARCHAR(3)) + 'S' + CAST(StockStatusId AS VARCHAR(3))
FROM t_VirtualStatusMap
WHERE (#VirtualStatusId IS NULL
OR #VirtualStatusId IN (0,-1) OR VirtualStatusId = #VirtualStatusId)))
If you skipped building the strings, since they are comprised of the same two columns and just compared the two columns directly that may speed things up.
2.) Have you tried adding the index which it suggests in the picture you attached? Without seeing your schema and an execution plan it is hard to suggest appropriate ones but it might be worth adding the one suggested (right click the green writing and it will generate the code to add the suggested index). I would read up on indexes and ensure there is an appropriate index for the query to use. ConfirmedDate seems like an obvious one, as well as all the join keys.
3.) As Gordon suggested using dynamic sql or if you are not comfortable with that - maybe splitting the query out into a few queries and switching between each using an IF statement, could help SQL generate a good plan for each scenario, instead of trying to find a generic plan to work for all cases.

Delphi 7 SQL Parameter found in Select Statement, but not Insert Statement

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