VBA: szybkie łączenie wielu plików do jednej tabeli bez ręcznego importu

0
8
Rate this post

Nawigacja:

Dlaczego ręczne łączenie plików się nie opłaca

Codzienny scenariusz: kopiuj-wklej z dziesiątek raportów

Wyobraź sobie klasyczną sytuację: co miesiąc spływają raporty sprzedaży z kilkunastu oddziałów. Każdy oddział wysyła swój plik Excela, układ kolumn jest podobny, ale wszystko siedzi w osobnych plikach. Trzeba to potem scalić do jednej tabeli zbiorczej, żeby zrobić pivoty, wykresy, KPI. Najczęściej kończy się to powtarzalną sekwencją:

  • otwarcie pierwszego pliku, zaznaczenie zakresu, kopiuj, przejście do pliku zbiorczego, wklej,
  • przeskok do kolejnego pliku, znowu zaznaczanie, kopiowanie, wklejanie pod spodem,
  • pilnowanie, żeby pominąć nagłówki, nie nadpisać istniejących danych i niczego nie zgubić.

Przy kilku plikach jest to do przełknięcia. Przy trzydziestu – męczy. Przy stu – zamienia się w żmudną, ryzykowną robotę, którą i tak ktoś musi powtarzać co miesiąc, tydzień, a czasem codziennie.

Porównanie czasu: ręcznie vs jedno makro VBA

Przyjrzyjmy się prostemu porównaniu. Załóżmy, że trzeba scalić dane z 30 plików:

  • Ręczne łączenie: średnio 30–60 sekund na każdy plik (otwarcie, znalezienie danych, kopiuj, wklej, zapis). Przy 30 plikach robi się z tego około 15–30 minut nudnego klikania. Jeśli raporty spływają co tydzień, to już 1–2 godziny miesięcznie.
  • Makro VBA: jedna konfiguracja kodu, a potem uruchomienie zajmuje kilka sekund. Samo przetworzenie 30 plików zwykle zamyka się w 10–60 sekund w zależności od wielkości plików i laptopa.

Różnica przy jednym uruchomieniu może nie robić wrażenia, ale przy powtarzalnych zadaniach efekt skali jest wyraźny. Makro można odpalić w trakcie innej czynności, a cała żmudna praca dzieje się samoistnie w tle.

Koszt błędów: zgubione wiersze, duplikaty, nadpisane dane

Każde ręczne kopiowanie zwiększa ryzyko pomyłki. Najczęstsze problemy przy konsolidacji danych Excel to:

  • zaznaczenie o jeden wiersz za małego lub za dużego zakresu,
  • przypadkowe skopiowanie nagłówków kolejny raz i wymieszanie ich z danymi,
  • nadpisanie już wklejonych danych, gdy ktoś zacznie od złego wiersza,
  • pomylenie plików lub pominięcie jednego z raportów,
  • wklejenie w złą kolumnę, gdy struktura nie jest idealnie jednolita.

Koszt takiej pomyłki bywa realny: błędne decyzje na podstawie niekompletnych raportów, nerwowe poprawki przed spotkaniem, dodatkowy czas na weryfikacje i szukanie „co się nie zgadza”. Makro VBA działa według z góry ustalonego schematu, więc przy prawidłowej konfiguracji ryzyko błędów spada drastycznie.

Kiedy makro ma sens – granica opłacalności

Napisanie prostego makra do łączenia plików to zwykle 30–60 minut pracy, nawet przy podstawowej znajomości VBA. Taki jednorazowy koszt zwraca się szybko, jeśli:

  • regularnie scalasz dane z więcej niż 5–10 plików,
  • robisz to częściej niż raz w miesiącu,
  • układ plików źródłowych jest w miarę powtarzalny.

Jeśli raz w roku łączysz dwa arkusze – nie ma sensu komplikować. Ale przy cyklicznych raportach z kilkunastu oddziałów, magazynów, projektów czy zespołów, makro skraca pracę z godzin do minut i szybko staje się najtańszym „pracownikiem”, który nigdy nie marudzi i nie popełnia przypadkowych błędów zaznaczenia.

Założenia rozwiązania i wymagania wstępne

Co dokładnie ma robić makro scalające pliki

Cel makra jest prosty: po jednym kliknięciu połączyć dane z wielu plików w jedną tabelę. Przyjmijmy następujące założenia funkcjonalne:

  • użytkownik wskazuje folder z plikami (bez ręcznego wpisywania ścieżki),
  • makro przechodzi po wszystkich plikach w tym folderze, spełniających określone kryteria (np. tylko XLSX),
  • z każdego pliku pobierany jest określony arkusz (np. o nazwie „Dane”) lub pierwszy arkusz,
  • dane z każdego pliku są kopiowane bez nagłówka i dopisywane pod istniejącymi w tabeli zbiorczej,
  • wszystko ląduje w jednym arkuszu w skoroszycie makra, tworząc spójną tabelę do dalszych analiz.

Taki zakres wystarcza do 90% typowych zastosowań: raporty, zamówienia, logi zdarzeń, listy produktów, dane operacyjne z różnych lokalizacji.

Wersja „budżetowa”: proste założenia struktury danych

Żeby nie komplikować kodu na start, przyjmijmy kilka pragmatycznych ograniczeń:

  • każdy plik ma jedną zakładkę z danymi w powtarzalnym układzie (lub przynajmniej jeden docelowy arkusz o ustalonej nazwie),
  • nagłówki kolumn są takie same we wszystkich plikach – ta sama liczba kolumn, ten sam układ, nazwy nie muszą być identyczne znak w znak, ale powinny odpowiadać tym samym danym,
  • w każdym pliku dane zaczynają się od tego samego wiersza (np. nagłówek w wierszu 1, dane od 2),
  • nie ma scalonych komórek w obszarze danych (scalone nagłówki da się przeżyć, ale najlepiej ich unikać).

