Nauč se Python > Kurzy > Datový kurz PyLadies > Databáze a SQL > Databáze a SQL

Databáze a SQL #

Obsah:

  • Úvod do databází a SQL
  • Jak s SQL databázemi pracovat v Pythonu
  • SQL 101 a cvičení

Úvod do databází #

Doteď jsme na kurzu pracovali se soubory - odněkud soubor stáhli na svůj počítač, a tam ho zpracovali, a pak třeba zase výsledek někam nahráli. Tento postup ale nestačí, pokud jsou data příliš veliká, s těmito daty pracuje naráz více lidí najednou a zároveň je i aktualizuje. To se řeší tak, že spustíte databázový systém (DBMS), ten data u sebe spravuje a vy se na tento systém připojujete přes klienta. DBMS může běžet na stejném počítači jako váš Python - nebo také úplně jinde.

Např. e-shop bude pracovat s databází, ve které jsou uloženy informace o prodávaném zboží, rozdělení zboží do kategorií, objednávky, reklamace, registrovaní uživatelé... Takovou databázi můžeme označit za "transakční". Vy jako datoví analytici, inženýři apod. se pravděpodobně budete chtít do takovéto databáze připojit a stáhnout si z ní část dat pro svou další práci. Nebo dokonce může být vaším úkolem vytvořit novou "analytickou" databázi, která obsahuje data v jiné podobě, např. prodávané zboží bez popisků, bez fotografií, ale zato se statistikami návštěvnosti a prodejnosti.

Vlastnosti databází #

Od databáze (resp. databázového systému) očekáváme několik vlastností:

  • Že bude uchovávat data :) Data musí zůstat beze ztráty uložena i v případě výpadku napájení, když dojde místo na disku...
  • Práce s daty "z různých úhlů". Např. v e-shopu chceme jednou vypsat zboží podle kategorie, jindy podle stavu skladových zásob, jindy na základě full-textového vyhledávání..
  • Podpora uživatelů, rolí, oprávnění a kontroly přístupu k datům, security.
  • Rychlost, výkon, propustnost, latence
  • Škálovatelnost - jak do velikosti (gigabajty, terabajty, petabajty), tak do počtu prováděných operací za sekundu

Když se nad tím zamyslíte, jedná se o protichůdné požadavky. Tvůrci databázových systémů tak musí zvolit nějaký kompomis. Žádná databáze není "nejlepší" a vhodná na všechno. Zrovna co se týče rozdílu mezi "transakční" a "analytickou" databází, tak často pro tyto oblasti jsou vhodné odlišné systémy a přístupy.

Na začátku budeme pro jednoduchost pracovat s databází SQLite. Ta je poněkud specifická tím, že je uložena v jednom souboru (s příponou .sqlite nebo .db), není to tedy samostatně běžící server, ke kterému byste se připojovali. O to snadněji se ale s SQLite pracuje. A SQL jazyk, který si zde osvojíte je stejný (nebo minimálně velmi podobný) jako u složitějších databázových systémů jako MySQL nebo PostgreSQL.

SQL #

SQL je jazyk, kterým komunikujeme s databází. Umí vyjádřit různé operace nad tabulkami - výběr sloupců, řádků, vztahy mezi více tabulkami, agregace hodnot z více řádků... Tedy něco, co jsem v Pythonu

Příklad:

SELECT title, year FROM movies;

UPDATE directors SET birthdate = "1950-01-02" WHERE director_id = 3;

Jak s SQL databázemi pracovat v Pythonu #

Každá databáze se používá trochu jinak - liší se název modulu, který je potřeba importovat, mírně se liší způsob předávání dat použitých v SQL dotazech, může se lišit forma dat vrácených ve výsledku...

Tento problém řeší knihovna SQLAlchemy, která poskytuje stejné rozhraní pro všechny širokou třídu databází. Nejprve si ji tedy nainstalujte.

In [1]:
# smažte # na následujícím řádku pro instalaci balíčku sqlalchemy
# %pip install sqlalchemy

Knihovnu SQLAlchemy naimportujeme následovně

In [2]:
import sqlalchemy

a jako vždy, importujeme i Pandas:

In [3]:
import pandas as pd

