Консолидация данных с нескольких листов, переупорядочение данных в соответствии с именем столбца - PullRequest
0 голосов
/ 28 сентября 2011

Я хочу, чтобы макрос объединял данные из нескольких листов в один лист ... здесь я привел пример ..

Sheet 1     
a1:Name     b1:Age

a2:sathish  b2:22   
a3:sarathi  b3:24

.

sheet 2     

a1:Age  b1:Name     c1:Dept
a2:60   b2:saran    c2:Comp sce
a3:31   b3:rajan    c3:B.com

результат должен быть похож наэтот

сводный лист

a1:Name     b1:Age  c1:Dept

a2:sathish  b2:22   
a3:sarathi  b3:24   
a4:saran    b4:60   c4:Comp sce
a5:rajan    b5:31   c5:B.com

Вот код, который я использовал для консолидации данных-

Sub консолидировать ()

Dim sh As Worksheet

Dim DestSh As Worksheet

Dim Last As Long

Dim shLast As Long

Dim CopyRng As Range

Dim StartRow As Long

With Application
    .ScreenUpdating = False
    .EnableEvents = False
End With

   Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("RDBMergeSheet").Delete
On Error GoTo 0
Application.DisplayAlerts = True


Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "RDBMergeSheet"


StartRow = 1


For Each sh In ActiveWorkbook.Worksheets
    If sh.Name <> DestSh.Name Then


        Last = LastRow(DestSh)
        shLast = LastRow(sh)


    If shLast > 0 And shLast >= StartRow Then
                Set CopyRng = sh.Range(sh.Rows(StartRow), sh.Rows(shLast))


       If Last + CopyRng.Rows.Count > DestSh.Rows.Count Then
               MsgBox "There are not enough rows in the " & _
               "summary worksheet to place the data."
               GoTo ExitTheSub
            End If

                CopyRng.Copy
            With DestSh.Cells(Last + 1, "A")
                .PasteSpecial xlPasteValues
                .PasteSpecial xlPasteFormats
                Application.CutCopyMode = False
            End With

        End If

    End If
Next

ExitTheSub:

Application.Goto DestSh.Cells(1)

    DestSh.Columns.AutoFit

With Application
    .ScreenUpdating = True
    .EnableEvents = True
End With

End Sub

Функция LastRow (sh As Worksheet)

On Error Resume Next

LastRow = sh.Cells.Find(What:="*", _
                        After:=sh.Range("A1"), _
                        Lookat:=xlPart, _
                        LookIn:=xlFormulas, _
                        SearchOrder:=xlByRows, _
                        SearchDirection:=xlPrevious, _
                        MatchCase:=False).Row
On Error GoTo 0

End Function

Функция LastCol (sh As Worksheet) При ошибкеВозобновить Далее

LastCol = sh.Cells.Find(What:="*", _
                        After:=sh.Range("A1"), _
                        Lookat:=xlPart, _
                        LookIn:=xlFormulas, _
                        SearchOrder:=xlByColumns, _
                        SearchDirection:=xlPrevious, _
                        MatchCase:=False).Column
On Error GoTo 0

Завершить функцию

Я могу объединить данные, но не могу изменить порядок в соответствии с заголовком столбца .. Пожалуйста, помогите мне в этом .. Спасибо заранее

1 Ответ

1 голос
/ 23 января 2012

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

Выпуск 1

Основная цельиз On Error, чтобы позволить вам аккуратно завершить работу в случае непредвиденной ошибки.Вы не должны использовать его, чтобы избежать ожидаемых ошибок и не должны игнорировать ошибки.

Рассмотрите функции LastRow и LastCol.В обоих случаях, если поиск не удался, вы игнорируете ошибку и продолжаете.Но это означает, что эти функции возвращают неверное значение, поэтому вы получаете еще одну ошибку в вызывающей подпрограмме.Если поиск не удастся, вы должны расследовать, а не игнорировать.Это верно для любой другой ошибки.

Выпуск 2

