Wszystkie artykuły

KajoData / Praca i rozwój

Pytania techniczne na rozmowie analityka: odpowiedź, przykład, ograniczenie

Sama definicja JOIN albo miary to dopiero początek odpowiedzi. Pokazuję, jak wyjaśnić działanie narzędzia, sprawdzić wynik i uczciwie nazwać ograniczenie.

Kajo RudzińskiOpublikowano Aktualizacja 11 min czytania

Tabela, model z klocków i lupa sprawdzająca brakujący element na żółto-granatowym warsztacie.

Znasz definicję LEFT JOIN, ale gdy ktoś prosi o przykład, nagle robi się cisza. Albo umiesz zbudować raport w Power BI, tylko trudno Ci wyjaśnić, dlaczego jedna liczba zmienia się po kliknięciu filtra. Takie miejsca warto znaleźć przed rozmową. Ja przygotowanie zaczynam właśnie od tłumaczenia prostych rzeczy na głos.

Poniżej przechodzę przez Excel, SQL, Power BI, Tableau i Pythona. To zestaw pytań do treningu na poziomie junior i junior+, bez udawania badania częstości pytań w rekrutacjach. Konkretna firma może używać tylko części tych narzędzi. Zacznij od ogłoszenia i zakresu stanowiska, bo kolekcjonowanie nazw programów potrafi skutecznie zająć cały tydzień.

Wszystkie dane i scenariusze ćwiczeń są syntetyczne. Chcę pokazać sposób rozumowania, który można sprawdzić na kartce i odtworzyć w narzędziu. Nie obiecuję, że identyczne pytania pojawią się na Twojej rozmowie.

Jak układam odpowiedź techniczną

Przygotowuję krótką odpowiedź w trzech częściach. Najpierw wyjaśniam mechanizm jednym lub dwoma zdaniami. Potem pokazuję mały przykład z przewidywanym wynikiem. Na końcu dodaję ograniczenie, które ma znaczenie w tym przykładzie. Dzięki temu rozmówca może sprawdzić, czy rozumiem słowa, których używam.

Weźmy łączenie klientów z zamówieniami. Powiedziałbym: „Potrzebuję zachować wszystkich klientów, także bez zakupów, więc użyję LEFT JOIN. Klient z dwoma zamówieniami pojawi się dwa razy. Przed sumowaniem sprawdzę, na jakim poziomie jest każdy wiersz i czy słownik klientów ma unikalne identyfikatory”. To już pozwala zadać sensowne pytanie uzupełniające.

Nie zaczynałbym od pięciu wyjątków i historii standardu SQL. Najpierw daję odpowiedź na zadane pytanie. Dopiero potem rozwijam fragment, który rozmówca chce pogłębić. Można rozumieć temat bardzo dobrze i skutecznie ukryć to pod dziesięciominutowym monologiem. Mikrofon niestety nie ma funkcji automatycznego streszczenia.

Gdy zadanie jest niejasne, doprecyzowuję wynik: jeden wiersz na klienta czy na zamówienie? Czy klient bez zakupu ma zostać? Jaki okres obejmuje raport? Te pytania zmieniają rozwiązanie. Dopytywanie o nazwę koloru wykresu na tym etapie raczej go nie zmieni.

Oddzielam też niepamiętanie składni od nieznajomości mechanizmu. Mogę powiedzieć, że chcę filtrować grupy po agregacji i sprawdzę dokładny zapis w danym silniku. Jeżeli nie rozumiem, które wiersze weszły do sumy, sama dokumentacja składni problemu nie załatwi. Podobny trening dowodów opisuję przy trudnych pytaniach rekrutacyjnych.

Excel: wyszukiwanie i znaczenie brakującej wartości

Jakie ograniczenia ma VLOOKUP?

W zwykłym wyszukiwaniu VLOOKUP, czyli WYSZUKAJ.PIONOWO, klucz musi być w pierwszej kolumnie podanego zakresu. Kolumnę wyniku wskazuję numerem. Gdy układ tabeli się zmieni, sprawdzam, czy ten numer nadal oznacza właściwe pole. Dla identyfikatora klienta jawnie wybieram dopasowanie dokładne; pominięty ostatni argument oznacza dopasowanie przybliżone. Zasady opisuje Microsoft.

Mój przykład: słownik zawiera identyfikator klienta i jego region. Szukam klienta 42, żeby dopisać region do listy zamówień. Zanim wybiorę funkcję, sprawdzam, czy 42 występuje w słowniku tylko raz. Jeżeli są dwa różne regiony, otrzymanie jakiejś odpowiedzi z formuły jeszcze nie rozstrzyga, który region jest prawidłowy.