Pro první ukázku práce s databází použijeme data od Míry Brabence. Jde o databázi typu SQLite - skládá se z jediného souboru, ve kterém jsou všecha data. To znamená, že se nepřipojujeme k žádnému serveru pomocí jména a hesla jako u "klasických" databází. Databáze v podobě SQLite souboru má výhodu v tom, že když něco rozbijete, tak ten soubor prostě smažete a uděláte si nový :)

In [4]:
# smažte # na následujících řádcích pro stažení dat movies.sqlite
#from pathlib import Path
#import requests
#db_data = requests.get('https://github.com/PyDataCZ/pyladies-kurz/raw/main/lessons/pydata/databases/movies.sqlite').content
#Path('movies.sqlite').write_bytes(db_data)

Otevření databáze, průzkum struktury #

In [5]:
engine = sqlalchemy.create_engine('sqlite:///movies.sqlite')

Objekt engine obsahuje připojení do databáze a skrz něj budeme provádět operace s danou databází.

Začneme tím, že se podíváme, jaká je struktura té databáze - jaké tabulky obsahuje a co obsahují ty tabulky. Každá databáze na to má jiné finty, jak toto zjistit (někdy funguje SQL příkaz SHOW TABLES, jindy je zase potřeba SELECT z metadat), naštěstí SQLAlchemy nám s tím pomůže.

In [6]:
sqlalchemy.inspect(engine).get_table_names()
Out[6]:
['actor', 'casting', 'movie']

Pro průzkum databáze jsem připravil funkci prozkoumat.

In [7]:
def prozkoumat(engine):
    inspector = sqlalchemy.inspect(engine)
    for schema in inspector.get_schema_names():
        print('Schema:', schema)
        for table_name in inspector.get_table_names(schema=schema):
            print()
            print('  Table:', table_name)
            print()
            for column in inspector.get_columns(table_name, schema=schema):
                print('      Column:', column['name'].ljust(12), column['type'])
In [8]:
prozkoumat(engine)
Schema: main

  Table: actor

      Column: id           INTEGER
      Column: name         VARCHAR(50)

  Table: casting

      Column: movieid      INTEGER
      Column: actorid      INTEGER
      Column: ord          INTEGER

  Table: movie

      Column: id           INTEGER
      Column: title        VARCHAR(50)
      Column: yr           INTEGER
      Column: director     INTEGER
      Column: budget       INTEGER
      Column: gross        INTEGER

Celou tabulku stáhneme pomocí funkce pd.read_sql_table.

In [9]:
pd.read_sql_table('actor', engine)
Out[9]:
id name
0 2
1 3 Richard Brooks
2 4 Warren Beatty
3 5 Goldie Hawn
4 6 Gert Fröbe
... ... ...
48143 48162 David Kernan
48144 48163 Peter Gill
48145 48164 Dickie Owen
48146 48165 Dennis Folbigge
48147 48166 Douglas Hickox

48148 rows × 2 columns

Reálná databáze ale může obsahovat tak velké, že je celé stáhnout nemůžete nebo nechcete. Stáhnete tedy pouze jejich část určenou pomocí SQL příkazu SELECT (funkce pd.read_sql_query). Takže pokud např. chcete zjistit údaje k filmu "Chasing Amy".

In [10]:
pd.read_sql_query('SELECT title, yr, budget, gross FROM movie WHERE title = "Chasing Amy"', engine)
Out[10]:
title yr budget gross
0 Chasing Amy 1997 250000 12021272

SQL 101 #

Jedna hodina je příliš málo na to, abychom probrali SQL a databáze do hloubky. Soustředíme se tedy na nejčastější případ použití: databázi nespravujete vy, ale nějaký datový inženýr z firmy a vy jste dostali práva pro čtení (nikoli pro zápis). Z databáze potřebujete získat data, abyste s nimi dále pracovali.

Učite se budeme na příkladech. Budeme však potřeboval ještě několik datových sad.

In [11]:
# ostraňte # na začátku následujících řádků pro stažení souboru world.sqlite
#db_data = requests.get('https://github.com/PyDataCZ/pyladies-kurz/raw/main/lessons/pydata/databases/world.sqlite').content
#Path('world.sqlite').write_bytes(db_data)
In [12]:
engine_world = sqlalchemy.create_engine('sqlite:///world.sqlite')

