Salta el contingut

Datasets i destí

Exercici datasets-and-destination-annex.pdf · PDF

A la lliçó anterior vas unir pokemon amb generations, amb les teves pròpies mans. On és ara aquella consulta?

Busca-la. En el millor dels casos és en algun lloc de SQL → Query History. El més probable és que hagi desaparegut, i la setmana que ve la tornaràs a escriure de memòria, i et sortirà una mica diferent. Una consulta que has de tornar a escriure és una consulta que la propera vegada faràs malament. Aquesta lliçó et dona dues maneres de guardar la teva feina. Una guarda la pregunta. L'altra guarda la resposta. No són el mateix, i triar la que no toca és com un informe acaba ensenyant les xifres del mes passat.

Què aprendràs

  • Distingir un dataset físic d'un dataset virtual a Superset
  • Desar una consulta de SQL Lab com a dataset virtual
  • Escriure el resultat d'una consulta a la base de dades com a taula nova amb CREATE TABLE ... AS
  • Predir quin dels dos es queda desfasat quan canvia l'origen, i triar entre ells
  • Portar dades d'una API a Superset: JSON → script → CSV → càrrega
  • Desactivar la trampa del dia: una taula creada a partir d'una consulta no continua connectada a aquesta consulta

Recepta o carmanyola

Fas una lasanya boníssima. Com t'assegures de poder-la tornar a menjar d'aquí a un mes?

Dues respostes. Apuntar la recepta. O cuinar-ne una bona tanda i guardar-la en una carmanyola a la nevera. Una analogia per a tota aquesta pàgina: la base de dades és una cuina, i les taules de PostgreSQL són els ingredients del rebost.

Recepta Carmanyola
Què guardes La pregunta (el SQL) La resposta (les files)
A Superset Un dataset virtual Una taula nova, feta amb CREATE TABLE ... AS
On viu A Superset: només el text de la consulta A PostgreSQL: una taula real, al costat de pokemon
Quan l'uses Es torna a cuinar cada vegada Se serveix tal com es va guardar
Per tant és Sempre fresca, una mica més lenta Ràpida, però tan vella com el dia que la vas cuinar
flowchart TB
    Q["<b>La teva consulta</b><br>pokemon JOIN generations"]
    Q -- "guardes la pregunta" --> V["<b>Dataset virtual</b><br>(la recepta)<br>desat a Superset com a text"]
    Q -- "guardes la resposta" --> T["<b>Taula nova</b><br>(la carmanyola)<br>desada a PostgreSQL com a files"]
    V -- "torna a executar la consulta<br>cada vegada" --> C["Gràfics"]
    T -- "llegeix les files desades" --> C

En el joc de crafting d'aquest mòdul, aquest és el moment en què deixes de portar el botí a les mans i el poses al cofre de la teva base. Les dades que només existeixen a la pantalla encara no estan extretes. Han d'arribar a algun lloc: el seu destí.

Datasets: de què pot dibuixar Superset

Un dataset és el que Superset anomena "alguna cosa a partir de la qual puc construir gràfics".

Superset no dibuixa gràfics directament d'una taula ni d'una consulta. Els dibuixa a partir d'un dataset, i n'hi ha de dos tipus:

Tipus Què és A la cuina
Dataset físic Apunta a una taula que existeix a la base de dades Una etiqueta en un pot del rebost
Dataset virtual Una consulta desada que Superset executa per tu Una fitxa de recepta

Obre Datasets al menú superior i mira la columna Type. Ja en tens dos: pokemon i generations, tots dos Physical. Superset els va registrar quan vas pujar els fitxers CSV.

Compte: un dataset no és una còpia

Un dataset físic és una etiqueta al pot, no un segon pot. Una taula, zero còpies. Si elimines el dataset, la taula continua a PostgreSQL amb totes les files. I si la taula canvia, el dataset ensenya el canvi, perquè mai no va guardar dades pròpies.

Guardar la pregunta: un dataset virtual

Hora d'escriure la primera recepta. Obre SQL → SQL Lab, base de dades Dades classe, esquema public, i executa el join de la lliçó anterior amb les columnes enumerades una per una:

SELECT p.id, p.pokemon, p.type_1, p.type_2, p.hp, p.attack, p.defense, p.speed,
       g.generation_id, g.region, g.release_year