Поиск не возвращает ничего, если лист пустой.Вы вызываете функции LastRow и LastCol для рабочего листа "RDBMergeSheet", когда он пуст.Код должен быть:

Set Rng = sh.Cells.Find( ...)

If Rng Is Nothing Then
  ' Sheet sh is empty
  LastRow = 0
Else
  LastRow = Rng.Row
End If

Здесь я установил LastRow равным 0, если рабочая таблица пуста.Это перестает быть побочным эффектом ошибки, но задокументирована особенность функции: «Возвращаемое значение = 0 означает, что лист пуст».Вызывающая процедура должна проверить это значение и пропустить все пустые листы.Существуют и другие подходы, но ключевой момент заключается в следующем: предоставить код для аккуратной обработки ожидаемых или возможных ошибок.Для функции LastCol вам нужно LastCol = Rng.Column.

Выпуск 3

Минимальный синтаксис для оператора функции:

Function Name( ... parameters ...) As ReturnType

Два оператора функции должны заканчиваться: As Long.

Выпуск 4

Обратите внимание: "ActiveWorkbook.Worksheets (" RDBMergeSheet ")"

Если вы работаете с несколькими книгами, ActiveWorkbook недовольно.Если вы работаете только с одной книгой, ActiveWorkbook не требуется.Пожалуйста, не работайте с несколькими книгами, пока ваше понимание Excel VBA не улучшится.

Выпуск 5

Вы удаляете лист "RDBMergeSheet", а затем воссоздаете его, что ранит мою душу,Что еще более важно, вы потеряли заголовки столбцов.Я буду обсуждать этот вопрос далее в разделе Редизайн.

Заменить:

 Application.DisplayAlerts = False
 On Error Resume Next
 ActiveWorkbook.Worksheets("RDBMergeSheet").Delete
 On Error GoTo 0
 Application.DisplayAlerts = True

 Set DestSh = ActiveWorkbook.Worksheets.Add
 DestSh.Name = "RDBMergeSheet"

на:

 Set DestSh = Worksheets("RDBMergeSheet")
 With DestSh
   .Range(.Cells(2, 1), .Cells(Rows.Count, Columns.Count)).EntireRow.Delete
 End With

Вы используете Rows.Count, With и Cells в своем коде, поэтому я не буду их объяснять.

.Range(.Cells(RowTop, ColLeft), .Cells(RowBottom, ColRight)) - это простой способ задания диапазона с верхними левыми и нижними правыми ячейками.

Я использовал .EntireRow, поэтому мне не нужны номера столбцов.Следующее дает тот же эффект:

.Rows("2:" & Rows.Count).EntireRow.Delete

Насколько я знаю ClearContents (что предпочитают некоторые люди) имеет тот же эффект, что и Delete.Это, конечно, занимает столько же микросекунд.При использовании выше, оба удаляют любые значения или форматирование со второй строки до последней строки листа.

Приведенное выше изменение означает, что строка 1 не изменяется и ширина столбцов не теряется.Мне не нужна AutoFit, которую вы использовали.

Issue 6

Пожалуйста, будьте систематичны в именовании ваших переменных.Вы используете StartRow в качестве первой строки и shLast в качестве последней строки исходного листа, а Last - в качестве последнего ряда конечного листа.Будет ли коллега, который берет на себя ведение вашего макроса, легко это понять?Помните ли вы его через шесть месяцев, когда этот макрос потребует некоторого обслуживания?

Разработайте систему именования, которая будет работать для вас.А еще лучше, соберитесь с коллегами и договоритесь о единой системе, чтобы все макросы вашего работодателя выглядели одинаково.Документируйте эту систему в интересах будущих сотрудников.Я бы назвал эти переменные: RowNumDestLast, RowNumSrcStart и RowNumSrcLast.То есть:,Эта система работает для меня, но ваша система может быть совершенно другой.Ключевой особенностью хорошей системы является то, что вы можете просмотреть свой код в течение года и сразу узнать, что делает каждый оператор.

Выпуск 7

If shLast > 0 And shLast >= StartRow Then

