sqlschool.gr logo

articles

Articles of SQLschool.gr Team

Polymorphic Association Anti-Pattern in SQL Server

Antonios Chatzipavlis
Friday 12 December 2025

Εισαγωγή

Το Polymorphic Association είναι ένα σχήμα όπου ένας πίνακας (π.χ. Comments) συνδέεται με πολλαπλούς πιθανούς parents μέσω δύο πεδίων: target_id και target_type (π.χ. 'Order', 'Product'). Αν και φαίνεται ευέλικτο από πλευράς εφαρμογής (λιγότερες αλλαγές στο schema όταν προστίθεται νέος τύπος), σε σχεσιακές βάσεις όπως το SQL Server αποτελεί Anti‑Pattern επειδή παραβιάζει τo Referential Integrity, αυξάνει την πολυπλοκότητα των queries, περιορίζει τη βελτιστοποίηση του optimizer και καθιστά δύσκολους τους μηχανισμούς cascade.

image
Polymorphic Association Anti‑Pattern

Παράδειγμα Anti‑Pattern


-- Parent tables
CREATE TABLE dbo.Orders (
    order_id      int PRIMARY KEY,
    order_number  nvarchar(50) NOT NULL
);

CREATE TABLE dbo.Products (
    product_id    int PRIMARY KEY,
    product_name  nvarchar(200) NOT NULL
);

-- Πολυμορφικός πίνακας σχολίων (ANTI-PATTERN)
CREATE TABLE dbo.Comments (
    comment_id    int IDENTITY PRIMARY KEY,
    target_id     int NOT NULL,           -- δείχνει είτε σε Orders είτε σε Products
    target_type   varchar(20) NOT NULL,   -- 'Order' ή 'Product'
    comment_text  nvarchar(1000) NOT NULL,
    created_at    datetime2(0) NOT NULL DEFAULT sysdatetime()
    -- ΠΡΟΣΟΧΗ: Δεν υπάρχει FK εδώ!
);

-- Εισαγωγή δεδομένων (με ορφανό παράδειγμα)
INSERT dbo.Orders(order_id, order_number) VALUES (1, N'ORD-001');
INSERT dbo.Products(product_id, product_name)   VALUES (10, N'Keyboard');

INSERT dbo.Comments(target_id, target_type, comment_text)
VALUES
  (1,  'Order',   N'Πρώτο σχόλιο για την παραγγελία 1'),
  (10, 'Product', N'Σχόλιο για το προϊόν 10'),
  (999, 'Order',  N'Ορφανό σχόλιο (Order 999 δεν υπάρχει)');

Ενδεικτικό query (δύσχρηστο & εύθραυστο)


SELECT
    c.comment_id,
    c.comment_text,
    c.target_type,
    COALESCE(o.order_number, p.product_name) AS parent_display
FROM dbo.Comments AS c
LEFT JOIN dbo.Orders   AS o ON c.target_type = 'Order'   AND c.target_id = o.order_id
LEFT JOIN dbo.Products AS p ON c.target_type = 'Product' AND c.target_id = p.product_id;

Τι προβλήματα δημιουργεί

  • Έλλειψη Referential Integrity: Χωρίς FK, προκύπτουν ορφανά. Η ακεραιότητα βασίζεται στην εφαρμογή αντί για τη DB.
  • Δυσκολία στη βελτιστοποίηση: Ο optimizer δεν έχει σαφή στατιστικά σχέσεων, τα indexes είναι σύνθετα (π.χ. (target_type, target_id)) και συχνά ανεπαρκή.
  • Cascades/Constraints: Δεν μπορείς να ορίσεις ON DELETE CASCADE προς πολλούς parent tables. Triggers/constraints γίνονται περίπλοκα και εύθραυστα.
  • Maintainability: Κάθε προσθήκη νέου target_type επιβάλλει αλλαγές σε queries, indexes, triggers, reports, κ.λπ.
  • Σύνθετη επιχειρησιακή λογική: Πολλά CASE/IF σε stored procedures, views και ETL.

Γιατί παρόλα αυτά χρησιμοποιείται

  • Ευελιξία στο application layer: Ελάχιστες αλλαγές στο schema όταν προστίθεται νέος τύπος οντότητας.
  • Γρήγορη αρχική ανάπτυξη: Ενιαίος πίνακας για «σχόλια» ή «συνημμένα».
  • ORM υποστήριξη: Κάποια ORMs διευκολύνουν το pattern στο code‑first επίπεδο.
Danger

Παρ’ όλα αυτά, στα transactional συστήματα με απαιτήσεις ακεραιότητας/απόδοσης, το pattern συνήθως πληρώνεται ακριβά μακροπρόθεσμα.

Εναλλακτικές Λύσεις

Εναλλακτική Α: Ξεχωριστά Association Tables (ρητά FKs)

Διατηρείς διαφορετικούς πίνακες συσχέτισης ανά parent table, με καθαρά Foreign Keys.

image
Εναλλακτική Α: Ξεχωριστά Association Tables

Πλεονεκτήματα: Καθαρά FKs, ON DELETE CASCADE, σαφή στατιστικά, απλά JOINs.

Κόστος: Περισσότεροι πίνακες (επαναλαμβανόμενη δομή).

Ξεχωριστά Association Tables (ρητά FKs)



CREATE TABLE dbo.OrderComments (
    comment_id   int IDENTITY PRIMARY KEY,
    order_id     int NOT NULL FOREIGN KEY REFERENCES dbo.Orders(order_id),
    comment_text nvarchar(1000) NOT NULL,
    created_at   datetime2(0) NOT NULL DEFAULT sysdatetime()
);

