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

 

Решение стандартными формулами
Стандартными функциями это сделать весьма проблематично, т.к. заранее неизвестно сколько будет этих оценок и фамилий.. Microsoft работает над усовершенствованием Excel и теперь стало возможным сделать это и стандартными функциями. Правда, с небольшими ограничениями: сделать это могут только пользователи Excel 2019 и выше или Office 365 по подписке. В итоге счастливые обладатели новейших версий могут использовать достаточно несложные формулы:
=ОБЪЕДИНИТЬ("; ";1;ФИЛЬТР(B2:B20;A2:A20=A2;""))
=TEXTJOIN("; ",1,FILTER(B2:B20,A2:A20=A2,""))

Аргументы функции:

  • ("; ") - символ(или несколько символов), которым необходимо объединять найденные значения
  • (A2:A20) - диапазон, в котором искать критерий
  • (A2) - критерий. Значение, на основании которого необходимо сцеплять значения. Значение просматривается в диапазоне значений(A2:A20)
  • (B2:B20) - из этого диапазона берется значение для сцепления, если значение напротив в диапазонe(A2:A20) совпадает с искомым значением A2

Для любителей "старой школы" можно вместо функции ФИЛЬТР(FILTER) использовать стандартную ЕСЛИ(IF):
=ОБЪЕДИНИТЬ("; ";1;ЕСЛИ(A2:A20=A2;B2:B20;""))
=TEXTJOIN("; ",1,IF(A2:A20=A2,B2:B20,""))

так же это можно использовать в Excel 2019 в случае, если функция ФИЛЬТР отсутствует - да, может быть и такое, хоть Microsoft и пишет, что она там поддерживается
Аргументы точно такие же, как в формуле выше. Правда эта формула вводится в ячейку как формула массива(т.е. одновременным нажатием трех клавиш Ctrl+Shift+Enter).
Хотя в самых новых версия(а-ля 365) вводить тремя клавишами уже не обязательно - Excel сам поймет, что требуется обработка массива ячеек.


 

Объединяем текст по критерию при помощи написания собственной UDF - СцепитьЕсли
А для пользователей Excel 2016 и ниже я написал небольшую функцию пользователя на VBA, которая решает данную проблему. Так же подобную функцию называют "многоразовый ВПР", потому что она по критерию возвращает ВСЕ значения для этого критерия, а не только первое.