Вы устанавливаете StartRow на 1 и никогда не меняете его, так что если shLast >= StartRow, то shLast > 0.Достаточно следующего:

If shLast >= StartRow Then

Выпуск 8

If Last + CopyRng.Rows.Count > DestSh.Rows.Count Then
  MsgBox "There are not enough rows in the " & _
             "summary worksheet to place the data."
  GoTo ExitTheSub
End If

Хорошо, что вы проверяете условия, которые приведут к фатальным ошибкам, но является ли это наиболее вероятной ошибкой? Даже если вы используете Excel 2003, у вас есть место для 65 535 человек и заголовок. Вы превысите ограничение на размер книги, прежде чем превысите максимальное количество строк.

Выпуск 9

Set CopyRng = sh.Range(sh.Rows(StartRow), sh.Rows(shLast))

Включает строку заголовка в диапазоне, который нужно скопировать. Поскольку позже я предложу совершенно другой метод, я не буду предлагать исправление.

Выпуск 10

With DestSh.Cells(Last + 1, "A")
  .PasteSpecial xlPasteValues
  .PasteSpecial xlPasteFormats

Почему вы вставляете значения и форматы отдельно?

Перестройка

С исправлениями выше, код вроде работает. С вашими исходными данными он устанавливает лист назначения на:

Age      Name    Dept
Name     Age    
Sathish  22 
Sarathi  24 
Age      Name    Dept
60       Saran   Comp sce
31       Rajan   B.com

Это не то, что вы ищете. Итак, остальная часть этого ответа касается дизайна: как вы добиваетесь внешнего вида, который ищете? Есть много подходов, но я предлагаю один и объясняю, почему я выбрал его, не обсуждая альтернативы.

Ключевые вопросы:

  • Как определить, какие столбцы нужно объединить и в какой последовательности?
  • Если в исходной таблице есть столбец, который вы не ожидаете, что вы будете делать? Кто-то собирает информацию, для которой нет центрального интереса, или имя столбца написано неправильно?

Я решил использовать существующие имена столбцов в рабочей таблице «RDBMergeSheet» для определения последовательности. Чтобы подготовить макрос для нового имени столбца, просто добавьте это имя в «RDBMergeSheet». Если я обнаружу имя столбца на исходном листе, которого нет в «RDBMergeSheet», я добавлю его справа. Это второе решение высветит ошибку, если имя столбца написано с ошибкой, но не принесет пользы, если кто-то собирает дополнительную информацию в исходной рабочей таблице.

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

Новые заявления и объяснения

  Const RowFirstData As Long = 2
  Const WShtDestName As String = "RDBMergeSheet"

Константа означает, что я использую имя в коде и могу изменить значение, изменив оператор Const.

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

    ColNumDestLast = .Cells(1, Columns.Count).End(xlToLeft).Column

.Cells(1, Columns.Count) определяет последний столбец строки 1, который, как я полагаю, является пустым. .End(xlToLeft) является VBA-эквивалентом клавиатуры Ctrl + Left. Если .Cells(1, Columns.Count) пусто, .Cells(1, Columns.Count).End(xlToLeft) возвращает первую ячейку слева, которая не является пустой. .Column дает номер столбца этой ячейки. То есть этот оператор устанавливает для ColNumDestStart номер столбца последней ячейки в строке 1 со значением.

    ColHeadDest = .Range(.Cells(1, 1), .Cells(1, ColNumDestLast)).Value

Копирует значения из строки 1 в вариантный массив ColHeadDest. ColHeadDest будет переопределен этим оператором до (1 to 1, 1 to ColNumDestLast). Первое измерение предназначено для строк, из которых только один, а второе - для столбцов.

Запасной консолидированный

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