Takie podejście jest „budżetowe”, bo minimalizuje ilość logiki warunkowej w makrze. Zamiast próbować inteligentnie dopasowywać różniące się struktury, zakładamy porządek po stronie plików źródłowych i wykorzystujemy to do maksymalnego uproszczenia kodu.

Uprawnienia, bezpieczeństwo i ustawienia Excela

Żeby makra VBA mogły swobodnie działać, trzeba mieć odpowiednie ustawienia bezpieczeństwa:

  • Włączone makra – w Centrum zaufania należy zezwolić na uruchamianie makr przynajmniej w zaufanych lokalizacjach.
  • Zaufana lokalizacja – warto umieścić skoroszyt z makrem (pliki XLSM) w folderze oznaczonym jako zaufany, aby Excel nie blokował i nie dopytywał za każdym razem.
  • Format pliku XLSM – skoroszyt zawierający kod VBA musi być zapisany jako .xlsm (lub .xlsb) – zwykły .xlsx makr nie przechowa.

Dodatkowo dobrze, jeśli użytkownik ma prawa do odczytu plików w folderach sieciowych, z których będą zaciągane dane. Makro nie „przeskoczy” ograniczeń systemu plików.

Minimalna znajomość VBA potrzebna do obsługi rozwiązania

Żeby poradzić sobie z poniższymi przykładami, wystarczy podstawowy zestaw umiejętności:

  • otworzenie Edytora VBA (ALT + F11),
  • utworzenie nowego modułu (Insert → Module),
  • wklejenie i zapisanie kodu,
  • uruchomienie makra (ALT + F8 lub przypisanie do przycisku).

Nie trzeba być programistą – ważne jest zrozumienie ogólnej logiki: pętla po plikach, kopiowanie zakresu, wklejanie do tabeli zbiorczej. Resztę można potraktować jak gotową „funkcję”, którą po prostu się uruchamia.

Organizacja plików źródłowych – porządek, który ułatwia makro

Jeden folder roboczy „Do_łączenia”

Najprostszy i najtańszy sposób na uproszczenie kodu VBA to utrzymanie porządku w systemie plików. Zamiast szukać raportów po wielu lokalizacjach, lepiej stworzyć jeden dedykowany folder, np.:

  • C:RaportyDo_łączenia

do którego co miesiąc będą trafiać wszystkie pliki do konsolidacji. Zalet jest kilka:

  • makro nie musi filtrować niepotrzebnych plików spośród setek innych,
  • łatwo sprawdzić „gołym okiem”, czy wszystko jest gotowe przed odpaleniem makra,
  • można bezpiecznie wyczyścić lub przenieść pliki po konsolidacji, zachowując porządek.

Jeśli chcesz elastyczności, makro może za każdym razem pytać o folder za pomocą okienka wyboru – ale i tak lepiej, żeby struktura po stronie użytkownika była stała i sensowna.

Stała nazwa arkusza z danymi lub pierwszy arkusz

Druga decyzja organizacyjna dotyczy tego, skąd w pliku brać dane. Tu są dwa główne podejścia:

  • Stała nazwa arkusza – np. w każdym pliku arkusz z danymi nazywa się „Dane” albo „Raport”. Makro zawsze odwołuje się do Sheets(„Dane”). Zaletą jest kontrola; wadą – wszystkie pliki muszą być poprawnie nazwane.
  • Pierwszy arkusz w pliku – makro pobiera dane z Sheets(1). Sprawdza się, gdy użytkownicy nie dbają o nazwy, ale masz pewność, że dane są zawsze na pierwszej zakładce.

W wariancie „budżetowym” najbezpieczniej jest narzucić stałą nazwę arkusza i poświęcić dwie minuty na przeklikanie plików źródłowych, niż tworzyć w kodzie mechanizmy zgadywania, gdzie co jest.

Jednolity układ kolumn i nagłówków

Jeśli każda jednostka przesyła trochę inaczej sformatowane pliki, makro szybko zamieni się w potwora warunków. Lepiej ustalić prostą zasadę:

  • wszystkie pliki mają identyczny układ kolumn – ta sama liczba kolumn, ten sam porządek, zbliżone nazwy nagłówków,
  • nagłówek jest zawsze w wierszu 1, dane od wiersza 2,
  • nie ma dodatkowych wierszy z opisami, sumami, komentarzami pomiędzy nagłówkiem a danymi.

Dzięki temu makro może bez głębszej analizy po prostu skopiować wszystko poniżej nagłówka. To proste, stabilne i nie wymaga skomplikowanego kodu.

Szybka checklista spójności plików przed startem

Przed pierwszym uruchomieniem makra dobrze jest przeprowadzić krótką kontrolę jakości plików źródłowych. Pomaga tu krótka checklista:

  • W każdym pliku arkusz z danymi istnieje i ma właściwą nazwę (np. „Dane”).
  • W każdym arkuszu nagłówki są w wierszu 1, bez pustych wierszy nad nimi.
  • Nie ma scalonych komórek w obszarze danych (szczególnie w kolumnach kluczowych).
  • Kolumny występują w tej samej kolejności (nawet jeśli niektóre są puste).
  • Nie ma makr, które automatycznie uruchamiają się przy otwieraniu i mogłyby blokować przepływ.

Taka jednorazowa weryfikacja oszczędza mnóstwo czasu na późniejsze debugowanie. Jeśli plików jest bardzo dużo, można sprawdzić kilka losowo wybranych; zwykle dobrze odzwierciedlają ogólny stan.

Prosty szkielet makra do łączenia plików – wersja minimum

Logika krok po kroku: od wyboru folderu do tabeli zbiorczej

Proste makro do łączenia plików w VBA zwykle ma taką strukturę:

  1. Użytkownik wskazuje folder z plikami (Application.FileDialog).
  2. Makro przygotowuje arkusz zbiorczy: czyści stare dane, zostawia nagłówek.
  3. Makro uruchamia pętlę po plikach w danym folderze (Dir lub FileSystemObject).
  4. Dla każdego pliku:
    • otwiera skoroszyt,
    • znajduje ostatni wiersz z danymi,
    • kopiuje zakres bez nagłówka,
    • wkleja na koniec tabeli zbiorczej,
    • zamyka plik bez zapisu.
  5. Na końcu przywraca ustawienia Excela i sygnalizuje zakończenie pracy.