XLOOKUP pozwala oddzielnie wskazać zakres wyszukiwania i wyników oraz domyślnie szuka dokładnego dopasowania. Microsoft zaznacza, że funkcja nie jest dostępna w Excelu 2016 i 2019. Dlatego pytam również o wersję używaną przez odbiorcę pliku. Szczegóły są w dokumentacji XLOOKUP. Sama zamiana funkcji nie naprawi niejednoznacznego słownika.

Co robię z błędem #N/A?

W dokładnym wyszukiwaniu #N/A może sygnalizować brak dopasowania. Najpierw sprawdzam istnienie klucza, typ danych i dodatkowe spacje. Identyfikator zapisany jako tekst oraz podobnie wyglądająca liczba mogą wymagać uporządkowania. Zachowuję oryginalne wartości, żeby móc ustalić, gdzie powstała różnica.

IFNA przechwytuje konkretnie #N/A. IFERROR obsługuje szerszy zestaw błędów, więc może też zasłonić zepsute odwołanie. W odpowiedzi na rozmowie powiedziałbym, jaki brak obsługuję i dlaczego wybrany komunikat jest uczciwy. „Nie znaleziono regionu” daje inną informację niż zero.

Wyobraź sobie raport, w którym dziesięciu klientom nie przypisano regionu. Wrzucenie ich do pustej kategorii i pokazanie liczby takich przypadków pomaga zlecić uzupełnienie słownika. Usunięcie tych wierszy zmieni liczbę klientów. Zastąpienie brakującej ceny zerem zaniży wartość zamówień. To konsekwencje, które potrafię wyjaśnić bez popisu znajomości wszystkich funkcji Excela.

SQL: przewidź wynik przed uruchomieniem

INNER JOIN i LEFT JOIN na trzech klientach

Poniższe przykłady uruchamiam w SQLite. Najpierw tworzę syntetyczne dane. Jeden wiersz tabeli orders oznacza jedno zamówienie; kwoty są umownymi jednostkami, bez podatków, zwrotów i przeliczeń walut.

-- example: fixture
CREATE TABLE customers (customer_id INTEGER PRIMARY KEY);
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER,
  amount INTEGER,
  status TEXT
);
INSERT INTO customers VALUES (1), (2), (3);
INSERT INTO orders VALUES
  (101, 1, 100, 'completed'),
  (102, 1, 50, 'cancelled'),
  (103, 2, 80, 'completed');

Klient 1 ma dwa zamówienia, klient 2 jedno, a klient 3 żadnego. Zwykły INNER JOIN po customer_id zwróci trzy dopasowania. LEFT JOIN, z klientami po lewej, zwróci cztery wiersze: dodatkowo klienta 3 z pustymi polami zamówienia. Wynika to z semantyki SELECT i JOIN w SQLite.

Oba złączenia mogą powielać dane lewej strony, gdy istnieje wiele dopasowań. Wybór LEFT JOIN nie gwarantuje relacji jeden do jednego. Przy danych klienta powtórzonych obok zamówień nie sumuję bez zastanowienia jego limitu kredytowego czy rocznego celu. To wartości zapisane na innym poziomie szczegółowości.

Gdzie umieścić filtr statusu?

Chcę policzyć zakończone zamówienia każdego klienta, zachowując również wynik zero. Warunek statusu umieszczam w ON, żeby ograniczał dopasowania zamówień:

-- example: completed
SELECT c.customer_id, COUNT(o.order_id) AS completed_orders
FROM customers AS c
LEFT JOIN orders AS o
  ON c.customer_id = o.customer_id
 AND o.status = 'completed'
GROUP BY c.customer_id
ORDER BY c.customer_id;

Wyniki to kolejno (1, 1), (2, 1), (3, 0). Przeniesienie warunku statusu do WHERE usunęłoby klienta 3 z wyniku. Sprawdziłbym też klienta, który ma wyłącznie anulowane zamówienia. To dobry przypadek kontrolny, bo ujawnia błąd nawet wtedy, gdy każdy klient ma jakiś zakup.

Używam COUNT(o.order_id), ponieważ liczy niepuste identyfikatory zamówień. COUNT(*) policzyłoby również wiersz pozostawiony dla klienta bez dopasowania. Różnicę opisuje dokumentacja agregacji SQLite. Dokładniejszy trening łączenia znajdziesz w moim tekście o rodzajach JOIN.