FROM pokemon p
JOIN generations g ON p.generation_id = g.generation_id

(JOIN sol vol dir INNER JOIN, i l'AS abans d'un àlies és opcional.) Després desa-la:

Pas Fes això Hauries de veure
1 Executa la consulta i comprova el resultat 802 files
2 Clica la fletxa al costat de Save, després Save dataset Un petit diàleg
3 Tria Save as new, escriu el nom pokemon_with_region, prem Save & Explore El constructor de gràfics s'obre en una pestanya nova del navegador
4 Tanca aquesta pestanya (els gràfics són la Unitat 3) i obre Datasets pokemon_with_region a la llista, Type Virtual

802 files, no 949: és un INNER JOIN, així que les 147 formes alternatives sense generació queden fora. Ja saps per què. Ara aquesta decisió queda escrita en un sol lloc, en comptes de tornar-la a escriure cada vegada.

Compte: tres maneres d'espatllar una recepta

  • SELECT * en un join. Les dues taules tenen una generation_id, així que el dataset rep dues columnes amb gairebé el mateix nom i més endavant falla amb column reference "generation_id" is ambiguous. Enumera les columnes, com més amunt.
  • Un LIMIT oblidat. Superset embolcalla la teva consulta dins la seva. Un LIMIT 10 que vas deixar mentre proves es queda a la recepta: aquest dataset tindrà 10 files per sempre.
  • Un nom que ja està agafat. Dos datasets no poden compartir nom, així que no anomenis el dataset virtual pokemon.

Guardar la resposta: CREATE TABLE AS

CREATE TABLE nom AS SELECT ... executa una consulta una sola vegada i en desa el resultat com a taula nova a la base de dades.

Ara la carmanyola: un resum petit amb una fila per generació. Primer executa només el SELECT (des de la segona línia) i mira el resultat. Després afegeix la primera línia i executa-ho tot:

CREATE TABLE pokemon_by_generation AS
SELECT g.generation_id, g.region, g.release_year,
       COUNT(p.id) AS n_pokemon,
       ROUND(AVG(p.attack)) AS avg_attack,
       MAX(p.speed) AS max_speed
FROM generations g
LEFT JOIN pokemon p ON p.generation_id = g.generation_id
GROUP BY g.generation_id, g.region, g.release_year;

Aquesta vegada no hi ha quadrícula de resultats. SQL Lab diu The query returned no data. No ha fallat res: les files han anat a la base de dades, no a la teva pantalla. Pregunta a la taula nova:

SELECT * FROM pokemon_by_generation ORDER BY generation_id;
generation_id region release_year n_pokemon avg_attack max_speed
1 Kanto 1996 151 73 150
2 Johto 1999 100 68 130
3 Hoenn 2002 135 73 160
4 Sinnoh 2006 107 80 125
5 Unova 2010 156 81 145
6 Kalos 2013 72 73 126
7 Alola 2016 81 83 151
8 Galar 2019 0

Vuit files, una per generació. El LEFT JOIN comença des de generations, així que Galar es queda, amb n_pokemon = 0. Els seus avg_attack i max_speed són buits, no 0: no hi ha res per fer-ne la mitjana, i escriure 0 seria mentir.

La taula existeix a PostgreSQL, però Superset encara no té cap dataset per a ella. Posa l'etiqueta al pot:

Pas Fes això Hauries de veure
1 Datasets → + Dataset El formulari de dataset nou
2 Base de dades Dades classe, Schema public, Table pokemon_by_generation Les columnes de la taula
3 Clica la fletxa al costat de Create and explore dataset, després Create dataset pokemon_by_generation a la llista, Type Physical

Si SQL Lab et respon malament

  • relation "pokemon_by_generation" already exists: has executat la sentència dues vegades. Bones notícies, no un error per témer: la taula hi és.
  • This database does not allow for DDL/DML: la connexió només deixa llegir a SQL Lab. Això és una configuració de la màquina de classe, no una cosa que hagis fet tu: avisa el teu professor.

Quin es queda desfasat?

Aquesta és la idea del dia. Una novena generació de Pokémon va sortir el 18 de novembre de 2022: la regió de Paldea, als jocs Scarlet i Violet. La nostra taula generations no ho sap. Imagina que algú hi afegeix aquesta novena fila.

