Нормализация баз данных: 1NF, 2NF, 3NF, BCNF и аномалии
Нормализация баз данных — это систематический процесс организации данных в реляционных базах данных. Целью является снижение избыточности и предотвращение аномалий при манипулировании данными.
Что такое нормализация?
Нормализация разбивает сложные структуры данных на небольшие логически связанные таблицы. Соблюдение нормальных форм обеспечивает целостность и согласованность данных.
Цели нормализации
- Избежать избыточности: хранить данные только один раз
- Исключить аномалии: избежать аномалий обновления, вставки и удаления
- Целостность данных: обеспечить согласованность данных
- Удобство обслуживания: упростить изменения и расширения
Функциональные зависимости
Основные понятия
Функциональная зависимость описывает отношение между атрибутами в отношении.
-- Пример: отношение студентов
CREATE TABLE Studenten (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT,
Fachbereich VARCHAR(50)
);
-- Функциональные зависимости:
-- MatrikelNr → Name (каждый номер студента имеет ровно одно имя)
-- MatrikelNr → Semester (каждый номер студента имеет ровно один семестр)
-- MatrikelNr → Fachbereich (каждый номер студента имеет ровно одно направление)
Типы функциональных зависимостей
-- Полная функциональная зависимость
CREATE TABLE Noten (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr)
);
-- (MatrikelNr, VorlesungNr) → Note (полностью зависит)
-- MatrikelNr ↛ Note (не зависит)
-- VorlesungNr ↛ Note (не зависит)
-- Транзитивная функциональная зависимость
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50),
FachbereichLeiter VARCHAR(50)
);
-- DozentenNr → Fachbereich
-- Fachbereich → FachbereichLeiter
-- DozentenNr → FachbereichLeiter (транзитивно)
Аномалии в ненормализованных данных
Пример аномалий
-- Ненормализованная таблица с проблемами
CREATE TABLE Probleme_Tabelle (
MatrikelNr INT,
StudentName VARCHAR(50),
VorlesungNr INT,
VorlesungName VARCHAR(50),
DozentNr INT,
DozentName VARCHAR(50),
Note DECIMAL(3,1),
Semester INT
);
-- Данные:
-- 101, 'Max Mustermann', 301, 'Datenbanken', 501, 'Prof. Schmidt', 1.3, 5
-- 101, 'Max Mustermann', 302, 'Algorithmen', 502, 'Prof. Mueller', 2.0, 5
-- 102, 'Erika Mustermann', 301, 'Datenbanken', 501, 'Prof. Schmidt', 1.7, 3
Типы аномалий
1. Аномалия обновления
-- Проблема: изменить имя преподавателя
UPDATE Probleme_Tabelle
SET DozentName = 'Prof. Dr. Schmidt'
WHERE DozentNr = 501;
-- Нужно сделать это для всех вхождений!
2. Аномалия вставки
-- Проблема: добавить новый курс без студентов
INSERT INTO Probleme_Tabelle (MatrikelNr, VorlesungNr, VorlesungName, DozentNr, DozentName)
VALUES (NULL, 303, 'Software Engineering', 503, 'Prof. Weber');
-- Проблема: MatrikelNr не может быть NULL (первичный ключ)
3. Аномалия удаления
-- Проблема: удалить последнего студента с курса
DELETE FROM Probleme_Tabelle
WHERE MatrikelNr = 101 AND VorlesungNr = 302;
-- Теряется информация о курсе 302 и преподавателе 502
Первая нормальная форма (1NF)
Определение и правила
Отношение находится в 1NF, если:
- Все атрибуты атомарны (нет повторяющихся групп)
- Каждая строка однозначно идентифицируется (первичный ключ)
- Все значения атрибутов из одного домена
Преобразование в 1NF
-- До: не в 1NF (повторяющиеся группы)
CREATE TABLE Studenten_Vorher (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Vorlesungen VARCHAR(200), -- "301,302,303"
Noten VARCHAR(100) -- "1.3,2.0,1.7"
);
-- После: в 1NF (атомарные атрибуты)
CREATE TABLE Studenten_1NF (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Belegungen_1NF (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten_1NF(MatrikelNr)
);
-- Пример атомарных значений
INSERT INTO Studenten_1NF VALUES (101, 'Max Mustermann', 5);
INSERT INTO Belegungen_1NF VALUES (101, 301, 1.3);
INSERT INTO Belegungen_1NF VALUES (101, 302, 2.0);
1NF с JSON/XML (современные подходы)
-- Пример PostgreSQL с JSON
CREATE TABLE Studenten_Modern (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Belegungen JSONB
);
INSERT INTO Studenten_Modern VALUES (
101,
'Max Mustermann',
'[
{"vorlesungNr": 301, "note": 1.3},
{"vorlesungNr": 302, "note": 2.0}
]'
);
-- Запрос с JSON-функциями
SELECT
MatrikelNr,
Name,
vorlesung->>'vorlesungNr' AS Vorlesung,
vorlesung->>'note' AS Note
FROM Studenten_Modern,
jsonb_array_elements(Belegungen) AS vorlesung
WHERE MatrikelNr = 101;
Вторая нормальная форма (2NF)
Определение и правила
Отношение находится в 2NF, если:
- Оно находится в 1NF
- Все неключевые атрибуты полностью зависят от первичного ключа
Проблема: частичные зависимости
-- Проблема: не в 2NF (частичные зависимости)
CREATE TABLE Belegungen_Problem (
MatrikelNr INT,
VorlesungNr INT,
StudentName VARCHAR(50), -- Зависит только от MatrikelNr
VorlesungName VARCHAR(50), -- Зависит только от VorlesungNr
DozentNr INT, -- Зависит только от VorlesungNr
Note DECIMAL(3,1), -- Зависит от (MatrikelNr, VorlesungNr)
PRIMARY KEY (MatrikelNr, VorlesungNr)
);
-- Частичные зависимости:
-- MatrikelNr → StudentName (частичная)
-- VorlesungNr → VorlesungName, DozentNr (частичная)
-- (MatrikelNr, VorlesungNr) → Note (полная)
Преобразование в 2NF
-- Разбиение на 2NF
CREATE TABLE Studenten_2NF (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Vorlesungen_2NF (
VorlesungNr INT PRIMARY KEY,
Name VARCHAR(50),
DozentNr INT
);
CREATE TABLE Belegungen_2NF (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten_2NF(MatrikelNr),
FOREIGN KEY (VorlesungNr) REFERENCES Vorlesungen_2NF(VorlesungNr)
);
-- Примеры данных
INSERT INTO Studenten_2NF VALUES (101, 'Max Mustermann', 5);
INSERT INTO Vorlesungen_2NF VALUES (301, 'Datenbanken', 501);
INSERT INTO Belegungen_2NF VALUES (101, 301, 1.3);
Проверка на 2NF
-- Проверка на частичные зависимости
-- Для каждого неключевого атрибута проверить:
-- Зависит ли он от всего ключа?
-- Пример: StudentName
-- Зависит от MatrikelNr? Да
-- Зависит от VorlesungNr? Нет
-- → Частичная зависимость → Не в 2NF
-- Пример: Note
-- Зависит от MatrikelNr? Нет
-- Зависит от VorlesungNr? Нет
-- Зависит от (MatrikelNr, VorlesungNr)? Да
-- → Полная зависимость → В 2NF
Третья нормальная форма (3NF)
Определение и правила
Отношение находится в 3NF, если:
- Оно находится в 2NF
- Нет транзитивных зависимостей неключевых атрибутов
Проблема: транзитивные зависимости
-- Проблема: не в 3NF (транзитивные зависимости)
CREATE TABLE Dozenten_Problem (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50),
FachbereichLeiter VARCHAR(50)
);
-- Транзитивные зависимости:
-- DozentenNr → Fachbereich
-- Fachbereich → FachbereichLeiter
-- DozentenNr → FachbereichLeiter (транзитивно)
Приведение к 3NF
-- Разделение на 3NF
CREATE TABLE Dozenten_3NF (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereiche_3NF (
Fachbereich VARCHAR(50) PRIMARY KEY,
Leiter VARCHAR(50)
);
-- Пример данных
INSERT INTO Dozenten_3NF VALUES (501, 'Prof. Schmidt', 'Informatik');
INSERT INTO Fachbereiche_3NF VALUES ('Informatik', 'Prof. Dr. Meier');
Сложный пример 3NF
-- Полная 3NF-схема для университета
CREATE TABLE Studenten (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
Geburtsdatum DATE,
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereiche (
Fachbereich VARCHAR(50) PRIMARY KEY,
Dekan VARCHAR(50),
Gebaeude VARCHAR(20)
);
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
Fachbereich VARCHAR(50),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Vorlesungen (
VorlesungNr INT PRIMARY KEY,
Titel VARCHAR(100) NOT NULL,
DozentenNr INT,
Fachbereich VARCHAR(50),
FOREIGN KEY (DozentenNr) REFERENCES Dozenten(DozentenNr),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Belegungen (
MatrikelNr INT,
VorlesungNr INT,
Semester INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr, Semester),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten(MatrikelNr),
FOREIGN KEY (VorlesungNr) REFERENCES Vorlesungen(VorlesungNr)
);
Нормальная форма Бойса — Кодда (BCNF)
Определение и правила
Отношение находится в BCNF, если:
- Оно находится в 3NF
- Для каждой функциональной зависимости X → Y верно: X является суперключом
Проблема: нарушение BCNF
-- Проблема: не в BCNF
CREATE TABLE Lehrveranstaltungen (
DozentenNr INT,
Fachbereich VARCHAR(50),
VorlesungNr INT,
PRIMARY KEY (DozentenNr, Fachbereich)
);
-- Данные:
-- 501, 'Informatik', 301
-- 501, 'Mathematik', 401
-- 502, 'Informatik', 302
-- Функциональные зависимости:
-- (DozentenNr, Fachbereich) → VorlesungNr (первичный ключ)
-- DozentenNr → Fachbereich (каждый преподаватель относится ровно к одному факультету)
-- Проблема: DozentenNr → Fachbereich
-- DozentenNr не является суперключом → нарушение BCNF!
Приведение к BCNF
-- Разделение на BCNF
CREATE TABLE Dozenten_Fachbereich (
DozentenNr INT PRIMARY KEY,
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereich_Vorlesungen (
Fachbereich VARCHAR(50),
DozentenNr INT,
VorlesungNr INT,
PRIMARY KEY (Fachbereich, DozentenNr)
);
-- Альтернативное решение в BCNF
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Fachbereich VARCHAR(50)
);
CREATE TABLE Vorlesungen_Dozenten (
VorlesungNr INT,
DozentenNr INT,
PRIMARY KEY (VorlesungNr, DozentenNr),
FOREIGN KEY (DozentenNr) REFERENCES Dozenten(DozentenNr)
);
BCNF vs 3NF
-- Пример случая, когда 3NF ≠ BCNF
CREATE TABLE Projektmitarbeiter (
ProjektNr INT,
MitarbeiterNr INT,
Rolle VARCHAR(50),
PRIMARY KEY (ProjektNr, MitarbeiterNr)
);
-- Предположение: каждый сотрудник имеет ровно одну роль в каждом проекте
-- Однако: один сотрудник может иметь одну и ту же роль в разных проектах
-- Функциональные зависимости:
-- (ProjektNr, MitarbeiterNr) → Rolle (первичный ключ)
-- MitarbeiterNr → Rolle (каждый сотрудник имеет фиксированную роль)
-- Нарушение BCNF: MitarbeiterNr → Rolle, но MitarbeiterNr не является суперключом
-- Решение в BCNF:
CREATE TABLE Mitarbeiter_Rolle (
MitarbeiterNr INT PRIMARY KEY,
Rolle VARCHAR(50)
);
CREATE TABLE Projekt_Mitarbeiter (
ProjektNr INT,
MitarbeiterNr INT,
PRIMARY KEY (ProjektNr, MitarbeiterNr),
FOREIGN KEY (MitarbeiterNr) REFERENCES Mitarbeiter_Rolle(MitarbeiterNr)
);
Процесс нормализации
Пошаговое руководство
-- Шаг 0: исходная таблица (не нормализована)
CREATE TABLE Uni_Daten (
MatrikelNr INT,
StudentName VARCHAR(50),
VorlesungNr INT,
VorlesungName VARCHAR(50),
DozentNr INT,
DozentName VARCHAR(50),
Fachbereich VARCHAR(50),
Note DECIMAL(3,1),
Semester INT
);
-- Шаг 1: 1NF - атомарные значения
-- (в этом примере уже атомарны)
-- Шаг 2: 2NF - устранение частичных зависимостей
CREATE TABLE Studenten_2NF (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Vorlesungen_2NF (
VorlesungNr INT PRIMARY KEY,
Name VARCHAR(50),
DozentNr INT,
Fachbereich VARCHAR(50)
);
CREATE TABLE Noten_2NF (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr)
);
-- Шаг 3: 3NF - устранение транзитивных зависимостей
CREATE TABLE Dozenten_3NF (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50)
);
CREATE TABLE Fachbereiche_3NF (
Fachbereich VARCHAR(50) PRIMARY KEY
);
-- Итоговая 3NF-структура
CREATE TABLE Studenten (
MatrikelNr INT PRIMARY KEY,
Name VARCHAR(50),
Semester INT
);
CREATE TABLE Fachbereiche (
Fachbereich VARCHAR(50) PRIMARY KEY
);
CREATE TABLE Dozenten (
DozentenNr INT PRIMARY KEY,
Name VARCHAR(50),
Fachbereich VARCHAR(50),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Vorlesungen (
VorlesungNr INT PRIMARY KEY,
Name VARCHAR(50),
DozentenNr INT,
Fachbereich VARCHAR(50),
FOREIGN KEY (DozentenNr) REFERENCES Dozenten(DozentenNr),
FOREIGN KEY (Fachbereich) REFERENCES Fachbereiche(Fachbereich)
);
CREATE TABLE Belegungen (
MatrikelNr INT,
VorlesungNr INT,
Note DECIMAL(3,1),
PRIMARY KEY (MatrikelNr, VorlesungNr),
FOREIGN KEY (MatrikelNr) REFERENCES Studenten(MatrikelNr),
FOREIGN KEY (VorlesungNr) REFERENCES Vorlesungen(VorlesungNr)
);
Алгоритмы нормализации
Алгоритм синтеза
-- Анализ функциональных зависимостей
-- F = {MatrikelNr → Name, VorlesungNr → Titel, DozentNr → Name,
-- (MatrikelNr, VorlesungNr) → Note}
-- Шаг 1: находим минимальное покрытие
-- F_min = {MatrikelNr → Name, VorlesungNr → Titel,
-- DozentNr → Name, (MatrikelNr, VorlesungNr) → Note}
-- Шаг 2: группируем по левой части
-- Группа 1: MatrikelNr → Name → отношение Studenten(MatrikelNr, Name)
-- Группа 2: VorlesungNr → Titel → отношение Vorlesungen(VorlesungNr, Titel)
-- Группа 3: DozentNr → Name → отношение Dozenten(DozentenNr, Name)
-- Группа 4: (MatrikelNr, VorlesungNr) → Note → отношение Noten(MatrikelNr, VorlesungNr, Note)
-- Шаг 3: определяем ключи-кандидаты и дополняем
-- Ключ-кандидат: (MatrikelNr, VorlesungNr) для Noten
-- Остальные отношения имеют свои первичные ключи
Алгоритм декомпозиции
-- Исходная таблица с аномалиями
CREATE TABLE Probleme (
A INT,
B INT,
C INT,
D INT,
PRIMARY KEY (A, B)
);
-- Функциональные зависимости: A → C, B → D
-- Шаг 1: проверка 2NF
-- C зависит только от A (частично) → разделяем
CREATE TABLE R1 (A INT PRIMARY KEY, C INT);
CREATE TABLE R2 (A INT, B INT, D INT, PRIMARY KEY (A, B));
-- Шаг 2: проверка 3NF
-- В R2: B → D (транзитивно через (A,B) → B) → разделяем
CREATE TABLE R3 (B INT PRIMARY KEY, D INT);
CREATE TABLE R4 (A INT, B INT, PRIMARY KEY (A, B));
-- Итоговая 3NF-структура
-- R1(A, C), R3(B, D), R4(A, B)
Практические примеры
База данных для электронной коммерции
-- Нормализованная схема интернет-магазина
CREATE TABLE Kunden (
KundenNr INT PRIMARY KEY,
Name VARCHAR(50),
Email VARCHAR(100) UNIQUE,
Adresse VARCHAR(200),
Stadt VARCHAR(50),
PLZ VARCHAR(10)
);
CREATE TABLE Kategorien (
KategorieNr INT PRIMARY KEY,
Name VARCHAR(50),
Beschreibung TEXT
);
CREATE TABLE Produkte (
ProduktNr INT PRIMARY KEY,
Name VARCHAR(100),
Preis DECIMAL(10,2),
Beschreibung TEXT,
KategorieNr INT,
Lagerbestand INT,
FOREIGN KEY (KategorieNr) REFERENCES Kategorien(KategorieNr)
);
CREATE TABLE Bestellungen (
BestellNr INT PRIMARY KEY,
KundenNr INT,
Bestelldatum DATE,
Gesamtsumme DECIMAL(10,2),
Status VARCHAR(20),
FOREIGN KEY (KundenNr) REFERENCES Kunden(KundenNr)
);
CREATE TABLE Bestellpositionen (
BestellNr INT,
ProduktNr INT,
Menge INT,
Einzelpreis DECIMAL(10,2),
PRIMARY KEY (BestellNr, ProduktNr),
FOREIGN KEY (BestellNr) REFERENCES Bestellungen(BestellNr),
FOREIGN KEY (ProduktNr) REFERENCES Produkte(ProduktNr)
);
Система управления библиотекой
-- Нормализованная схема библиотеки
CREATE TABLE Autoren (
AutorNr INT PRIMARY KEY,
Name VARCHAR(50),
Geburtsjahr INT
);
CREATE TABLE Buecher (
BuchNr INT PRIMARY KEY,
Titel VARCHAR(100),
ISBN VARCHAR(20) UNIQUE,
Erscheinungsjahr INT,
Verlag VARCHAR(50)
);
CREATE TABLE Buch_Autoren (
BuchNr INT,
AutorNr INT,
PRIMARY KEY (BuchNr, AutorNr),
FOREIGN KEY (BuchNr) REFERENCES Buecher(BuchNr),
FOREIGN KEY (AutorNr) REFERENCES Autoren(AutorNr)
);
CREATE TABLE Leser (
Lesernummer INT PRIMARY KEY,
Name VARCHAR(50),
Adresse VARCHAR(200),
Telefon VARCHAR(20)
);
CREATE TABLE Exemplare (
ExemplarNr INT PRIMARY KEY,
BuchNr INT,
Status VARCHAR(20),
FOREIGN KEY (BuchNr) REFERENCES Buecher(BuchNr)
);
CREATE TABLE Ausleihen (
AusleihNr INT PRIMARY KEY,
Lesernummer INT,
ExemplarNr INT,
Ausleihdatum DATE,
Rueckgabedatum DATE,
FOREIGN KEY (Lesernummer) REFERENCES Leser(Lesernummer),
FOREIGN KEY (ExemplarNr) REFERENCES Exemplare(ExemplarNr)
);
Денормализация
Когда и зачем денормализировать
-- Оптимизированная по производительности схема (денормализованная)
CREATE TABLE Produkt_Statistiken (
ProduktNr INT PRIMARY KEY,
Name VARCHAR(100),
Preis DECIMAL(10,2),
KategorieName VARCHAR(50), -- Денормализовано
KategorieBeschreibung TEXT, -- Денормализовано
GesamtVerkaufsmenge INT, -- Денормализовано (агрегат)
LetzteBestellung DATE -- Денормализовано (агрегат)
);
-- Преимущества:
-- Требуется меньше JOINов
-- Быстрее выполняются запросы
-- Лучше производительность чтения
-- Недостатки:
-- Избыточность данных
-- Аномалии обновления
-- Больше требуется памяти
Стратегии денормализации
-- 1. Предвычисленные агрегаты
CREATE TABLE Monatsverkaufe (
Monat DATE,
ProduktNr INT,
Verkaufsmenge INT,
Umsatz DECIMAL(12,2),
PRIMARY KEY (Monat, ProduktNr)
);
-- 2. Копированные атрибуты
CREATE TABLE Bestellungen_Kurz (
BestellNr INT PRIMARY KEY,
KundenName VARCHAR(50), -- Скопировано из Kunden
KundenEmail VARCHAR(100), -- Скопировано из Kunden
Bestelldatum DATE,
Gesamtsumme DECIMAL(10,2)
);
-- 3. Иерархические данные
CREATE TABLE Mitarbeiter_Hierarchie (
MitarbeiterNr INT PRIMARY KEY,
Name VARCHAR(50),
VorgesetzterNr INT,
Pfad VARCHAR(200), -- Денормализованный путь
Ebene INT -- Денормализованная глубина
);
Концепции для экзамена
Важные определения
- Функциональная зависимость: X → Y означает, что Y зависит от X
- Полная зависимость: Y зависит от всего X
- Частичная зависимость: Y зависит от части X
- Транзитивная зависимость: X → Y и Y → Z следовательно X → Z
Обзор нормальных форм
| Нормальная форма | Основная проблема | Решение |
|---|---|---|
| 1NF | Повторяющиеся группы | Атомарные значения |
| 2NF | Частичные зависимости | Разделение по ключам |
| 3NF | Транзитивные зависимости | Устранение транзитивности |
| BCNF | Детерминанты, не являющиеся ключами | Каждый детерминант является суперключом |
Типичные экзаменационные задачи
- Определить функциональные зависимости
- Нормализовать заданные отношения
- Объяснить аномалии и их причины
- Сравнить нормальные формы
- Принять решение о денормализации
Резюме
Нормализация является основой качественного дизайна баз данных:
- 1NF: атомарные значения и уникальные строки
- 2NF: устраняет частичные зависимости
- 3NF: устраняет транзитивные зависимости
- BCNF: самая строгая нормальная форма для практического применения
Правильный баланс между нормализацией и производительностью имеет решающее значение для успешных систем баз данных.
Рекомендуемая литература: Базы данных
Keine Bücher für Kategorie "datenbanken" gefunden.