'---------------------------------------------------------------------------------------
' Author : The_Prist(Щербаков Дмитрий)
'          Профессиональная разработка приложений для MS Office любой сложности
'          Проведение тренингов по MS Excel
'          http://www.excel-vba.ru
' Purpose:
'---------------------------------------------------------------------------------------
Function СцепитьЕсли(ByRef Диапазон As Range, ByVal Критерий As String, ByRef Диапазон_сцепления As Range, Optional Разделитель As String = " ", Optional БезПовторов As Boolean = False) As String
    Dim li As Long, sStr As String, avItem, avDateArr(), avRezArr(), lUBnd As Long
    If Диапазон.Count > 1 Then
        avDateArr = Intersect(Диапазон, Диапазон.Parent.UsedRange).Value
        avRezArr = Intersect(Диапазон_сцепления, Диапазон_сцепления.Parent.UsedRange).Value
        If Диапазон.Rows.Count = 1 Then
            avDateArr = Application.Transpose(avDateArr)
            avRezArr = Application.Transpose(avRezArr)
        End If
    Else
        ReDim avDateArr(1, 1): ReDim avRezArr(1, 1)
        avDateArr(1, 1) = Диапазон.Value
        avRezArr(1, 1) = Диапазон_сцепления.Value
    End If
    lUBnd = UBound(avDateArr, 1)
    'Определяем вхождение операторов сравнения в Критерий
    Dim objRegExp As Object, objMatches As Object
    Set objRegExp = CreateObject("VBScript.RegExp")
    objRegExp.Global = False: objRegExp.Pattern = "=|<>|=>|>=|<=|=<|>|<"
    Set objMatches = objRegExp.Execute(Критерий)
    'Если есть вхождения
    If objMatches.Count > 0 Then
        Dim sStrMatch As String
        sStrMatch = objMatches.Item(0)
        Критерий = Replace(Replace(Критерий, sStrMatch, "", 1, 1), Chr(34), "", 1, 2)
        If IsNumeric(Критерий) And Критерий <> "" Then
            Критерий = CDbl(Критерий)
        End If
        Select Case sStrMatch
        Case "="
            For li = 1 To lUBnd
                If avDateArr(li, 1) = Критерий Then
                    If Trim(avRezArr(li, 1)) <> "" Then _
                       sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
                End If
            Next li
        Case "<>"
            For li = 1 To lUBnd
                If avDateArr(li, 1) <> Критерий Then
                    If Trim(avRezArr(li, 1)) <> "" Then _
                       sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
                End If
            Next li
        Case ">=", "=>"
            For li = 1 To lUBnd
                If avDateArr(li, 1) >= Критерий Then
                    If Trim(avRezArr(li, 1)) <> "" Then _
                       sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
                End If
            Next li
        Case "<=", "=<"
            For li = 1 To lUBnd
                If avDateArr(li, 1) <= Критерий Then
                    If Trim(avRezArr(li, 1)) <> "" Then _
                       sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
                End If
            Next li
        Case ">"
            For li = 1 To lUBnd
                If avDateArr(li, 1) > Критерий Then
                    If Trim(avRezArr(li, 1)) <> "" Then _
                       sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
                End If
            Next li
        Case "<"
            For li = 1 To lUBnd
                If avDateArr(li, 1) < Критерий Then
                    If Trim(avRezArr(li, 1)) <> "" Then _
                       sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
                End If
            Next li
        End Select
    Else    'Если нет вхождения
        For li = 1 To lUBnd
            If avDateArr(li, 1) Like Критерий Then
                If Trim(avRezArr(li, 1)) <> "" Then _
                   sStr = sStr & IIf(sStr <> "", Разделитель, "") & avRezArr(li, 1)
            End If
        Next li
    End If
 
    If БезПовторов Then
        Dim oDict As Object, sTmpStr
        Set oDict = CreateObject("Scripting.Dictionary")
        sTmpStr = Split(sStr, Разделитель)
        On Error Resume Next
        For li = LBound(sTmpStr) To UBound(sTmpStr)
            oDict.Add sTmpStr(li), sTmpStr(li)
        Next li
        sStr = ""
        sTmpStr = oDict.keys
        For li = LBound(sTmpStr) To UBound(sTmpStr)
            sStr = sStr & IIf(sStr <> "", Разделитель, "") & sTmpStr(li)
        Next li
    End If
    СцепитьЕсли = sStr
End Function

Чтобы правильно использовать приведенный код, необходимо сначала ознакомиться со статьей Что такое функция пользователя(UDF)?. Вкратце: скопировать текст кода выше, перейти в редактор VBA(Alt+F11) -создать стандартный модуль(Insert -Module) и в него вставить скопированный текст. После чего функцию СцепитьЕсли можно будет вызвать из Диспетчера функций(Shift+F3), отыскав её в категории Определенные пользователем (User Defined Functions).
Синтаксис записи в ячейку листа:
=СцепитьЕсли(A2:A20;A2;B2:B20;"; ";0)

По принципу работы функция похожа на стандартную СУММЕСЛИ. Указывается диапазон значений(где просматривать значение), критерий и диапазон значений для сцепления. Символ для разделения слов указывать необязательно.

Диапазон(A2:A20) - диапазон, в котором искать критерий(указывается один столбец). В данном случае это столбец с названиями отделов.

Критерий(A2) - критерий. Значение, на основании которого необходимо сцеплять значения. Значение просматривается в диапазоне, указанном аргументом Диапазон. Может содержать символы подстановки - * и ? и символы сравнения (<>"", <23, >0, "<>"&A1 и т.п.). Если при сравнении с заданным критерием значение в диапазоне(Диапазон) считается отвечающим условию, то значение из Диапазона_Сцепления добавляется к результату с выбранным разделителем.

Диапазон_сцепления(B2:B20) - из этого диапазона берется значение для сцепления, если значение в аргументе Диапазон отвечает условию, заданному аргументом Критерий(указывается один столбец). Если в Диапазоне значение 5-ой строки совпадает с критерием, то из Диапазона_Сцепления будет взято так же значение из 5-ой строки этого диапазона и сцеплено с результатом.