WHERE, HAVING i UNION

WHERE określa wiersze wchodzące do grupowania, a HAVING warunek dotyczący grup. W tym przykładzie najpierw wybieram zakończone zamówienia, następnie zostawiam klientów z ich łączną kwotą co najmniej 90:

-- example: having
SELECT customer_id, SUM(amount) AS total_amount
FROM orders
WHERE status = 'completed'
GROUP BY customer_id
HAVING SUM(amount) >= 90
ORDER BY customer_id;

Dostanę (1, 100). Anulowane 50 nie wejdzie do sumy, a klient 2 z kwotą 80 nie przejdzie progu. Gdybym filtrował pojedyncze kwoty zamiast sumy klienta, odpowiedziałbym na inne pytanie. Do samodzielnego sprawdzenia: dodaj klientowi 2 drugie zakończone zamówienie na 20. Suma ma wtedy wynosić 100, mimo że żadne z jego zamówień osobno nie osiąga progu 90.

Przy UNION rozmawiam o składaniu zgodnych zestawów wynikowych. UNION ALL zachowuje powtórzenia; UNION usuwa identyczne wiersze wyniku. Dwa zamówienia z kwotą 100 mogą być odrębnymi zdarzeniami. Jeśli wybiorę tylko kwotę i użyję UNION, mogę je scalić. To dlatego metafora „dokładania tabel pod sobą” wymaga dopowiedzenia o duplikatach oraz liczbie i znaczeniu kolumn. W SQLite trzeba też uważać na dynamiczne typowanie; inne silniki mogą wymagać jawnych konwersji.

Lupa nad parą identycznych klocków obok osobnego niedopasowanego elementu.

Power BI: skąd miara bierze swoją liczbę

Miara a kolumna obliczeniowa

Odpowiedź zaczynam od scenariusza. Przyjmuję model Import z zamówieniami. Kolumna może przypisywać wierszowi kategorię wielkości zamówienia. Jej wartości są obliczane przy przeliczeniu modelu, w tym podczas odświeżania. Miara daje wynik w bieżącym kontekście filtrów, na przykład sumę kwot dla wybranego klienta. Takie rozróżnienie pokazuje dokumentacja obliczeń Power BI.

Na naszych danych suma wszystkich kwot wynosi 230. Po ograniczeniu do statusu completed powinna wynieść 180. Po dodatkowym wyborze klienta 1 będzie to 100. Te trzy liczby zapisałbym przed budową raportu. Dzięki temu mogę sprawdzić, czy model faktycznie reaguje na filtr, zamiast ufać temu, że karta wygląda profesjonalnie.

Kolumna z kategorią nadal może służyć do filtrowania. Jej wartość w pojedynczym wierszu nie zmienia się jednak tylko dlatego, że odbiorca kliknął slicer. Z kolei miara może celowo usuwać część filtrów w swoim wyrażeniu. Dlatego po usłyszeniu „wynik się nie zmienia” pytam o definicję obliczenia, relacje oraz zakres działania filtra. Nie stawiam diagnozy na podstawie samego obrazka.

Relacje, kierunek filtrowania i slicery

W prostym modelu lista klientów ma unikalny klucz, a zamówienia mogą zawierać wiele wierszy na klienta. Relacja jeden do wielu pozwala filtrować zamówienia wyborem klienta. Sprawdzam obie strony relacji i aktywną ścieżkę propagacji filtra. Nie włączam filtrowania w obie strony tylko dlatego, że brzmi bardziej wszechstronnie. Microsoft opisuje koszty i zastosowania relacji dwukierunkowych.

Slicer jest widocznym na stronie sterowaniem filtrem. Filtry mogą mieć również zakres wizualizacji, strony albo całego raportu. Interakcje wizualizacji i synchronizacja slicerów wpływają na to, co odbiorca zobaczy. Dokumentacja slicerów pomaga oddzielić te ustawienia od samego obliczenia.

W zadaniu rekrutacyjnym dopisałbym obok wyniku, jakie dane zostały wybrane. „Sprzedaż: 180” bez informacji o statusie i okresie zostawia za dużo miejsca na interpretację. Jeżeli dopiero poznajesz modelowanie, rozszerzeniem tego fragmentu jest tekst o umiejętnościach Power BI.

Tableau: świeżość danych, LOD i osie

Czego nie obiecuje połączenie Live?

