#excel #vba
#excel #vba
Вопрос:
У меня есть 99 столбцов в одной таблице под названием tbl_raw
. Мне нужно скопировать 96 из этих столбцов в другую таблицу с точно такими же названиями заголовков, но они переставлены в другом порядке. Какой наиболее эффективный способ сделать это?
Единственный способ, который я знал, был:
raw_data.Range("tbl_raw[EMPLOYEE]").Copy
processed_data.Range("tbl_processed[EMPLOYEE]").PasteSpecial
Однако для этого потребовалось бы много кода (96 * 2 = 192 строки), и я не был уверен, есть ли более эффективный способ сделать это.
Я пытался использовать https://www.thespreadsheetguru.com/blog/2014/6/20/the-vba-guide-to-listobject-excel-tables, но я также не смог найти способ сделать это с этой информацией.
Любые рекомендации были бы высоко оценены.
Комментарии:
1. Выполните цикл по ListObject(«tbl_processed»). Изменение заголовка для сбора каждого соответствующего столбца в ListObject(«tbl_raw»). Изменение заголовка. Неясно, добавляете ли вы данные в tbl_processed или заменяете их.
2. Я заменяю данные. Первое, что я делаю, это очищаю содержимое таблицы, за исключением первой строки данных, поскольку я мог бы добавить несколько столбцов с некоторыми формулами.
3. Я не уверен, как достичь вашего решения. Я не самый лучший специалист по VBA, но мне удалось создать несколько сценариев, и я учусь на практике. Будет ли это что-то вроде: `Если tbl_raw.HeaderRowRange = tbl_processed. ЗАТЕМ измените заголовок… Я не уверен точно, как динамически подойти к этому.
Ответ №1:
Избегайте копирования столбцов ListObject и используйте прямую передачу значений.
Option Explicit
Sub raw2processed()
Dim lc As Long, mc As Variant, x As Variant
Dim raw_data As Worksheet, processed_data As Worksheet
Dim raw_tbl As ListObject, processed_tbl As ListObject
Set raw_data = Worksheets("raw")
Set processed_data = Worksheets("processed")
Set raw_tbl = raw_data.ListObjects("tbl_raw")
Set processed_tbl = processed_data.ListObjects("tbl_processed")
With processed_tbl
'clear target table
On Error Resume Next
.DataBodyRange.Clear
.Resize .Range.Resize(raw_tbl.ListRows.Count 1, .ListColumns.Count)
On Error GoTo 0
'loop through target header and collect columns from raw_tbl
For lc = 1 To .ListColumns.Count
Debug.Print .HeaderRowRange(lc)
mc = Application.Match(.HeaderRowRange(lc), raw_tbl.HeaderRowRange, 0)
If Not IsError(mc) Then
x = raw_tbl.ListColumns(mc).DataBodyRange.Value
.ListColumns(lc).DataBodyRange = x
End If
Next lc
End With
End Sub
Комментарии:
1. Это отлично работает! Это было также очень быстро, намного быстрее, чем копирование и вставка. У меня есть одна проблема, из-за которой столбцы теряют свой формат. У меня есть значения ‘001’, и мне нужно, чтобы они оставались строкой, но они преобразуются в число. Есть ли способ сохранить форматирование значения при использовании метода прямой передачи значения? Я даже не начал разбирать ваш код, чтобы понять это. Я надеюсь, что смогу задать несколько уточняющих вопросов после того, как попытаюсь разобраться в этом подробно! Я действительно ценю ответ!
Ответ №2:
Вот простой пример копирования всех столбцов из одной таблицы в другую, кроме некоторых:
Dim tbl1 As ListObject, tbl2 As ListObject
Dim h As ListColumn
Set tbl1 = ActiveSheet.ListObjects("Table1")
Set tbl2 = ActiveSheet.ListObjects("Table2")
'loop over the headers from the source table
For Each h In tbl1.ListColumns
'is the column name in the "excluded" list?
If IsError(Application.Match(h.Name, Array("col10", "col11"), 0)) Then
'ok to copy...
h.DataBodyRange.Copy tbl2.ListColumns(h.Name).DataBodyRange(1)
End If
Next h
Комментарии:
1. Я думаю, что я понимаю это. Я пытался выполнить этот код в своем скрипте, но я не мог заставить его работать. Я предполагаю, что
Application.Match
это то, что проверяет имя столбца в tbl1. Однако я не вижу, где это сопоставляется с tbl2. Я также не понимаю функцию ‘Array’ там.Dim h As ListColumn For Each h In tbl_raw.ListColumns If IsError(Application.Match(h.Name, Array("col10", "col11"), 0)) Then h.DataBodyRange.Copy tbl_imd.ListColumns(h.Name).DataBodyRange(1) End If Next h
Спасибо за ответ и будем признательны за любую дальнейшую помощь!2.
Array()
просто создает массив из аргументов — он используется, поскольку для сопоставления нужен массив для сопоставления. Для соответствия таблице 2 предполагается, что имена столбцов совпадают, иtbl2.ListColumns(h.Name).DataBodyRange(1)
указывается место назначения вставки в качестве первой ячейки в столбце Table2 с заголовкомh.Name
3. Я думаю, что op хочет скопировать столбцы, в которых совпадают заголовки, а не те, которые этого не делают. В вашем массиве поиска также должно быть 96 элементов, чтобы его было проще использовать
tbl1.HeaderRowRange
.4. Я думаю, я понял, о чем спрашивал OP, и моему массиву нужны только три заголовка «не копировать»…
Ответ №3:
ForEach /For — это волшебство работы с массивами и коллекциями. Есть способы сделать следующий код более эффективным, но я думаю, что это может помешать пониманию происходящего. Прошло около 6 месяцев или около того с тех пор, как я в последний раз работал с VBA, но я считаю, что это должно сработать. Я предлагаю перейти к вашим локальным файлам и понаблюдать за ними, чтобы увидеть, что происходит. Если возникают проблемы с назначением переменных, возможно, потребуется заменить ‘Let’ на ‘Set’. Следующий код:
'// PROBLEM:
'// Copy data from one list to a second list.
'// Both lists have the same column names and the same number of columns.
'// Copy data based on the column name.
'// Modify to return a custom source-destination association.
Private Function GetColumnTranslations(zLeftColumns As ListColumns, zRightColumns As ListColumns) As Variant
Dim zReturn(,) As Variant
ReDim zReturn(0 To zLeftColumns.Count As Long, 0 To 1 As Long)
Dim zReturnOffset As Long '// Specifies what index we are working at during our ForEach interations.
Dim zLeftVar As Variant
Dim zRightVar As Variant
ForEach zLeftVar in zLeftColumns
'// Go through each 'left' column to Find the first 'right' column that matches the name of the 'left' column.
'// Only the first 'right' column with a matching name will be used. Issue is solved with another ForEach, but beyond forum question's scope.
ForEach zRightVar in zRightColumns
If zLeftVar.Name = zRightVar.Name Then
'// Store the association and exit the nested ForEach.
Let zReturn(zReturnOffset, 0) = zLeftVar.Range.Column '// Source.
Let zReturn(zReturnOffset, 1) = zRightVar.Range.Column '// Destination.
Let zReturnOffset = zReturnOffset 1
Exit ForEach
End If
Next zRightVar
Next zLeftVar
'// Assign return value.
Let GetColumnTranslations = zReturn
End Function
'// Take each source row and copy the value to a new destination row.
'// New rows are added to the end of the destination list.
Public Sub CopyList(zSourceList As ListObject, zDestinationList As ListObject)
Dim zColumnTranslations As Variant '// Will be 2-dimensional array.
Dim zTranslationVar As Variant '// Will be array of 2 elements.
Let zColumnTranslations = GetColumnTranslations(zSourceList.Columns, zDestinationList.Columns)
Dim zSourceRowVar As Variant '// Will translate to Range.
Dim zDestinationRow As Range
'// Every source row needs copied to a new row in destination.
ForEach zSourceRowVar in zSourceList.Rows
Set zDestinationRow = zDestinationList.Rows.Add.Range
ForEach zTranslationVar in zColumnTranslations
'// Value may copy formula.
Let zDestinationRow(0,zTranslationVar(1)).Value = zSourceRowVar(0,zTranslationVar(0)).Value
Next zTranslationVar
Next zSourceRowVar
End Sub
Комментарии:
1. Извините, я немного работал над этим перед вышеприведенными публикациями.
2. Это здорово! Я действительно ценю комментарии и разбивку. Я действительно хочу узнать как можно больше, и это мне очень помогает! Я собираюсь начать разбирать и этот код, чтобы посмотреть, как все работает. Как бы вы сказали, что это решение соотносится с решением, опубликованным @user11198948?
3. Мой код не очищает целевую таблицу. Мой код добавляет строки по мере необходимости. Я не знаю, работает ли код пользователя 11198948 быстрее (в настоящее время у меня нет Microsoft Office), но я закодировал свой, чтобы разрешить изменения, и поэтому вы можете понять, что происходит (я самоучка и помню трудности). GetColumnTranslations позволяет возвращать пользовательский массив ассоциаций.
4. (Я все еще изучаю этот веб-сайт.) Чтобы скопировать форматирование, попробуйте добавить строку под комментарием «Значение может копировать формулу» => Пусть zDestinationRow(0,zTranslationVar(1)).Style = zSourceRowVar(0,zTranslationVar(0)). Стиль
5. Что касается места для изучения лучших практик … изучите, что вам нужно, используя » learn.microsoft.com/en-us/office/vba/api » поскольку именно здесь я извлек информацию сверху. Комментируйте все, чтобы вы — и другие — могли быстро прочитать, что он делает, даже если вы повторяете это слово в слово. Сохраняйте простоту. Не бойтесь разбивать вещи на части, когда это становится сложным (как я сделал в вышеупомянутом решении). Это то, что я делаю.