To absolutne minimum, które już radykalnie przyspiesza konsolidację danych Excel.

Wybór folderu za pomocą Application.FileDialog

Zamiast wpisywać ścieżkę do folderu „na sztywno” w kodzie, można każdorazowo pozwolić użytkownikowi wskazać lokalizację. Służy do tego obiekt Application.FileDialog z typem msoFileDialogFolderPicker. Przykład:

Praktyczny kod wyboru folderu z danymi

Najprostsza wersja procedury wybierającej folder i zapisującej jego ścieżkę do zmiennej wygląda tak:

Function WybierzFolder() As String
    Dim fd As FileDialog
    Dim wynik As String
    
    wynik = ""
    Set fd = Application.FileDialog(msoFileDialogFolderPicker)
    
    With fd
        .Title = "Wybierz folder z plikami do połączenia"
        .AllowMultiSelect = False
        If .Show = -1 Then
            wynik = .SelectedItems(1)
        End If
    End With
    
    WybierzFolder = wynik
End Function

Ta funkcja:

  • otwiera okno wyboru folderu,
  • po zatwierdzeniu zwraca ścieżkę jako tekst,
  • jeśli użytkownik kliknie Anuluj – zwraca pusty ciąg.

Dzięki temu w głównym makrze można w jednej linijce pobrać ścieżkę i w razie potrzeby grzecznie zakończyć działanie:

Dim sciezkaFolderu As String
sciezkaFolderu = WybierzFolder()
If sciezkaFolderu = "" Then Exit Sub

Przygotowanie arkusza zbiorczego i nagłówków

Zanim pętla zacznie zaciągać dane, trzeba mieć czyste miejsce docelowe. Najtaniej czasowo jest zrobić dedykowany arkusz, np. „Zbiorcze”, i używać go wyłącznie jako tabeli wynikowej.

Prosty schemat:

  • zachować wiersz nagłówków,
  • usunąć wszystkie wiersze danych poniżej,
  • ustawić wskaźnik na pierwszy pusty wiersz (zwykle 2).
Sub PrzygotujArkuszZbiorczy()
    Dim ws As Worksheet
    Dim ostatniWiersz As Long
    
    Set ws = ThisWorkbook.Worksheets("Zbiorcze")
    
    'Znajdź ostatni wiersz
    ostatniWiersz = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    
    'Jeśli są dane poniżej nagłówka – wyczyść
    If ostatniWiersz > 1 Then
        ws.Rows("2:" & ostatniWiersz).ClearContents
    End If
End Sub

Wywołanie tej procedury na początku głównego makra oszczędza nerwów: nie ma ryzyka, że nowe dane dopiszą się pod starymi z poprzedniego miesiąca.

Prosty szkielet pętli po plikach przy użyciu Dir

Do przechodzenia po plikach w folderze nie potrzeba żadnych dodatków. Wystarczy funkcja Dir. To najlżejsze i najszybsze rozwiązanie.

Sub PolaczPliki_Minimum()
    Dim sciezkaFolderu As String
    Dim nazwaPliku As String
    Dim wbZrodlowy As Workbook
    Dim wsZrodlowy As Worksheet
    Dim wsCel As Worksheet
    Dim ostatniWierszCel As Long
    Dim ostatniWierszZrodlo As Long
    
    sciezkaFolderu = WybierzFolder()
    If sciezkaFolderu = "" Then Exit Sub
    
    Set wsCel = ThisWorkbook.Worksheets("Zbiorcze")
    Call PrzygotujArkuszZbiorczy
    
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    
    'Pierwszy plik XLSX
    nazwaPliku = Dir(sciezkaFolderu & "*.xlsx")
    
    Do While nazwaPliku <> ""
        Set wbZrodlowy = Workbooks.Open(sciezkaFolderu & "" & nazwaPliku)
        
        'Przyjmijmy, że dane są na pierwszym arkuszu
        Set wsZrodlowy = wbZrodlowy.Worksheets(1)
        
        'Znajdź ostatni wiersz z danymi w pliku źródłowym
        ostatniWierszZrodlo = wsZrodlowy.Cells(wsZrodlowy.Rows.Count, "A").End(xlUp).Row
        
        'Jeśli są jakieś dane poniżej nagłówka
        If ostatniWierszZrodlo > 1 Then
            'Znajdź pierwszy pusty wiersz w arkuszu zbiorczym
            ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1
            
            'Skopiuj zakres od wiersza 2 do ostatniego
            wsZrodlowy.Range("A2:" & wsZrodlowy.Cells(ostatniWierszZrodlo, _
                wsZrodlowy.Cells(1, wsZrodlowy.Columns.Count).End(xlToLeft).Column).Address).Copy _
                Destination:=wsCel.Cells(ostatniWierszCel, 1)
        End If
        
        wbZrodlowy.Close SaveChanges:=False
        
        nazwaPliku = Dir() 'kolejny plik
    Loop
    
    Application.EnableEvents = True
    Application.ScreenUpdating = True
End Sub

Ten kod to „goła” wersja bez raportowania błędów, ale już działa i w większości scenariuszy skraca pracę z godzin do kilku minut.

Programista piszący kod VBA przy monitorze z widocznym skryptem
Źródło: Pexels | Autor: Jakub Zerdzicki

Coctyliwy przykład: łączenie wszystkich plików XLSX z jednego folderu

Scenariusz: miesięczne raporty z oddziałów

Typowy przypadek: kilkanaście lub kilkadziesiąt oddziałów przesyła miesięczny raport w postaci pliku XLSX. Każdy plik ma arkusz „Dane”, a w nim identyczny układ kolumn. Zamiast otwierać po kolei każdy raport, wpisywać filtry i kopiować dane, lepiej raz złożyć makro i po prostu wskazywać folder z nową paczką plików.

