Is there a way to make multiple TADOQuery have access to the same session or transaction information?
Both TADOQuery have the same TADOCOnnection, but that doesn't work.
Example - I have one query that generates a temp table, I want to use that temp table in another query, but when I try, it doesn't know about the temp table.
procedure Foo(dbCon : TADOConnection);
var
q1 : TADOQuery; //TODO : set your ADOQuery to use a forward only read only cursor.
q2 : TADOQuery;
begin
q1 := TADOQuery.Create(nil);
q1.Connection := dbCon;
q1.SQL.Text := 'SELECT id INTO #TempT FROM dFTNodes;';
q1.Active := true;
q2 := TADOQuery.Create(nil);
q2.Connection := dbCon;
q2.SQL.Text := 'SELECT id FROM #TempT;';
q2.Active := true; //Fails here does not know table #TempT
end;
If your TADOConnection is not defined to KeepConnection=True, which is default,
the connection is dropped after executing of q1, your session is lost and #TempT is gone.
In general if there are no living TCustomAdodatasets using the connection a connection using KeepConnection=False will be closed.
q2 will get a new session and not be able to access #TempT.
Related
My code is like:
with ArrangementClientDataSet do
begin
close;
dataset.commandText := 'exec getArrangementData';
open;
edit;
end;
In procedure the code is like
select cyear,ccode from #arrangement
So when I click query button the dbgrid shows the data.
And now I want to add a save button so that when I click, the data can be saved into another table 'arrangement'.
The code on Save button is:
procedure SaveToolButtonClick(Sender:TObject);
begin
with ArrangementClientDataSet do
begin
first;
ApplyUpdates(0);
end;
end;
I tried to set
procedure DataSetProviderGetTableName(Sender:TObject;DataSet:TDataSet;var TableName :String);
begin
TableName := 'arrangement';
end;
procedure ADOQueryAfterOpen(DataSet:TDataSet);
begin
with ArrangementADOQuery do
begin
fieldByName('cyear').providerFlags := [pfInUpdate,pfInKey];
end;
end;
But it doesn't work, SQL Server Profiler can't catch the insert SQL.
If I change a value in dbgrid and then click the save button SQL Server Profiler can catch a update SQL like
update arrangement set ccode='1' where pfInKeyField = '2022'
But it's not right because data is not yet contained in table
So what should I do?
I've got a small problem that I could not find an answer to. I need to create/alter some procedures from Delphi.
So this is my code that take the code from a file and tries to execute it.
procedure TfrmMainApp.actRulezaScriptExecute(Sender: TObject);
var
j: Int32;
sql: string;
commandFile: TextFile;
Linie: string;
affRows: Int32;
err: string;
begin
for j := 0 to filesToExecute.Count - 1 do
begin
sql := 'USE ' + DBName + #10#13;
sql := sql + ' GO ' + #10#13;
AssignFile(commandFile, filesToExecute[j]);
Reset(commandFile);
while not EOF(commandFile) do
begin
Readln(commandFile, Linie);
sql := sql + #10#13 + Linie;
end;
dmMainScriptRun.ExecuteCommand(sql, err, affRows);
if err <> '' then
break;
Memo1.Lines.Add('Affected rows:' + IntToStr(affRows));
end;
end;
function TdmMainScriptRun.ExecuteCommand(sqlCommand: string; var err: string;
var affRows: Int32): Boolean;
begin
err := '';
try
with cmd do
begin
CommandText := sqlCommand;
Execute(affRows, EmptyParam);
end;
except
on E: Exception do
begin
err := E.Message;
end;
end;
end;
So my file looks like this
CREATE PROCEDURE sp_TestSebi
AS
print ('testSebi')
My command looks like this ( it was taken from the SQL Server Profiler )
USE Test
GO
CREATE PROCEDURE sp_TestSebi
AS
print ('testSebi')
Executin this command returns err Incorrect syntax near 'GO' running the script without the GO statement return err CREATE/ALTER PROCEDURE' must be the first statement in a query batch because of the USE clause.
Is there a way I can create a procedure from Delphi? I need the use statement because i'm trying to execute a script on multiple databases.
The way you are attempting this can be improved in several ways.
Firstly, try this:
Create a new VCL project
Drop a TAdoConnection and a TAdoQuery on the form. Also drop a TButton on it.
Connect the TAdoQuery to the TAdoConnection.
Set the TAdoConnection to connect to your Sql Server
in the code below, modify the scUse constant to refer to your target database
and scCreateView to refer to a non-existing view and a valid table in your db. This is to ensure that the Create View will not fail because the view already exists or the table does not exist.
Run the code and you should get a complaint that the Create View refers to an invalid object name, because the AdoConnection isn't connected to your target database when the Create View executes.
Then change KeepConnection to True and retest. This time the view should be successfully created.
const
scUse ='use m4common';
scCreateView = 'create view vwtest as select * from btnames';
procedure TForm1.Button1Click(Sender: TObject);
begin
AdoConnection1.DefaultDatabase := 'Master';
// The point of changing the default database in the line above
// is to ensure for demo purposes that the 'Use' statement
// is necessary to change to the correct db.
AdoConnection1.KeepConnection := False; // retest with this set to True
AdoQuery1.SQL.Text := scUse;
AdoQuery1.ExecSQL;
AdoQuery1.SQL.Text := scCreateView;
AdoQuery1.ExecSQL;
end;
So, the point of KeepConnection is to allow you to execute two or more Sql batches in the same connection context and to satisfy the server that the Create View statement (or similar) can be the first statement in the batch, and at the same time, the database to which the Create View applies is the same one as you "USEd" in the previous batch.
Secondly, your AssignFile ... while not Eof is unnecessarily long-winded and error-prone. Try something like this instead:
var
TL : TStringList;
begin
TL := TStringList.Create;
try
for j := 0 to filesToExecute.Count - 1 do
begin
sql := 'USE ' + DBName + #13#10;
sql := sql + ' GO ' + #13#10;
TL.BeginUpdate; // for better performance with longer scripts
TL.LoadFromFile(filesToExecute[j]);
TL.Insert(0, sql);
TL.EndUpdate;
// execute the Sql here etc
end;
finally
TL.Free;
end;
end;
Note that I've reversed the order of your #10 and #13 so that it is correct.
The other point is that as the Lines property of your Memo already has a LoadFromFile method, you don't really need my temporary TStringList, TL, because you could do the load into your memo (though you might prefer to keep the two uses separate).
You can drop the connection each time and specify the USE in the params. In fact if you are clever you can establish one connection per db and parallelize the whole thing.
(For readers who don't use Delphi: Although the following is couched in terms of Delphi coding, my actual technical question isn't Delphi-specific, but is about how to find out how the Sql Server will "understand" a TransactSql batch submitted to it. A "TAdoQuery" is a Delphi class which basically wraps an ADO Command and a RecordSet and submits a TSql batch to a Sql Server. Usually, using the TAdoQuery, the batch is a single statement, but my q is specifically concerned with the possibility that the batch may contain more than one statement.)
Suppose I have a TAdoQuery whose Sql.Text contains a TransactSql batch comprising
one or more statements S1[...Sn].
What I'm trying to do is to find out without executing the batch whether a) the first (or only) statement, S1, in the batch will return a result set (even if empty) e.g by dint of it being a SELECT statement or an invocation of a stored procedure or table function, or whatever, AND b) how many statements the server thinks there are in the batch.
Regular users of Delphi's TAdoQuery will know that it's easy but slightly messy to test whether the first (or only) statement in a batch returns a result set just by calling TAdoQuery.Open. If it does, then it retrieves that result set, but if doesn't, then calling .Open will provoke an exception.
So, instead, I do something like this:
type
TMyDataSet = class(TDataSet);
procedure TForm1.Button1Click(Sender: TObject);
begin
if AdoQuery1.Active then
AdoQuery1.Close;
AdoQuery1.FieldDefs.Clear;
TMyDataSet(AdoQuery1).OpenCursor(True);
AdoQuery1.FieldDefList.Update;
//AdoQuery1.FieldList.Update;
//Listbox1.Items.Assign(AdoQuery1.FieldList);
end;
The call to .OpenCursor with its InfoQuery param set to true causes the AdoQuery's FieldDefs
to be populated iff the first statement in its Sql would return a result set, but, unlike calling .Open, it will not cause the batch to be executed.
So far, so good. Here's my question:
How (via the AdoQuery or otherwise) do I get the Sql Server to tell me how many
statements it thinks the batch contains? (I think I may have stumbled on a way (subject to a lot more testing), but am interested in whether anyone knows an "official" technique for doing this.)
Btw, for now I'm using an antique (Sql Server 2000!) server via its OleDB driver for Sql Server.
You can use the SET SHOWPLAN_ALL function to analyze the statement in SQL server instead of executing the query. Please note that you can't use this functionality with TADOQuery but only with a TADOCommand object (Like TADOConnection.Execute).
test table:
USE [TestCustomer]
GO
CREATE TABLE [dbo].[Tbl_test](
[Id] [int] NULL,
[col1] [varchar](50) NULL
) ON [PRIMARY]
GO
small demo program:
program SO27007086;
{$APPTYPE CONSOLE}
uses
ActiveX,
Db,
AdoDb,
SysUtils;
var
DbConn : TADOConnection;
function GetNumberOfStatements(SQLQuery: String): Integer;
var
Rs : _RecordSet;
LastId : Integer;
begin
Result := 0;
LastId := -1;
DbConn.Execute('SET SHOWPLAN_ALL ON');
Rs := DbConn.Execute(SQLQuery, cmdText, []);
while not Rs.EOF do
begin
if Rs.Fields['StmtId'].Value <> LastId then
begin
Inc(Result);
LastId := Rs.Fields['StmtId'].Value;
end;
if Rs.Fields['Parent'].Value = 0 then
Writeln(Rs.Fields['Type'].Value);
Rs.MoveNext;
end;
DbConn.Execute('SET SHOWPLAN_ALL OFF');
end;
begin
try
try
CoInitialize(nil);
DbConn := TADOConnection.Create(nil);
try
DbConn.ConnectionString := 'Provider=SQLOLEDB;Integrated Security=SSPI;Initial Catalog=TestCustomer;Data Source=localhost\SQLEXPRESS;MARS Connection=True;';
DbConn.Connected := True;
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test'));
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test DELETE FROM Tbl_test WHERE 1 = 2'));
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test INSERT INTO Tbl_Test (Id, Col1) VALUES (3, ''c''),(4, ''d'')'#13#10'DELETE FROM Tbl_test WHERE 1 = 2'));
finally
DbConn.Free;
end;
finally
CoUninitialize;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Output:
SELECT
1
SELECT
DELETE
2
SELECT
INSERT
DELETE
3
I want create a database and a table programmatically with my Delphi Program on Windows 7 using Zeoslib component. From what I have found online so far, Zeoslib is expecting database to be created before using it. If so, is there a way to create a database and a table using Zeoslib tools.
Normal this question will be closed because you did not show what have you tried so far.
With ZeosLib it's easy
Safety Note:
Of course you should use parameterized queries. only in order to simplify the procedure, it has been omitted here
Create the Database
procedure TForm1.CreateClick(Sender: TObject);
begin
ZConnection1.Protocol:='sqlite-3';
ZConnection1.Database:='F:\Programme\stack\SQLite\Database.sqlite';
ZConnection1.Connect;
ZConnection1.Disconnect;
end;
Create a Table and Insert
procedure TForm1.CreateInsertClick(Sender: TObject);
begin
ZQuery1.SQL.Text := 'CREATE TABLE hardware (id INTEGER PRIMARY KEY, compname VARCHAR(30), username VARCHAR(30), model VARCHAR(30))';
ZQuery1.ExecSQL;
ZQuery1.SQL.Text := 'CREATE INDEX sHardware ON hardware(compname)';
ZQuery1.ExecSQL;
ZQuery1.SQL.Text := 'INSERT INTO hardware(id, compname, username, model) VALUES (1, "AMD8537", "OMonge", "Gigabyte");';
ZQuery1.ExecSQL;
end;
To see Values Connect again
procedure TForm1.ConnectClick(Sender: TObject);
begin
ZConnection1.Connect;
end;
Show Values
procedure TForm1.OpenClick(Sender: TObject);
begin
ZQuery1.SQL.Text := 'SELECT id, compname FROM hardware';
ZQuery1.Open;
end;
Form
Running
If database file does not exists - SQLite creates it on connect.
Below is a very simple but functioning example:
procedure TForm1.Button1Click(Sender: TObject);
begin
ZConnection1.Protocol := 'sqlite-3';
ZConnection1.Database := 'foo.s3db';
if not FileExists('foo.s3db') then
begin
ZConnection1.Connect;
ZConnection1.ExecuteDirect('create table foo (bar integer)');
end
else
ZConnection1.Connect;
ZConnection1.Disconnect;
end;
I experience difficulties with the Delphi/ADO error handling when executing an SQL script containing more than one INSERT, UPDATE,... statement. Only when the first SQL statement of the script fails, I get an exception in Delphi. If the first statement passes, there will be no exception in Delphi, whatever happens further in the script.
This is the Delphi code I use:
var
DataSet: TADOQuery;
begin
...
try
DataSet.Close;
DataSet.ParamCheck := true;
DataSet.SQL.LoadFromFile(FileName);
DataSet.Prepared := true;
try
DataSet.ExecSQL;
finally
DataSet.Close;
end;
except
on E: Exception do
Logging.AddText(E.ClassName + ' error raised when executing ' + FileName + '. Message: ' + E.Message);
end;
...
end;
For testing I used this simple script:
INSERT INTO TESTTABLE
VALUES ('John', 24);
INSERT INTO TESTTABLE
VALUES ('Ed', '32');
where TESTTABLE is just a simple table containing two columns: Name NVARCHAR(50) and Age INT.
When you replace, for example, 24 by 'twentyfour' in the first INSERT statement and run the script with the Delphi code, Delphi/ADO will raise an exception. But when you replace, for example, 32 by 'thirtytwo' in the second INSERT statement, there will be no exception.
I tried to solve this by putting the script in a stored procedure "dbo.ErrorHandling" and sending
EXEC dbo.ErrorHandling
to ADO, but it did not help.
CREATE PROCEDURE dbo.ErrorHandling
AS
BEGIN
INSERT INTO TESTTABLE
VALUES ('John', 24);
INSERT INTO TESTTABLE
VALUES ('Ed', '32');
END
I can solve the problem by using TRY and CATCH in the script, and letting it log the errors to a LOGGING table. Delphi can check this table for new errors after each script execution.
However, is it possible to catch all SQL server errors in Delphi, or do I have to execute INSERTS, UPDATES,... one by one?
I use Delphi XE6 and SQLServer 2008 R2
I would t take a look at the Errors Collection of the AdoConnection.
TAdoConnection.Errors
I've never found any way of reliably processing several queries at once and sensibly detecting problems. I believe the correct solution is to execute the statements one at a time.
This is code from one of my programs doing exactly what J__ describes in his comment. It processes an SQL Server style script with a GO after every statement. You could replace the "GO" detector with some other indication of the end of a statement. It batches the whole thing up as a single all-or-nothing transaction. The last finally has some code to save the last query into an on screen memo so you can see what failed if there was an exception.
procedure TDupFrame.LoadButtonClick(Sender: TObject);
var
Query: TADOQuery;
Reader: TStreamReader;
Line: string;
begin
Query := TADOQuery.Create(nil);
try
Query.Connection := ConfModule.ADOConnection;
Query.Connection.BeginTrans;
if ScriptOpenDialog.Execute(Self.Handle) then
begin
Reader := TStreamReader.Create(ScriptOpenDialog.FileName);
try
Query.SQL.BeginUpdate;
while not Reader.EndOfStream do
begin
Line := Reader.ReadLine;
if not SameText(Line, 'GO') then
begin
Query.SQL.Add(Line);
end
else
begin
Query.SQL.EndUpdate;
Query.ExecSQL;
Query.SQL.Clear;
Query.SQL.BeginUpdate;
end;
end;
Query.SQL.EndUpdate;
if Query.SQL.Count > 0 then Query.ExecSQL;
finally
Reader.Free;
end;
Query.Connection.CommitTrans;
end;
finally
SQLMemo.Lines.Assign(Query.SQL);
// rollback if we have missed the commit (ie an exception occurred)
if Query.Connection.InTransaction then
Query.Connection.RollbackTrans;
Query.Free;
end;
end;
You need to configure the TADOQuery or TADOCommand with the ExecuteNoRecords Option:
Query := TADOQuery.Create(nil);
Query.ExecuteOptions := [eoExecuteNoRecords];
It's the same in .NET. If you use ExecuteReader() or ExecuteScalar() on a SqlCommand, no exception is thrown if the first of a sequence of statements within a single command succeeds - no matter of how many subsequent statements fail.
If you call ExecuteNonQuery() however, a proper exception is being thrown in any case.