Использование рекордсета для обновления (поиск и вставка) данных

Можно и так
Дата: 05.02.2016 16:27:03
Начало здесь:
Оптимизация

При обновлении таблиц данными из внешнего источника часто удобно использовать рекордсеты.
Способ, приведенный выше действительно можно оптимизировать, если для поиска и вставки применить два независимых рекордсета, а также возможности ADO.

Для примера использовал базу norhwind.mdf (MS SQL), таблицу [Order Details] и ее локальную копию OrderDetails как внешние данные.
Внимание!
1. Сначала надо прилинковать табл [Order Details] из NorthWind под именем [dbo_Order Details]
2. Выполнение DoIt или data_prepare из кода примера удалит 1/2 данных из табл. [Order Details]

Пример:
+
Option Compare Database
Option Explicit

Sub doit()
    data_prepare
    noobs
    Debug.Print "*******************"
    data_prepare
    var_1
    Debug.Print "*******************"
    data_prepare
    var_2
End Sub

Sub noobs()
    Dim rs As Object
    Dim rs2 As Object
    Dim sqlDel As String
    Dim objConnectionEx, objConnectionSer
    Dim t#, strsql$, i&
    t = Timer
    Set objConnectionEx = CreateObject("ADODB.Connection")
    Set objConnectionSer = CreateObject("ADODB.Connection")
    Set rs = CreateObject("ADODB.Recordset")
    Set rs2 = CreateObject("ADODB.Recordset")
    Set objConnectionEx = CurrentProject.AccessConnection
    strsql = "select * from orderdetails"
    rs.Open strsql, objConnectionEx, 3, 3 ' открываем рекордсет excel
    If rs.BOF And rs.EOF Then
    MsgBox "Файл пуст"
    Else
    objConnectionSer.Open "Provider=SQLOLEDB;Data Source=RadioX\FData;Database=northwind;User ID=sa;Password=0" 'строка подключения к skid
    rs.MoveLast: rs.MoveFirst 'прогон рекордсета на последнюю запись и назад к первой для правильного подсчета количества записей
    rs2.Open "[dbo].[Order Details]", objConnectionSer, 3, 3  'открываем рекордсет  таблицы Skid
    
    For i = 1 To rs.RecordCount 'цикл по записям рекордсета из excel
    If DCount("*", "[dbo_Order Details]", "[OrderID]=" & rs.Fields(0) & " and [ProductID]=" & rs.Fields(1)) = 0 Then
    rs2.AddNew
    rs2.Fields("OrderID") = rs.Fields("OrderID") '0
    rs2.Fields("ProductID") = rs.Fields("ProductID") '1
    rs2.Fields("UnitPrice") = rs.Fields("UnitPrice") '2
    rs2.Fields("Quantity") = rs.Fields("Quantity") '3
    rs2.Fields("Discount") = rs.Fields("Discount") '3
    rs2.Update
    End If
    If i < rs.RecordCount Then rs.MoveNext 'если переменная цикла не добралась до конца то двигаем рекордсет eXcel на следующую запись
    Next i
    End If
    Debug.Print "var Noobs Итого - " & Timer - t
    rs2.Close
    rs.Close
    Set rs = Nothing
    Set rs2 = Nothing
End Sub

