Обновление больших наборов записей в нормализованную базу данных Access - PullRequest
0 голосов
/ 21 апреля 2011

Я использую Access 2007 для создания нормализованной базы данных, чтобы заменить ту, которая использует пару плоских таблиц с несколькими полями. Моя проблема в том, что я часто получаю листы Excel с большим количеством обновлений, которые я импортирую в виде таблиц, а затем присоединяюсь к существующей таблице для выполнения обновлений. Но теперь это будет сложнее, когда я нормализуюсь. Вот пример кода VBA для обновления значения:

function updateBoxCategory(boxID As String, newCategory As String) as long

Dim boxKey As Long
Dim catKey As Long
Dim db As Database
Dim ustr As String

Set db = CurrentDb


boxKey = getKey(db, "boxes", "boxID", boxID)

'exit if box not found'
If boxKey = 0 Then
 Exit Sub
End If

catKey = getKey(db, "categories", "category", newCategory)

'exit if category not found'
If catKey = 0 Then
 Exit Sub
End If

ustr = "update boxes set catKey=" & catKey & " where ID=" & boxKey
db.Execute ustr, dbFailOnError

End Sub

getKey ("dbObject", "table", "field", "value") возвращает первичный ключ уникального значения.

Я обеспокоен тем, что если, например, мне нужно обновить 100 000 записей, мне придется циклически выполнять запросы этой процедуры по каждой записи, а это значит, что я буду выполнять 100 000 запросов select для таблицы с 100 000 записей - и это беспокоит меня из-за проблем с производительностью, хотя все проиндексировано.

По сути, мой вопрос таков: является ли этот код подходящим способом обработки обновлений нормализованных данных?

Ответы [ 2 ]

1 голос
/ 21 апреля 2011

В SQL мы избегаем такого процедурного кода в пользу решений на основе множеств. Идея в том, что вы сообщаете оптимизатору в одном выражении SQL, чего вы хотите достичь, и он (а не вы) решает, как лучше всего это сделать.

Я предполагаю, что у вас есть промежуточная таблица (может быть в Excel, может быть связанной таблицей) со столбцами для реальных ключей, boxID и newCategory. Однако вы не можете использовать эти значения в вашей таблице Boxes, поскольку в схеме есть некоторая косвенность: вам нужно найти значения «суррогатных» ключей с помощью справочных таблиц (я настоятельно рекомендую вам исправить эту «особенность» в ваш дизайн, так что вы можете просто использовать реальные значения ключа :)

Вот как это можно сделать с помощью стандартного SQL: 2003 (например, работает на SQL Server 2008):

MERGE INTO Boxes
   USING (
          SELECT B1.ID AS boxKey, C1.ID AS catKey
            FROM YourStagingTable AS S1
                 INNER JOIN Boxes AS B1
                    ON B1.boxID = S1.boxID
                 INNER JOIN Categories AS C1
                    ON C1.category = S1.NewCategory
         ) AS source (
                      boxKey, catKey
                     )
      ON Boxes.ID = source.boxKey
WHEN MATCHED THEN
   UPDATE
      SET catKey = source.catKey;

Вот эквивалент в стандарте SQL-92, который требовал использования скалярных подзапросов:

UPDATE Boxes
   SET catKey = 
                (
                 SELECT C1.ID AS catKey
                   FROM YourStagingTable AS S1
                         INNER JOIN Boxes AS B1
                            ON B1.boxID = S1.boxID
                         INNER JOIN Categories AS C1
                            ON C1.category = S1.NewCategory
                  WHERE Boxes.ID = B1.ID
                ) 
 WHERE EXISTS (
               SELECT * 
                 FROM YourStagingTable AS S1
                       INNER JOIN Boxes AS B1
                          ON B1.boxID = S1.boxID
                WHERE Boxes.ID = B1.ID
              );

К сожалению, Access (Jet, ACE, что угодно) не поддерживает ни один из современных стандартов SQL даже на начальном уровне (если что-то из 1992 года действительно можно считать «современным» :) Скорее, Access настаивает на том, чтобы вы использовали его уместность UPDATE..JOIN синтаксис, с которым я никогда не был по-настоящему знаком. Надеемся, что вышеизложенное укажет вам правильное направление для доступа (или, возможно, кто-то может отредактировать этот ответ, чтобы добавить эквивалентный диалект доступа ...?)

