Perform multiple SQL Server updates without looping - sql

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;

Related

FireDAC: possibility to make query faster - Delphi

I use bind variables in Delphi and on the other side there is Oracle database with Database link (#dblink). When I build a SELECT statement without bind variables, it has no problem with performance. Only if I query SELECT statement with bind variables it takes very long (sometimes 1-2 hour). Is it possible in FireDAC to make the query faster without changing SQL-query? I need to use bind variables to avoid SQL injection.
This SQL is very Fast in Delphi:
SELECT
DLGH_START_D Datum,
TRUNC((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 60)||' Min '||
TRUNC(MOD(((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 3600), 60))||' Sek' Dauer
FROM
dialoghistory d
WHERE
d.DLGH_PARAMETER_C = 'Name of Parameter' AND <--
d.dlgh_funktion_c = 'SQLS' AND
d.DLGH_START_D > '01.02.2020' <--
order by 1
This SQL is very slow in Delphi:
SELECT
DLGH_START_D Datum,
TRUNC((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 60)||' Min '||
TRUNC(MOD(((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 3600), 60))||' Sek' Dauer
FROM
dialoghistory d
WHERE
d.DLGH_PARAMETER_C = :B_Name AND <--
d.dlgh_funktion_c = 'SQLS' AND
d.DLGH_START_D > :Datum <--
order by 1
---------------------------
//slow execution period , because of bind variables (1 h)
qry := TFDQuery.CreateSQL(Application, sSqlText_.Text);
with qry do begin
...
Param.AsString := value; //set value of bind variable
...
Open;
Table dialoghistory looks like this
My Solution:
tmpQuery := TFDQuery.Create(self); // Create Query
tmpQuery.Sql.Text := 'SELECT * FROM dlgHistory d where d.DLGH_PARAMETER = :par';
...
// Set Data Type, Param Type and Size yourself
with tmpQuery.Params do begin
Clear;
with Add do begin
Name := 'par';
DataType := ftString;
Size := 128;
ParamType := ptInput;
end;
end;
tmpQuery.Params[0].AsString := 'Value'; //assign a value
tmpQuery.Prepare;
tmpQuery.Open; //And it works perfect!

Dynamic PL SQL in where condition for different operands

I have three tables, viz:
Mains_Control
Control_Mapping
Control_Details
along with several conditions and columns. But I am performing dynamic operations on the tables columns given below.
I am unable to write a dynamic SQL block in PL-SQL procedure for the below conditions. Could anyone please assist me on this particular scenario.
SELECT *
FROM CUR_ALL_CONTENTS
WHERE MAINS_CONTROL SWITCH = $SWITCH
AND ($ATTRIB_COLUMNS = $ATTRIB_VAL
OR $ATTRIB_COLUMNS = $ATTRIB_VAL)
The above business condition is this
SELECT *
FROM CUR_ALL_CONTENTS
WHERE MAINS_CONTROL_SWITCH = 'TYPE'
AND (SWITCH_MODE = 'THRUST' OR SWITCH_MODE_GEAR = 'SEC_GEAR')
Here SWITCH attribute value can change, ATTRIB_COLUMNS and ATTRIB_VAL can change.
Mains_Control table has the following columns
SWITCH ACTION_CODE RULE_MAP_1
TYPE ON R1
TYPE OFF R2
METHOD HOLD R3
METHOD TERM_IN R4
Control_Mapping table has the following columns
RULE_MAP_1 RULE_MAP_2
R1 M11
R2 M22
R3 M33
R4 M44
Control_Details table has the following columns
RULE_MAP_2 ATTRIB_COLUMNS OPERAND ATTRIB_VAL
M11 SWTICH_MODE = THRUST
M22 SWITCH_MODE_GEAR = SEC_GEAR
M33 HOLD_RELEASE <> END
Still not clear for me how you determine which condition shall be used and when. Let's give this example with hard-coded rule set:
DECLARE
sqlstr VARCHAR2(30000);
cur INTEGER;
res INTEGER;
refCur SYS_REFCURSOR;
val Mains_Control.SWITCH%TYPE;
CURSOR Conditions IS
SELECT *
FROM RULE_MAP_2 IN ('M11', 'M22');
BEGIN
cur := DBMS_SQL.OPEN_CURSOR;
sqlstr := 'SELECT * '||CHR(13);
sqlstr := sqlstr || 'FROM CUR_ALL_CONTENTS '||CHR(13);
sqlstr := sqlstr || 'WHERE MAINS_CONTROL_SWITCH = :switch AND (';
FOR aCond IN Conditions LOOP
sqlstr := sqlstr || aCond.ATTRIB_COLUMNS ||aCond.OPERAND||' :'||RULE_MAP_2 ||' OR '
END LOOP;
sqlstr := REGEXP_REPLACE(sqlstr, ' OR ', ')');
DBMS_OUTPUT.PUT_LINE(sqlStr); --> verify generated statement
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
SELECT SWITCH
INTO val
FROM Mains_Control
WHERE RULE_MAP_1 = 'R1';
DBMS_SQL.BIND_VARIABLE(cur, ':switch', sw);
FOR aCond IN Conditions LOOP
DBMS_SQL.BIND_VARIABLE(cur, ':'||aCond.RULE_MAP_2, aCond.ATTRIB_VAL);
END LOOP;
res := DBMS_SQL.EXECUTE(cur);
refCur := DBMS_SQL.TO_REFCURSOR(cur);
FETCH refCur BULK COLLECT INTO ...;
END;
The code would be simpler without DBMS_SQL.BIND_VARIABLE, however using bind variables is the proper way of doing it.

Proc oracle issue - call executed but no insertion ( ORACLE / PLSQL )

Im trying to make a procedure in oracle with PL/SQL. This proc has to call a function with a value return and then update a colown with this value. My proc is executing normaly but the insertion to try the proc are not working. Im a bit lost.
The proc :
PROCEDURE depot_enrichment(babas number) IS
-- Curseur
CURSOR V_CURS_CDIS_CTC IS SELECT DISTINCT CD_REGATE,
OPT_SITE_DEPOT,
TYP_PARUTION_DECLA,
NUM_PUBLICATION_DECLA,
CD_POSTAL,
ID_LIB_PREP
FROM DEPOT_IMPORT
WHERE OPT_SITE_DEPOT = 81001210 OR OPT_SITE_DEPOT = 81001211;
-- Variables proc
p_opt_site varchar2(40); -- DepotCDIS/DepotCTC
-- Variables parameters function
v_CodeSiteDepot VARCHAR2(40); ----- 531980
v_OptionSiteDepot VARCHAR2(40); ---- 81001210/81001211
v_TypeParution VARCHAR2(40); ---- N1, N2, N3 ..........
v_NoPublication VARCHAR2(40);---- 1222K82523
v_codePostal VARCHAR2(40); ----- 55000
v_NiveauPrepa VARCHAR2(40); ---- 50001,50002,5003
-- Libelle parameters function
v_LibOptionSiteDepot VARCHAR2(40); ---- DepotCDIS/DepotCTC
v_LibTypeParution VARCHAR2(40); ---- NUM , FCS ..........
v_LibNiveauPrepa VARCHAR2(40); ---- ATTF, LATD, LDCP, LDQL ou PEM
BEGIN
-----------------------------------------------------------------------------------------------------
-- SUB-PROC OPT SIT DEPOT ENRICHIMENT - ENR4 :détermination de OptionSiteDepot pour les produits Presse
-- Enrichment of the opt_site_depot - JIRA : 1003
-----------------------------------------------------------------------------------------------------
-- option DepotCDIS / CTC
OPEN V_CURS_CDIS_CTC;
LOOP
fetch V_CURS_CDIS_CTC into v_CodeSiteDepot,v_OptionSiteDepot,v_TypeParution,v_NoPublication,v_codePostal,v_NiveauPrepa;
EXIT WHEN V_CURS_CDIS_CTC%notfound;
Insert into babas values ('Parametres : ' || v_CodeSiteDepot || ' / ' || v_OptionSiteDepot || ' / ' || v_TypeParution || ' / ' || v_NoPublication || ' / ' || v_codePostal || ' / ' || v_NiveauPrepa);
-- GET LIBELLE FROM PARAMETERS VARIABLES
SELECT LIB.LIB_LIBELLE INTO v_LibOptionSiteDepot FROM LIBELLE LIB WHERE typ_libelle = 'OSD' and id_libelle = v_CodeSiteDepot;
SELECT TYP.LIB_PARUTION INTO v_LibTypeParution FROM TYPE_PARUTION TYP WHERE id_type_parution = v_TypeParution;
SELECT LIB.VAL_LIBELLE INTO v_LibNiveauPrepa FROM LIBELLE LIB WHERE typ_libelle = 'PEC' and id_libelle = v_NiveauPrepa;
Insert into babas values ('Parametres libelles: ' || v_CodeSiteDepot || ' / ' || v_LibOptionSiteDepot || ' / ' || v_LibTypeParution || ' / ' || v_NoPublication || ' / ' || v_codePostal || ' / ' || v_LibNiveauPrepa);
-- CALL FUNCTION
p_opt_site := return_option_CDIS_CTC(v_CodeSiteDepot,v_LibOptionSiteDepot,v_LibTypeParution,v_NoPublication,v_codePostal,v_LibNiveauPrepa);
Insert into babas values ('option site dépot : ' || p_opt_site);
-- UPDATE ROWS
UPDATE DEPOT_IMPORT SET OPT_SITE_DEPOT = p_opt_site;
END LOOP;
CLOSE V_CURS_CDIS_CTC;
END depot_enrichment;
When i call the proc :
what is inserted in babas table :
I've the impression the modification code into libelle ( the 3 select ) is not working, i try the query alone and it returns me the right value. I dont understand, if you have any documentions or advises, will be really cool :)
Commit is necessary to be placed at the end of the procedure to reflect the changes.
....
....
UPDATE DEPOT_IMPORT SET OPT_SITE_DEPOT = p_opt_site;
END LOOP;
COMMIT; -- this is needed here
CLOSE V_CURS_CDIS_CTC;
There are 3 insert into babas statements within the loop, but - according to output you get, only the first one:
Insert into babas values ('Parametres : ' ...
does its job. Where are other two?
When calling the procedure, you specified owner name:
call sagapec_import.depot_enrichment(0);
--------------
this
Is the babas table owned by that user? Are you connected as sagapec_import? Because, maybe you're executing procedure in one schema and check the result in another.
Finally, this:
UPDATE DEPOT_IMPORT SET OPT_SITE_DEPOT = p_opt_site;
doesn't contain WHERE clause which means that you'll every time update all rows in that table. Is that correct?

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

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;