Sub var_1()
    Dim con As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim r1 As ADODB.Recordset
    Dim r2 As ADODB.Recordset
    Dim r3 As ADODB.Recordset
    Dim s$, t1#, t2#, i&
    
    t1 = Timer
    Set r1 = New ADODB.Recordset
    r1.Open "select * from orderdetails", CurrentProject.AccessConnection, adOpenForwardOnly, adLockReadOnly
    
    Set con = New ADODB.Connection
    s = "Provider=SQLOLEDB.1;Data Source=RadioX\FData;Database=northwind;User ID=sa;Password=0"
    con.CursorLocation = adUseClient
    con.Open s
    
    s = "select 1 from [order details] where orderid=? and productid=?"
    Set cmd = New ADODB.Command
    cmd.CommandType = adCmdText
    cmd.CommandText = s
    cmd.ActiveConnection = con
    
    Set r2 = New ADODB.Recordset
    r2.Open "select * from [order details] where 1=0 ", con, adOpenStatic, adLockBatchOptimistic
    Set r2.ActiveConnection = Nothing
    r2.Fields("orderid").Properties("Optimize") = True
    r2.Fields("productid").Properties("Optimize") = True
    
    Set r3 = New ADODB.Recordset
    
    Do Until r1.EOF
        cmd.Parameters(0) = r1!OrderID
        cmd.Parameters(1) = r1!productid
        r3.Open cmd, , adOpenStatic, adLockReadOnly
        If r3.EOF Then
            r2.AddNew
            r2!OrderID = r1!OrderID
            r2!productid = r1!productid
            r2!UnitPrice = r1!UnitPrice
            r2!Quantity = r1!Quantity
            r2!Discount = r1!Discount
        End If
        r3.Close
        r1.MoveNext
     Loop
    Debug.Print "Var 1 Поиск - ", Timer - t1
    t2 = Timer
    
    Set r2.ActiveConnection = con
    r2.UpdateBatch
    
    On Error Resume Next
    r3.Close: Set r3 = Nothing
    r1.Close: Set r1 = Nothing
    r2.Close: Set r2 = Nothing
    
    Set cmd = Nothing
    con.Close: Set cmd = Nothing
    Debug.Print "Var 1 Вставка - ", Timer - t2
    Debug.Print "Var 1 Итого - ", Timer - t1
End Sub

Sub var_2()
    Dim con As ADODB.Connection
    Dim r1 As ADODB.Recordset
    Dim r2 As ADODB.Recordset
    Dim r3 As ADODB.Recordset
    Dim s$, t1#, t2#, i&
    
    t1 = Timer
    Set r1 = New ADODB.Recordset
    r1.Open "select * from orderdetails", CurrentProject.AccessConnection, adOpenForwardOnly, adLockReadOnly
    
    Set con = New ADODB.Connection
    s = "Provider=SQLOLEDB.1;Data Source=RadioX\FData;Database=northwind;User ID=sa;Password=0"
    con.CursorLocation = adUseClient
    con.Open s
    
    s = "select * from [order details] where 1 = 0"
    Set r2 = New ADODB.Recordset
    r2.Open s, con, adOpenStatic, adLockBatchOptimistic
    Set r2.ActiveConnection = Nothing
    
    s = "select * from [order details]"
    Set r3 = New ADODB.Recordset
    r3.Open s, con, adOpenStatic, adLockBatchOptimistic
    Set r3.ActiveConnection = Nothing
    r3.Fields("orderid").Properties("Optimize") = True
    r3.Fields("productid").Properties("Optimize") = True
    
    Do Until r1.EOF
        r3.Filter = ""
        r3.Filter = "orderid=" & r1!OrderID & " and productid=" & r1!productid
        If r3.EOF Then
            r2.AddNew
            r2!OrderID = r1!OrderID
            r2!productid = r1!productid
            r2!UnitPrice = r1!UnitPrice
            r2!Quantity = r1!Quantity
            r2!Discount = r1!Discount
        End If
        r1.MoveNext
     Loop
    Debug.Print "var 2 Поиск - ", Timer - t1
    t2 = Timer
    
    Set r2.ActiveConnection = con
    r2.UpdateBatch
    
    On Error Resume Next
    r1.Close: Set r1 = Nothing
    r2.Close: Set r2 = Nothing
    r3.Close: Set r3 = Nothing
    con.Close: Set con = Nothing
    Debug.Print "var 2 Вставка - ", Timer - t2
    Debug.Print "var 2 Итого - ", Timer - t1
End Sub