Założenia:

  • folder zawiera tylko pliki do połączenia (ewentualne inne typy będą pomijane),
  • każdy plik ma arkusz o nazwie „Dane”,
  • nagłówek w wierszu 1, dane od wiersza 2,
  • w arkuszu „Zbiorcze” nagłówek jest już przygotowany.

Makro łączące wszystkie pliki XLSX z arkuszem „Dane”

Kod oparty na tym samym szkielecie, ale trochę dopracowany pod realne użycie:

Sub PolaczRaportyOddzialow()
    Dim sciezkaFolderu As String
    Dim nazwaPliku As String
    Dim wbZrodlowy As Workbook
    Dim wsZrodlowy As Worksheet
    Dim wsCel As Worksheet
    Dim ostatniWierszCel As Long
    Dim ostatniWierszZrodlo As Long
    Dim ostatniaKolumna As Long
    
    sciezkaFolderu = WybierzFolder()
    If sciezkaFolderu = "" Then Exit Sub
    
    Set wsCel = ThisWorkbook.Worksheets("Zbiorcze")
    Call PrzygotujArkuszZbiorczy
    
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    
    nazwaPliku = Dir(sciezkaFolderu & "*.xlsx")
    
    Do While nazwaPliku <> ""
        On Error Resume Next
        Set wbZrodlowy = Workbooks.Open(sciezkaFolderu & "" & nazwaPliku, ReadOnly:=True)
        If Err.Number <> 0 Then
            'Jeśli plik nie chce się otworzyć – pomiń
            Err.Clear
            nazwaPliku = Dir()
            On Error GoTo 0
            GoTo KontynuujPetle
        End If
        On Error GoTo 0
        
        'Sprawdź, czy arkusz "Dane" istnieje
        On Error Resume Next
        Set wsZrodlowy = wbZrodlowy.Worksheets("Dane")
        If wsZrodlowy Is Nothing Then
            'Brak arkusza "Dane" – zamknij i pomiń
            wbZrodlowy.Close SaveChanges:=False
            Set wsZrodlowy = Nothing
            nazwaPliku = Dir()
            On Error GoTo 0
            GoTo KontynuujPetle
        End If
        On Error GoTo 0
        
        'Znajdź ostatni wiersz i kolumnę
        ostatniWierszZrodlo = wsZrodlowy.Cells(wsZrodlowy.Rows.Count, "A").End(xlUp).Row
        If ostatniWierszZrodlo > 1 Then
            ostatniaKolumna = wsZrodlowy.Cells(1, wsZrodlowy.Columns.Count).End(xlToLeft).Column
            ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1
            
            wsZrodlowy.Range(wsZrodlowy.Cells(2, 1), _
                             wsZrodlowy.Cells(ostatniWierszZrodlo, ostatniaKolumna)).Copy _
                             Destination:=wsCel.Cells(ostatniWierszCel, 1)
        End If
        
        wbZrodlowy.Close SaveChanges:=False
        
        nazwaPliku = Dir()
KontynuujPetle:
    Loop
    
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.ScreenUpdating = True
End Sub

Taka wersja:

  • pomija pliki, których nie da się otworzyć, zamiast wywracać się błędem,
  • ignoruje pliki bez arkusza „Dane”,
  • wyłącza przeliczanie formuł na czas pracy (co przy większych skoroszytach ma duże znaczenie).

Dodanie kolumny z nazwą pliku źródłowego

W praktyce często przydaje się informacja, z którego pliku pochodzi dany wiersz. Pozwala to szybko odfiltrować dane danego oddziału lub miesiąca, jeśli pliki są nazwane sensownie (np. Oddzial01_2023-01.xlsx).

Najprościej dodać kolumnę „Źródło” po prawej stronie istniejących kolumn i wklejać do niej nazwę pliku dla całej porcji danych z danego skoroszytu.

'... w środku pętli, po wyznaczeniu ostatniaKolumna i ostatniWierszZrodlo:
Dim zakresDocelowy As Range
Dim zakresZrodlo As Range
Dim ostatniaKolumnaCel As Long

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

Set zakresZrodlo = wsZrodlowy.Range(wsZrodlowy.Cells(2, 1), _
    wsZrodlowy.Cells(ostatniWierszZrodlo, ostatniaKolumna))

ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1

Set zakresDocelowy = wsCel.Cells(ostatniWierszCel, 1)

zakresZrodlo.Copy Destination:=zakresDocelowy

'Wpisz nazwę pliku w dodatkową kolumnę
wsCel.Range(wsCel.Cells(ostatniWierszCel, ostatniaKolumnaCel + 1), _
           wsCel.Cells(ostatniWierszCel + zakresZrodlo.Rows.Count - 1, _
           ostatniaKolumnaCel + 1)).Value = nazwaPliku

To drobiazg, a w późniejszej analizie pozwala uniknąć domysłów, skąd wzięły się konkretne rekordy.

Wariant oszczędzający czas: działanie na zamkniętych plikach

Kiedy opłaca się nie otwierać plików w oknach

Otwarcie i zamknięcie dużego skoroszytu potrafi trwać po kilka sekund. Przy kilkuset plikach robi się z tego godzina czekania. Excel ma jednak możliwość czytania danych z plików, które pozostają „zamknięte” z punktu widzenia interfejsu – bez animacji, bez odświeżania okien.

Najprostsza metoda, która nie wymaga dodatków, to użycie kwerend (QueryTables) zewnętrznych lub funkcji Workbooks.Open na niewidocznych oknach. Ten drugi wariant jest najprostszy do wdrożenia:

  • pliki nadal są technicznie otwierane,
  • ale nie migają użytkownikowi przed oczami,
  • Excel nie przełącza aktywnego okna przy każdym pliku.

Otwieranie plików „po cichu”

Żeby zminimalizować narzut interfejsu, wystarczy kilka linijek:

Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False

Dodatkowo można ukryć używane skoroszyty pomocnicze:

