fix: les 6
This commit is contained in:
@@ -1,412 +1,372 @@
|
||||
# Les 7: Next.js Fundamentals 2 - API Routes & Data Fetching
|
||||
# Les 7: Database Principles
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 5-9)
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
|
||||
## Beschrijving
|
||||
Leer data fetching in Next.js: Server Components, Client Components, API routes en React Query. Bouw een volledig werkende app met data.
|
||||
Leer de basisprincipes van relationele databases voordat we Supabase gaan gebruiken. Begrijp tabellen, relaties, keys en normalisatie - essentiële kennis voor elke developer.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Server Components vs Client Components
|
||||
### Wat is een Relationele Database?
|
||||
|
||||
**Server Components (default in Next.js):**
|
||||
- Renderen op de server
|
||||
- Geen JavaScript naar de browser
|
||||
- Kunnen direct data fetchen (async/await)
|
||||
- Kunnen NIET: useState, useEffect, event handlers
|
||||
**Een database is:** Een georganiseerde verzameling van data.
|
||||
|
||||
**Client Components:**
|
||||
- Renderen in de browser
|
||||
- JavaScript naar de browser
|
||||
- Kunnen interactief zijn
|
||||
- Markeer met `'use client'` bovenaan
|
||||
**Relationeel betekent:** Data is opgeslagen in tabellen die aan elkaar gerelateerd zijn.
|
||||
|
||||
**Vergelijk het met Excel:**
|
||||
- Database = Excel workbook
|
||||
- Tabel = Excel sheet
|
||||
- Kolom = Excel kolom (field)
|
||||
- Rij = Excel rij (record)
|
||||
|
||||
---
|
||||
|
||||
### Wanneer Wat?
|
||||
### Tabellen, Kolommen en Rijen
|
||||
|
||||
```
|
||||
Server Component → Data tonen, geen interactie
|
||||
Client Component → Interactie nodig (forms, buttons, state)
|
||||
```
|
||||
**Voorbeeld: Users tabel**
|
||||
|
||||
| id | name | email | created_at |
|
||||
|----|------|-------|------------|
|
||||
| 1 | Tim | tim@email.com | 2024-01-15 |
|
||||
| 2 | Anna | anna@email.com | 2024-01-16 |
|
||||
| 3 | Jan | jan@email.com | 2024-01-17 |
|
||||
|
||||
**Terminologie:**
|
||||
- **Tabel:** users
|
||||
- **Kolommen:** id, name, email, created_at
|
||||
- **Rijen:** 3 records (Tim, Anna, Jan)
|
||||
- **Cell:** Eén specifieke waarde (bijv. "tim@email.com")
|
||||
|
||||
---
|
||||
|
||||
### Data Types
|
||||
|
||||
Elke kolom heeft een type:
|
||||
|
||||
| Type | Beschrijving | Voorbeeld |
|
||||
|------|--------------|-----------|
|
||||
| `text` / `varchar` | Tekst | "Tim", "Hello world" |
|
||||
| `integer` / `int` | Hele getallen | 1, 42, -5 |
|
||||
| `decimal` / `numeric` | Decimalen | 19.99, 3.14 |
|
||||
| `boolean` | True/False | true, false |
|
||||
| `timestamp` | Datum + tijd | 2024-01-15 14:30:00 |
|
||||
| `uuid` | Unieke identifier | a1b2c3d4-e5f6-... |
|
||||
|
||||
**Kies het juiste type:**
|
||||
- Prijs? → `decimal` (niet `integer`, want centen)
|
||||
- Is actief? → `boolean`
|
||||
- Naam? → `text`
|
||||
- Aantal? → `integer`
|
||||
|
||||
---
|
||||
|
||||
### Primary Keys
|
||||
|
||||
**Wat:** Een kolom die elke rij UNIEK identificeert.
|
||||
|
||||
**Regels:**
|
||||
- Moet uniek zijn per rij
|
||||
- Mag nooit NULL zijn
|
||||
- Verandert nooit
|
||||
|
||||
**Voorbeeld:**
|
||||
```tsx
|
||||
// Server Component - data ophalen
|
||||
async function ProductList() {
|
||||
const products = await fetchProducts() // Direct fetchen!
|
||||
return <ul>{products.map(p => <li>{p.name}</li>)}</ul>
|
||||
}
|
||||
```
|
||||
users
|
||||
------
|
||||
id (PRIMARY KEY) | name | email
|
||||
1 | Tim | tim@email.com
|
||||
2 | Anna | anna@email.com
|
||||
```
|
||||
|
||||
// Client Component - interactie
|
||||
'use client'
|
||||
function AddToCartButton({ productId }) {
|
||||
const [added, setAdded] = useState(false)
|
||||
return <button onClick={() => setAdded(true)}>Add</button>
|
||||
}
|
||||
**Waarom niet `email` als primary key?**
|
||||
- Emails kunnen veranderen
|
||||
- `id` is stabiel en snel
|
||||
|
||||
---
|
||||
|
||||
### Foreign Keys
|
||||
|
||||
**Wat:** Een kolom die verwijst naar de primary key van een andere tabel.
|
||||
|
||||
**Voorbeeld: Posts tabel**
|
||||
```
|
||||
posts
|
||||
------
|
||||
id | title | user_id (FOREIGN KEY → users.id)
|
||||
1 | "Mijn blog" | 1
|
||||
2 | "Hello world" | 1
|
||||
3 | "Tips" | 2
|
||||
```
|
||||
|
||||
**Wat zegt dit?**
|
||||
- Post 1 en 2 zijn van user 1 (Tim)
|
||||
- Post 3 is van user 2 (Anna)
|
||||
|
||||
---
|
||||
|
||||
### Relatie Types
|
||||
|
||||
**One-to-Many (1:N)** - Meest voorkomend!
|
||||
```
|
||||
Eén user → meerdere posts
|
||||
Eén category → meerdere products
|
||||
```
|
||||
|
||||
**One-to-One (1:1)** - Zeldzaam
|
||||
```
|
||||
Eén user → één profile
|
||||
```
|
||||
|
||||
**Many-to-Many (N:N)** - Via tussentabel
|
||||
```
|
||||
Posts ↔ Tags (een post heeft meerdere tags, een tag heeft meerdere posts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Data Fetching in Server Components
|
||||
### One-to-Many Voorbeeld
|
||||
|
||||
Simpelweg `async/await` gebruiken:
|
||||
```
|
||||
users posts
|
||||
------ ------
|
||||
id | name id | title | user_id
|
||||
1 | Tim ←────────── 1 | "Blog 1" | 1
|
||||
2 | Anna ←────┬───── 2 | "Blog 2" | 1
|
||||
└───── 3 | "Tips" | 2
|
||||
```
|
||||
|
||||
```tsx
|
||||
// app/products/page.tsx
|
||||
interface Product {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
}
|
||||
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
|
||||
|
||||
async function getProducts(): Promise<Product[]> {
|
||||
const res = await fetch('https://api.example.com/products')
|
||||
return res.json()
|
||||
}
|
||||
---
|
||||
|
||||
export default async function ProductsPage() {
|
||||
const products = await getProducts()
|
||||
### Many-to-Many met Tussentabel
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Producten</h1>
|
||||
<ul>
|
||||
{products.map(product => (
|
||||
<li key={product.id}>
|
||||
{product.name} - €{product.price}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
posts post_tags tags
|
||||
------ --------- ------
|
||||
id | title post_id | tag_id id | name
|
||||
1 | "React tips" 1 | 1 1 | "react"
|
||||
2 | "CSS guide" 1 | 2 2 | "frontend"
|
||||
2 | 2 3 | "css"
|
||||
2 | 3
|
||||
```
|
||||
|
||||
**Lees:**
|
||||
- Post 1 heeft tags: react, frontend
|
||||
- Post 2 heeft tags: frontend, css
|
||||
|
||||
---
|
||||
|
||||
### Normalisatie Basics
|
||||
|
||||
**Probleem: Data duplicatie**
|
||||
```
|
||||
orders (SLECHT)
|
||||
------
|
||||
id | customer_name | customer_email | product_name | price
|
||||
1 | Tim | tim@email.com | Laptop | 999
|
||||
2 | Tim | tim@email.com | Phone | 699
|
||||
3 | Anna | anna@email.com | Laptop | 999
|
||||
```
|
||||
|
||||
**Problemen:**
|
||||
- Tim's email staat 2x (als hij verandert: 2 plekken updaten)
|
||||
- "Laptop" en prijs staan 2x
|
||||
|
||||
---
|
||||
|
||||
### Genormaliseerde Versie
|
||||
|
||||
```
|
||||
users products orders
|
||||
------ -------- ------
|
||||
id | name | email id | name | price id | user_id | product_id
|
||||
1 | Tim | tim@... 1 | Laptop | 999 1 | 1 | 1
|
||||
2 | Anna | anna@... 2 | Phone | 699 2 | 1 | 2
|
||||
3 | 2 | 1
|
||||
```
|
||||
|
||||
**Voordelen:**
|
||||
- Elk gegeven staat 1x
|
||||
- Update op 1 plek
|
||||
- Minder opslagruimte
|
||||
|
||||
---
|
||||
|
||||
### NULL Values
|
||||
|
||||
**NULL = "geen waarde" (niet 0, niet "")**
|
||||
|
||||
```
|
||||
users
|
||||
------
|
||||
id | name | phone
|
||||
1 | Tim | 0612345678
|
||||
2 | Anna | NULL ← Geen telefoon bekend
|
||||
```
|
||||
|
||||
**Wanneer NULL toestaan?**
|
||||
- Optionele velden (phone, description)
|
||||
- Niet bij verplichte velden (name, email)
|
||||
|
||||
---
|
||||
|
||||
### Defaults
|
||||
|
||||
**Automatische waarde als je niks opgeeft:**
|
||||
|
||||
```
|
||||
todos
|
||||
------
|
||||
id | title | completed | created_at
|
||||
| | DEFAULT: false | DEFAULT: now()
|
||||
```
|
||||
|
||||
Bij `INSERT INTO todos (title) VALUES ('Test')`:
|
||||
```
|
||||
id | title | completed | created_at
|
||||
1 | Test | false | 2024-01-15 10:30:00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### API Routes (Route Handlers)
|
||||
### Database Schema Tekenen
|
||||
|
||||
Bouw je eigen API in Next.js:
|
||||
**Tools:** draw.io, Excalidraw, pen en papier
|
||||
|
||||
**Folder structuur:**
|
||||
**Conventie:**
|
||||
```
|
||||
app/
|
||||
└── api/
|
||||
└── products/
|
||||
└── route.ts → /api/products
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ users │ │ posts │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ id (PK) │───┐ │ id (PK) │
|
||||
│ name │ │ │ title │
|
||||
│ email │ └────→│ user_id (FK) │
|
||||
│ created_at │ │ content │
|
||||
└──────────────┘ │ created_at │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
**GET request (`app/api/products/route.ts`):**
|
||||
```typescript
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
const products = [
|
||||
{ id: 1, name: 'Laptop', price: 999 },
|
||||
{ id: 2, name: 'Phone', price: 699 },
|
||||
]
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json(products)
|
||||
}
|
||||
```
|
||||
|
||||
**POST request:**
|
||||
```typescript
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json()
|
||||
|
||||
const newProduct = {
|
||||
id: Date.now(),
|
||||
name: body.name,
|
||||
price: body.price,
|
||||
}
|
||||
|
||||
products.push(newProduct)
|
||||
|
||||
return NextResponse.json(newProduct, { status: 201 })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 'use client' Directive
|
||||
|
||||
Wanneer je interactie nodig hebt:
|
||||
|
||||
```tsx
|
||||
'use client' // MOET bovenaan!
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Count: {count}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### React Query (TanStack Query)
|
||||
|
||||
**Waarom React Query?**
|
||||
- Automatische caching
|
||||
- Loading en error states
|
||||
- Refetching (focus, interval)
|
||||
- Optimistic updates
|
||||
|
||||
**Installatie:**
|
||||
```bash
|
||||
npm install @tanstack/react-query
|
||||
```
|
||||
|
||||
**Setup Provider (`app/providers.tsx`):**
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const [queryClient] = useState(() => new QueryClient())
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**In Layout:**
|
||||
```tsx
|
||||
import { Providers } from './providers'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### useQuery - Data Ophalen
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
interface Product {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
}
|
||||
|
||||
async function fetchProducts(): Promise<Product[]> {
|
||||
const res = await fetch('/api/products')
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function ProductList() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['products'],
|
||||
queryFn: fetchProducts,
|
||||
})
|
||||
|
||||
if (isLoading) return <div>Laden...</div>
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{data?.map(product => (
|
||||
<li key={product.id}>{product.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### useMutation - Data Wijzigen
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
interface NewProduct {
|
||||
name: string
|
||||
price: number
|
||||
}
|
||||
|
||||
async function createProduct(product: NewProduct) {
|
||||
const res = await fetch('/api/products', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(product),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function AddProductForm() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: createProduct,
|
||||
onSuccess: () => {
|
||||
// Invalidate and refetch
|
||||
queryClient.invalidateQueries({ queryKey: ['products'] })
|
||||
},
|
||||
})
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
mutation.mutate({ name: 'New Product', price: 99 })
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? 'Toevoegen...' : 'Voeg toe'}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Combineren: Server + Client
|
||||
|
||||
```tsx
|
||||
// app/products/page.tsx (Server Component)
|
||||
import { ProductList } from './product-list'
|
||||
import { AddProductForm } from './add-product-form'
|
||||
|
||||
export default function ProductsPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Producten</h1>
|
||||
<AddProductForm /> {/* Client Component */}
|
||||
<ProductList /> {/* Client Component */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
PK = Primary Key
|
||||
FK = Foreign Key
|
||||
Pijl = Relatie richting
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Next.js 14
|
||||
- React Query (TanStack Query)
|
||||
- TypeScript
|
||||
- OpenCode/WebStorm
|
||||
- Pen en papier / Excalidraw / draw.io
|
||||
- Supabase Table Editor (vooruitblik)
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
|
||||
### Bouw CRUD App met API Routes
|
||||
### Database Design Oefening
|
||||
|
||||
**Deel 1: API Routes (40 min)**
|
||||
**Deel 1: Blog Database Ontwerpen (45 min)**
|
||||
|
||||
1. Maak `app/api/products/route.ts`
|
||||
2. Implementeer GET (alle producten)
|
||||
3. Implementeer POST (product toevoegen)
|
||||
4. Test met browser/Postman: `/api/products`
|
||||
Ontwerp een database voor een blog met:
|
||||
- Users (kunnen posts schrijven)
|
||||
- Posts (hebben een auteur)
|
||||
- Comments (op posts, door users)
|
||||
|
||||
**Deel 2: React Query Setup (20 min)**
|
||||
Voor elke tabel:
|
||||
1. Teken de tabel met kolommen
|
||||
2. Bepaal data types
|
||||
3. Markeer Primary Keys
|
||||
4. Markeer Foreign Keys
|
||||
5. Teken de relaties
|
||||
|
||||
1. Installeer `@tanstack/react-query`
|
||||
2. Maak `app/providers.tsx`
|
||||
3. Wrap app in `QueryClientProvider`
|
||||
**Deel 2: Normalisatie Oefening (30 min)**
|
||||
|
||||
**Deel 3: Data Tonen met useQuery (30 min)**
|
||||
Gegeven deze "slechte" tabel:
|
||||
|
||||
1. Maak `ProductList` Client Component
|
||||
2. Gebruik `useQuery` om data te fetchen
|
||||
3. Toon loading state
|
||||
4. Toon error state
|
||||
5. Render product lijst
|
||||
```
|
||||
library
|
||||
-------
|
||||
book_title | author_name | author_email | borrower_name | borrowed_date
|
||||
"1984" | "Orwell" | orwell@... | "Tim" | 2024-01-15
|
||||
"1984" | "Orwell" | orwell@... | "Anna" | 2024-01-10
|
||||
"Dune" | "Herbert" | herbert@... | "Tim" | 2024-01-12
|
||||
```
|
||||
|
||||
**Deel 4: Data Toevoegen met useMutation (30 min)**
|
||||
Normaliseer naar aparte tabellen:
|
||||
1. Welke entiteiten zie je?
|
||||
2. Maak aparte tabellen
|
||||
3. Voeg relaties toe
|
||||
|
||||
1. Maak `AddProductForm` Client Component
|
||||
2. Gebruik `useMutation` voor POST
|
||||
3. Invalidate query na success
|
||||
4. Toon "Adding..." state
|
||||
**Deel 3: Eindproject Schema (45 min)**
|
||||
|
||||
Ontwerp het database schema voor jouw eindproject:
|
||||
1. Welke entiteiten heb je nodig?
|
||||
2. Teken elke tabel met kolommen
|
||||
3. Bepaal relaties
|
||||
4. Documenteer je keuzes
|
||||
|
||||
### Deliverable
|
||||
- Werkende API routes (GET, POST)
|
||||
- ProductList met useQuery
|
||||
- AddProductForm met useMutation
|
||||
- Loading en error states
|
||||
- Blog database schema (tekening)
|
||||
- Genormaliseerde library database
|
||||
- Eindproject database schema
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Volledige CRUD Interface
|
||||
### Verdieping en Voorbereiding
|
||||
|
||||
**Deel 1: PUT en DELETE Routes (45 min)**
|
||||
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
|
||||
|
||||
1. Maak `app/api/products/[id]/route.ts`
|
||||
2. Implementeer PUT (update product)
|
||||
3. Implementeer DELETE (verwijder product)
|
||||
4. Test beide endpoints
|
||||
Werk je database schema volledig uit:
|
||||
|
||||
**Deel 2: Update Functionaliteit (45 min)**
|
||||
1. **Per tabel:**
|
||||
- Naam
|
||||
- Alle kolommen met data types
|
||||
- Primary key
|
||||
- Foreign keys
|
||||
- Defaults
|
||||
- Nullable fields
|
||||
|
||||
1. Maak edit form in ProductList
|
||||
2. Gebruik useMutation voor PUT
|
||||
3. Inline editing OF modal
|
||||
4. Invalidate query na success
|
||||
2. **Documenteer:**
|
||||
- Waarom deze structuur?
|
||||
- Welke relaties?
|
||||
- Eventuele alternatieve overwegingen
|
||||
|
||||
**Deel 3: Delete Functionaliteit (30 min)**
|
||||
**Deel 2: Supabase Account (30 min)**
|
||||
|
||||
1. Voeg delete button toe per product
|
||||
2. Gebruik useMutation voor DELETE
|
||||
3. Voeg confirmation dialog toe
|
||||
4. Invalidate query na success
|
||||
Bereid je voor op volgende les:
|
||||
1. Maak account op [supabase.com](https://supabase.com)
|
||||
2. Verken de interface
|
||||
3. Bekijk de Table Editor
|
||||
|
||||
**Bonus:** Optimistic Updates
|
||||
- Product direct uit UI verwijderen
|
||||
- Rollback als server faalt
|
||||
**Deel 3: Reflectie (30 min)**
|
||||
|
||||
Beantwoord deze vragen (kort):
|
||||
1. Wat is het verschil tussen primary en foreign key?
|
||||
2. Waarom normaliseren we data?
|
||||
3. Wanneer gebruik je one-to-many vs many-to-many?
|
||||
4. Welke tabellen heeft jouw eindproject nodig?
|
||||
|
||||
### Deliverable
|
||||
- Complete CRUD API (GET, POST, PUT, DELETE)
|
||||
- UI voor alle operaties
|
||||
- Error handling
|
||||
- Optimistic updates (bonus)
|
||||
- Volledig uitgewerkt database schema voor eindproject
|
||||
- Supabase account aangemaakt
|
||||
- Reflectie vragen beantwoord
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Uitleggen wanneer Server vs Client Components
|
||||
- De 'use client' directive correct gebruiken
|
||||
- Data fetchen in Server Components met async/await
|
||||
- API routes maken met Route Handlers
|
||||
- GET en POST requests implementeren
|
||||
- React Query installeren en configureren
|
||||
- useQuery gebruiken voor data fetching
|
||||
- useMutation gebruiken voor data mutations
|
||||
- Loading en error states afhandelen
|
||||
- Query invalidation toepassen
|
||||
- Uitleggen wat een relationele database is
|
||||
- Tabellen, kolommen en rijen beschrijven
|
||||
- De juiste data types kiezen
|
||||
- Primary keys en hun doel uitleggen
|
||||
- Foreign keys en relaties begrijpen
|
||||
- One-to-many en many-to-many relaties herkennen
|
||||
- Het probleem van data duplicatie identificeren
|
||||
- Een database normaliseren
|
||||
- NULL values en defaults begrijpen
|
||||
- Een database schema ontwerpen en tekenen
|
||||
|
||||
Reference in New Issue
Block a user