Разделитель("; ") - По умолчанию пробел, но можно задать любой другой символ или группу символов. Если необходимо в качестве разделителя использовать символ переноса на новую строку, то его следует записать как функцию: СИМВОЛ(10) (CHAR(10)). Для отображения переноса в ячейке с функцией необходимо для ячейки включить отображение переносов строк(вкладка Главная -группа Выравнивание -Переносить текст).

БезПовторов - если указать 1 или ИСТИНА(TRUE), то в результате получится строка, в которой нет одинаковых значений. Если указать 0 или ЛОЖЬ(FALSE), то будут выведены все значения. По умолчанию значение ЛОЖЬ(FALSE), т.е. строка будет содержать все найденные значения, даже если они повторяются.

Скачать пример

  Пример СцепитьЕсли.xls (68,0 КиБ, 16 919 скачиваний)


 

Объединяем текст по нескольким критериям при помощи UDF - СцепитьЕсли2
По многочисленным просьбам написал UDF, которая может объединять значения по нескольким условиям. Допускается до 4-х условий, но при наличии желания и небольших навыков можно расширить это число хоть до 100.
Основная функция - СцепитьЕсли2. Но она использует еще и вспомогательные функции для более удобного добавления новых критериев: GetArr, GetIntersectRange и Get_Match. Пояснения к каждой функции написаны в заголовках функций, так что понять, что делает какая из них не должно быть проблемой для тех, у кого появится желание доработать код.