wbZrodlowy.Windows(1).Visible = False

Takie podejście nie jest „idealnie bez otwierania”, ale w praktyce różnica czasowa jest ogromna w porównaniu z mrugającymi dziesiątkami okien.

Odczyt danych z zamkniętych plików przez QueryTables

Drugi wariant polega na tym, że Excel traktuje inne skoroszyty jak zewnętrzne źródło danych – podobnie jak plik tekstowy czy bazę SQL. Dla plików XLSX można zbudować kwerendę, która wciągnie od razu cały zakres z konkretnego arkusza.

Schemat:

  1. w arkuszu zbiorczym tworzona jest tymczasowa kwerenda do konkretnego pliku,
  2. dane są wstawiane w określone miejsce,
  3. kwerenda jest usuwana, a dane zostają jako zwykłe wartości.

Prosty przykład dla jednego pliku (rozbudowany później do pętli):

Sub WczytajZamknietyPlik()
    Dim sciezkaPliku As String
    Dim wsCel As Worksheet
    Dim qt As QueryTable
    Dim ostatniWierszCel As Long
    
    sciezkaPliku = "C:RaportyDo_łączeniaRaport1.xlsx"
    Set wsCel = ThisWorkbook.Worksheets("Zbiorcze")
    
    ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1
    
    With wsCel.QueryTables.Add( _
        Connection:="OLEDB;Provider=Microsoft.ACE.OLEDB.12.0;" & _
        "Data Source=" & sciezkaPliku & ";" & _
        "Extended Properties=""Excel 12.0 Xml;HDR=YES"";", _
        Destination:=wsCel.Cells(ostatniWierszCel, 1))
        
        .CommandText = "SELECT * FROM [Dane$]"
        .Name = "TmpImport"
        .Refresh BackgroundQuery:=False
    End With
    
    'Usuń kwerendę, dane zostają
    For Each qt In wsCel.QueryTables
        If qt.Name = "TmpImport" Then
            qt.Delete
            Exit For
        End If
    Next qt
End Sub

To rozwiązanie jest szybsze przy dużej liczbie plików i dużych zakresach, natomiast wymaga dostępnego dostawcy OLEDB (ACE).

Pętla po wielu plikach z użyciem QueryTables

Ten sam schemat można zastosować do całego folderu, bez ręcznego podawania ścieżki do każdego pliku. Mechanicznie jest to podobne do wersji z Workbooks.Open – zmienia się jedynie sposób wczytania zawartości.

Sub PolaczRaporty_QueryTables()
    Dim sciezkaFolderu As String
    Dim nazwaPliku As String
    Dim wsCel As Worksheet
    Dim ostatniWierszCel As Long
    Dim pelnaSciezka As String
    Dim qt As QueryTable
    Dim polaczenie As String
    
    sciezkaFolderu = WybierzFolder()
    If sciezkaFolderu = "" Then Exit Sub
    
    Set wsCel = ThisWorkbook.Worksheets("Zbiorcze")
    Call PrzygotujArkuszZbiorczy
    
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    
    nazwaPliku = Dir(sciezkaFolderu & "*.xlsx")
    
    Do While nazwaPliku <> ""
        pelnaSciezka = sciezkaFolderu & "" & nazwaPliku
        
        ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1
        
        polaczenie = "OLEDB;Provider=Microsoft.ACE.OLEDB.12.0;" & _
                     "Data Source=" & pelnaSciezka & ";" & _
                     "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
        
        On Error Resume Next
        Set qt = wsCel.QueryTables.Add( _
            Connection:=polaczenie, _
            Destination:=wsCel.Cells(ostatniWierszCel, 1))
        
        If Err.Number <> 0 Then
            'Brak sterownika ACE lub problem z plikiem – pomiń i jedź dalej
            Err.Clear
            On Error GoTo 0
            nazwaPliku = Dir()
            GoTo Kontynuuj
        End If
        On Error GoTo 0
        
        With qt
            .CommandText = "SELECT * FROM [Dane$]"
            .Name = "TmpImport"
            .FieldNames = True
            .RowNumbers = False
            .RefreshStyle = xlInsertDeleteCells
            .Refresh BackgroundQuery:=False
        End With
        
        'Usunięcie kwerendy, same dane zostają
        qt.Delete
        
Kontynuuj:
        nazwaPliku = Dir()
    Loop
    
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.ScreenUpdating = True
End Sub

Ten sposób dobrze sprawdza się przy dużych paczkach plików, ale ma jeden haczyk: zależy od zainstalowanego dostawcy ACE. Na starszych lub „odchudzonych” instalacjach Office może go po prostu nie być.

Czy QueryTables faktycznie są szybsze

Różnica nie jest liniowa. Przy pojedynczych plikach lub małych zakresach klasyczne otwieranie skoroszytu często wypada podobnie albo nawet szybciej, bo nie ma narzutu na składnię OLEDB. Zysk pojawia się wtedy, gdy:

  • każdy plik ma dużo wierszy (setki tysięcy),
  • paczka zawiera dziesiątki lub setki raportów,
  • Excel podczas otwierania standardowego pliku bardzo długo „mieli” formuły, formatowanie lub makra w tamtym skoroszycie.

Jeśli celem jest jednorazowe scalenie kilkunastu niedużych raportów, nie ma sensu inwestować czasu w QueryTables – prostszy kod z Workbooks.Open będzie tańszy w utrzymaniu. Tryb OLEDB warto zostawić na moment, gdy zlecenie zamiany ręcznego łączenia na makro dotyczy już setek plików dziennie.

Obsługa różnych formatów plików: XLSX, XLS, CSV

W realnych katalogach rzadko wszystko jest w jednym, idealnym formacie. Ktoś coś wyeksportuje do CSV, ktoś inny wyśle stary plik XLS. Da się to spiąć jednym makrem, o ile rozsądnie zaplanuje się logikę.

Strategia: jeden silnik, kilka „gałęzi” logicznych

