-- Absender („Korrespondent") an Dateien. -- -- Warum neben Etiketten noch ein Feld: Es sind **zwei verschiedene Fragen**. -- Ein Etikett sagt, *wofür* ein Dokument gebraucht wird („absetzbar", -- „garantie") – davon trägt eine Datei beliebig viele. Der Absender sagt, *von -- wem* es kommt („Stadtwerke", „Finanzamt") – davon hat eine Datei genau einen. -- -- Als Etikett gelöst stünden beide in derselben Liste: „Stadtwerke" neben -- „absetzbar", Absender und Verwendungszweck durcheinander. Bei zwanzig -- Etiketten fällt das nicht auf, bei achtzig ist die Liste unbenutzbar. Und -- „alle Rechnungen von X" ließe sich nicht von „alles Absetzbare" trennen – -- genau die Verknüpfung, für die es gebaut wird. -- -- Die Eins-Regel ist dabei kein Mangel, sondern der Zweck: Sie erzwingt eine -- Entscheidung und hält die Liste kurz. CREATE TABLE correspondents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Prinzipal-Modell wie bei tags: entweder ein Konto oder eine Gruppe. -- In einem Gruppenordner müssen alle dieselben Absender sehen, sonst -- sortiert jeder dieselben Dokumente für sich neu ein. owner_id UUID REFERENCES users(id) ON DELETE CASCADE, group_id UUID REFERENCES groups(id) ON DELETE CASCADE, name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT correspondents_one_principal CHECK ( (owner_id IS NOT NULL AND group_id IS NULL) OR (owner_id IS NULL AND group_id IS NOT NULL) ), CONSTRAINT correspondents_name_not_empty CHECK (length(btrim(name)) > 0) ); -- Eindeutig je Prinzipal, ohne Rücksicht auf Groß-/Kleinschreibung – dieselbe -- Regel wie bei Etiketten. Hier wiegt sie schwerer: „Amazon" und „amazon" -- nebeneinander hieße, dass „alle Rechnungen von Amazon" nur die Hälfte -- findet, und zwar unauffällig. CREATE UNIQUE INDEX idx_correspondents_owner_name ON correspondents (owner_id, lower(btrim(name))) WHERE owner_id IS NOT NULL; CREATE UNIQUE INDEX idx_correspondents_group_name ON correspondents (group_id, lower(btrim(name))) WHERE group_id IS NOT NULL; -- Am Datei-Datensatz, nicht in einer Verknüpfungstabelle: Genau einer je -- Datei – das steht damit im Schema und nicht bloß in einer Zusicherung des -- Codes. -- -- ON DELETE SET NULL statt CASCADE: Wer einen Absender löscht, will ihn aus -- der Liste haben – nicht seine Rechnungen. ALTER TABLE files ADD COLUMN correspondent_id UUID REFERENCES correspondents(id) ON DELETE SET NULL; -- „Alles von Stadtwerke" ist die Abfrage, für die das Feld gebaut wird. CREATE INDEX idx_files_correspondent ON files (correspondent_id) WHERE correspondent_id IS NOT NULL;