Your SlideShare is downloading. ×
0
Practical
object-oriented
models in SQL
Bill Karwin
PostgreSQL Conference West 09 • 2009/10/17
Me
• 20+ years experience
  •   Application/SDK developer
  •   Support, Training, Proj Mgmt
  •   C, Java, Perl, PHP

• S...
Object-Oriented vs. Relational


•   Impedance mismatch
•   OO operates on instances;
    RDBMS operates on sets
•   Compr...
Example database
Entity-Attribute-Value
If you try and take a cat apart to see how it works, the
first thing you have on your hands is a non...
Entity-Attribute-Value
 • Objective:          extensibility
    •    Variable sets of attributes

bug_id   bug_type   prio...
Entity-Attribute-Value

             Meta-Table


CREATE TABLE eav (

 bug_id

 
 INT NOT NULL,

 attr_name

 VARCHAR(20) ...
Entity-Attribute-Value

What does this look like?

bug_id   attr_name                   attr_value
 1234     priority     ...
Entity-Attribute-Value

         Problems: names


bug_id    attr_name                   attr_value
 1234       created   ...
Entity-Attribute-Value

         Problems: values


bug_id    attr_name                   attr_value
 1234     created_dat...
Entity-Attribute-Value

      Problems: constraints
                                           NOT NULL


• Difficult to en...
Entity-Attribute-Value

        Problems: constraints
• Difficult to use lookup tables
  bug_id        attr_name           ...
Entity-Attribute-Value

                     Problems: queries
    • Difficult to reconstruct a row of attributes:
        ...
Entity-Attribute-Value

        Solution?



Do it all in application logic?

      ☒☒☒
Entity-Attribute-Value

                 Solution


     Use metadata for metadata
•   Define attributes in columns

•   AL...
Entity-Attribute-Value

     Single Table Inheritance
• One table with many columns
• Inapplicable columns are NULL
  CREA...
Entity-Attribute-Value

       Concrete Table Inheritance
     • Define similar tables for similar types
     • Duplicate c...
Entity-Attribute-Value

 Concrete Table Inheritance
• Use UNION to search both tables:
    SELECT u.* FROM (
    
 SELECT ...
Entity-Attribute-Value

          Class Table Inheritance
   • Common columns in base table
   • Subtype-specific columns i...
Entity-Attribute-Value

     Class Table Inheritance
• Easy to query common columns:
    SELECT * FROM Issues
    WHERE de...
Entity-Attribute-Value

    Using EAV appropriately

• If attributes must be fully dynamic
• Enforce constraints in applic...
Polymorphic Associations
    Of course, some people do go both ways.
                        — The Scarecrow
Polymorphic Assocations
• Objective: reference multiple parents
                                  BUGS


  COMMENTS


    ...
Polymorphic Assocations

           What does this look like?


             comment                                      ...
Polymorphic Assocations

         Problem: constraints
• A FOREIGN KEY constraint
  can’t reference two tables:
  CREATE T...
Polymorphic Assocations

         Problem: constraints
• You have to define table with
  no referential integrity:
  CREATE...
Polymorphic Assocations

             Problem: queries
•   You can’t use a different table for each row.

    SELECT * FRO...
Polymorphic Assocations

              Problem: queries
•   You have to join to each parent table:
    SELECT *
    FROM C...
Polymorphic Assocations

               Solutions


• Exclusive arcs
• Reverse the relationship
• Base parent table
Polymorphic Assocations

            Exclusive Arcs

CREATE TABLE Comments (

 comment_id
 SERIAL PRIMARY KEY,

 comment
 ...
Polymorphic Assocations

             Exclusive Arcs
•   Referential integrity is enforced
•   But hard to ensure exactly ...
Polymorphic Assocations

   Reverse the relationship

             BUGS
           COMMENTS                   BUGS



COMM...
Polymorphic Assocations

   Reverse the relationship
CREATE TABLE BugsComments (

 comment_id
 INT NOT NULL,             n...
Polymorphic Assocations

     Reverse the relationship
• Referential integrity is enforced
• Query comments for a given bu...
Polymorphic Assocations

       Base parent table


BUGS            ISSUES              FEATURES




            COMMENTS
Polymorphic Assocations

                    Base parent table                         works great with
        
   
   CR...
Polymorphic Assocations

           Base parent table
•   Referential integrity is enforced
•   Queries are easier:
     S...
Polymorphic Assocations

 Enforcing disjoint subtypes
CREATE TABLE Issues (

 issue_id
 SERIAL PRIMARY KEY,
          

 i...
Naive Trees
A tree is a tree – how many more do you need to look at?
                                     — Ronald Reagan
Naive Trees


• Objective:        store & query hierarchical data
  •   Categories/subcategories

  •   Bill of materials
...
Naive Trees

What does this look like?
                              (1) Fran:
                           What’s the cause...
Naive Trees

          Adjacency List design
• Naive solution nearly everyone uses
• Each entry knows its immediate parent...
Naive Trees

   Adjacency List strengths

• Easy to inserting a new comment:
   INSERT INTO Comments (parent_id, author, c...
Naive Trees

     Adjacency List strengths

• Querying a node’s 
children is easy:
     SELECT * FROM Comments c1
     LEF...
Naive Trees

     Adjacency List problems
• Hard to query all descendants in a deep tree:
  SELECT * FROM Comments c1
  LE...
Naive Trees

      SQL-99 recursive syntax
WITH RECURSIVE CommentTree

 (comment_id, bug_id, parent_id, author, comment, d...
Naive Trees

          Path Enumeration
• Store chain of ancestors in each node
                                          ...
Naive Trees

  Path Enumeration strengths

• Easy to query ancestors of comment #7:
    SELECT * FROM Comments
    WHERE ‘...
Naive Trees

  Path Enumeration strengths

• Easy to add child of comment 7:
     INSERT INTO Comments (author, comment)
 ...
Naive Trees

                Nested Sets
• Each comment encodes its descendants
  using two numbers:
  •   A comment’s lef...
Naive Trees

Nested Sets illustration

                                        (1) Fran:
                                 ...
Naive Trees

             Nested Sets example

comment_id    nsleft   nsright     author             comment
    1        ...
Naive Trees

       Nested Sets strengths


• Easy to query all ancestors of comment #7:
     SELECT * FROM Comments child...
Naive Trees

Nested Sets strengths

                                       (1) Fran:
                                     ...
Naive Trees

      Nested Sets strengths


• Easy to query descendants of comment #4:
    SELECT * FROM Comments parent
  ...
Naive Trees

Nested Sets strengths

                                       (1) Fran:                                    pa...
Naive Trees

       Nested Sets problems
• Hard to insert a new child of comment #5:
     UPDATE Comments
     SET
nsleft ...
Naive Trees

Nested Sets problems

                                       (1) Fran:
                                    Wh...
Naive Trees

         Nested Sets problems
• Hard to query the parent of comment #6:
  SELECT parent.* FROM Comments AS c
...
Naive Trees

             Closure Tables
• Store every path from ancestors to
  descendants
• Requires an additional table...
Naive Trees

Closure Tables illustration

                                     (1) Fran:
                                 ...
Naive Trees

     Closure Tables example
                                                      ancestor descendant
       ...
Naive Trees

    Closure Tables strengths


• Easy to query descendants of comment #4:
    SELECT c.* FROM Comments c
    ...
Naive Trees

     Closure Tables strengths


• Easy to query ancestors of comment #6:
    SELECT c.* FROM Comments c
    J...
Naive Trees

     Closure Tables strengths

• Easy to insert a new child of comment #5:
     INSERT INTO Comments
     
 V...
Naive Trees

     Closure Tables strengths



• Easy to delete a child comment #7:
     DELETE FROM TreePaths
     WHERE d...
Naive Trees

     Closure Tables strengths
• Easy to delete subtree under comment #4:
    DELETE FROM TreePaths
    WHERE ...
Naive Trees

       Closure Tables depth
                                      ancestor descendant   depth
               ...
Naive Trees

                 Choose the right design

                        Query    Query Delete     Add   Move Refere...
Magic Beans
Essentially, all models are wrong, but some are useful.
                                  — George E. P. Box
Magic Beans



• Objective:
  •   Model-View-Controller application development

  •   Object-Relational Mapping (ORM)
Magic Beans

      Active Record

The Golden Hammer of database access
     in most MVC frameworks:
Magic Beans

Model is-a Active Record?
                 Active
                 Record
                                   ...
Magic Beans

        Problem: OO design

• “Model” : Active Record          inheritance
                                  ...
Magic Beans

        Problem: MVC design

• Controllers need to know              “T.M.I.” !!
    database structure

•   ...
Magic Beans

         Problem: testability

                                               tests are
• Harder to test Mode...
OO is about decoupling
Magic Beans

Model has-a Active Record(s)
                 Active
                 Record
                                ...
Magic Beans

       Model characteristics

• Extend no base class
• Abstract the database
• Expose only domain-specific int...
Magic Beans

              Model benefits

• Models are decoupled from DB
  •   Supports mock objects

  •   Supports depen...
Magic Beans

Decouple your Models
     You can use this design




 ...even in MVC frameworks that
     encourage the anti...
Copyright 2008-2009 Bill Karwin
        www.slideshare.net/billkarwin
              Released under a Creative Commons 3.0 ...
Upcoming SlideShare
Loading in...5
×

Practical Object Oriented Models In Sql

27,385

Published on

Presentation given at OSCON 2009 and PostgreSQL West 09. Describes SQL solutions to a selection of object-oriented problems:

- Extensibility
- Polymorphism
- Hierarchies
- Using ORM in MVC application architecture

These slides are excerpted from another presentation, "SQL Antipatterns Strike Back."

Published in: Technology
3 Comments
57 Likes
Statistics
Notes
  • I know this is now an old post but it is still very relevant. I was particularly interested in the topic of hierarchical data. Very helpful but I believe one common requirement was not discussed - querying the whole set of data to return the full hierarchy. It seems to me that the Path Enumeration approach would be best for that because a simple query or one table ordered by the path would give the required result.
    I am also wondering, as it would be a small overhead, whether combining the Path Enumeration and Adjacent List approaches might provide the best solution, with each resolving the the omission in the other.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Excellent treatment of solutions to Polymorphic associations (one column pointing to FKs in multiple tables)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • thank you
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
27,385
On Slideshare
0
From Embeds
0
Number of Embeds
11
Actions
Shares
0
Downloads
1,025
Comments
3
Likes
57
Embeds 0
No embeds

No notes for slide

Transcript of "Practical Object Oriented Models In Sql"

  1. 1. Practical object-oriented models in SQL Bill Karwin PostgreSQL Conference West 09 • 2009/10/17
  2. 2. Me • 20+ years experience • Application/SDK developer • Support, Training, Proj Mgmt • C, Java, Perl, PHP • SQL maven • MySQL, PostgreSQL, InterBase • Zend Framework • Oracle, SQL Server, IBM DB2, SQLite • Community contributor
  3. 3. Object-Oriented vs. Relational • Impedance mismatch • OO operates on instances; RDBMS operates on sets • Compromise either OO strengths or relational strengths
  4. 4. Example database
  5. 5. Entity-Attribute-Value If you try and take a cat apart to see how it works, the first thing you have on your hands is a non-working cat. — Richard Dawkins
  6. 6. Entity-Attribute-Value • Objective: extensibility • Variable sets of attributes bug_id bug_type priority description severity sponsor crashes when loss of 1234 BUG high saving functionality 3456 FEATURE low support XML Acme Corp.
  7. 7. Entity-Attribute-Value Meta-Table CREATE TABLE eav ( bug_id INT NOT NULL, attr_name VARCHAR(20) NOT NULL, attr_value VARCHAR(100), PRIMARY KEY (bug_id, attr_name), FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id) ); mixing data with metadata
  8. 8. Entity-Attribute-Value What does this look like? bug_id attr_name attr_value 1234 priority high 1234 description crashes when saving 1234 severity loss of functionality 3456 priority low 3456 description support XML 3456 sponsor Acme Corp.
  9. 9. Entity-Attribute-Value Problems: names bug_id attr_name attr_value 1234 created 2008-04-01 3456 created_date 2008-04-01
  10. 10. Entity-Attribute-Value Problems: values bug_id attr_name attr_value 1234 created_date 2008-02-31 3456 created_date banana
  11. 11. Entity-Attribute-Value Problems: constraints NOT NULL • Difficult to enforce mandatory attributes • SQL constraints apply to columns, not rows • No way to declare that a row must exist with a certain attr_name value (‘created_date’)
  12. 12. Entity-Attribute-Value Problems: constraints • Difficult to use lookup tables bug_id attr_name attr_value 1234 priority high 3456 priority medium 5678 priority banana • Constraints apply to all rows in the column, not selected rows depending on value in attr_name
  13. 13. Entity-Attribute-Value Problems: queries • Difficult to reconstruct a row of attributes: SELECT b.bug_id, need one JOIN e1.attr_value AS created_date, e2.attr_value AS priority, per attribute e3.attr_value AS description, e4.attr_value AS status, e5.attr_value AS reported_by FROM Bugs b LEFT JOIN eav e1 ON (b.bug_id = e1.bug_id AND e1.attr_name = ‘created_date’) LEFT JOIN eav e2 ON (b.bug_id = e2.bug_id AND e2.attr_name = ‘priority’) LEFT JOIN eav e3 ON (b.bug_id = e3.bug_id AND e3.attr_name = ‘description’) LEFT JOIN eav e4 ON (b.bug_id = e4.bug_id AND e4.attr_name = ‘status’) LEFT JOIN eav e5 ON (b.bug_id = e5.bug_id AND e5.attr_name = ‘reported_by’); bug_id created_date priority description status reported_by 1234 2008-04-01 high Crashes when I save. NEW Bill
  14. 14. Entity-Attribute-Value Solution? Do it all in application logic? ☒☒☒
  15. 15. Entity-Attribute-Value Solution Use metadata for metadata • Define attributes in columns • ALTER TABLE to add attribute columns • Define related tables for related types ☑
  16. 16. Entity-Attribute-Value Single Table Inheritance • One table with many columns • Inapplicable columns are NULL CREATE TABLE Issues ( issue_id SERIAL PRIMARY KEY, created_date DATE NOT NULL, priority VARCHAR(20), description TEXT, issue_type CHAR(1) CHECK (issue_type IN (‘B’, ‘F’)), bug_severity VARCHAR(20), feature_sponsor VARCHAR(100) );
  17. 17. Entity-Attribute-Value Concrete Table Inheritance • Define similar tables for similar types • Duplicate common columns in each table CREATE TABLE Bugs ( CREATE TABLE Features ( bug_id SERIAL PRIMARY KEY, bug_id SERIAL PRIMARY KEY, created_date DATE NOT NULL, created_date DATE NOT NULL, priority VARCHAR(20), priority VARCHAR(20), description TEXT, description TEXT, severity VARCHAR(20) sponsor VARCHAR(100) ); );
  18. 18. Entity-Attribute-Value Concrete Table Inheritance • Use UNION to search both tables: SELECT u.* FROM ( SELECT issue_id, description FROM Bugs UNION ALL SELECT issue_id, description FROM Features )u WHERE u.description LIKE ...
  19. 19. Entity-Attribute-Value Class Table Inheritance • Common columns in base table • Subtype-specific columns in subtype tables CREATE TABLE Bugs ( CREATE TABLE Features ( issue_id INT PRIMARY KEY, issue_id INT PRIMARY KEY, severity VARCHAR(20), sponsor VARCHAR(100), FOREIGN KEY (issue_id) FOREIGN KEY (issue_id) REFERENCES Issues (issue_id) REFERENCES Issues (issue_id) ); ); CREATE TABLE Issues ( issue_id SERIAL PRIMARY KEY, created_date DATE NOT NULL priority VARCHAR(20), description TEXT );
  20. 20. Entity-Attribute-Value Class Table Inheritance • Easy to query common columns: SELECT * FROM Issues WHERE description LIKE ... ; • Easy to query one subtype at a time: SELECT * FROM Issues JOIN Bugs USING (issue_id) WHERE description LIKE ... ;
  21. 21. Entity-Attribute-Value Using EAV appropriately • If attributes must be fully dynamic • Enforce constraints in application code • Don’t try to fetch a single row per entity • Consider non-relational solutions for semi-structured data, e.g. RDF/XML
  22. 22. Polymorphic Associations Of course, some people do go both ways. — The Scarecrow
  23. 23. Polymorphic Assocations • Objective: reference multiple parents BUGS COMMENTS FEATURES
  24. 24. Polymorphic Assocations What does this look like? comment issue_id issue_id comment issue_type issue_id id ... ... 6789 “It crashes” Bugs 1234 1234 2345 9876 “Great idea!” Features 2345 Bugs Features Comments mixing data with metadata
  25. 25. Polymorphic Assocations Problem: constraints • A FOREIGN KEY constraint can’t reference two tables: CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, comment TEXT NOT NULL, issue_type VARCHAR(15) NOT NULL CHECK (issue_type IN (‘Bugs’, ‘Features’)), issue_id INT NOT NULL, FOREIGN KEY issue_id REFERENCES ); you need this to be Bugs or Features
  26. 26. Polymorphic Assocations Problem: constraints • You have to define table with no referential integrity: CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, comment TEXT NOT NULL, issue_type VARCHAR(15) NOT NULL CHECK (issue_type IN (‘Bugs’, ‘Features’)), issue_id INT NOT NULL );
  27. 27. Polymorphic Assocations Problem: queries • You can’t use a different table for each row. SELECT * FROM Comments JOIN USING (issue_id); you need this to be Bugs or Features
  28. 28. Polymorphic Assocations Problem: queries • You have to join to each parent table: SELECT * FROM Comments c LEFT JOIN Bugs b ON (c.issue_type = ‘Bugs’ AND c.issue_id = b.issue_id) LEFT JOIN Features f ON (c.issue_type = ‘Features’ AND c.issue_id = f.issue_id); you have to get these strings right
  29. 29. Polymorphic Assocations Solutions • Exclusive arcs • Reverse the relationship • Base parent table
  30. 30. Polymorphic Assocations Exclusive Arcs CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, comment TEXT NOT NULL, bug_id INT NULL, both columns nullable; feature_id INT NULL, exactly one must be non-null FOREIGN KEY bug_id REFERENCES Bugs(bug_id), FOREIGN KEY feature_id REFERENCES Features(feature_id) );
  31. 31. Polymorphic Assocations Exclusive Arcs • Referential integrity is enforced • But hard to ensure exactly one is non-null • Queries are easier: SELECT * FROM Comments c LEFT JOIN Bugs b USING (bug_id) LEFT JOIN Features f USING (feature_id);
  32. 32. Polymorphic Assocations Reverse the relationship BUGS COMMENTS BUGS COMMENTS FEATURES COMMENTS FEATURES
  33. 33. Polymorphic Assocations Reverse the relationship CREATE TABLE BugsComments ( comment_id INT NOT NULL, not many-to-many bug_id INT NOT NULL, PRIMARY KEY (comment_id), FOREIGN KEY (comment_id) REFERENCES Comments(comment_id), FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id) ); CREATE TABLE FeaturesComments ( comment_id INT NOT NULL, feature_id INT NOT NULL, PRIMARY KEY (comment_id), FOREIGN KEY (comment_id) REFERENCES Comments(comment_id), FOREIGN KEY (feature_id) REFERENCES Features(feature_id) );
  34. 34. Polymorphic Assocations Reverse the relationship • Referential integrity is enforced • Query comments for a given bug: SELECT * FROM BugsComments b JOIN Comments c USING (comment_id) WHERE b.bug_id = 1234; • Query bug/feature for a given comment: SELECT * FROM Comments LEFT JOIN (BugsComments JOIN Bugs USING (bug_id)) USING (comment_id) LEFT JOIN (FeaturesComments JOIN Features USING (feature_id)) USING (comment_id) WHERE comment_id = 9876;
  35. 35. Polymorphic Assocations Base parent table BUGS ISSUES FEATURES COMMENTS
  36. 36. Polymorphic Assocations Base parent table works great with CREATE TABLE Issues ( Class Table issue_id SERIAL PRIMARY KEY Inheritance ); CREATE TABLE Bugs ( CREATE TABLE Features ( issue_id INT PRIMARY KEY, issue_id INT PRIMARY KEY, ... ... FOREIGN KEY (issue_id) FOREIGN KEY (issue_id) REFERENCES Issues(issue_id) REFERENCES Issues(issue_id) ); ); CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, comment TEXT NOT NULL, issue_id INT NOT NULL, FOREIGN KEY (issue_id) REFRENCES Issues(issue_id) );
  37. 37. Polymorphic Assocations Base parent table • Referential integrity is enforced • Queries are easier: SELECT * FROM Comments JOIN Issues USING (issue_id) LEFT JOIN Bugs USING (issue_id) LEFT JOIN Features USING (issue_id);
  38. 38. Polymorphic Assocations Enforcing disjoint subtypes CREATE TABLE Issues ( issue_id SERIAL PRIMARY KEY, issue_type CHAR(1) CHECK (issue_type IN (‘B’, ‘F’)), UNIQUE KEY (issue_id, issue_type) ); referential CREATE TABLE Bugs ( integrity issue_id INT PRIMARY KEY, issue_type CHAR(1) CHECK (issue_type = ‘B’), ... FOREIGN KEY (issue_id, issue_type) REFERENCES Issues(issue_id, issue_type) );
  39. 39. Naive Trees A tree is a tree – how many more do you need to look at? — Ronald Reagan
  40. 40. Naive Trees • Objective: store & query hierarchical data • Categories/subcategories • Bill of materials • Threaded discussions
  41. 41. Naive Trees What does this look like? (1) Fran: What’s the cause of this bug? (2) Ollie: (4) Kukla: I think it’s a null We need to pointer. check valid input. (3) Fran: (6) Fran: (5) Ollie: No, I checked for Yes, please add a Yes, that’s a bug. that. check. (7) Kukla: That fixed it.
  42. 42. Naive Trees Adjacency List design • Naive solution nearly everyone uses • Each entry knows its immediate parent comment_id parent_id author comment 1 NULL Fran What’s the cause of this bug? 2 1 Ollie I think it’s a null pointer. 3 2 Fran No, I checked for that. 4 1 Kukla We need to check valid input. 5 4 Ollie Yes, that’s a bug. 6 4 Fran Yes, please add a check 7 6 Kukla That fixed it.
  43. 43. Naive Trees Adjacency List strengths • Easy to inserting a new comment: INSERT INTO Comments (parent_id, author, comment) VALUES (7, ‘Kukla’, ‘Thanks!’); • Easy to move a subtree to a new position: UPDATE Comments SET parent_id = 3 WHERE comment_id = 6;
  44. 44. Naive Trees Adjacency List strengths • Querying a node’s children is easy: SELECT * FROM Comments c1 LEFT JOIN Comments c2 ON (c2.parent_id = c1.comment_id); • Querying a node’s parent is easy: SELECT * FROM Comments c1 JOIN Comments c2 ON (c1.parent_id = c2.comment_id);
  45. 45. Naive Trees Adjacency List problems • Hard to query all descendants in a deep tree: SELECT * FROM Comments c1 LEFT JOIN Comments c2 ON (c2.parent_id = c1.comment_id) LEFT JOIN Comments c3 ON (c3.parent_id = c2.comment_id) LEFT JOIN Comments c4 ON (c4.parent_id = c3.comment_id) LEFT JOIN Comments c5 ON (c5.parent_id = c4.comment_id) LEFT JOIN Comments c6 ON (c6.parent_id = c5.comment_id) LEFT JOIN Comments c7 ON (c7.parent_id = c6.comment_id) LEFT JOIN Comments c8 ON (c8.parent_id = c7.comment_id) LEFT JOIN Comments c9 ON (c9.parent_id = c8.comment_id) LEFT JOIN Comments c10 ON (c10.parent_id = c9.comment_id) ... it still doesn’t support unlimited depth!
  46. 46. Naive Trees SQL-99 recursive syntax WITH RECURSIVE CommentTree (comment_id, bug_id, parent_id, author, comment, depth) AS ( SELECT *, 0 AS depth FROM Comments WHERE parent_id IS NULL UNION ALL SELECT c.*, ct.depth+1 AS depth FROM CommentTree ct JOIN Comments c ON (ct.comment_id = c.parent_id) ) SELECT * FROM CommentTree WHERE bug_id = 1234; supported in PostgreSQL 8.4 & MS SQL Server 2005
  47. 47. Naive Trees Path Enumeration • Store chain of ancestors in each node good for breadcrumbs comment_id path author comment 1 1/ Fran What’s the cause of this bug? 2 1/2/ Ollie I think it’s a null pointer. 3 1/2/3/ Fran No, I checked for that. 4 1/4/ Kukla We need to check valid input. 5 1/4/5/ Ollie Yes, that’s a bug. 6 1/4/6/ Fran Yes, please add a check 7 1/4/6/7/ Kukla That fixed it.
  48. 48. Naive Trees Path Enumeration strengths • Easy to query ancestors of comment #7: SELECT * FROM Comments WHERE ‘1/4/6/7/’ LIKE path || ‘%’; • Easy to query descendants of comment #4: SELECT * FROM Comments WHERE path LIKE ‘1/4/%’;
  49. 49. Naive Trees Path Enumeration strengths • Easy to add child of comment 7: INSERT INTO Comments (author, comment) VALUES (‘Ollie’, ‘Good job!’); SELECT path FROM Comments WHERE comment_id = 7; UPDATE Comments SET path = $parent_path || LASTVAL() || ‘/’ WHERE comment_id = LASTVAL();
  50. 50. Naive Trees Nested Sets • Each comment encodes its descendants using two numbers: • A comment’s left number is less than all numbers used by the comment’s descendants. • A comment’s right number is greater than all numbers used by the comment’s descendants. • A comment’s numbers are between all numbers used by the comment’s ancestors.
  51. 51. Naive Trees Nested Sets illustration (1) Fran: What’s the cause of this bug? 1 14 (2) Ollie: (4) Kukla: I think it’s a null We need to check pointer. valid input. 2 5 6 13 (3) Fran: (6) Fran: (5) Ollie: No, I checked for Yes, please add a Yes, that’s a bug. that. check. 3 4 7 8 9 12 (7) Kukla: That fixed it. 10 11
  52. 52. Naive Trees Nested Sets example comment_id nsleft nsright author comment 1 1 14 Fran What’s the cause of this bug? 2 2 5 Ollie I think it’s a null pointer. 3 3 4 Fran No, I checked for that. 4 6 13 Kukla We need to check valid input. 5 7 8 Ollie Yes, that’s a bug. 6 9 12 Fran Yes, please add a check 7 10 11 Kukla That fixed it. these are not foreign keys
  53. 53. Naive Trees Nested Sets strengths • Easy to query all ancestors of comment #7: SELECT * FROM Comments child JOIN Comments ancestor ON (child.nsleft BETWEEN ancestor.nsleft AND ancestor.nsright) WHERE child.comment_id = 7;
  54. 54. Naive Trees Nested Sets strengths (1) Fran: ancestors What’s the cause of this bug? 1 14 (2) Ollie: (4) Kukla: I think it’s a null We need to check pointer. valid input. 2 5 6 13 child (3) Fran: (6) Fran: (5) Ollie: No, I checked for Yes, please add a Yes, that’s a bug. that. check. 3 4 7 8 9 12 (7) Kukla: That fixed it. 10 11
  55. 55. Naive Trees Nested Sets strengths • Easy to query descendants of comment #4: SELECT * FROM Comments parent JOIN Comments descendant ON (descendant.nsleft BETWEEN parent.nsleft AND parent.nsright) WHERE parent.comment_id = 4;
  56. 56. Naive Trees Nested Sets strengths (1) Fran: parent What’s the cause of this bug? 1 14 (2) Ollie: (4) Kukla: descendants I think it’s a null We need to check pointer. valid input. 2 5 6 13 (3) Fran: (6) Fran: (5) Ollie: No, I checked for Yes, please add a Yes, that’s a bug. that. check. 3 4 7 8 9 12 (7) Kukla: That fixed it. 10 11
  57. 57. Naive Trees Nested Sets problems • Hard to insert a new child of comment #5: UPDATE Comments SET nsleft = CASE WHEN nsleft >= 8 THEN nsleft+2 ELSE nsleft END, nsright = nsright+2 WHERE nsright >= 7; INSERT INTO Comments (nsleft, nsright, author, comment) VALUES (8, 9, 'Fran', 'I agree!'); • Recalculate left values for all nodes to the right of the new child. Recalculate right values for all nodes above and to the right.
  58. 58. Naive Trees Nested Sets problems (1) Fran: What’s the cause of this bug? 1 16 14 (2) Ollie: (4) Kukla: I think it’s a null We need to check pointer. valid input. 2 5 6 15 13 (3) Fran: (6) Fran: (5) Ollie: No, I checked for Yes, please add a Yes, that’s a bug. that. check. 3 4 7 8 9 10 11 14 12 (8) Fran: (7) Kukla: I agree! That fixed it. 8 9 10 12 13 11
  59. 59. Naive Trees Nested Sets problems • Hard to query the parent of comment #6: SELECT parent.* FROM Comments AS c JOIN Comments AS parent ON (c.nsleft BETWEEN parent.nsleft AND parent.nsright) LEFT OUTER JOIN Comments AS in_between ON (c.nsleft BETWEEN in_between.nsleft AND in_between.nsright AND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright) WHERE c.comment_id = 6 AND in_between.comment_id IS NULL; • Parent of #6 is an ancestor who has no descendant who is also an ancestor of #6. • Querying a child is a similar problem.
  60. 60. Naive Trees Closure Tables • Store every path from ancestors to descendants • Requires an additional table: CREATE TABLE TreePaths ( ancestor INT NOT NULL, descendant INT NOT NULL, PRIMARY KEY (ancestor, descendant), FOREIGN KEY(ancestor) REFERENCES Comments(comment_id), FOREIGN KEY(descendant) REFERENCES Comments(comment_id), );
  61. 61. Naive Trees Closure Tables illustration (1) Fran: What’s the cause of this bug? (2) Ollie: (4) Kukla: I think it’s a null We need to check pointer. valid input. (3) Fran: (6) Fran: (5) Ollie: No, I checked for Yes, please add a Yes, that’s a bug. that. check. (7) Kukla: That fixed it.
  62. 62. Naive Trees Closure Tables example ancestor descendant 1 1 1 2 comment_id author comment 1 3 1 Fran What’s the cause of this bug? 1 4 2 Ollie I think it’s a null pointer. 1 5 3 Fran No, I checked for that. 1 6 4 Kukla We need to check valid input. 1 7 5 Ollie Yes, that’s a bug. 2 2 6 Fran Yes, please add a check 2 3 7 Kukla That fixed it. 3 3 4 4 4 5 requires O(n²) rows 4 6 (but far fewer in practice) 4 5 7 5 6 6 6 7 7 7
  63. 63. Naive Trees Closure Tables strengths • Easy to query descendants of comment #4: SELECT c.* FROM Comments c JOIN TreePaths t ON (c.comment_id = t.descendant) WHERE t.ancestor = 4;
  64. 64. Naive Trees Closure Tables strengths • Easy to query ancestors of comment #6: SELECT c.* FROM Comments c JOIN TreePaths t ON (c.comment_id = t.ancestor) WHERE t.descendant = 6;
  65. 65. Naive Trees Closure Tables strengths • Easy to insert a new child of comment #5: INSERT INTO Comments VALUES (8, ‘Fran’, ‘I agree!’); INSERT INTO TreePaths (ancestor, descendant) SELECT ancestor, 8 FROM TreePaths WHERE descendant = 5 UNION ALL SELECT 8, 8;
  66. 66. Naive Trees Closure Tables strengths • Easy to delete a child comment #7: DELETE FROM TreePaths WHERE descendant = 7;
  67. 67. Naive Trees Closure Tables strengths • Easy to delete subtree under comment #4: DELETE FROM TreePaths WHERE descendant IN (SELECT descendant FROM TreePaths WHERE ancestor = 4); • PostgreSQL multi-table DELETE syntax: DELETE FROM TreePaths p USING TreePaths a WHERE p.descendant = a.descendant AND a.ancestor = 4;
  68. 68. Naive Trees Closure Tables depth ancestor descendant depth 1 1 0 • Add a depth column to make it 1 2 1 1 3 2 easier to query immediate 1 1 4 5 1 2 parent or child: 1 6 2 1 7 3 SELECT c.* 2 2 0 2 3 1 FROM Comments c JOIN TreePaths t 3 3 0 ON (c.comment_id = t.descendant) 4 4 0 WHERE t.ancestor = 4 4 5 1 4 6 1 AND t.depth = 1; 4 7 2 5 5 0 6 6 0 6 7 1 7 7 0
  69. 69. Naive Trees Choose the right design Query Query Delete Add Move Referential Design Tables Child Subtree Node Node Subtree Integrity Adjacency List 1 Easy Hard Easy Easy Easy Yes Path 1 Easy Easy Easy Easy Easy No Enumeration Nested Sets 1 Hard Easy Hard Hard Hard No Closure Table 2 Easy Easy Easy Easy Hard Yes
  70. 70. Magic Beans Essentially, all models are wrong, but some are useful. — George E. P. Box
  71. 71. Magic Beans • Objective: • Model-View-Controller application development • Object-Relational Mapping (ORM)
  72. 72. Magic Beans Active Record The Golden Hammer of database access in most MVC frameworks:
  73. 73. Magic Beans Model is-a Active Record? Active Record inheritance (IS-A) Products Bugs Comments Controller View aggregation (HAS-A)
  74. 74. Magic Beans Problem: OO design • “Model” : Active Record inheritance (IS-A) • Model tied to database schema coupling • Product → Bug, or unclear assignment of responsibilities Bug → Product? • Models expose Active Record interface, not domain-specific interface poor encapsulation
  75. 75. Magic Beans Problem: MVC design • Controllers need to know “T.M.I.” !! database structure • Database changes require not “DRY” code changes • http://www.martinfowler.com/bliki/ (appeal to AnemicDomainModel.html authority)
  76. 76. Magic Beans Problem: testability tests are • Harder to test Model without database slow • Need database “fixtures” tests are • Fat Controller makes it harder even slower to test business logic mocking HTTP Request, scraping HTML output
  77. 77. OO is about decoupling
  78. 78. Magic Beans Model has-a Active Record(s) Active Record inheritance (IS-A) Products Bugs Comments composition (HAS-A) BugReport Controller View (Model) aggregation (HAS-A)
  79. 79. Magic Beans Model characteristics • Extend no base class • Abstract the database • Expose only domain-specific interface • Encapsulate complex business logic
  80. 80. Magic Beans Model benefits • Models are decoupled from DB • Supports mock objects • Supports dependency injection • Unit-testing Models in isolation is faster • Unit-testing Thin Controllers is easier
  81. 81. Magic Beans Decouple your Models You can use this design ...even in MVC frameworks that encourage the antipattern.
  82. 82. Copyright 2008-2009 Bill Karwin www.slideshare.net/billkarwin Released under a Creative Commons 3.0 License: http://creativecommons.org/licenses/by-nc-nd/3.0/ You are free to share - to copy, distribute and transmit this work, under the following conditions: Attribution. Noncommercial. No Derivative Works. You must attribute this You may not use this work You may not alter, work to Bill Karwin. for commercial purposes. transform, or build upon this work.
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×