prozkoumat(engine_world)
Schema: main

  Table: world

      Column: name         VARCHAR(50)
      Column: continent    VARCHAR(60)
      Column: area         DECIMAL(10, 0)
      Column: population   DECIMAL(11, 0)
      Column: gdp          DECIMAL(14, 0)
      Column: capital      VARCHAR(60)
      Column: tld          VARCHAR(5)
      Column: flag         VARCHAR(255)
In [13]:
pd.read_sql_query('SELECT * FROM world LIMIT 3', engine_world)
Out[13]:
name continent area population gdp capital tld flag
0 Afghanistan Asia 652230 25500100 20364000000 Kabul .af //upload.wikimedia.org/wikipedia/commons/9/9a/...
1 Albania Europe 28748 2821977 12044000000 Tirana .al //upload.wikimedia.org/wikipedia/commons/3/36/...
2 Algeria Africa 2381741 38700000 207021000000 Algiers .dz //upload.wikimedia.org/wikipedia/commons/7/77/...

SELECT #

Upravte následující příklady a zkontrolujte, že jste dostali očekávaný výsledek. Nulté cvičení projdeme společně.

Cvičení 0: Kód níže získá z databáze počet obyvatel Francie. Upravte jej a zjistěte počet obyvatel Německa (řešení 80716000).

In [14]:
sql_statement = """
SELECT population FROM world
  WHERE name = 'France'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[14]:
population
0 65906000

Cvičení 1: Pomocí operátoru IN můžete testovat, jestli sloupec nabývá jedné z několika hodnot. Kód níže získá z databáze tabulku se jménem státu a počtem obyvatel pro Brazílii, Rusko, Indii a Čínu.

Upravte jej a získejte počet obyvatel pro skandinávské země ('Sweden', 'Norway', 'Denmark'). Která z těchto tří zemí má nejméně obyvatel? (řešení: Norsko - 5124383)

In [15]:
sql_statement = """
SELECT name, population FROM world
  WHERE name IN ('Brazil', 'Russia', 'India', 'China');
"""

pd.read_sql_query(sql_statement, engine_world)
Out[15]:
name population
0 Brazil 202794000
1 China 1365370000
2 India 1246160000
3 Russia 146000000

Cvičení 2: Které státy nejsou příliš malé, ani příliš velké? Funkce BETWEEN umožňuje zadat rozsah (hraniční hodnoty). Níže uvedený příklad zobrazuje státy o rozloze 200 000-250 000 km². Upravte jej tak, aby zobrazoval jméno státu a jeho plochu pro státy s rozlohou mezi 250 000 a 300 000 km². (řešení: Burkina Faso, Ecuador, Gabon, New Zealand)

In [16]:
sql_statement = """
SELECT name, area FROM world
  WHERE area BETWEEN 200000 AND 250000
"""

pd.read_sql_query(sql_statement, engine_world)
Out[16]:
name area
0 Belarus 207600
1 Ghana 238533
2 Guinea 245857
3 Guyana 214969
4 Laos 236800
5 Romania 238391
6 Uganda 241550
7 United Kingdom 242900

Cvičení 3: K nalezení států začínajících na písmeno B můžete použít WHERE name LIKE 'B%' (% nahradí cokoli).

Kolik je v tabulce států začínajících na Y? (řešení: je právě jeden)

In [17]:
sql_statement = """
SELECT name FROM world
  WHERE name LIKE 'F%'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[17]:
name
0 Fiji
1 Finland
2 France

Cvičení 4: Kolik je států, jejichž jméno končí písmenem y? (řešení: osm)

In [18]:
sql_statement = """
SELECT name FROM world
  WHERE name LIKE 'F%'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[18]:
name
0 Fiji
1 Finland
2 France

Cvičení 5: Kolik států má ve jméně písmeno x? Kolik jich končí -land? A kolik jich začíná písmenem C a končí -ia? (řešení: 2, 8, 3)

In [19]:
sql_statement = """
SELECT name FROM world
  WHERE name LIKE 'F%'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[19]:
name
0 Fiji
1 Finland
2 France

Cvičení 6: Indie a Angola mají jako druhý znak písmeno n. Podtržítko můžete použít jako zástupný znak pro právě jeden znak.