'---------------------------------------------------------------------------------------
' Author : Щербаков Дмитрий(The_Prist)
'          Профессиональная разработка приложений для MS Office любой сложности
'          Проведение тренингов по MS Excel
'          https://www.excel-vba.ru
'          info@excel-vba.ru
' Purpose:
'---------------------------------------------------------------------------------------
Option Explicit
Option Compare Text
Dim objRegExp As Object
'---------------------------------------------------------------------------------------
' Purpose: Функция СцепитьЕсли2 объединяет строки по заданным условиям. Допускается указание до 4-х условий
'   Критерий                    - условие отбора. Ссылка на ячейку или значение. Может начинаться на операторы сравнения: =; <>; =>; >=; <=; =<; >; <
'   Диапазон                    - диапазон, в котором просматривать условия(критерии)
'   Диапазон_сцепления          - диапазон из одной строки или столбца, из которого брать значения для объединения в одну строку
'   Разделитель                 - Символ или символы, который используется для объединения отобранных значений
'   БезПовторов                 - 1 или ИСТИНА(True) - в итоговую строку будут собраны только уникальные значения
'   Диапазон2,Критерий2 и т.д.  - дополнительные связки Диапазон-Критерий. До 4-х
'---------------------------------------------------------------------------------------
Function СцепитьЕсли2(ByRef Диапазон As Range, ByVal Критерий, ByRef Диапазон_сцепления As Range, _
                     Optional Разделитель As String = " ", Optional БезПовторов As Boolean = False, _
                     Optional ByRef Диапазон2 As Range, Optional ByVal Критерий2, _
                     Optional ByRef Диапазон3 As Range, Optional ByVal Критерий3, _
                     Optional ByRef Диапазон4 As Range, Optional ByVal Критерий4) As String
 
    Dim oDict As Object
    Dim li As Long, sStr As String, avItem, avDateArr(), avRezArr(), lUBnd As Long
    Dim avDateArr2(), avDateArr3(), avDateArr4()
    Dim rr As Range
    Dim bCompare As Boolean, IsR2 As Boolean, IsR3 As Boolean, IsR4 As Boolean
    Dim skey$, stxt$
    Dim sTmpStr
    Set oDict = CreateObject("Scripting.Dictionary")
    oDict.comparemode = 1
 
    'определяем, все ли связки Диапазон-Критерий указаны
    'далее проверяем только условия, для которых есть Диапазон и Критерий
    IsR2 = Not IsMissing(Критерий2) And Not Диапазон2 Is Nothing
    IsR3 = Not IsMissing(Критерий3) And Not Диапазон3 Is Nothing
    IsR4 = Not IsMissing(Критерий4) And Not Диапазон4 Is Nothing
    'определяем вхождение диапазонов в рабочий диапазон
    'нужно, чтобы отсечь лишние строки/столбцы при указании полных строк и столбцов
    'Диапазон 1
    Set rr = GetIntersectRange(Диапазон)
    If rr Is Nothing Then
        Exit Function
    End If
    avDateArr = GetArr(rr)
    'Диапазон Сцепления
    Set rr = GetIntersectRange(Диапазон_сцепления)
    If rr Is Nothing Then
        Exit Function
    End If
    avRezArr = GetArr(rr)
    'Диапазон 2
    If IsR2 Then
        Set rr = GetIntersectRange(Диапазон2)
        If rr Is Nothing Then
            IsR2 = False
        Else
            avDateArr2 = GetArr(rr)
        End If
    End If
    'Диапазон 3
    If IsR3 Then
        Set rr = GetIntersectRange(Диапазон3)
        If rr Is Nothing Then
            IsR3 = False
        Else
            avDateArr3 = GetArr(rr)
        End If
    End If
    'Диапазон 4
    If IsR4 Then
        Set rr = GetIntersectRange(Диапазон4)
        If rr Is Nothing Then
            IsR4 = False
        Else
            avDateArr4 = GetArr(rr)
        End If
    End If
    'если диапазоны указаны горизонтально - разворачиваем в вертикальный массив
    If Диапазон.Columns.Count > 1 And Диапазон.Rows.Count = 1 Then
        avDateArr = Application.Transpose(avDateArr)
        If IsR2 Then
            avDateArr2 = Application.Transpose(avDateArr2)
        End If
        If IsR3 Then
            avDateArr3 = Application.Transpose(avDateArr3)
        End If
        If IsR4 Then
            avDateArr4 = Application.Transpose(avDateArr4)
        End If
        avRezArr = Application.Transpose(avRezArr)
    End If
 
    lUBnd = UBound(avDateArr, 1)
    'Опрееделяем вхождение операторов сравнения в Критерий
    If objRegExp Is Nothing Then
        Set objRegExp = CreateObject("VBScript.RegExp")
        objRegExp.Global = False
        objRegExp.Pattern = "=|<>|=>|>=|<=|=<|>|<"
    End If
 
    For li = 1 To lUBnd
        bCompare = True
        If Not Get_Match(Критерий, avDateArr(li, 1)) Then
            bCompare = False
        End If
        '2 диапазон и критерий
        If IsR2 And bCompare Then
            If Not Get_Match(Критерий2, avDateArr2(li, 1)) Then
                bCompare = False
            End If
        End If
        '3 диапазон и критерий
        If IsR3 And bCompare Then
            If Not Get_Match(Критерий3, avDateArr3(li, 1)) Then
                bCompare = False
            End If
        End If
        '4 диапазон и критерий
        If IsR4 And bCompare Then
            If Not Get_Match(Критерий4, avDateArr4(li, 1)) Then
                bCompare = False
            End If
        End If
        If bCompare Then
            stxt = Trim(avRezArr(li, 1))
            If Len(stxt) Then
                If БезПовторов Then
                    skey = stxt
                Else
                    skey = oDict.Count + 1
                End If
                oDict.Item(skey) = stxt
            End If
        End If
    Next li
 
    sTmpStr = oDict.items
    For li = LBound(sTmpStr) To UBound(sTmpStr)
        If Len(sStr) = 0 Then
            sStr = sTmpStr(li)
        Else
            sStr = sStr & Разделитель & sTmpStr(li)
        End If
    Next
 
    СцепитьЕсли2 = sStr
End Function
'---------------------------------------------------------------------------------------
' Procedure : GetArr
'             rr    - ссылка на диапазон, из значений которого надо создать массив
'---------------------------------------------------------------------------------------
Function GetArr(rr As Range)
    Dim arr
    arr = rr.Value
    If Not IsArray(arr) Then
        ReDim arr(1 To 1, 1 To 1)
        arr(1, 1) = rr.Value
    End If
    GetArr = arr
End Function
'---------------------------------------------------------------------------------------
' Procedure : GetArr
'             получаем только тот диапазон, который используется на листе
'             Диапазон    - ссылка на диапазон
'---------------------------------------------------------------------------------------
Function GetIntersectRange(Диапазон As Range)
    Set GetIntersectRange = Intersect(Диапазон, Диапазон.Parent.UsedRange)
