human: I am designing a database using Postgres. For my use case, I need a modular data model. Specifically, I need to store Entities of different types. All Entities have a set of common fields, and then they have additional properties depending on their type. For instance, suppose that Entities represent Events. All Events have a title and a description. But if an Event is of type Comment, it will also have additional fields like topic and number of likes.
My main requirements are as follows:
- I need to enforce NOT NULL constraints for the type-specific columns
- The data model should be easily extendable. Adding a new type of Entity should be easy, and require no downtime for the database
- In my application, I will have a view showing all Entities, sorted by date. So I need to be able to query all Entities, independently of their type, in a fast and efficient way

Can you explain to me a design pattern that can be used to achieve these requirements? Please describe it with a concrete example.

assistant: One possible design pattern that can be used to achieve these requirements is the Entity-Attribute-Value (EAV) pattern. In the EAV pattern, the common fields are stored in one table (the "entity" table), and the type-specific fields are stored in another table (the "attribute" table), along with their values. This allows for a flexible and extensible data model.

Here is an example of how you could use the EAV pattern to store Events of different types in a Postgres database:
1. First, create a table to store the common fields of all Events:
``` 
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    description TEXT NOT NULL,
    date TIMESTAMP NOT NULL
);
``` 

2. Next, create a table to store the type-specific fields of Events:
``` 
CREATE TABLE event_attributes (
    id SERIAL PRIMARY KEY,
    event_id INTEGER NOT NULL,
    attribute_name TEXT NOT NULL,
    attribute_value TEXT NOT NULL,
    CONSTRAINT event_attributes_event_id_fkey FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE
);
``` 
In this table, each row represents a specific attribute of a specific Event. The "attribute_name" column contains the name of the attribute (e.g., "topic" or "number of likes"), and the "attribute_value" column contains the value of the attribute.

3. To add a new type of Event, you would simply add new rows to the "event_attributes" table with the appropriate "attribute_name" and "attribute_value" values. For example, to add a Comment Event with a "topic" attribute and a "number of likes" attribute, you could do the following:
``` 
-- Add a new Comment Event
INSERT INTO events (title, description, date) VALUES ('My Comment Event', 'This is my comment', NOW());
-- Add the "topic" attribute
INSERT INTO event_attributes (event_id, attribute_name, attribute_value) VALUES (1, 'topic', 'My Topic');
-- Add the "number of likes" attribute
INSERT INTO event_attributes (event_id, attribute_name, attribute_value) VALUES (1, 'number of likes', '10');
``` 

4. To query all Events, regardless of their type, you could use a JOIN query:
``` 
SELECT events.*, event_attributes.attribute_name, event_attributes.attribute_value
FROM events
LEFT JOIN event_attributes ON events.id = event_attributes.event_id
ORDER BY events.date DESC;
``` 

This query will return all Events, along with their attributes, sorted by date.

Using this EAV pattern, you can enforce NOT NULL constraints on the type-specific columns by simply adding the constraints to the "event_attributes" table. And because the data model is modular and extensible, you can easily add new types of Entities without downtime for the database.