| Version 1 (modified by , 7 days ago) ( diff ) |
|---|
Advanced Database Development (SQL DDL)
Custom Domains
-- Check email format
CREATE DOMAIN valid_email AS VARCHAR(255)
CHECK (VALUE ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
-- Check rating range from 0 to 5
CREATE DOMAIN story_rating AS DECIMAL(3,2)
CHECK (VALUE >= 0.00 AND VALUE <= 5.00);
-- Status of accepted values
CREATE DOMAIN story_status AS VARCHAR(50)
CHECK (VALUE IN ('draft', 'published', 'archived'));
-- Permission level from 1 to 5
CREATE DOMAIN permission_lvl AS INTEGER
CHECK (VALUE >= 1 AND VALUE <= 5);
-- URL / image path
CREATE DOMAIN valid_url AS VARCHAR(2048)
CHECK (VALUE ~* '^https?://.+');
Views
-- Detailed view for each story with statistic
CREATE OR REPLACE VIEW story_details_view AS
SELECT
s.story_id,
s.short_description,
s.mature_content,
s.image,
s.story_created_at,
u.username AS writer,
u.user_name,
u.surname,
st.status,
COUNT(DISTINCT ch.chapter_id) AS total_chapters,
COALESCE(SUM(ch.view_count), 0) AS total_views,
ROUND(AVG(ch.rating), 2) AS avg_rating,
COUNT(DISTINCT l.user_id) AS total_likes,
COUNT(DISTINCT c.comment_id) AS total_comments,
COUNT(DISTINCT hg.genre_id) AS total_genres,
COUNT(DISTINCT col.user_id) AS total_collaborators
FROM story s
JOIN writer w ON s.user_id = w.user_id
JOIN users u ON w.user_id = u.user_id
JOIN "status" st ON s.story_id = st.story_id
LEFT JOIN chapter ch ON s.story_id = ch.story_id
LEFT JOIN likes l ON s.story_id = l.story_id
LEFT JOIN "comment" c ON s.story_id = c.story_id
LEFT JOIN has_genre hg ON s.story_id = hg.story_id
LEFT JOIN collaboration col ON s.story_id = col.story_id
GROUP BY
s.story_id, s.short_description, s.mature_content,
s.image, s.story_created_at,
u.username, u.user_name, u.surname, st.status;
-- Top 10 most popular stories by likes count =
CREATE MATERIALIZED VIEW top_stories_by_likes AS
SELECT
s.story_id,
s.short_description,
u.username AS writer,
st.status,
COUNT(DISTINCT l.user_id) AS total_likes,
COUNT(DISTINCT c.comment_id) AS total_comments,
ROUND(AVG(ch.rating), 2) AS avg_rating
FROM story s
JOIN writer w ON s.user_id = w.user_id
JOIN users u ON w.user_id = u.user_id
JOIN "status" st ON s.story_id = st.story_id
AND st.status = 'published'
LEFT JOIN likes l ON s.story_id = l.story_id
LEFT JOIN "comment" c ON s.story_id = c.story_id
LEFT JOIN chapter ch ON s.story_id = ch.story_id
GROUP BY
s.story_id, s.short_description,
u.username, st.status
ORDER BY total_likes DESC
LIMIT 10;
-- View for user profile with all his stories
CREATE OR REPLACE VIEW writer_profile_view AS
SELECT
u.user_id,
u.username,
u.user_name,
u.surname,
u.user_created_at,
COUNT(DISTINCT s.story_id)AS total_stories,
COUNT(DISTINCT ch.chapter_id) AS total_chapters,
COALESCE(SUM(ch.view_count), 0) AS total_views,
COUNT(DISTINCT l.user_id) AS total_likes,
COUNT(DISTINCT c.comment_id) AS total_comments,
ROUND(AVG(ch.rating), 2) AS avg_rating
FROM users u
JOIN writer w ON u.user_id = w.user_id
LEFT JOIN story s ON w.user_id = s.user_id
LEFT JOIN chapter ch ON s.story_id = ch.story_id
LEFT JOIN likes l ON s.story_id = l.story_id
LEFT JOIN comment c ON s.story_id = c.story_id
GROUP BY
u.user_id, u.username, u.user_name,
u.surname, u.user_created_at;
Stored Procedures / Functions
-- Table for daily top story CREATE TABLE daily_top_stories (
day DATE PRIMARY KEY, story_id INTEGER NULL REFERENCES story(story_id), total_likes BIGINT
);
-- Procedure: saves the most liked story of the previous day CREATE OR REPLACE PROCEDURE get_daily_top_story() LANGUAGE plpgsql AS $$ DECLARE
yesterday TIMESTAMP;
BEGIN
yesterday := CURRENT_DATE - 1;
IF EXISTS (
SELECT 1 FROM likes WHERE like_created_at BETWEEN yesterday AND CURRENT_DATE
) THEN
INSERT INTO daily_top_stories (day, story_id, total_likes) SELECT
CURRENT_DATE - 1, s.story_id, COUNT(l.user_id) AS total_likes
FROM story s JOIN likes l ON s.story_id = l.story_id WHERE l.like_created_at BETWEEN yesterday AND CURRENT_DATE GROUP BY s.story_id ORDER BY total_likes DESC LIMIT 1;
ELSE
INSERT INTO daily_top_stories (day, story_id, total_likes) VALUES (CURRENT_DATE - 1, NULL, 0);
END IF;
END; $$;
-- Procedure: sends a notification to all collaborators of a story CREATE OR REPLACE PROCEDURE notify_collaborators(
p_story_id INTEGER, p_message TEXT, p_type VARCHAR(50), p_link VARCHAR(500)
) LANGUAGE plpgsql AS $$ DECLARE
collab RECORD;
BEGIN
FOR collab IN
SELECT c.user_id FROM collaboration c WHERE c.story_id = p_story_id
LOOP
INSERT INTO notification (
notification_content, is_read, recipient_user_id, type, link, notification_created_at
) VALUES (
p_message, FALSE, collab.user_id, p_type, p_link, CURRENT_TIMESTAMP
);
END LOOP;
END; $$;
-- Function: calculates the engagement score for a given story CREATE OR REPLACE FUNCTION calculate_engagement_score(p_story_id INTEGER) RETURNS DECIMAL LANGUAGE sql AS $$
SELECT ROUND(
(COUNT(DISTINCT l.user_id) + COUNT(DISTINCT c.comment_id))::DECIMAL / NULLIF(COALESCE(SUM(ch.view_count), 0), 0) * 100, 2
) FROM story s LEFT JOIN likes l ON s.story_id = l.story_id LEFT JOIN "comment" c ON s.story_id = c.story_id LEFT JOIN chapter ch ON s.story_id = ch.story_id WHERE s.story_id = p_story_id;
$$;