Option Explicit
Sub consolidate()

  Dim ColHeadCrnt As String
  Dim ColHeadDest() As Variant
  Dim ColNumDestCrnt As Long
  Dim ColNumDestLast As Long
  Dim ColNumSrcCrnt As Long
  Dim ColNumSrcLast As Long
  Dim Found As Boolean
  Dim RowNumDestCrnt As Long
  Dim RowNumDestStart As Long
  Dim RowNumSrcCrnt As Long
  Dim RowNumSrcLast As Long
  Dim WShtDest As Worksheet
  Dim WShtSrc As Worksheet
  Dim WShtSrcData() As Variant

  Const RowNumFirstData As Long = 2
  Const WShtDestName As String = "RDBMergeSheet"

  'With Application
  '  .ScreenUpdating = False        ' Don't use these
  '  .EnableEvents = False          ' during development
  'End With

  Set WShtDest = Worksheets(WShtDestName)
  With WShtDest
    ' Clear existing data and load column headings to ColHeadDest
    .Rows("2:" & Rows.Count).EntireRow.Delete
    ColNumDestLast = .Cells(1, Columns.Count).End(xlToLeft).Column
    ColHeadDest = .Range(.Cells(1, 1), _
                         .Cells(1, ColNumDestLast)).Value
  End With

  ' Used during development to check array loaded correctly
  'For ColNumDestCrnt = 1 To ColNumDestLast
  '  Debug.Print ColHeadDest(1, ColNumDestCrnt)
  'Next

  RowNumDestStart = RowNumFirstData    ' Start for first source worksheet

  For Each WShtSrc In Worksheets
    ColNumSrcLast = LastCol(WShtSrc)
    RowNumSrcLast = LastRow(WShtSrc)
    If WShtSrc.Name <> WShtDestName And _
       RowNumSrcLast <> 0 Then
      ' Source sheet is not destination sheet and it is not empty.
      With WShtSrc
        ' Load entire worksheet to array
        WShtSrcData = .Range(.Cells(1, 1), _
                        .Cells(RowNumSrcLast, ColNumSrcLast)).Value
      End With
      With WShtDest
        For ColNumSrcCrnt = 1 To ColNumSrcLast
          ' For each column in source worksheet
          Found = False
          ColHeadCrnt = WShtSrcData(1, ColNumSrcCrnt)
          ' Find matching column in destination worksheet
          For ColNumDestCrnt = 1 To ColNumDestLast
            If ColHeadCrnt = ColHeadDest(1, ColNumDestCrnt) Then
              Found = True
              Exit For
            End If
          Next ColNumDestCrnt
          If Not Found Then
            ' Current source column's name is not present in the
            ' destination sheet Add new column name to array and
            ' destination worksheet
            ColNumDestLast = ColNumDestLast + 1
            ReDim Preserve ColHeadDest(1 To 1, 1 To ColNumDestLast)
            ColNumDestCrnt = ColNumDestLast
            With .Cells(1, ColNumDestCrnt)
              .Value = ColHeadCrnt
              .Font.Color = RGB(255, 0, 0)
            End With
            ColHeadDest(1, ColNumDestCrnt) = ColHeadCrnt
          End If
          ' I could extract data from WShtSrcData to another array
          ' suitable for downloading to a column of a worksheet but
          ' it is easier to move the data directly to the worksheet.
          ' Also, athought downloading via an array is marginally
          ' faster than direct access, loading the array will reduce,
          ' and perhaps eliminate, the time benefit of using an array.
          RowNumDestCrnt = RowNumDestStart
          For RowNumSrcCrnt = RowNumFirstData To RowNumSrcLast
            ' Copy value from array of source data to destination sheet
            .Cells(RowNumDestCrnt, ColNumDestCrnt) = _
                              WShtSrcData(RowNumSrcCrnt, ColNumSrcCrnt)
            RowNumDestCrnt = RowNumDestCrnt + 1
          Next
        Next ColNumSrcCrnt
      End With  ' WShtDest
      ' Adjust RowNumDestStart ready for next source worksheet
      RowNumDestStart = RowNumDestStart + RowNumSrcLast - RowNumFirstData + 1
    End If  ' Not destination sheet and not empty source sheet
  Next WShtSrc

  With WShtDest
    ' Leave workbook with destination worksheet visible
    .Activate
  End With

  'With Application
  '  .ScreenUpdating = True
  '  .EnableEvents = True
  'End With

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