#vba #loops #search #copy #copy-paste
#excel #vba #циклы #Поиск #копировать-вставить
Вопрос:
У меня есть лист с данными более 30 000 строк, и я хочу скопировать все строки в новый файл Excel, если столбец определенной (например, «B») строки содержит определенные значения (список этих значений будет в другом листе «Code»). Так, например:
- На листе «Код» у меня есть десять (может быть даже 30) разных чисел (критериев) в столбце «A».
- Запустите поиск, чтобы скопировать все строки (в новый файл Excel), содержащие любое из этих чисел, из листа «Код» в столбце «A».
Пока не очень хорошо разбираюсь в VBA, но работаю над этим 🙂 Спасибо всем за помощь!
Комментарии:
1. хорошим методом копирования / вставки является использование вместо объектов диапазона (например, диапазон источника и диапазон назначения) и передача значений между диапазонами. Для критериев col A я рекомендую вам заглянуть в диапазон. Автофильтр. наконец, если вы не разбираетесь в VBA, по крайней мере, покажите свою работу или попробуйте 🙂
Ответ №1:
Фильтровать по нескольким критериям и экспортировать в другую книгу
- Просто чтобы продемонстрировать, почему вопрос не так хорошо принят. Это своего рода 50 вопросов в одном.
- Отрегулируйте значения в разделе константы, и все должно быть готово.
- «Лист2» на самом деле является вашим рабочим листом «Code». «Лист1» — это первый рабочий лист.
Код
Option Explicit
Sub exportMultiToWorkbook()
' Error Handler
' Initialize error handling.
Const procName As String = "exportMultiToWorkbook"
On Error GoTo clearError ' Turn on error trapping.
' Constants
' Criteria
Const critName As String = "Sheet2"
Const critFirstCell As String = "A2"
' Source
Const srcName As String = "Sheet1"
Const srcFirstCell As String = "A1"
Const srcCritColumn As Long = 2
Dim wbs As Workbook
Set wbs = ThisWorkbook ' The workbook containing this code.
' Target
Const tgtFirstCell As String = "A1"
Dim tgtPath As String
' The same path as Source Workbook ('wbs'). Change if necessary.
tgtPath = wbs.Path amp; Application.PathSeparator amp; "Criteria"
' Other
Dim Success As Boolean
Dim AfterCop As Boolean
' Criteria
' Define Criteria Worksheet ('crit').
Dim crit As Worksheet
Set crit = wbs.Worksheets(critName)
' Define Criteria First Cell Range ('fcel').
Dim fcel As Range
Set fcel = crit.Range(critFirstCell)
' Define Criteria Processing Column Range ('pcr').
Dim pcr As Range
Set pcr = fcel.Resize(crit.Rows.Count - fcel.Row 1)
' Define Criteria Last Non-Empty Cell Range ('lcel').
Dim lcel As Range
Set lcel = pcr.Find(What:="*", _
LookIn:=xlFormulas, _
SearchDirection:=xlPrevious)
' Validate Last Non-Empty Cell Range.
If lcel Is Nothing Then
GoTo ProcExit
End If
' Define Criteria Column Range ('cr').
Dim cr As Range
Set cr = crit.Range(fcel, lcel)
' Write values from Criteria Column Range to 1D Criteria Array ('Criteria'),
' probably using Criteria 2D Array ('Crit2D').
Dim Criteria As Variant
Dim i As Long
If cr.Rows.Count > 1 Then
' Criteria Column Range has multiple cells (rows).
' Write values from Criteria Range to Criteria 2D Array.
Dim Crit2D As Variant
Crit2D = cr.Value
' Write values from Criteria 2D Array to 1D Criteria Array.
ReDim Criteria(1 To UBound(Crit2D, 1))
For i = 1 To UBound(Crit2D)
Criteria(i) = CStr(Crit2D(i, 1)) ' AutoFilter prefers strings.
Next i
Else
' Criteria Column Range has one cell (row) only.
' Write the only value from Criteria Column Range to Criteria Array.
ReDim Criteria(1)
Criteria(1) = CStr(cr.Value) ' AutoFilter prefers strings.
End If
' Source
' Define Source Worksheet ('src').
Dim src As Worksheet
Set src = wbs.Worksheets(srcName)
' Define Source First Cell Range ('fcel').
Set fcel = src.Range(srcFirstCell)
' Define Source Last Cell Range ('lcel').
Set lcel = fcel.End(xlToRight).End(xlDown)
' Define Copy Range
Dim cop As Range
Set cop = src.Range(fcel, lcel)
' Turn off screen updating.
Application.ScreenUpdating = False
' Turn off filter, if on.
If src.FilterMode Then
cop.AutoFilter
End If
' Filter data. AutoFilter prefers the whole range.
cop.AutoFilter Field:=srcCritColumn, _
Criteria1:=Criteria, _
Operator:=xlFilterValues
' Enable the use of 'SafeExit' instead of 'ProcExit' after possible error.
AfterCop = True
' Target
' Add a new workbook.
With Workbooks.Add
' Copy Copy Range to the first sheet of a new workbook.
cop.Copy .Worksheets(1).Range(tgtFirstCell)
' I prefer to save this way; always a different file.
tgtPath = tgtPath amp; " " amp; Format(Now, "YYYYMMDD_HHMMSS")
.SaveAs Filename:=tgtPath, _
FileFormat:=xlOpenXMLWorkbook ' i.e. ".xlsx"
' If you prefer the file to have the same name and for it to be
' overwritten without Excel complaining, then rather use the following:
' Application.DisplayAlerts = False
' .SaveAs Filename:=tgtPath, _
' FileFormat:=xlOpenXMLWorkbook ' i.e. ".xlsx"
' Application.DisplayAlerts = True
.Close
End With
Success = True
SafeExit:
' Source
' Turn off filter.
cop.AutoFilter
wbs.Saved = True
' Turn on screen updating.
Application.ScreenUpdating = True
ProcExit:
' Inform user.
If Success Then
MsgBox Prompt:="Created file '" amp; tgtPath amp; "'.", _
Buttons:=vbInformation, _
Title:="Multiple Criteria Filter - Success"
Else
MsgBox Prompt:="Could not finish task.", _
Buttons:=vbCritical, _
Title:="Multiple Criteria Filter - Fail"
End If
Exit Sub
clearError:
Debug.Print "'" amp; procName amp; "': " amp; vbLf _
amp; " " amp; "Run-time error '" amp; Err.Number amp; "':" amp; vbLf _
amp; " " amp; Err.Description
On Error GoTo 0 ' Turn off error trapping.
If Not AfterCop Then
GoTo ProcExit
Else
GoTo SafeExit
End If
End Sub
Комментарии:
1. ВАУ! Спасибо!!! Это работает. Но как, например, также включить в мой исходный лист такие вещи, как пустые строки? например, данные идут от 1 до 500 строк, затем 2-3 пустых строки, а затем продолжаются. Прямо сейчас фильтр применяется до первой пустой строки.
2. Хочу поблагодарить вас еще раз! Итак, я меняю одну строку кода, чтобы решить мою проблему, и теперь это работает! Не такое мягкое решение, как ваш код, но все же моя попытка:) ‘Определите диапазон последней ячейки источника (‘lcel’). Установите lcel = ячейки (строки. Count, «L»).End(xlUp)
Ответ №2:
Я понимаю, что вы новичок и не хочу отговаривать вас от обращения за помощью в будущем. Пожалуйста, попытайтесь задать более конкретные вопросы в будущем. Например, вы можете спросить, как узнать, соответствует ли значение одной ячейки значению любой ячейки в диапазоне ячеек. Тем не менее, у меня такое чувство, что вы не знали, с чего начать, поэтому я попробую. VBasic 2008 уже предоставил отличный ответ и фактически написал для вас код, которого вы не должны ожидать. Код VBasic 2008 великолепен, но он больше, чем вам нужно, а также немного сложен для понимания новичком. В приведенном ниже коде вам действительно нужно всего лишь изменить три строки «set» в процедуре CopyFilteredDemo.
Ниже приведен простой код, который делает несколько упрощающих предположений. Исходя из вашего запроса, я предполагаю, что это соответствует вашим потребностям. Если нет, добавьте больше конкретики. Многие из этих ограничений предположений, перечисленных ниже, легко преодолеваются, но я не хочу писать код для этого.
- Либо исходные и целевые книги одинаковы, либо они обе открыты. (Я тестировал только разные листы в одной книге, но он должен работать во всех книгах.)
- Исходные и целевые листы не совпадают. Намеренно возникает ошибка, если они совпадают.
- Целевой рабочий лист уже существует. $) Рабочий лист desitnation будет полностью очищен и перезаписан. Измените True на False в CopyFilteredDemo, чтобы передать, чтобы передать False в CopyFiltered .
- Выполняйте поиск только в первом столбце исходного диапазона для точного соответствия в диапазоне фильтров. Поскольку копируется вся строка, не имеет значения, какой столбец вы задаете в качестве первого столбца в fromRange. Просто выберите столбец, который вы хотите сравнить со значениями в filterRange.
- Если он не отфильтрован, будет скопирована вся строка рабочего листа.
- В критериях фильтрации нет дубликатов. Я не проверял это, чтобы увидеть, вызывает ли это дубликаты на целевом листе.
- Производительность не проверялась на тысячах строк. Если вы видите проблемы, сначала установите приложение.Обновление экрана = False. Включите его снова в конце. Убедитесь, что у вас есть обработка ошибок, которую можно включить в случае ошибки. В противном случае обновление экрана останется отключенным, что, как вы обнаружите, крайне нежелательно. Если это выходит за рамки вашего текущего уровня комфорта, не отключайте обновление экрана.
В качестве схемы основной процедурой является CopyFiltered, которая копирует данные с одного листа на другой. Эта процедура вызывает функцию IsInRange, которая возвращает true, если аргумент valueToFind точно соответствует значению в диапазоне, указанном аргументом RangeToSearch . Итак, при сравнении исходного диапазона (fromRange) с критериями фильтрации (filterRange) сравнивается первый столбец fromRange . fromRange не определяет, какие столбцы копируются, поскольку вы запросили скопировать целые строки. Скорее, fromRange имеет 2 цели. Во-первых, он определяет строки, из которых нужно копировать. Во-вторых, первый столбец fromRange сравнивается с filterRange для соответствия.
Я поместил в код большое количество комментариев, поэтому я надеюсь, что его относительно легко понять.
Option Explicit
' Option Explicit must be the first line of code in the module.
' It forces you to declare every variable. It may seem a nuisance
' to a beginner, but you will quickly learn its value. It will
' keep you from spelling the same variable two ways and failing
' to understand why your code failed. There are other benefits
' that you'll pick up over time, such as conserving memory and
' forcing data typing.
Public Function IsInRange(ByVal valueToFind, ByVal RangeToSearch As Range)
' If any cell in RangeToSearch = valueToFind, return True
' Else return False.
Dim x
' If valueToFind is not in RangeToSearch, expect
' error 91. That's okay, we'll handle that error
' and return False. If we get a differnt error,
' we'll raise it.
On Error GoTo EH
x = RangeToSearch.Find(valueToFind)
On Error GoTo 0
' If we made it this far, we found it!
IsInRange = True
Exit Function
EH:
If Err.Number = 91 Then
' this error is expected if valueToFind is not in RangeToSearch
IsInRange = False
Err.Clear
Else
' Unexpected error.
Err.Raise Number:=Err.Number, Source:=Err.Source _
, Description:=Err.Description
End If
End Function
Sub CopyFiltered(ByVal fromRange As Range, ByVal toRange As Range _
, ByVal filterRange As Range _
, Optional clearFirst As Boolean = True)
' Arguments:
' fromRange: the full range from which to copy
' toRange: the top left cell fromRange will be pasted to the
' top left cell of toRange. The size of toRange
' is irrelevant. Only the top left cell is used
' for reference.
' fitlerRange: a range containing values with which to filter.
' clearFirst: if True, clear all content from range containing
' toRange before pasting new values.
Dim rng As Range, rowOffset As Integer
Dim rowNum As Integer, colNum As Integer, i As Integer
Dim errMsg As String, cell As Range
Set toRange = toRange.Cells(1, 1)
Set fromRange = fromRange.Columns(1)
' If fromRange and toRange are on the same worksheet,
' raise an exception.
If fromRange.Worksheet.Name = toRange.Worksheet.Name Then
errMsg = "fromRange and toRange cannot be on the same worksheet."
Err.Raise 1000, "CopyFiltered", errMsg
Exit Sub
End If
' Clear all content from the destination worksheet.
toRange.Worksheet.Cells.ClearContents
'
' Loop through each row of fromRange
rowOffset = -1
For i = 1 To fromRange.Rows.Count
Set cell = fromRange.Cells(i, 1)
Debug.Print cell.Address
' If the the cell in the first column of fromRange
' exaclty equals any cell in filterRange, proceed.
If IsInRange(cell.Value, filterRange) Then
' Add one to rowOffset, so we copy this row
' below the last pasted row of the sheet
' containing toRange
rowOffset = rowOffset 1
cell.EntireRow.Copy toRange.Offset(rowOffset, 0).EntireRow
End If
Next i
End Sub
Sub CopyFilteredDemo()
Dim fromRange As Range, toRange As Range, filterRange As Range
' Set our to, from and filter ranges
Set fromRange = Sheets("Sheet1").Range("c10:c40")
Set toRange = Sheets("Sheet2").Range("A2")
Set filterRange = Sheets("Sheet1").Range("B2:B6")
' Run our filtered copy procedure.
CopyFiltered fromRange, toRange, filterRange, True
End Sub