End Function
'---------------------------------------------------------------------------------------
' Procedure : Get_Match
'             получаем только тот диапазон, который используется на листе
'             vCr       - условие, на которое проверяем значение диапазона(vCompare)
'             vCompare  - значение, которое проверяем на соответствие заданному критерию(vCr)
'---------------------------------------------------------------------------------------
Function Get_Match(ByVal vCr, ByVal vCompare) As Boolean
    Dim sStr As String, sStrMatch As String
    Dim objMatches As Object
 
    Set objMatches = objRegExp.Execute(vCr)     'Если есть вхождения
    If objMatches.Count > 0 Then
        sStrMatch = objMatches.Item(0)
        vCr = Replace(Replace(vCr, sStrMatch, "", 1, 1), Chr(34), "", 1, 2)
    End If
 
    If IsNumeric(vCr) And vCr <> "" Then
        vCr = CDbl(vCr)
    End If
    Select Case sStrMatch
    Case "="
         If vCompare = vCr Then Get_Match = True
    Case "<>"
        If vCompare <> vCr Then Get_Match = True
    Case ">=", "=>"
        If vCompare >= vCr Then Get_Match = True
    Case "<=", "=<"
        If vCompare <= vCr Then Get_Match = True
    Case ">"
        If vCompare > vCr Then Get_Match = True
    Case "<"
        If vCompare < vCr Then Get_Match = True
    Case Else
        If vCompare Like vCr Then Get_Match = True
    End Select
End Function

Аргументы функции точно такие же, как и в описанной выше СцепитьЕсли, но с возможностью последними аргументами добавить еще критерия и диапазоны, где их просматривать. Требования к ним точно такие же, как и к аргументам Диапазон и Критерий.
На примере таблицы сотрудников отберем не повторяющихся сотрудников планового отдела, год рождения которых позже 1978:
Пример работы функции СцепитьЕсли2
Синтаксис записи в ячейку листа:
=СцепитьЕсли2(A2:A20;A2;B2:B20;"; ";1;C2:C20;">1970")

Как видно, все основные аргументы записаны в том же порядке, за исключением последних двух:
Диапазон2(С2:С20) - диапазон для просмотра второго условия отбора. В данном случае это столбец с годом рождения.
Критерий2(">1978") - т.к. нам необходимо отобрать только тех сотрудников, год рождения которых позже 1978, то для этого аргумента указываем значение ">1978".

Также см.:
ВПР_МН
Сцепить_МН
СцепитьЕсли
Что такое функция пользователя(UDF)?
ВПР с возвратом всех значений

96 комментариев

  1. Огромное спасибо за формулу!
    А может подскажите, как правильно написать код, чтобы было 2 диапазона условия?
    т.е. диапазон1; Критерий1; диапазон2; Критерий2;
    как в СуммЕслиМН. оеально это сделать? спасибо!

  2. Отличная функция, но работает если данные на одном листе. Не получилось ни с разных файлов сравнивать (тупо вылетает Excel по ошибке), ни с разных листов....

    1. Если ячеек более 37000 то функция не срабатывает, это факт. Причем если критерии находятся в другом файле, то Excel вываливается по ошибке. Вместо того что придираться к словам обратили бы внимание на свои ошибки. Удачи Вам.

  3. Макс, как скажете. Не буду придираться. Но сравнивать и сцеплять понятия разные - кто знает как Вы поняли функцию и для чего применить пытаетесь. Может Вы хотите навязать ей то, чего она не умеет. Поэтому я уточнил, но нисколько не придирался. Не надо по себе людей судить.
    И вот другой факт: у меня функция РАБОТАЕТ. И не только у меня, судя по комментариям. Без Вашего примера сказать почему не работает у Вас нереально. Может у Вас книги в разных процессах открыты. А может еще что.
    Плюс, если у Вас так много значений - есть вероятность, что результирующая строка содержит больше символов, чем допускается для записи в ячейку. И функция просто завершается с ошибкой, т.к. не может занести данные. Ничего с этим не сделать, т.к. это ограничения самого Excel.
    И еще момент: нехватка свободной памяти для обработки таких объемных данных. VBA просто не может поместить их в массивы.

    P.S. Про "обратили бы внимание на свои ошибки". А Вам не кажется, что я ничем Вам не обязан, чтобы что-то для Вас лично исправлять? Функция не работает только у Вас. Причины я описал выше. Хотите с ними разобраться - выкладывайте файл со своими данными на файлообменник, а ссылку на него сюда. Не забудьте прописать там функцию. Я скачаю, посмотрю. И тогда будет видно, где и чья ошибка.

Добавить комментарий для Ренат Отменить ответ

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.