0 голосов
/ 26 апреля 2011

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

То есть вместо запуска SQL UPDATE для каждой строки, запустите одну для каждой столбца.

Вот код, который делает это.Предполагается, что две сравниваемые таблицы имеют общий первичный ключ и имеют одинаковые имена полей (хотя вы можете написать запрос, в котором поля одной псевдонима совпадают с именами в другой):

  Public Function UpdateTableData(ByVal strSourceTable As String, _
       ByVal strTargetTable As String, ByVal strJoinField As String, _
       ByRef db As DAO.Database, Optional ByVal strExcludeFieldsList As String, _
       Optional ByVal strUpdatedBy As String = "Auto Update", _
       Optional strAdditionalCriteria As String) As Boolean
    Dim strUpdate As String
    Dim rsFields As DAO.Recordset
    Dim fld As DAO.Field
    Dim strFieldName As String
    Dim strNZValue As String
    Dim strSet As String
    Dim strWhere As String
    Dim STR_QUOTE = """"

    strUpdate = "UPDATE " & strTargetTable & " INNER JOIN " & strSourceTable _
       & " ON " & strTargetTable & "." & strJoinField & " = " _
       & strSourceTable & "." & strJoinField
    ' if the fields don't have the same names in both tables,
    '   create a query that aliases the fields to have the names of the
    '   target table
    ' if the source table is in a different database and you don't
    '   want to create a linked table, create a query and specify
    '   the external database as the source of the table
    ' alternatively, for strTargetTable, supply a SQL string with
    '   the external connect string
    Set rsFields = db.OpenRecordset(strSourceTable)
    For Each fld In rsFields.Fields
      strFieldName = fld.Name
      If strFieldName <> strJoinField Or (InStr(", " & strExcludeFieldsList _
           & ",", strFieldName & ",") <> 0) Then
         Select Case fld.Type
           Case dbText, dbMemo
             strNZValue = "''"
           Case Else
             strNZValue = "0"
         End Select
         strSet = " SET " & strTargetTable & "." & strFieldName _
           & " = varZLSToNull(" & strSourceTable & "." & strFieldName & ")"
         strSet = strSet & ", " & strTargetTable & ".Updated = #" & Date & "#"
         strSet = strSet & ", " & strTargetTable & ".UpdatedBy = " _
           & STR_QUOTE & strUpdatedBy & STR_QUOTE
         strWhere = " WHERE Nz(" & strTargetTable & "." & strFieldName _
           & ", " & strNZValue & ") <> Nz(" & strSourceTable & "." _
           & strFieldName & ", " & strNZValue & ")"
         If db.TableDefs(strTargetTable).Fields(fld.Name).Required Then
            strWhere = strWhere & " AND " & strSourceTable & "." _
              & strFieldName & " Is Not Null"
         End If
         If Len(strAdditionalCriteria) > 0 Then
            strWhere = strWhere & " AND " & strAdditionalCriteria
         End If
         Debug.Print strUpdate & strSet & strWhere
         Debug.Print SQLRun(strUpdate & strSet & strWhere, dbLocal) & " " _
           & strFieldName & " updated."
      End If
    Next fld
    Debug.Print dbLocal.OpenRecordset("SELECT COUNT(*) FROM " _
      & strTargetTable & " WHERE Updated=#" & Date _
      & "# AND UpdatedBy=" & STR_QUOTE & strUpdatedBy & STR_QUOTE)(0) _
      & " total records updated in " & strTargetTable
    rsFields.Close
    Set rsFields = Nothing
    UpdateTableData = True
  End Function

Я использую варианты этого кода уже более десяти лет, и это намного быстрее и эффективнее, чем построчно.

Обратите внимание, что в него заложены некоторые предположения (например, тот факт, что в каждой таблице есть поля Updated и updatedBy).Но это должно помочь вам начать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...