Abans de continuar llegint, prediu. Quantes files mostrarà la taula pokemon_by_generation? I el mateix SELECT, executat una altra vegada com a consulta?

Abans de la fila nova Després de la fila nova
La carmanyola: la taula pokemon_by_generation 8 files 8 files
La recepta: el SELECT, executat una altra vegada 8 files 9 files, Paldea amb 0 Pokémon

La recepta ha vist l'ingredient nou. La carmanyola, no.

Compte: la taula no està connectada a la seva consulta

"He creat la taula a partir d'una consulta, així que continua enllaçada a la consulta." No. CREATE TABLE ... AS executa la consulta una sola vegada i en desa les files. Una carmanyola no es torna a omplir sola quan compres més tomàquets. Si l'origen canvia, tornes a cuinar: elimina la taula vella amb DROP TABLE pokemon_by_generation; i torna a executar el CREATE TABLE ... AS. Elimina només taules que hagis creat tu. Un dataset virtual no desa res, així que res no se li pot quedar desfasat.

Doncs quan és la carmanyola la tria correcta?

Tria Quan
Dataset virtual (recepta) Les xifres han d'estar sempre al dia, i la consulta és prou ràpida
CREATE TABLE ... AS (carmanyola) La consulta és lenta, una altra eina necessita una taula real, o vols conservar com estaven les coses un dia concret

Una font que no és un fitxer: una API

Els teus Pokémon van arribar en un fitxer CSV. Si demà surt un Pokémon nou, ho sabrà el fitxer? No: un fitxer també és una carmanyola. Doncs d'on treu una aplicació la llista fresca?

