I have a stored procedure I am calling to insert items in a table. If it only inserts an item into the table I get a 'Operation aborted' exception. If I add a select after the insert, it works fine.
What do I need to do different so I don't get the exception with only the insert?
Delphi code
procedure AddItem(dbCon : TADOConnection; sourcePath : String);
var
addProc : TADOStoredProc;
begin
if FileExists(sourcePath) then
begin
try
addProc := TADOStoredProc.Create(nil);
addProc.Connection := dbCon;
addProc.ProcedureName := 'spTest';
addProc.Open;
finally
addProc.Free();
end;
end;
end;
Stored Procedure
ALTER PROCEDURE [dbo].[spTest]
AS
BEGIN
INSERT INTO dbo.ToSolve (Data, SolveStatus)
VALUES (null, 1)
--SELECT * from dbo.ToSolve --I must have a select or I get and exception
END
As you found out, TADOStoredProc.Open is used in cases where there is a recordset returned.
Use TADOStoredProc.ExecProc when no recordsets are returned. The same goes for TADOQuery, use ExecSQL for INSERT/UPDATE/DELETE statements and Open for SELECT statements. So your example should be like this:
procedure AddItem(dbCon : TADOConnection; sourcePath : String);
var
addProc : TADOStoredProc;
begin
if FileExists(sourcePath) then
begin
addProc := TADOStoredProc.Create(nil);
try
addProc.Connection := dbCon;
addProc.ProcedureName := 'spTest';
addProc.ExecProc;
finally
addProc.Free;
end;
end;
end;
Related
Am learning how to use insert into statements and, with my access database, am trying to insert a single record. The table I'm inserting a new record into has three fields: StockID (AutoN), Description (Text), Cost (Number). I've looked at previous posts but the posted solutions seem to go beyond my basic level of Insert Into...which is what I'm interested in. Anyway, here is my code...
adoQuery1.Active := true;
adoQuery1.SQL.Clear;
adoQuery1.SQL.Add('INSERT INTO Stock (StockID,Description,Cost) VALUES (4,Cheese,5)');
adoQuery1.open;
adoQuery1.Close;
It compiles fine, but when press a command button to invoke the above, I get the following message:
'ADOQuery1: "Missing SQL property".'
what am I doing wrong?
Thanks, Abelisto. Your last post looks complex indeed...but I did my own little version since your last solution got me up and running. It works so I'm very chuffed. Am now going to focus on DELETE FROM using combobox (for field selection) and user value. Here was my solution I got working... ;)
x:=strtoint(txtStockID.Text);
y:=txtDescription.Text;
z:=strtoCurr(txtCost.Text);
adoQuery1.SQL.Clear;
adoQuery1.SQL.Add('INSERT INTO tblStock (StockID,Description,Cost)');
adoQuery1.SQL.Add('VALUES (:StockID,:Description,:Cost)'); // ':StockID' denotes a parameter
adoQuery1.Parameters.ParamByName('StockID').Value:= x;
adoQuery1.Parameters.ParamByName('Description').Value:= y;
adoQuery1.Parameters.ParamByName('Cost').Value:= z;
adoQuery1.ExecSQL;
adoQuery1.Close;
Using parameters is more efficient then constant SQL statements.
Additional to my comments here is some useful functions which I using frequently to call SQL statements with parameters (Maybe it will be useful for you too):
function TCore.ExecQuery(const ASQL: String; const AParamNames: array of string;
const AParamValues: array of Variant): Integer;
var
q: TADOQuery;
i: Integer;
begin
if Length(AParamNames) <> Length(AParamValues) then
raise Exception.Create('There are different number of parameter names and values.');
q := GetQuery(ASQL) as TADOQuery;
try
for i := Low(AParamNames) to High(AParamNames) do
SetParamValue(q, AParamNames[i], AParamValues[i]);
q.ExecSQL;
Result := q.RowsAffected;
finally
q.Free;
end;
end;
function TCore.GetQuery(const ASQL: String): TDataSet;
begin
Result := TADOQuery.Create(Self);
(Result as TADOQuery).CommandTimeout := 0;
(Result as TADOQuery).Connection := Connection;
(Result as TADOQuery).SQL.Text := ASQL;
end;
procedure TCore.SetParamValue(AQuery: TDataSet; const AName: string; const AValue: Variant);
var
i: Integer;
q: TADOQuery;
begin
q := AQuery as TADOQuery;
for i := 0 to q.Parameters.Count - 1 do
if AnsiSameText(AName, q.Parameters[i].Name) then
begin
case VarType(AValue) of
varString, varUString:
q.Parameters[i].DataType := ftString;
varInteger:
q.Parameters[i].DataType := ftInteger;
varInt64:
q.Parameters[i].DataType := ftLargeint;
end;
q.Parameters[i].Value := AValue;
end;
end;
And usage example in your case:
Core.ExecQuery(
'INSERT INTO Stock (StockID, Description, Cost) VALUES (:PStockID, :PDescription, :PCost)',
['PStockID', 'PDescription', 'PCost'],
[4, 'Cheese', 5]);
I have an record type for each Table in SQL Server. So when I want to insert (or update) a record on table I define a function to do so. Each field in delphi record has equivalent field in SQL table (with exact same name an type).
It is too interesting to me to write a function to do this ( for example Inserting) for all record types using Retti. I mean a function that have a parameter for receive any record and creates insert query.
I searched a lot and asked this question but finally not find the solution. Can anyone guide me to solution. I mean how to pass any record (any type) into a function as parameter?
Try something like this:
type
TSqlHlpr<T: record> = class
public
class procedure Insert(const Rec: T);
end;
class procedure TSqlHlpr<T>.Insert(const Rec: T);
var
Ctx: TRttiContext;
RecType: TRttiType;
TableName: String;
Field: TRttiField;
Value: TValue;
FieldValues: TDictionary<String, TValue>;
FieldPair: TPair<String, TValue>;
begin
FieldValues := TDictionary<String, TValue>.Create;
try
Ctx := TRttiContext.Create;
try
RecType := Ctx.GetType(TypeInfo(T));
TableName := RecType.Name;
// massage TableName as needed to match the DB...
for Field in RecType.GetFields do
begin
Value := Field.GetValue(#Rec);
FieldValues.Add(Field.Name, Value);
end;
finally
Ctx.Free;
end;
for FieldPair in FieldValues do
begin
// insert FieldPair.Value into FieldPair.Key column of TableName as needed...
end;
finally
FieldValues.Free;
end;
end;
type
TMyTable = record
// table fields here...
end;
var
rec: TMyTable;
begin
// fill rec as needed...
TSqlHlpr<TMyTable>.Insert(rec);
end;
OK I found my answer. Yes it is possible and so simple using untyped parameters like this :
procedure CreateSQLInsert(var Data)
begin
// and do whatever with your any type record using Retti
end;
Firstly, I am new to these and the question could be stupid. Anyway, I have a procedure like this:
procedure Tform1.QueryChange(sqltext : String; query : Integer);
begin
if query = 1 then begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add(sqltext);
ADOQuery1.Open;
end;
if query = 2 then begin
ADOQuery2.Close;
ADOQuery2.SQL.Clear;
ADOQuery2.SQL.Add(sqltext);
ADOQuery2.Open;
end;
I would like to remove the if blocks and make one united code:
ADOQuery+query.Close; (know that looks very silly)
ADOQuery+query.SQL.Clear;
ADOQuery+query.SQL.Add(sqltext);
ADOQuery+query.Open;
My goal is when query=1 code will use ADOQuery1.Close; etc.
when query=2 code will use ADOQuery2.Close;
You could create a local variable that referred to the TADOQuery object that you wish to operate on. Like this:
var
ADOQuery: TADOQuery;
begin
if query=1 then
ADOQuery := ADOQuery1
else if query=2 then
ADOQuery := ADOQuery2;
ADOQuery.Close;
ADOQuery.SQL.Clear;
ADOQuery.SQL.Add(sqltext);
ADOQuery.Open;
end;
Instead of creating variables ADOQuery1, ADOQuery2, ADOQuery3 etc of type TADOQuery, create an array:
ADOQueries: array of TADOQuery;
Then set the number of elements in it, when you know how many they will be:
SetLength(ADOQueries, NumberOfQueries);
Alternatively, if you know from the beginning how many elements there will be, you can define ADOQueries to be a static array instead:
ADOQueries: array[0..7] of TADOQuery;
Now you can do
procedure TForm1.QueryChange(sqltext: String; query: Integer);
begin
ADOQueries[Query].Close;
ADOQueries[Query].SQL.Clear;
ADOQueries[Query].SQL.Add(sqltext);
ADOQueries[Query].Open;
end;
Another way you can do this is with the FindComponent method, ie assuming the form owns the query components
procedure Tform1.QueryChange(sqltext : String; query : Integer);
var cmp: TComponent;
Query: TADOQuery;
begin
cmp := FindComponent('ADOQuery' + IntToStr(query));
if cmp <> nil then begin
Query := cmp as TADOQuery;
Query.Close;
...
end;
end;
The program I'm working on uses an if statement to add a line to the SQL for the contents of another combo box
procedure TFmNewGarage.ComboBoxCountryEnter(Sender: TObject);
begin
ADOQueryCountry.SQL.Clear;
ADOQueryCountry.SQL.Add('SELECT DISTINCT Country');
ADOQueryCountry.SQL.Add(' FROM TblBaseCar');
ADOQueryCountry.Open;
while not ADOQueryCountry.Eof do
begin
ComboBoxCountry.Items.Add(ADOQueryCountry['Country']);
ADOQueryCountry.Next;
end;
end;
procedure TFmNewGarage.ComboBoxCountryChange(Sender: TObject);
begin
SelA:=True;
ComboBoxManufacturer.Show;
ComboBoxCountry.Hide;
end;
procedure TFmNewGarage.ComboBoxManufacturerEnter(Sender: TObject);
begin
ADOQueryManufacturer.SQL.Clear;
ADOQueryManufacturer.SQL.Add('SELECT DISTINCT Manufacturer');
ADOQueryManufacturer.SQL.Add(' FROM TblBaseCar');
if SelA=true then
ADOQueryManufacturer.SQL.Add(' WHERE Country=(ComboBoxCountry.seltext)');
ADOQueryManufacturer.Open;
while not ADOQueryManufacturer.Eof do
begin
ComboBoxManufacturer.Items.Add(ADOQueryManufacturer['Manufacturer']);
ADOQueryManufacturer.Next;
end;
end;
At runtime this results in the error ComboBoxCountry.seltext has no default value, can anyone help me to rectify this?
SelText is not the property you should be using. You need the combobox Items value for the chosen ItemIndex:
var
Country: string;
begin
...
if ComboBoxCountry.ItemIndex <> -1 then
begin
Country := ComboBoxCountryItems[ComboBoxCountry.ItemIndex];
ADOQueryManufacturer.SQL.Add('WHERE Country = ' + QuotedStr(Country));
end;
end;
My table Customers has a field UserID which is indexed.
Now when I am dropping this Field from delphi, I am getting EOleExecption as its a indexed field.
I tried with following code:
ObjCustomers := TADOTable.Create(nil);
ObjCustomers.Connection := Connection;
ObjCustomers.TableName := 'Customers';
ObjCustomers.Open;
if (ObjCustomers.FindField('UserID').IsIndexField) then
begin
ExecuteSQLStatements(['DROP INDEX UserID ON Customers']);
end;
But this Tfield.IsIndexField is coming up False for this case.
Further I dont wanna do something like this:
try
ExecuteSQLStatements(['DROP INDEX UserID ON Customers']);
except
on E: exception do
end;
Is there any way so that I can check whether the field is Indexed, before executing SQL query?
Thankx in advance!
GetIsIndexField is not implemented by TADODataSet, and the result will be False.
Use TADOConnection.OpenSchema to retrieves table indexes:
var DataSet: TADODataSet;
DataSet := TADODataSet.Create(nil);
try
Connection.OpenSchema(siIndexes, VarArrayOf([Unassigned, Unassigned, Unassigned, Unassigned, 'Customers']), EmptyParam, DataSet);
while not DataSet.Eof do begin
ShowMessage(DataSet.FieldByName('INDEX_NAME').AsString);
DataSet.Next;
end;
finally
DataSet.Free;
end;
To make this answer complete:
As suggested by TLama you can use the TADODataSet method GetIndexNames.
ADO is internally using Command.ActiveConnection.OpenSchema(adSchemaIndexes...
function IsIndexField(DataSet: TADODataSet; FieldName: string): Boolean;
var
SL: TStringList;
begin
SL := TStringList.Create;
try
DataSet.GetIndexNames(SL);
Result := SL.IndexOf(FieldName) <> -1;
finally
SL.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ObjCustomers: TADOTable;
begin
ObjCustomers := TADOTable.Create(nil);
ObjCustomers.Connection := Connection;
ObjCustomers.TableName := 'Customers';
if IsIndexField(TADODataSet(ObjCustomers), 'UserID') then
begin
Showmessage('Index');
Connection.Execute('DROP INDEX UserID ON Customers');
end
else
Showmessage('Not Index');
// ObjCustomers.Open;
ObjCustomers.Free;
end;
VAR
AdoTbl:TAdoDataset;
BEGIN
AdoTbl:=TAdoDataset.Create(Self); // use TAdoDataset
AdoTbl.Connection :=MyAdoConnection;
AdoTbl.CommandType:=cmdTable; //Importent !!
AdoTbl.CommandText:='Refx_Ceramics_Hist_PreHist'; //Tablename
AdoTbl.GetIndexNames(ListBox1.Items);
END;
This works for me on DelphiXE2