Sub data_prepare()
    Dim con As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim r As ADODB.Recordset
    Dim s$
    
    Set con = New ADODB.Connection
    s = "Provider=SQLOLEDB.1;Data Source=RadioX\FData;Database=northwind;User ID=sa;Password=0"
    con.CursorLocation = adUseClient
    con.Open s
    
    s = "delete from [order details] where orderid=? and productid=?"
    Set cmd = New ADODB.Command
    cmd.CommandType = adCmdText
    cmd.CommandText = s
    cmd.ActiveConnection = con
    
    Set r = New ADODB.Recordset
    r.Open "select * from [order details] ", con, adOpenStatic, adLockBatchOptimistic
    Set r.ActiveConnection = Nothing
    Do Until r.EOF
        If r.AbsolutePosition / 2 = r.AbsolutePosition \ 2 Then
            With cmd
                .Parameters(0) = r!OrderID
                .Parameters(1) = r!productid
                .Execute
            End With
        End If
        r.MoveNext
     Loop
    On Error Resume Next
    r.Close: Set r = Nothing
    Set cmd = Nothing
    con.Close: Set cmd = Nothing
End Sub


На моем компе получил такие результаты:
+
var Noobs Итого - 6.20375000000058
*******************
Var 1 Поиск -                0.781499999997322 
Var 1 Вставка -              0.484875000001921 
Var 1 Итого -                1.26587499999732 
*******************
var 2 Поиск -                0.203125 
var 2 Вставка -              0.515749999998661 
var 2 Итого -                0.71875 


Может, кто-нибудь покажет более быстрый способ?
Predeclared
Дата: 05.02.2016 18:09:56
Не знаю, насколько это изменит общее время в целом, но:
если попробовать кешировать ссылки на поля рекордсетов?

В "чистом и частном" случае, при добавлении в родную табличку с 10 полями 32000 записей (Jet, DAO)
разница по времени почти в два раза.
Можно и так
Дата: 05.02.2016 18:28:10
кешировать

Это как?
Predeclared
Дата: 05.02.2016 18:36:04
Вот привычная запись, где (в цикле) происходит поиск нужного поля в коллекции полей:
+
Public Sub First()
    Dim l As Long
    With CurrentDb.OpenRecordset("Select * from Items1", , dbAppendOnly)
        For l = 1& To 32000&
            .AddNew
            .Fields("fld1") = l
            .Fields("fld2") = l
            .Fields("fld3") = l
            .Fields("fld4") = l
            .Fields("fld5") = l
            .Fields("fld6") = l
            .Fields("fld7") = l
            .Fields("fld8") = l
            .Fields("fld9") = l
            .Update
        Next l
        .Close
    End With
End Sub


Вот с кешированием полей, вынесенным из цикла:
+
Public Sub Second()
    Dim l As Long
    Dim fld1 As DAO.Field
    Dim fld2 As DAO.Field
    Dim fld3 As DAO.Field
    Dim fld4 As DAO.Field
    Dim fld5 As DAO.Field
    Dim fld6 As DAO.Field
    Dim fld7 As DAO.Field
    Dim fld8 As DAO.Field
    Dim fld9 As DAO.Field
    
    With CurrentDb.OpenRecordset("Select * from Items2", , dbAppendOnly)
        Set fld1 = .Fields("fld1")
        Set fld2 = .Fields("fld2")
        Set fld3 = .Fields("fld3")
        Set fld4 = .Fields("fld4")
        Set fld5 = .Fields("fld5")
        Set fld6 = .Fields("fld6")
        Set fld7 = .Fields("fld7")
        Set fld8 = .Fields("fld8")
        Set fld9 = .Fields("fld9")
    
        For l = 1& To 32000&
            .AddNew
            fld1 = l
            fld2 = l
            fld3 = l
            fld4 = l
            fld5 = l
            fld6 = l
            fld7 = l
            fld8 = l
            fld9 = l
            .Update
        Next l
        .Close
    End With
End Sub
Можно и так
Дата: 06.02.2016 12:45:19
кешировать

В моем примере не помогло, да и не должно было - исп. updatebatch.
Но спасибо за добавление к коллекции способов, ускоряющих работу с рекордсетами.

Хотя, кажется, Вы об этом уже говорили в прежней своей ипостаси.
Predeclared
Дата: 06.02.2016 13:24:20
Можно и так
... В моем примере не помогло, да и не должно было - исп. updatebatch...

Применение пакетного обновления совершенно не отменило беганий по коллекциям в поисках нужного поля,
просто выигрыш по времени оказался мизерным, по сравнению с другими временными затратами.