Kolik států má jako druhý znak písmeno t? (řešení: dva)

In [20]:
sql_statement = """
SELECT name FROM world
 WHERE name LIKE '_n%'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[20]:
name
0 Andorra
1 Angola
2 Antigua and Barbuda
3 India
4 Indonesia
5 United Arab Emirates
6 United Kingdom
7 United States

Cvičení 7: Kolik názvů států má právě 4 písmena? (řešení: 10)

In [21]:
sql_statement = """
SELECT name FROM world
 WHERE name LIKE '___'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[21]:
name

Cvičení 8: Hlavní město Luxembourg je Luxembourg. Kolik je států, kde se hlavní město jmenuje stejně jako stát sám? (řešení: čtyři)

In [22]:
sql_statement = """
SELECT name, capital, continent FROM world
"""

pd.read_sql_query(sql_statement, engine_world)
Out[22]:
name capital continent
0 Afghanistan Kabul Asia
1 Albania Tirana Europe
2 Algeria Algiers Africa
3 Andorra Andorra la Vella Europe
4 Angola Luanda Africa
... ... ... ...
190 Venezuela Caracas South America
191 Vietnam Hanoi Asia
192 Yemen Sana‘a Asia
193 Zambia Lusaka Africa
194 Zimbabwe Harare Africa

195 rows × 3 columns

Vnořený SELECT (SELECT uvnitř SELECTu) #

Pro komplexní dotazy potřebujeme často porovnat hodnotu s hodnotou, kterou samotnou musíme z databáze získat. K tomu slouží možnost vnořit jeden SELECT do druhého.

Cvičení 0: Které státy mají více obyvatel než Rusko? To už umíme - na dva dotazy... (povšimněte si použití ORDER - seřadí výsledek podle jednoho či více sloupců)

In [23]:
sql_statement = """
SELECT name, population FROM world
      WHERE name='Russia'
"""

pd.read_sql_query(sql_statement, engine_world)
Out[23]:
name population
0 Russia 146000000
In [24]:
sql_statement = """
SELECT name FROM world
  WHERE population > 146000000
  ORDER by name
"""

pd.read_sql_query(sql_statement, engine_world)
Out[24]:
name
0 Bangladesh
1 Brazil
2 China
3 India
4 Indonesia
5 Nigeria
6 Pakistan
7 United States

Ale s vnořeným SELECT nám stačí dotaz jeden:

In [25]:
sql_statement = """
SELECT name FROM world
  WHERE population >
     (SELECT population FROM world
      WHERE name='Russia')
  ORDER BY name
"""

pd.read_sql_query(sql_statement, engine_world)
Out[25]:
name
0 Bangladesh
1 Brazil
2 China
3 India
4 Indonesia
5 Nigeria
6 Pakistan
7 United States

Cvičení 1: Vypište evropské země, které mají HDP na osobu vyšší než Dánsko. (řešení: je jich šest)

In [26]:
sql_statement = """
SELECT name, gdp/population FROM world
  WHERE continent = 'Asia' AND population >
     (SELECT population FROM world
      WHERE name='Russia')
"""

pd.read_sql_query(sql_statement, engine_world)
Out[26]:
name gdp/population
0 Bangladesh 812
1 China 6121
2 India 1504
3 Indonesia 3482
4 Pakistan 1144

Můžeme použít MAX, MIN, SUM, AVG, COUNT pro maximum, minimum, součet, průměr a počet hodnot. Zemi s nejvyšším počtem obyvatel tedy nalezneme následovně.

In [27]:
sql_statement = """
SELECT name, population
  FROM world
  WHERE population = (SELECT MAX(population) FROM world)
"""

pd.read_sql_query(sql_statement, engine_world)
Out[27]:
name population
0 China 1365370000

Cvičení 2: Nalezněte státy, které mají HDP vyšší než libovolný evropský stát. (řešení: jsou tři)

In [28]:
sql_statement = """
SELECT name, gdp FROM world
  WHERE gdp >
     (SELECT gdp FROM world
      WHERE name='Japan')
"""

pd.read_sql_query(sql_statement, engine_world)
Out[28]:
name gdp
0 China 8358400000000
1 United States 16244600000000

GROUP BY a HAVING #