CREATE TABLE dbo.ProductComments (
    comment_id   int IDENTITY PRIMARY KEY,
    product_id   int NOT NULL FOREIGN KEY REFERENCES dbo.Products(product_id),
    comment_text nvarchar(1000) NOT NULL,
    created_at   datetime2(0) NOT NULL DEFAULT sysdatetime()
);

Εναλλακτική Β: Unified Base Entity (Table‑per‑Type με κοινό entity_id)

Όλα τα parent tables αποκτούν κοινή οντότητα (Entities) και μοιράζονται το ίδιο κλειδί.

image
Εναλλακτική Β: Unified Base Entity (Table‑per‑Type με κοινό entity_id)

Εναλλακτική Β: Unified Base Entity (Table‑per‑Type με κοινό entity_id)


CREATE TABLE dbo.Entities (
    entity_id   bigint IDENTITY PRIMARY KEY,
    entity_type varchar(20) NOT NULL -- 'Order','Product',...
);

CREATE TABLE dbo.Orders (
    order_id     bigint PRIMARY KEY,        -- == entity_id
    order_number nvarchar(50) NOT NULL,
    CONSTRAINT FK_Orders_Entity FOREIGN KEY (order_id)
        REFERENCES dbo.Entities(entity_id)
        ON DELETE CASCADE
);

CREATE TABLE dbo.Products (
    product_id    bigint PRIMARY KEY,       -- == entity_id
    product_name  nvarchar(200) NOT NULL,
    CONSTRAINT FK_Products_Entity FOREIGN KEY (product_id)
        REFERENCES dbo.Entities(entity_id)
        ON DELETE CASCADE
);

CREATE TABLE dbo.Comments (
    comment_id   bigint IDENTITY PRIMARY KEY,
    entity_id    bigint NOT NULL
        FOREIGN KEY REFERENCES dbo.Entities(entity_id) ON DELETE CASCADE,
    comment_text nvarchar(1000) NOT NULL,
    created_at   datetime2(0) NOT NULL DEFAULT sysdatetime()
);

Πλεονεκτήματα: Ένα FK για τα σχόλια, πλήρη cascades μέσω Entities, εύκολη επέκταση με νέους τύπους.

Κόστος: Απαιτεί ενοποιημένη ταυτότητα και διαδικασία εισαγωγής (πρώτα Entities, μετά το child).



Πίνακας Σύγκρισης
Κριτήριο Polymorphic Association (Anti-Pattern) Ξεχωριστά Association Tables Unified Entity Table
Referential Integrity Καμία εγγύηση Πλήρης μέσω FKs Πλήρης μέσω FKs
ON DELETE CASCADE Αδύνατο Υποστηρίζεται Υποστηρίζεται
Query Complexity Πολύπλοκα JOIN με CASE Απλά JOIN Απλά JOIN
Indexing & Statistics Δύσκολα, χωρίς FK στατιστικά Καθαρά στατιστικά ανά σχέση Καθαρά στατιστικά
Maintainability Δύσκολο, triggers για ακεραιότητα Εύκολο, απλή δομή Εύκολο, αλλά πιο σύνθετη εισαγωγή
Επεκτασιμότητα (νέοι τύποι) Εύκολη (προσθήκη νέου target_type) Νέος πίνακας για κάθε τύπο Εύκολη (νέος child table)
ORM Compatibility Υποστηρίζεται Υποστηρίζεται Υποστηρίζεται
Απόδοση σε μεγάλα datasets Χαμηλή (πολλά CASE & JOIN) Υψηλή Υψηλή

Συμπεράσματα & Best Practices

  • Απέφυγε το Polymorphic Association για core transactional δεδομένα. Η βραχυπρόθεσμη ευελιξία κοστίζει ακριβά σε ακεραιότητα, απόδοση και συντηρησιμότητα.
  • Προτίμησε:
    Ξεχωριστά association tables όταν οι τύποι είναι λίγοι/σταθεροί και θέλεις απλότητα.
    Unified base entity όταν χρειάζεσαι επεκτασιμότητα με κοινό identity και πλήρη cascades.
  • Χτίσε RBAC (roles), FKs, constraints και καθαρά indexes. Μην βασίζεις την ορθότητα σε caching ή triggers.
  • Σχεδίασε migrations με views/dual‑write/backfill/cutover για zero‑downtime μετάβαση από polymorphic σε καθαρές σχέσεις


Leave your comment

 

   

 

captcha
   

 

Antonios Chatzipavlis

Antonios Chatzipavlis

Antonios Chatzipavlis is a highly experienced Data Solutions Consultant and Trainer. He has been working in the IT industry since 1988, holding various roles such as senior developer, IT Manager, Data & AI Solutions Architect and Consultant.

Since 1995, Antonios has focused on modern technologies and software development tools, primarily by Microsoft. He has specialized in Data & AI since 2000, with expertise in Microsoft Data Platform (SQL Server, Azure SQL Databases, Azure Synapse Analytics, Microsoft Fabric, Power BI, AI) and Databricks.

Antonios is also a Microsoft Certified Trainer (MCT) for over 25 years, has been recognized as a Microsoft Most Valuable Professional (MVP) in Data Platform since 2010 and he is in the Data Expert 40 Powerlist 2024 by Boussias. He is the co-founder and visionary behind XLYTiCA, a company dedicated to Data & AI solutions.

Tip

SQL Server 2025 for Data Professionals

More Tips...

Become a member

If you want to receive updates from us become a member to our community.

Connect

Explore

Learn

sqlschool.gr © 2010-2026 All rights reserved

This site uses cookies for operational and analytics purposes only. By continuing to browse this site, you agree to their use.