Live korzysta z połączenia do źródła, a ekstrakt stanowi przygotowaną kopię danych. Ekstrakty Tableau można odświeżać i ograniczać do potrzebnego zakresu. Wybór zależy między innymi od źródła, wymagań aktualności i czasu odpowiedzi. Nie zakładam, że ekstrakt zawsze będzie szybszy w każdym zadaniu.

Przy Live sprawdzam także cache i sposób odświeżania widoku. Tableau opisuje osobne zasady świeżości danych. Sam napis „Live” nie gwarantuje, że otwarta przez godzinę karta przeglądarki pokazuje ostatnią zmianę w systemie źródłowym. Źródło też może otrzymywać dane z opóźnieniem.

W syntetycznym zadaniu raport poranny ma pokazywać dane kompletne do końca poprzedniego dnia. Najpierw sprawdziłbym moment zakończenia ładowania źródła, potem odświeżenie ekstraktu lub widoku. Raport odświeżony o 7:00 na źródle uzupełnianym do 7:15 może być pięknie niekompletny. To użyteczniejsza odpowiedź niż deklaracja, że jedno połączenie jest po prostu lepsze.

Co kontroluje LOD?

Wyrażenia Level of Detail pozwalają określić szczegółowość obliczenia. FIXED ustala wskazany poziom niezależnie od wymiarów samego widoku, natomiast INCLUDE i EXCLUDE odnoszą się do jego szczegółowości. Sprawdzam również kolejność filtrów względem LOD, bo FIXED uwzględnia między innymi filtry kontekstu, a zwykłe filtry wymiarów działają później.

Przykład do omówienia: klient A ma zamówienia 100 i 50, a klient B jedno zamówienie 80. Suma na klienta wynosi 150 i 80. Średnia tych dwóch sum to 115, natomiast średnia trzech zamówień wynosi około 76,67. Zanim napiszę LOD, nazywam jednostkę obserwacji. Jeżeli suma klienta zostanie powtórzona przy każdym jego zamówieniu i potem uśredniona, klient A otrzyma większą wagę. Sam skrót LOD nie zwalnia z kontrolowania mianownika.

Czy zawsze synchronizować osie?

Dwie miary na dwóch osiach wymagają czytelnego opisu jednostek i skal. Przy porównywaniu przychodów z kosztami wspólna skala może pomóc. Przy liczbie zamówień i procencie zwrotów mechaniczna synchronizacja nie ma takiego sensu. Tableau opisuje opcje wielu osi, ale decyzję o wykresie trzeba uzasadnić pytaniem odbiorcy.

Ja zacząłbym od dwóch prostych wykresów ze wspólną osią czasu. Jeśli używam Dual Axis, sprawdzam, czy zmiana zakresu jednej osi nie sugeruje związku, którego dane nie potwierdzają. Dwie podobnie biegnące linie mogą wyglądać przekonująco tylko dlatego, że dopasowano ich skale.

Python: struktura danych i kontrola założeń

Lista, słownik i DataFrame

Lista daje dostęp do elementów przez pozycję i może zachowywać powtórzenia. Słownik mapuje unikalne klucze na wartości; przypisanie do istniejącego klucza zastępuje jego wartość. Współczesny Python zachowuje kolejność wstawiania kluczy, więc opowieść, że słownik „nie ma kolejności”, jest zbyt uproszczona. Te właściwości opisuje dokumentacja Pythona.

Na małym przykładzie pokazuję konsekwencję wyboru struktury. Dane są syntetyczne, a wartości oznaczają liczbę jednostek z dwóch wierszy tego samego zamówienia. Kod korzysta tylko ze standardowego Pythona:

rows = [(101, 2), (101, 3), (102, 1)]
last_value = dict(rows)
totals = {}
for order_id, quantity in rows:
    totals[order_id] = totals.get(order_id, 0) + quantity

assert last_value == {101: 3, 102: 1}
assert totals == {101: 5, 102: 1}
print(totals)

Bezpośrednia konwersja do słownika zachowała ostatnią wartość 3 dla klucza 101. Pętla sumująca dała 5. Każdy wariant może być poprawny przy innym celu. Na rozmowie wyjaśniam więc, czy oczekuję sumy, ostatniego stanu, czy zgłoszenia błędu duplikatu. W prawdziwym pliku do ustalenia pozostaje również kolejność zdarzeń i znaczenie brakującej ilości.