Najwygodniej potraktować każdy typ pliku jako osobny przypadek wewnątrz jednej pętli. Pętla przebiega po wszystkich nazwach plików w folderze, a w środku:

  • rozpoznaje rozszerzenie,
  • dla XLS/XLSX używa otwierania skoroszytu,
  • dla CSV stosuje prostsze czytanie wierszy (np. przez Open ... For Input albo z wykorzystaniem drugiego, „tymczasowego” skoroszytu).

Takie podejście ogranicza duplikowanie kodu: struktura pętli, przygotowanie arkusza zbiorczego czy logika „Dodaj kolumnę Źródło” są jedne, a różni się tylko sposób dogrania danych z konkretnego formatu.

Rozpoznawanie rozszerzenia pliku

Najprostsza technika to Right plus LCase:

Dim rozszerzenie As String

rozszerzenie = LCase$(Mid$(nazwaPliku, InStrRev(nazwaPliku, ".") + 1))

Select Case rozszerzenie
    Case "xlsx", "xlsm", "xls"
        'obsługa Excela
    Case "csv"
        'obsługa pliku tekstowego
    Case Else
        'inne formaty – pomijamy
End Select

Tyle wystarczy, żeby jedno makro sensownie reagowało na mieszankę typów w jednym katalogu, bez ręcznego filtrowania plików przed uruchomieniem.

Import XLS/XLSX w jednej pętli

Pliki XLS i XLSX można zaciągać tym samym kodem, zmienia się głównie filtr w funkcji Dir i ewentualne właściwości po otwarciu. Dla uproszczenia można użyć dwóch przebiegów pętli po folderze:

  • pierwszy – dla XLSX/XLSM,
  • drugi – dla starego formatu XLS.

Łatwiej wtedy uniknąć egzotycznych błędów z nietypowymi plikami.

Sub PolaczRozneFormaty()
    Dim sciezkaFolderu As String
    Dim nazwaPliku As String
    Dim wsCel As Worksheet
    
    sciezkaFolderu = WybierzFolder()
    If sciezkaFolderu = "" Then Exit Sub
    
    Set wsCel = ThisWorkbook.Worksheets("Zbiorcze")
    Call PrzygotujArkuszZbiorczy
    
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    
    'Najpierw XLSX/XLSM
    nazwaPliku = Dir(sciezkaFolderu & "*.xls*")
    Do While nazwaPliku <> ""
        Call PrzetworzPlikExcel(sciezkaFolderu, nazwaPliku, wsCel)
        nazwaPliku = Dir()
    Loop
    
    'Potem CSV
    nazwaPliku = Dir(sciezkaFolderu & "*.csv")
    Do While nazwaPliku <> ""
        Call PrzetworzPlikCSV(sciezkaFolderu, nazwaPliku, wsCel)
        nazwaPliku = Dir()
    Loop
    
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.ScreenUpdating = True
End Sub

Funkcje PrzetworzPlikExcel i PrzetworzPlikCSV trzymają logikę specyficzną dla każdego formatu, dzięki czemu „główna” procedura zostaje przejrzysta.

Przetwarzanie plików Excela (XLS/XLSX)

Moduł dla Excela może być w zasadzie kopią wcześniej pokazanej procedury, zamkniętą w osobnej funkcji. To obniża koszt utrzymania kodu – zmiany w logice łączenia (np. nowe kolumny) robi się w jednym miejscu.

Sub PrzetworzPlikExcel( _
        ByVal sciezkaFolderu As String, _
        ByVal nazwaPliku As String, _
        ByVal wsCel As Worksheet)

    Dim wbZrodlowy As Workbook
    Dim wsZrodlowy As Worksheet
    Dim ostatniWierszZrodlo As Long
    Dim ostatniaKolumna As Long
    Dim ostatniWierszCel As Long
    Dim pelnaSciezka As String
    Dim ostatniaKolumnaCel As Long
    
    pelnaSciezka = sciezkaFolderu & "" & nazwaPliku
    
    On Error Resume Next
    Set wbZrodlowy = Workbooks.Open(pelnaSciezka, ReadOnly:=True)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo 0
        Exit Sub
    End If
    On Error GoTo 0
    
    On Error Resume Next
    Set wsZrodlowy = wbZrodlowy.Worksheets("Dane")
    If wsZrodlowy Is Nothing Then
        wbZrodlowy.Close SaveChanges:=False
        Set wsZrodlowy = Nothing
        On Error GoTo 0
        Exit Sub
    End If
    On Error GoTo 0
    
    ostatniWierszZrodlo = wsZrodlowy.Cells(wsZrodlowy.Rows.Count, "A").End(xlUp).Row
    If ostatniWierszZrodlo > 1 Then
        ostatniaKolumna = wsZrodlowy.Cells(1, wsZrodlowy.Columns.Count).End(xlToLeft).Column
        
        ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1
        ostatniaKolumnaCel = wsCel.Cells(1, wsCel.Columns.Count).End(xlToLeft).Column
        
        wsZrodlowy.Range(wsZrodlowy.Cells(2, 1), _
                         wsZrodlowy.Cells(ostatniWierszZrodlo, ostatniaKolumna)).Copy _
                         Destination:=wsCel.Cells(ostatniWierszCel, 1)
        
        'Dopisanie źródła
        wsCel.Range(wsCel.Cells(ostatniWierszCel, ostatniaKolumnaCel + 1), _
                    wsCel.Cells(ostatniWierszCel + ostatniWierszZrodlo - 2, _
                                ostatniaKolumnaCel + 1)).Value = nazwaPliku
    End If
    
    wbZrodlowy.Close SaveChanges:=False
End Sub

Tak wydzielona funkcja może być użyta z różnych miejsc: łączenie raportów sprzedaży, logów systemowych czy plików z ankietą – zmienia się tylko nazwa arkusza lub układ kolumn.

Przetwarzanie CSV: wariant „budżetowy”