Pokud chceme spočítat počet obyvatel Asie, můžeme použít SUM a WHERE z minulého oddílu.

In [29]:
sql_statement = """
SELECT continent, SUM(population)
  FROM world
  WHERE continent = "Asia"
"""

pd.read_sql_query(sql_statement, engine_world)
Out[29]:
continent SUM(population)
0 Asia 4342955676

Přehled všech kontinentů můžeme dostat pomocí funkce DISTINCT (v Pythonu existuje podobné unique).

In [30]:
sql_statement = """
SELECT DISTINCT(continent)
  FROM world
"""

pd.read_sql_query(sql_statement, engine_world)
Out[30]:
continent
0 Africa
1 Asia
2 Caribbean
3 Eurasia
4 Europe
5 North America
6 Oceania
7 South America

Jak to ale udělat, abychom dostali počet obyvatel každého kontinentu a nemuseli to dělat naosmkrát? Řešení je stejně jako v Pandas použití GROUP BY i s podobnout syntaxí.

In [31]:
sql_statement = """
SELECT continent, SUM(population)
  FROM world
  GROUP BY continent
"""

pd.read_sql_query(sql_statement, engine_world)
Out[31]:
continent SUM(population)
0 Africa 1016091005
1 Asia 4342955676
2 Caribbean 36149204
3 Eurasia 149017400
4 Europe 610261850
5 North America 518755156
6 Oceania 37783477
7 South America 407618970

Pokud bychom chtěli vyloučit malé kontinenty jako Oceania a Caribbean, filtrování výsledků po GROUP BY se provádí pomocí HAVING, tedy například...

In [32]:
sql_statement = """
SELECT continent, SUM(population)
  FROM world
  GROUP BY continent
  HAVING SUM(population) > 100000000
"""

pd.read_sql_query(sql_statement, engine_world)
Out[32]:
continent SUM(population)
0 Africa 1016091005
1 Asia 4342955676
2 Eurasia 149017400
3 Europe 610261850
4 North America 518755156
5 South America 407618970

Cvičení 1: Kolik je pro každý kontinent států v databázi? Použijte funkci COUNT (řešení: např. pro Evropu 44)

In [33]:
sql_statement = """
SELECT continent, SUM(population)
  FROM world
  GROUP BY continent
"""

pd.read_sql_query(sql_statement, engine_world)
Out[33]:
continent SUM(population)
0 Africa 1016091005
1 Asia 4342955676
2 Caribbean 36149204
3 Eurasia 149017400
4 Europe 610261850
5 North America 518755156
6 Oceania 37783477
7 South America 407618970

Cvičení 2: Některé státy jsou velmi malé. Zkuste modifikovat kód na Cvičení 1 tak, aby počítal jen státy, které mají alespoň 10 milionů obyvatel. A kdybychom filtrovali nikoli malé státy, ale malé kontinenty? Kdy se použije WHERE, kdy HAVING, a proč? (řešení: např. pro Evropu 14)

In [34]:
sql_statement = """
SELECT continent, SUM(population)
  FROM world
  GROUP BY continent
"""

pd.read_sql_query(sql_statement, engine_world)
Out[34]:
continent SUM(population)
0 Africa 1016091005
1 Asia 4342955676
2 Caribbean 36149204
3 Eurasia 149017400
4 Europe 610261850
5 North America 518755156
6 Oceania 37783477
7 South America 407618970

JOIN #

Spojování tabulek je velmi podobné jako v Pandas. Vraťme se nyní k databázi movie. Obsahuje tři tabulky, které jsou propojené následovně...

Cvičení 0: Vypište všechny herce, kteří hráli ve filmu Vetřelec (Alien).

In [35]:
sql_statement = """
SELECT actor.name
  FROM movie
  JOIN casting ON movie.id = casting.movieid
  JOIN actor ON actor.id = casting.actorid
  WHERE title = 'Alien'
"""

pd.read_sql_query(sql_statement, engine)
Out[35]:
name
0 John Hurt
1 Sigourney Weaver
2 Yaphet Kotto
3 Harry Dean Stanton
4 Ian Holm
5 Tom Skerritt
6 Veronica Cartwright

Cvičení 1: Vypište všechny filmy, v kterých hrála 'Hilary Swank'. (řešení: je jich 16)