DataFrame w pandas to dwuwymiarowa struktura z etykietami osi; kolumny mogą mieć różne typy. Analogia do tabeli pomaga na początku, ale nie przenoszę automatycznie wszystkich reguł SQL. Przy łączeniu przez merge mogę sprawdzić oczekiwaną relację parametrem validate i pochodzenie wierszy przez indicator. Brakujące klucze po obu stronach mogą się w pandas dopasować, inaczej niż przy zwykłym porównaniu z NULL w SQL.

Kiedy wybieram które narzędzie?

Jeżeli dane są już w bazie i potrzebuję agregacji, rozważyłbym wykonanie jej tam. Arkusz może wystarczyć do małego, wspólnie oglądanego szkicu. Python jest przydatny, gdy chcę powtarzać przetwarzanie i kontrolować je kodem. Zawsze pytam, kto będzie uruchamiał rozwiązanie, jak często i na jakiej ilości danych.

Nie obiecuję automatycznego skalowania po przepisaniu arkusza do pandas. Trzeba uwzględnić pamięć, czas wykonania oraz sposób obsługi błędów. Dobór narzędzia powinien zmniejszać problem odbiorcy, a nie tylko wzbogacać moje CV o kolejne logo.

Próba rozmowy, którą możesz zrobić dzisiaj

Wybierz trzy pytania związane z ogłoszeniem. Do każdego przygotuj krótką odpowiedź, przykład oraz przypadek, który zmienia wynik. Daj sobie orientacyjnie dwie minuty na pierwszą odpowiedź. To moja propozycja ćwiczenia, nie standard czasu obowiązujący w rekrutacjach.

Następnie poproś drugą osobę o zmianę jednego założenia. Co jeśli klient ma wyłącznie anulowane zakupy? Co jeśli identyfikator powtarza się w słowniku? Co jeśli raport korzysta z wczorajszej kopii? Jeśli ćwiczysz samodzielnie, zapisz wariant przed uruchomieniem kodu. Przewidywanie wyniku daje więcej informacji o zrozumieniu niż komentowanie gotowej tabelki.

Oceń każdą odpowiedź w czterech punktach: poprawność mechanizmu, konkretny wynik przykładu, wskazanie istotnego ograniczenia i czytelność wypowiedzi. Za każdy spełniony warunek przyznaj sobie jeden punkt. Wynik służy porównaniu własnych kolejnych prób; nie jest prognozą zatrudnienia ani oceną wszystkich kompetencji analityka.

Jeśli utkniesz, zapisz dokładnie miejsce. „Nie wiem, co liczy COUNT po LEFT JOIN” jest lepszą notatką do nauki niż „słabo znam SQL”. Wróć do próbki, policz wiersze ręcznie, sprawdź dokumentację i ponów odpowiedź następnego dnia. Odtworzenie rozwiązania po przerwie pokaże, czy zapamiętałeś rozumowanie.

Na prawdziwej rozmowie nie wymyślam doświadczenia z narzędziem, którego nie używałem. Mogę jasno powiedzieć, co przećwiczyłem, co rozumiem i co wymagałoby sprawdzenia. Przygotowanie ma dać mi podstawę do takiej odpowiedzi. O szerszym sprawdzaniu warsztatu piszę w artykule o kompetencjach analityka na jednym zadaniu. Na dziś wystarczy wybrać jedno pytanie i doprowadzić jego przykład do wyniku, który potrafisz obronić.

Najważniejsze w pigułce

Czy na rozmowę juniora trzeba znać wszystkie pięć narzędzi?

Zakres zależy od stanowiska. Zacznij od wymagań ogłoszenia i zadań zespołu. Ćwicz szczególnie narzędzia potrzebne w tej roli, a pozostałe traktuj jako uzupełnienie.

Jak odpowiedzieć na pytanie techniczne, kiedy nie pamiętam składni?

Wyjaśnij zamierzony wynik, założenia i sposób sprawdzenia. Wskaż konkretny fragment składni, który wymaga potwierdzenia. Nie przedstawiaj przypuszczenia jako sprawdzonego działania.

Czy LEFT JOIN zawsze jest bezpieczniejszy od INNER JOIN?

Nie. LEFT JOIN zachowuje niedopasowane wiersze lewej strony, ale wiele dopasowań nadal może zwielokrotnić dane. Wybór zależy od pytania, a kontrola wymaga sprawdzenia kluczy i liczby wierszy.

Kajo Rudziński

Newsletter KajoData

Zostańmy w kontakcie.

Piszę o analizie danych, uczeniu się i zmianie pracy. Dzielę się tym, co pomaga mi ogarniać technologię i zawodowe decyzje. Jeśli lubisz ten sposób myślenia, zapraszam do mojego newslettera.