Najmniej kosztowny w implementacji sposób pracy z CSV to otwarcie pliku jak zwykłego skoroszytu, a potem skopiowanie danych. Excel sam „rozpakuje” separator, wystarczy raz zadbać o ustawienia regionalne (przecinek vs średnik) lub liczyć się z tym, że kolumny tekstowe będą wymagały drobnej korekty.

Sub PrzetworzPlikCSV( _
        ByVal sciezkaFolderu As String, _
        ByVal nazwaPliku As String, _
        ByVal wsCel As Worksheet)

    Dim wbCSV As Workbook
    Dim wsCSV As Worksheet
    Dim ostatniWierszZrodlo As Long
    Dim ostatniaKolumna As Long
    Dim ostatniWierszCel As Long
    Dim pelnaSciezka As String
    Dim ostatniaKolumnaCel As Long
    
    pelnaSciezka = sciezkaFolderu & "" & nazwaPliku
    
    On Error Resume Next
    Set wbCSV = Workbooks.Open(Filename:=pelnaSciezka, Local:=True)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo 0
        Exit Sub
    End If
    On Error GoTo 0
    
    Set wsCSV = wbCSV.Worksheets(1)
    
    'Założenie: pierwszy wiersz to nagłówek, dane od wiersza 2
    ostatniWierszZrodlo = wsCSV.Cells(wsCSV.Rows.Count, "A").End(xlUp).Row
    If ostatniWierszZrodlo > 1 Then
        ostatniaKolumna = wsCSV.Cells(1, wsCSV.Columns.Count).End(xlToLeft).Column
        
        ostatniWierszCel = wsCel.Cells(wsCel.Rows.Count, "A").End(xlUp).Row + 1
        ostatniaKolumnaCel = wsCel.Cells(1, wsCel.Columns.Count).End(xlToLeft).Column
        
        wsCSV.Range(wsCSV.Cells(2, 1), _
                    wsCSV.Cells(ostatniWierszZrodlo, ostatniaKolumna)).Copy _
                    Destination:=wsCel.Cells(ostatniWierszCel, 1)
        
        'Źródło = nazwa pliku CSV
        wsCel.Range(wsCel.Cells(ostatniWierszCel, ostatniaKolumnaCel + 1), _
                    wsCel.Cells(ostatniWierszCel + ostatniWierszZrodlo - 2, _
                                ostatniaKolumnaCel + 1)).Value = nazwaPliku
    End If
    
    wbCSV.Close SaveChanges:=False
End Sub

To nie jest najszybszy z możliwych wariantów, ale ma dobry stosunek „czas napisania kodu” do „zysku z automatyzacji”. Gdy CSV są małe i jest ich niewiele, nie ma sensu komplikować rozwiązania parserem tekstowym na Open ... For Input.

Gdy CSV są duże lub liczone w tysiącach

Przy naprawdę dużych paczkach plików CSV otwieranie każdego z nich jako osobnego skoroszytu może stać się wąskim gardłem. Wtedy lepiej podejść do nich jak do zwykłych plików tekstowych i wczytywać linia po linii:

  • czytać kolejne wiersze funkcją Line Input #,
  • rozbijać po separatorze (Split),
  • przepisywać bezpośrednio do komórek w arkuszu „Zbiorcze”.

Kod robi się dłuższy, za to można uniknąć narzutu interfejsu Excela i precyzyjniej sterować mapowaniem kolumn. Opłaca się głównie wtedy, gdy CSV to duże logi systemowe albo eksporty z baz danych, które bez kompresji spokojnie przekraczają dziesiątki megabajtów.

Mieszanie formatów rozsądnie: kiedy co wybrać

