views:

480

answers:

4

In my application I use TADOQuery with select (MSSQL) and linked with it TClientDataSet. I have to insert about million records and ApplyUpdates.

So what I see in the SQL Server Profiler? I see that for each inserted row we have 3 queries: sp_prepare of insert script, sp_execute it with some values and sp_unprepare.

I want just to prepare sql once for all of the records before insert and unprepare it after. How can I do it?

Added after:

In the query I have a script for the stored procedure execution:

tmpQuery := DefineQuery(FConnection, [
  'exec up_getOperatorDataSet ',
  '  @tablename     = :tablename, ',
  '  @operator      = :operator, ',
  '  @forappend     = :forappend, ',
  '  @withlinksonly = :withlinksonly, ',
  '  @ids           = :ids '
], [
  Param(ftString, sTableName),
  Param(ftInteger, FOperatorId),
  Param(ftBoolean, opForAppendOnly in OpenParams),
  Param(ftBoolean, opOnlyWithModelLinks in OpenParams),
  Param(ftString, sIds)
], Result);

It selects all of the fields from table sTableName with some parameters.

Example of inserting from profiler:

step 1:

declare @p1 int
set @p1=486
exec sp_prepare @p1 output,N'@P1 int,@P2 int,@P3 datetime,@P4 int,@P5 int,@P6 int,@P7 int,@P8 int,@P9 varchar(128),@P10 bit,@P11 numeric(19,4),@P12 smallint,@P13 smallint,@P14 smallint,@P15 smallint',N'insert into parser_prices
  (operator_id, request_id, date, nights, model_hotel_id, model_meal_id, model_room_id, model_htplace_id, spo, hotelstop, price, frout_econom, frout_business, frback_econom, frback_business)
values
  (@P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8, @P9, @P10, @P11, @P12, @P13, @P14, @P15)
',1
select @p1

step 2:

exec sp_execute 486,21,2000450,'2009-12-04 00:00:00',14,2118,22,-9555,18,'2009-10.MSK.Bali.13.10.09-27.03.10',0,15530.0000,3,3,3,3

step 3:

exec sp_unprepare 486

and it is for all of the new rows.

+1  A: 

Because you're calling a stored procedure, and not an inline query in your code, then SQL Server is treating each call to the stored proc as a separate call, and so is preparing it and unpreparing it every time. I'm not sure if there is a way around this.

If whatever is happening within the stored proc can be done from a query in your code, then you could use a structure like this that would only prepare the SQL statement the first time:

{Prepare the insert query}
ADOQuery1.SQL.Append('INSERT INTO Tablename');
ADOQuery1.SQL.Append('(StringField1, IntField2)');             {repeat as necessary}
ADOQuery1.SQL.Append('VALUES (:sFieldValue1, :sFieldValue2)'); {repeat as necessary}
ADOQuery1.SQL.Prepare;

{In a For, While, Repeat loop, use:}
ADOQuery1.ParamByName('sFieldValue1').AsString := 'Value for field 1';
ADOQuery1.ParamByName('sFieldValue2').AsInteger := 2;
ADOQuery1.ExecSQL;

Apologies if I've not quite got the property and method names right for the ADOQuery component, I'm not on my Delphi PC at the moment and I don't usually use the TADO components, but the concept still applies as this is a TDataSet concept.

_J_
Yes, it is what I need, but... automatically :)
silent
What do you mean automatically? Do you mean it needs to be in a database trigger?
_J_
Automatically means that I want components to produce the correct query without additional objects that I must to create.
silent
I'm sorry I'm still not sure I understand. You say you're using TADOQuery and so I'm just using a component of that type to prepare a query. You can then set the parameter values and execute the query in a loop.
_J_
Server Side (TADOQuery) -> Client Side (TClientDataSet). When I change something in TClientDataSet and ApplyUpdate, it send changes to server side (by himself) and something like DataSetProvider or AdoQuery prepares this insert script automatically.Your solution is to handle changes on a server side and execute a prepared query by hand.
silent
A: 

Thoughts...

  1. You don't need to prepare a stored procedure call. In effect, it's already prepared. You can swicth it off in most client implementations.

  2. You might not be able to do it for a million rows in one go. You have a batch size limit (eg a single DB call) of 256 MB (assuming default 4k network packets).

  3. In other client implementations you can set a "batch size" (different concept to point 2), say 10,000, so you'd only make 100 calls not one million.

gbn
1. I do not prepare my select2. I know3. So what? :)
silent
@silent: 1. you are preparing because SQL profiler shows it: it may be automatic but can be switched off usually. 3. Can you batch calls.. after all, this is the point of your question, surely.
gbn
Insert SQL is generating with TSQLResolver in Provider, not by me. I debug it and found that before execution through TADOCommand it have no prepared flag.
silent
A: 

Hi, I think that the other answers may be good at helping to tune the performance, although I suppose what method you use to access the TClientDataSet doesn't really matter, since either way, the actual database update is separate (and automatically generated).

If the update works as a separate prepare for each line, then it seems that was a poor design choice by Borland, as it's obvious that updating many rows will require the same query, only with different parameters each time.

On the other hand, TClientDataSet was meant for in-memory databases, which means relatively small databases. Using something like a million rows probably goes beyond the intended use case.

On the other hand, swapping out the ClientDataSet from your application at this point might be troublesome. I would say that for the performance sensitive part of your application, I would track the modified rows myself and write a manual update routine using the routines mentioned above. Besides that, you could try modifying the TClientDataSet's source code to make it more efficient, or sub-classing it and overriding the method which applies the changes.

(Personally, I use SQLite3 for storage in my programs, so ClientDataSet is of little use and I haven't played with it much).

Noah
A: 

The answer was in provider used in TADOConnection. Switched from MSDASQL to SQLOLEDB and all is right now, without any additional queries.

silent