Excel VBA, имитирующий «не в» функциональности SQL

#excel #vba

#excel #vba

Вопрос:

Все —

У меня есть Excel на 2 листа.

Лист 1 состоит из трех столбцов (имя, дата, значение), лист 2 — это имя.

Я хочу написать скрипт VBA, который отображает все данные листа 1, в которых НЕТ ни одного поля имени, указанного на листе 2, в любом месте листа 1 (имя может отображаться в разных столбцах, поэтому в идеале он будет искать все ячейки на листе 1), чтобы появиться на листе 3

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

примерные данные

Комментарии:

1. Range.AutoFilter может быть полезно.

Ответ №1:

Если у вас есть Excel 365, вы можете использовать формулы динамического массива

 =LET(Names,FILTER(Sheet1!$C:$E,Sheet1!$C:$C<>""),FILTER(Names,ISERROR(MATCH(INDEX(Names,,1),Sheet2!$G:$G,0))))
  

Пример:

Данные (Лист1)

введите описание изображения здесь

Список исключений (Лист2)

введите описание изображения здесь

Результат

введите описание изображения здесь

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

Комментарии:

1. Черт, похоже, что с точки зрения формулы это сработало бы, но у меня нет office 365. Я предпочитаю vba, потому что у меня есть второй набор кода, который сохраняется как csv в определенном каталоге, я пытался изменить отправленное исходное решение, но не уверен, почему это решение для защиты от фильтров не работает

Ответ №2:

Без Excel 365. Я бы рекомендовал UDF

 Function FilterList(ByVal Data As Range, ByVal Exclusion As Range) As Variant
    Dim Res As Variant
    Dim Dat As Variant
    Dim Excl As Variant
    Dim rw As Long
    Dim idx As Long
    Dim cl As Long
    Dim ExcludeIt As Variant
    Dim Cols As Long
    Dim TopRow As Long
    
    ReDim Res(1 To Application.Caller.Rows.Count, 1 To Application.Caller.Columns.Count)
    
    If IsEmpty(Data.Cells(1, 1)) Then
        TopRow = Data.Cells(1, 1).End(xlDown).Row
        Set Data = Data.Resize(Data.Rows.Count - TopRow).Offset(TopRow - 1)
    End If
    
    If IsEmpty(Data.Cells(Data.Rows.Count, 1)) Then
        Set Data = Data.Resize(Data.Cells(Data.Rows.Count, 1).End(xlUp).Row - Data.Row   1)
    End If
    
    Dat = Data.Value
    Excl = Exclusion.Columns(1).Value
    
    Cols = Application.Min(UBound(Dat, 2), UBound(Res, 2))
    
    idx = 0
    For rw = 1 To UBound(Dat, 1)
        ExcludeIt = Application.Match(Dat(rw, 1), Excl, 0)
        If IsError(ExcludeIt) Then
            idx = idx   1
            For cl = 1 To Cols
                Res(idx, cl) = Dat(rw, cl)
            Next
        End If
    Next
    For rw = 1 To UBound(Res, 1)
        For cl = IIf(rw <= idx, UBound(Dat, 2)   1, 1) To UBound(Res, 2)
            Res(rw, cl) = vbNullString
        Next
    Next
    FilterList = Res
End Function
  

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

 =FilterList(Sheet1!$C:$E,Sheet2!$G:$G)
  

Ответ №3:

Добро пожаловать в Stack Overflow!

Вы не сказали, где начинаются исходная таблица и таблица критериев, или куда поместить результат «антифильтра». Я написал этот код, исходя из предположения, что все они начинаются с первой ячейки рабочего листа, A1:

 Sub AntiFilter()
Dim aSource As Range, aCriteria As Range, oCell As Range, oTarget As Range, countCells As Long
    Set aSource = Worksheets("Sheet1").Range("A1").CurrentRegion
    countCells = aSource.Columns.Count
    Set aCriteria = Worksheets("Sheet2").Range("A1").CurrentRegion
    Set oTarget = Worksheets("Sheet3").Range("A1")
    aSource.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=aCriteria, Unique:=False
    For Each oCell In Application.Intersect(aSource, aSource.Columns(1))
        If oCell.RowHeight < 1 Then
            oCell.Resize(1, countCells).Copy Destination:=oTarget
            Set oTarget = oTarget.Offset(1, 0)
        End If
    Next oCell
    On Error Resume Next
    aSource.Worksheet.ShowAllData
    On Error GOTO 0
End Sub
  

Демонстрационный макрос антифильтра

Рабочая тетрадь с макросом, тестовыми данными и примерами критериев выбора на листе 2

Если макрос работает не так, как ожидалось, убедитесь, что в вашей книге есть листы с именами Sheet1, Sheet2 и Sheet3, а диапазон исходных данных и диапазон критериев начинаются с ячеек A1. Если это не так, внесите необходимые изменения в текст макроса: EditMacro.png

Комментарии:

1. Спасибо за быстрый ответ! Вставка данных работает хорошо, но логика антифильтра фактически ничего не удаляет, она просто вставляет весь лист1 в Лист3. Есть мысли?

2. @Amateurhour35 «У меня нет Office 365» — возможно, это причина. Маловероятно, но возможно. Какой Excel у вас есть? Надеюсь, не 2003, ваш скриншот выглядит более современно.

3. Быстрый вопрос, при запуске этого в тандеме с другими моими макросами я теперь получаю эту ошибку: ошибка времени выполнения ‘1004’: метод ‘ShowAllData’ объекта ‘_Worksheet’ не удался. Есть идеи, почему введение этого кода вызвало это?

4. @Amateurhour35 Дело в том, что ShowAllData работает хорошо, если на листе есть скрытые строки (скрытые вручную или в результате фильтрации). Если таких строк нет, это ошибка, о которой вы пишете. Самый простой и надежный способ — добавить On Error Resume Next и On Error GoTo 0 обойти строку aSource.Worksheet.ShowAllData

5. Не уверен, что я понимаю — на листе нет скрытых строк. При следующем возобновлении работы с ошибкой и при ошибке GoTo 0 shoudl куда идти? В VBA он выделяет конкретную строку aSource. Рабочий лист. ShowAllData, мне нужно очистить фильтры или что-то еще? Извините за вопросы уровня newb