Nie ma jednego „świętego” sposobu łączenia wszystkiego. Żeby nie przepłacać czasem i komplikacją kodu, można przyjąć prosty schemat decyzyjny:

  • jeżeli dominują pliki Excela, a CSV pojawiają się sporadycznie – potraktować CSV jak małe skoroszyty (wariant budżetowy z Workbooks.Open),
  • jeżeli źródło to głównie duże CSV, a Excela jest mało – poświęcić chwilę i napisać prostego parsera tekstowego, a pliki XLSX/XLS obsłużyć „po drodze”,
  • Najczęściej zadawane pytania (FAQ)

    Jak napisać proste makro VBA do łączenia wielu plików Excela w jedną tabelę?

    Najprostszy schemat to: wybór folderu, pętla po wszystkich plikach, skopiowanie danych z wybranego arkusza i dopisanie ich do arkusza zbiorczego. W praktyce oznacza to jedno makro w module, które używa funkcji FileDialog (do wskazania folderu), Dir lub FileSystemObject (do przejścia po plikach) oraz Range.Copy/Value (do kopiowania danych bez nagłówków).

    Na start wystarczy wariant „budżetowy”: zakładasz, że wszystkie pliki mają ten sam układ, dane zaczynają się od tego samego wiersza i są na tym samym arkuszu (np. „Dane”). Dzięki temu kod jest krótki, przejrzysty i łatwo go poprawić, zamiast inwestować godziny w „inteligentne” dopasowanie każdego wyjątku.

    Kiedy opłaca się używać makra do łączenia plików, a kiedy lepiej zostać przy ręcznym kopiowaniu?

    Makro ma sens, gdy konsolidacja jest powtarzalna: regularnie łączysz dane z więcej niż 5–10 plików i robisz to co najmniej raz w miesiącu. Wtedy jednorazowe 30–60 minut na przygotowanie makra szybko się zwraca, bo każde kolejne uruchomienie trwa kilkadziesiąt sekund zamiast kilkunastu minut klikania.

    Ręczne kopiowanie wystarczy, gdy okazjonalnie sklejasz 2–3 arkusze raz do roku. Jeśli jednak co tydzień scalasz raporty z kilkunastu oddziałów, oszczędność czasu i niższe ryzyko błędów szybko przewyższają wysiłek włożony w napisanie prostego kodu.

    Jak przygotować pliki źródłowe, żeby makro VBA do łączenia działało bez problemów?

    Największą oszczędność czasu daje prosty porządek. Pliki trzymaj w jednym folderze (np. C:RaportyDo_łączenia), a w każdym z nich używaj tego samego układu tabeli: identyczna liczba kolumn, ten sam porządek kolumn, dane startujące w tym samym wierszu. Dzięki temu makro nie musi „zgadywać”, co z czym połączyć.

    Dobrą praktyką jest też stała nazwa arkusza z danymi, np. „Dane” albo „Raport”. Alternatywą jest pobieranie danych z pierwszego arkusza w pliku, ale wtedy trzeba pilnować, żeby nikt nie dorzucał dodatkowych zakładek przed właściwą. Im prostsze i bardziej spójne pliki źródłowe, tym mniej skomplikowany (i tańszy w utrzymaniu) kod.

    Jak uniknąć duplikatów, nadpisania danych i zgubionych wierszy przy łączeniu plików VBA?

    Podstawowe zabezpieczenie to konsekwentny schemat działania makra: zawsze doklejanie danych pod ostatnim niepustym wierszem w arkuszu zbiorczym, pomijanie wklejania nagłówków oraz identyczny zakres kopiowanych kolumn w każdym pliku. W praktyce sprowadza się to do obliczenia ostatniego wiersza w tabeli zbiorczej i rozpoczęcia wklejania od następnego.

    Dodatkowo można dodać proste „bezpieczniki”: kolumnę techniczną z nazwą pliku źródłowego, log z listą przetworzonych plików albo tymczasowe przenoszenie skonsolidowanych raportów do podfolderu „Przetworzone”. To tanie środki, które bardzo ułatwiają znalezienie problemu, jeśli coś kiedyś pójdzie nie tak.

    Czy da się połączyć pliki Excela o różnej strukturze za pomocą jednego makra?

    Jest to możliwe, ale koszt w postaci skomplikowanego kodu rośnie bardzo szybko. Dla plików o różnych układach trzeba w VBA dodać logikę mapującą kolumny (np. zamień „Oddział” na „Filia”, pomiń brakujące kolumny, dopasuj inne kolejności). W prostych przypadkach da się to ogarnąć warunkami i słownikiem nazw, ale wymaga to już solidniejszej wiedzy z VBA.

    W podejściu „budżetowym” zwykle taniej jest najpierw ustandaryzować szablony raportów (jednorazowa akcja organizacyjna), niż utrzymywać złożone, wrażliwe na zmiany makra. Jedno spójne szablonowe „Dane.xlsx” dla każdego oddziału często rozwiązuje 90% problemu.

    Jakie ustawienia Excela i uprawnienia są potrzebne, żeby makro łączące pliki działało?

    Podstawą jest włączona obsługa makr w Centrum zaufania oraz zapis skoroszytu z kodem w formacie .xlsm (lub .xlsb). Wygodnie jest też dodać folder z makrem do zaufanych lokalizacji, żeby Excel nie pytał za każdym razem o zgodę na uruchomienie VBA.

    Po stronie systemu plików potrzebne są przynajmniej prawa do odczytu folderów, z których pobierasz raporty. Makro nie obejdzie blokad na dyskach sieciowych czy OneDrive – jeśli użytkownik nie ma dostępu do pliku „ręcznie”, nie otworzy go również kod VBA.

    Czy potrzebna jest zaawansowana znajomość VBA, żeby używać makra do łączenia plików?

    Do codziennej obsługi wystarczy naprawdę podstawowy poziom: otwarcie Edytora VBA (ALT+F11), wklejenie gotowego kodu do modułu, zapis pliku jako .xlsm i uruchomienie makra z poziomu ALT+F8 lub przycisku na arkuszu. Bardziej doświadczeni użytkownicy mogą sami dopisywać drobne usprawnienia, ale nie jest to warunek konieczny.

    Najważniejsze jest zrozumienie ogólnej logiki: pętla po plikach, kopiowanie danych z ustalonego zakresu, dopisywanie do tabeli zbiorczej. Po przygotowaniu jednego działającego „szkieletu” szybko da się go dostosować do kolejnych raportów, zamiast za każdym razem zaczynać od zera.

    Źródła informacji

  • Excel 2019 Bible. Wiley (2018) – Obszerne omówienie Excela, w tym makra, VBA i automatyzacja zadań
  • Professional Excel Development: The Definitive Guide to Developing Applications Using Microsoft Excel, VBA, and .NET. Addison-Wesley Professional (2015) – Projektowanie profesjonalnych rozwiązań VBA i aplikacji w Excelu
  • Microsoft 365 – dokumentacja Excel VBA. Microsoft – Oficjalna dokumentacja obiektów, metod i zdarzeń VBA w Excelu
  • VBA Developer’s Guide. Microsoft Press – Przewodnik po tworzeniu rozwiązań w VBA, struktura kodu i dobre praktyki
  • Automating Excel with VBA and Macros. Que Publishing – Praktyczne przykłady automatyzacji zadań w Excelu za pomocą makr
  • Excel 2016 Power Programming with VBA. John Wiley & Sons (2016) – Praca z wieloma skoroszytami, pętle po plikach, kopiowanie zakresów
  • VBA and Macros: Microsoft Excel 2013. Pearson Education (2013) – Tworzenie makr do konsolidacji danych i automatyzacji raportów

Poprzedni artykułJak przygotować młodego konia do pierwszych zawodów skokowych: plan treningu, żywienie i profilaktyka zdrowia
Henryk Adamczyk
Henryk Adamczyk od lat uczy Excela w praktyce: od pierwszych formuł po zaawansowane modele raportowe. W NaukaExcel.pl tworzy kursy i instrukcje oparte na realnych danych z finansów i administracji, gdzie liczy się powtarzalność i kontrola błędów. Każdy materiał testuje na kilku wariantach plików, porównuje wyniki i opisuje typowe pułapki. Stawia na jasne kroki, skróty klawiaturowe i dobre nawyki pracy z arkuszem, tak aby czytelnik rozumiał nie tylko „jak”, ale też „dlaczego”.