In [36]:
sql_statement = """
SELECT actor.name
  FROM movie
  JOIN casting ON movie.id = casting.movieid
  JOIN actor ON actor.id = casting.actorid
  WHERE title = 'Alien'
"""

pd.read_sql_query(sql_statement, engine)
Out[36]:
name
0 John Hurt
1 Sigourney Weaver
2 Yaphet Kotto
3 Harry Dean Stanton
4 Ian Holm
5 Tom Skerritt
6 Veronica Cartwright

Cvičení 2: Vypište všechny filmy, v kterých hrál 'Brad Pitt', ale ne v hlavních dvou rolích (ord v casting listu je větší než 2). (řešení: je jich 12)

In [37]:
sql_statement = """
SELECT actor.name
  FROM movie
  JOIN casting ON movie.id = casting.movieid
  JOIN actor ON actor.id = casting.actorid
  WHERE title = 'Alien'
"""

pd.read_sql_query(sql_statement, engine)
Out[37]:
name
0 John Hurt
1 Sigourney Weaver
2 Yaphet Kotto
3 Harry Dean Stanton
4 Ian Holm
5 Tom Skerritt
6 Veronica Cartwright

Cvičení 3: Vypište název filmů, které byly uvedeny do kin v roce 1903, společně s hlavní hvězdou (ord v castingu je rovno jedné). (řešení: je jich pět, hlavní roli v Alice in Wonderland hrála May Clark)

In [38]:
sql_statement = """
SELECT actor.name
  FROM movie
  JOIN casting ON movie.id = casting.movieid
  JOIN actor ON actor.id = casting.actorid
  WHERE title = 'Alien'
"""

pd.read_sql_query(sql_statement, engine)
Out[38]:
name
0 John Hurt
1 Sigourney Weaver
2 Yaphet Kotto
3 Harry Dean Stanton
4 Ian Holm
5 Tom Skerritt
6 Veronica Cartwright

Obtížnější cvičení: #

Cvičení 4: Vypište všechny filmy, v kterých hrála Julie Andrews, a ke každému z nich uveďte i herce v hlavní roli.

Cviření 5: Najděte všechny herce, kteří hráli alespoň patnáctkrát v hlavní roli.

Cvičení 6: Vypište všechny filmy z roku 1978 seřazené podle počtu herců, kteří v nich hráli. V případě rovnosti řaďte podle názvu filmu.

Cvičení 7: Nalezněte všechny herce, kteří hráli ve filmu, v kterém hrál též Art Garfunkel.

Závěr #

  • Pokud se chcete dozvědět o SQL více, velmi doporučuji stránku https://sqlzoo.net/, z které jsem převzal databáze a většinu cvičení.

  • Většina databázových serverů není z bezpečnostních důvodů veřejně dostupných a přistupujete k nim skrz nějakou API službu (o těch si budeme vykládat příště). Pokud byste si ale chtěli vyzkoušet, jaké to je připojit se ke vzdálenému serveru, jeden z mála dostupných je Ensembl poskytující genomická data.

  • Možná vám přišlo, že dnešní SQL lekce byla velmi podobná prvním lekcím na Pandách. Jeden z významných rozdílů SQL tabulky je, že může obsahovat více indexů, zadímco pandas DataFrame má jen jeden. Dalším rozdílem je, že NA se v jazyce SQL nazývá NULL - testuje se na něj IS NULL, podobně jako v Pythonu.

  • Pokud vám SQL přijde intuitivní a chtěli byste takto pracovat i s Pandas tabulkami, je to možné pomocí knihovny pandasql.

  • A naopak, ve všech příkladech zde jsme používali SQLAlchemy v kombinaci s pandas. To ale vůbec není nutné, můžeme použít engine.execute(...) a dostaneme iterátor reprezentující výsledek SQL dotazu. Například...

In [39]:
result = engine.execute('SELECT id, title, yr FROM movie LIMIT 5')
for row in result:
    print(row['id'], row['title'], row['yr'])
10001 $ 1971
10002 "Crocodile" Dundee 1986
10003 "Crocodile" Dundee II 1988
10004 'Til There Was You 1997
10005 'Til We Meet Again 1940

Toto je stránka lekce z kurzu, který probíhá nebo proběhl naživo s instruktorem.