Una API (Application Programming Interface, interfície de programació d'aplicacions) és una adreça a la qual preguntes i que respon amb dades en comptes d'una pàgina web.

JSON (JavaScript Object Notation) és el format de text en què responen la majoria d'API: noms i valors, on un valor pot contenir més noms i valors a dins.

La PokéAPI és una API gratuïta. Sense compte, sense inici de sessió: l'adreça és tota la pregunta. Obre aquestes dues al navegador (Firefox mostra el JSON com un arbre plegable):

https://pokeapi.co/api/v2/pokemon?limit=20
{ "count": 1351,
  "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
  "previous": null,
  "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, ... ] }
https://pokeapi.co/api/v2/pokemon/charizard
{ "id": 6, "name": "charizard", "height": 17, "weight": 905,
  "types": [ { "slot": 1, "type": { "name": "fire" } },
             { "slot": 2, "type": { "name": "flying" } } ],
  "stats": [ { "base_stat": 78, "stat": { "name": "hp" } }, ... ] }

(Totes dues respostes estan retallades; count valia 1351 quan es va escriure aquesta pàgina i continua creixent.) Tres coses per llegir-hi:

  • La primera resposta diu 1351 però en mostra 20. La resta són a l'adreça next. Dividir una resposta en pàgines s'anomena paginació.
  • La part després del ? conté els paràmetres: instruccions addicionals com limit=20.
  • Els tipus són una llista dins de la resposta. Les dades dins de dades s'anomenen imbricades.

Ara compara-ho amb la teva taula: SELECT pokemon, height, weight, type_1, type_2 FROM pokemon WHERE pokemon = 'charizard';

A l'API A la taula pokemon
Alçada 17 (decímetres) 1.7 (metres)
Pes 905 (hectograms) 90.5 (quilograms)
Tipus Una llista imbricada de dos Dues cel·les: type_1 = fire, type_2 = flying

El mateix Pokémon, una altra forma i unes altres unitats. Una font mai no arriba amb la forma que necessites.

Del JSON a una taula: extreure, aplanar, carregar

Compte: Superset no es pot connectar a una API

Superset només consulta bases de dades SQL. No pot preguntar a una adreça web. Per tant, una API és una font de la qual has d'extreure i després carregar, exactament com un fitxer CSV.

Aplanar és convertir un JSON imbricat en files i columnes planes.

flowchart LR
    A["<b>API</b><br>PokéAPI<br>respon en JSON"] -- "pregunta" --> S["<b>Script</b><br>api_to_csv.py<br>tria i aplana"]
    S -- "escriu" --> C["<b>CSV</b><br>pokeapi_sample.csv<br>20 files, 7 columnes"]
    C -- "carrega" --> D["<b>Destí</b><br>taula pokeapi_sample<br>a PostgreSQL"]
    D --> DS["<b>Dataset físic</b><br>a Superset"]

Aplanar necessita un programa petit. Descarrega api_to_csv.py de la pàgina de Recursos. Són 16 línies de Python:

import csv, json, urllib.request

def get(url):
    req = urllib.request.Request(url, headers={"User-Agent": "erd-class"})
    return json.load(urllib.request.urlopen(req))

page = get("https://pokeapi.co/api/v2/pokemon?limit=20")   # page 1 of the list
with open("pokeapi_sample.csv", "w", newline="") as f:
    out = csv.writer(f)
    out.writerow(["id", "name", "height_m", "weight_kg", "type_1", "type_2", "hp"])
    for item in page["results"]:                  # each item: a name and a url
        p = get(item["url"])                      # one request per Pokemon
        types = [t["type"]["name"] for t in p["types"]] + [""]
        out.writerow([p["id"], p["name"], p["height"] / 10, p["weight"] / 10,
                      types[0], types[1], p["stats"][0]["base_stat"]])
print(page["count"], "Pokemon in the API. Next page:", page["next"])

En aquest mòdul no has d'escriure Python. Llegeix-lo com a quatre verbs: demanar la llista, demanar cada Pokémon, triar els valors (unitats convertides, tipus separats en dos), escriure una fila.

Pas Fes això Hauries de veure
1 En un terminal, a la carpeta de l'script: python3 api_to_csv.py Una línia com 1351 Pokemon in the API. Next page: ... i un fitxer nou pokeapi_sample.csv
2 Obre el CSV en un editor de text Una capçalera i 20 files: 6,charizard,1.7,90.5,fire,flying,78
3 A Superset: + → Data → Upload CSV to database, base de dades Dades classe, esquema public, nom de taula pokeapi_sample, Upload Un missatge de confirmació
4 A SQL Lab: SELECT type_1, COUNT(*) AS n FROM pokeapi_sample GROUP BY type_1 ORDER BY n DESC; bug 6, normal 5, després fire, grass i water amb 3 cadascun

Ara obre Datasets i mira el Type de pokeapi_sample: Physical. Un cop les dades són a la base de dades, Superset no pot saber que venen d'una API. I és una carmanyola: una instantània, una còpia de l'API tal com era el minut que es va executar l'script. Per refrescar-la, torna a extreure i a carregar.

La llista de comprovació d'una API

Sis preguntes per respondre abans de construir res sobre una API, per exemple al teu projecte.

Pregunta Què cal esbrinar PokéAPI
Adreça La URL, i els seus paràmetres després del ? .../pokemon?limit=20&offset=0
Clau Necessita un compte o una clau d'API? Una clau és una contrasenya: no la enganxis mai en un xat, un document compartit ni una captura de pantalla Cap: no cal autenticació
Límits Quantes peticions pots fer? Demana només el que necessites i desa el que reps Cap límit fix, però la seva política d'ús raonable demana que facis poques peticions
Paginació Una resposta ho conté tot? Quin camp apunta a la pàgina següent? 20 per pàgina per defecte; next i count
Llicència Pots reutilitzar i publicar les dades? A qui has d'atribuir-ne el mèrit? Llegeix la seva política d'ús raonable i cita la font
Instantània La teva taula és una còpia d'un moment. Apunta'n la data Torna a executar l'script per refrescar-la

Idees clau

  • Guarda la pregunta o guarda la resposta: un dataset virtual és una consulta desada (la recepta); CREATE TABLE ... AS desa les files en una taula nova (la carmanyola).
  • Un dataset és el que Superset fa servir per construir gràfics. Un dataset físic és una etiqueta sobre una taula, no una còpia.
  • Un dataset virtual està sempre fresc: executa la consulta cada vegada. Una taula feta amb CREATE TABLE ... AS és una instantània i es queda desfasada quan canvia l'origen.
  • CREATE TABLE ... AS no mostra cap quadrícula de resultats: les files han anat a la base de dades. Comprova-ho amb un SELECT.
  • Una API respon en JSON, sovint imbricat i paginat. Superset no la pot consultar: tu extreus, aplanes a un CSV i carregues.
  • Abans d'usar una API, passa la llista de comprovació: adreça, clau, límits, paginació, llicència, instantània.

Comprovació ràpida

1. Què és un dataset virtual a Superset?
  • Una còpia d'una taula, desada dins de Superset
  • Una consulta desada que Superset executa cada vegada que s'usa el dataset
  • Una taula creada amb CREATE TABLE ... AS
  • Un fitxer CSV que espera ser carregat

Un dataset virtual és la recepta: només es desa el text de la consulta, i es torna a cuinar cada vegada. Una taula feta amb CREATE TABLE ... AS és una taula real, que després registres com a dataset físic.

2. Elimines el dataset físic generations de la llista de Datasets de Superset. Què passa amb la taula de PostgreSQL?
  • També s'elimina
  • Perd les files però conserva les columnes
  • Res: la taula continua allà amb totes les files
  • Es converteix en un dataset virtual

Un dataset físic és una etiqueta al pot, no el pot. Una taula, zero còpies: treure l'etiqueta no toca el que hi ha al rebost.

3. Executes un CREATE TABLE ... AS SELECT ... a SQL Lab i no apareix cap quadrícula de resultats. Què vol dir?
  • La sentència ha fallat
  • La taula s'ha creat buida
  • Les files s'han escrit a la base de dades en lloc de mostrar-se en pantalla
  • Has de prémer Run una segona vegada

CREATE TABLE ... AS envia el resultat a una taula nova, així que no hi ha res per mostrar. Comprova-ho amb SELECT * FROM la taula nova. Executar-la una segona vegada només respondria already exists.

4. S'afegeix una fila nova a generations. Quina afirmació és certa?
  • Tant el dataset virtual com la taula feta amb CREATE TABLE ... AS la mostren
  • Cap dels dos la mostra fins que es reinicia Superset
  • Una consulta executada una altra vegada veu la fila nova; la taula feta abans amb CREATE TABLE ... AS no
  • La taula feta amb CREATE TABLE ... AS s'actualitza sola, perquè venia d'una consulta

CREATE TABLE ... AS executa la consulta una sola vegada: la carmanyola no es torna a omplir sola. La recepta es torna a cuinar cada vegada, així que veu l'ingredient nou. Per refrescar la taula l'has d'eliminar i tornar-la a crear.

5. Per què Superset no pot mostrar directament les dades de la PokéAPI?
  • Perquè la PokéAPI necessita una contrasenya
  • Perquè Superset només consulta bases de dades SQL, així que les dades d'una API s'han d'extreure i carregar primer
  • Perquè els fitxers JSON són massa grans per a Superset
  • Perquè la PokéAPI només funciona a Firefox

Superset pregunta a bases de dades, no a adreces web. El camí és extreure → aplanar → carregar: un script pregunta a l'API, escriu un CSV, i tu puges el CSV com a taula. La PokéAPI no necessita cap clau.

6. La PokéAPI respon amb un count de 1351, però la llista només conté 20 Pokémon. On són els altres?
  • Es van eliminar de l'API
  • Necessiten una clau d'API
  • A les pàgines següents: el camp next conté l'adreça de la següent
  • Al camp previous

Això és la paginació: una resposta llarga es divideix en pàgines, aquí de 20. count diu quants n'hi ha en total i next apunta a la pàgina següent.

7. El teu cap vol (a) un gràfic de vendes per botiga que estigui sempre al dia i (b) una taula amb els totals de divendres passat que no pot canviar mai. Tria les dues opcions correctes.
  • (a) usa un dataset virtual, perquè la consulta s'executa una altra vegada cada cop
  • (b) usa CREATE TABLE ... AS, perquè les files es desen una sola vegada i es queden com estaven
  • (a) usa CREATE TABLE ... AS, perquè una taula real és sempre més fresca
  • (b) usa un dataset virtual, perquè una consulta desada mai no canvia el resultat

Recepta per al fresc, carmanyola per al congelat. Un dataset virtual torna a executar la consulta, així que apareixen les vendes noves. Una taula feta amb CREATE TABLE ... AS és una instantània: exactament el que vols per a "com estaven les coses divendres".

Ja pots portar dades a casa i desar-les, amb Superset. La mateixa feina existeix a totes les eines de dades, amb altres botons. Següent: Obtenir dades amb Power BI, on pots fer la mateixa pregunta: aquesta eina guarda la recepta o la carmanyola?