-- DuckDB SQL Schema Definition for Eurovision 25 Homereview Backend -- Generated from schema documentation. -- Target Database: DuckDB -- Incorporates Sequences for Auto-Incrementing Primary Keys. -- Note: Timestamps default to creation time; application logic should handle 'updated_at' updates. -- Drop objects in reverse order of dependency and existence check DROP TABLE IF EXISTS Review; DROP TABLE IF EXISTS Song; DROP TABLE IF EXISTS "User"; -- Quoted because USER is a reserved keyword DROP TABLE IF EXISTS Team; DROP TABLE IF EXISTS Contest; DROP TABLE IF EXISTS CountryCodes; DROP VIEW IF EXISTS ReviewSummary; DROP VIEW IF EXISTS ReviewSummaryByTeam; DROP SEQUENCE IF EXISTS group_id_seq; DROP SEQUENCE IF EXISTS user_id_seq; DROP SEQUENCE IF EXISTS song_id_seq; DROP SEQUENCE IF EXISTS review_id_seq; DROP SEQUENCE IF EXISTS contest_id_seq; -- ============================================================================= -- Sequences for Primary Keys -- ============================================================================= CREATE SEQUENCE group_id_seq START 1; CREATE SEQUENCE user_id_seq START 1; CREATE SEQUENCE song_id_seq START 1; CREATE SEQUENCE review_id_seq START 1; CREATE SEQUENCE contest_id_seq START 1; -- ============================================================================= -- Table: Contest -- Stores information about Eurovision contests by year. -- ============================================================================= CREATE TABLE Contest ( id INTEGER PRIMARY KEY DEFAULT nextval('contest_id_seq'), -- Use sequence for auto-increment year INTEGER DEFAULT EXTRACT(YEAR FROM CURRENT_DATE) NOT NULL, -- The year of the Eurovision contest is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if this is the active contest finals_date DATE NOT NULL, -- Date of the finals created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the contest was created updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the contest was last updated ); -- Add index for faster lookups by year CREATE INDEX idx_contest_year ON Contest (year); -- ============================================================================= -- Table: Group -- Stores information about the different households or groups participating. -- ============================================================================= CREATE TABLE "Team" ( id INTEGER PRIMARY KEY DEFAULT nextval('group_id_seq'), -- Use sequence for auto-increment name VARCHAR UNIQUE NOT NULL, -- The name of the household/group created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the group was created updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the group was last updated ); -- ============================================================================= -- Table: User -- Stores information about individual users (players and admins). -- ============================================================================= CREATE TABLE "User" ( id INTEGER PRIMARY KEY DEFAULT nextval('user_id_seq'), -- Use sequence for auto-increment username VARCHAR UNIQUE NOT NULL, -- The user's login name first_name VARCHAR DEFAULT '', last_name VARCHAR DEFAULT '', hashed_password VARCHAR NOT NULL, -- The securely hashed password email VARCHAR UNIQUE, -- User's email address (nullable) team_id INTEGER NOT NULL, -- Foreign Key -> Group.id avatar_id INTEGER DEFAULT 0 NOT NULL, -- Identifier for the user's avatar is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if the user account is active is_admin BOOLEAN DEFAULT FALSE NOT NULL, -- Flag indicating if the user has admin privileges last_login TIMESTAMP DEFAULT '1970-01-01 00:00:00', -- Timestamp when the user was last logged in. Epoch (1970-01-01) means "never logged in" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was created updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was last updated FOREIGN KEY (team_id) REFERENCES "Team"(id) -- Link to the Team table ); -- ============================================================================= -- Table: Song -- Stores information about each participating song in the contest. -- ============================================================================= CREATE TABLE Song ( id INTEGER PRIMARY KEY DEFAULT nextval('song_id_seq'), -- Use sequence for auto-increment year INTEGER NOT NULL, -- The year of the Eurovision contest country VARCHAR NOT NULL, -- The participating country name artist VARCHAR NOT NULL, -- The name of the performing artist(s) title VARCHAR NOT NULL, -- The title of the song running_order INTEGER NOT NULL, -- Official order in the show (nullable) lyrics_url VARCHAR, -- URL to the original lyrics (nullable) artist_url VARCHAR, -- URL to the artist's page (nullable) flag_url VARCHAR, -- URL to the country flag (nullable) lyrics_original VARCHAR, -- Original lyrics (nullable) lyrics_translation_fi VARCHAR, -- Finnish translation (nullable) tags VARCHAR[], -- Comma-separated tags (nullable) confidence FLOAT NOT NULL DEFAULT 0.0, -- Confidence level of the translation (nullable) language VARCHAR NOT NULL, -- Language of the song (nullable) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the song entry was created updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the song entry was last updated ); -- Add index for faster lookups by year as specified in documentation CREATE INDEX idx_song_year ON Song (year); -- ============================================================================= -- Table: Review -- Stores the scores and comments submitted by a user for a specific song. -- ============================================================================= CREATE TABLE Review ( id INTEGER PRIMARY KEY DEFAULT nextval('review_id_seq'), -- Use sequence for auto-increment user_id INTEGER NOT NULL, -- Foreign Key -> User.id song_id INTEGER NOT NULL, -- Foreign Key -> Song.id score_song INTEGER NOT NULL, -- Score (1-100) for song quality score_show INTEGER NOT NULL, -- Score (1-100) for the stage show score_costume INTEGER NOT NULL, -- Score (1-100) for wardrobe/costumes text_review VARCHAR, -- Optional textual comments (nullable) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was created updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was last modified FOREIGN KEY (user_id) REFERENCES "User"(id), -- Link to the User table FOREIGN KEY (song_id) REFERENCES Song(id), -- Link to the Song table -- Ensure scores are within the valid range (1-100) CHECK (score_song >= 1 AND score_song <= 100), CHECK (score_show >= 1 AND score_show <= 100), CHECK (score_costume >= 1 AND score_costume <= 100), -- Ensure each user can only submit one review per song UNIQUE (user_id, song_id) ); CREATE TABLE CountryCodes ( code VARCHAR PRIMARY KEY, name_en VARCHAR NOT NULL, name_fi VARCHAR NOT NULL, name_sv VARCHAR NOT NULL ); CREATE VIEW ReviewSummaryGlobal AS SELECT song_id, cc.name_fi AS country_fi, cc.name_sv AS country_sv, s.artist, s.title, COUNT(*) AS total_reviews, AVG(score_song) AS avg_score_song, AVG(score_show) AS avg_score_show, AVG(score_costume) AS avg_score_costume, AVG((score_song + score_show + score_costume)/3) AS avg_total_score FROM Review r JOIN Song s ON r.song_id = s.id JOIN CountryCodes cc ON s.country = cc.code GROUP BY song_id, cc.name_fi, cc.name_sv, s.artist, s.title ORDER BY avg_total_score DESC; CREATE VIEW ReviewSummaryByTeam AS SELECT r.song_id, cc.name_fi AS country_fi, cc.name_sv AS country_sv, s.artist, s.title, u.team_id, COUNT(*) AS total_reviews, AVG(r.score_song) AS avg_score_song, AVG(r.score_show) AS avg_score_show, AVG(r.score_costume) AS avg_score_costume, AVG((r.score_song + r.score_show + r.score_costume)/3) AS avg_total_score FROM Review r JOIN "User" u ON r.user_id = u.id JOIN Song s ON r.song_id = s.id JOIN CountryCodes cc ON s.country = cc.code GROUP BY r.song_id, u.team_id, cc.name_fi, cc.name_sv, s.artist, s.title ORDER BY avg_total_score DESC; -- Insert country codes data INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ALB', 'Albania', 'Albania', 'Albanien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ARM', 'Armenia', 'Armenia', 'Armenien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AUS', 'Australia', 'Australia', 'Australien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AUT', 'Austria', 'Itävalta', 'Österrike'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AZE', 'Azerbaijan', 'Azerbaidžan', 'Azerbajdzjan'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('BEL', 'Belgium', 'Belgia', 'Belgien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('HRV', 'Croatia', 'Kroatia', 'Kroatien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CYP', 'Cyprus', 'Kypros', 'Cypern'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CZE', 'Czechia', 'Tšekki', 'Tjeckien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('DNK', 'Denmark', 'Tanska', 'Danmark'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('EST', 'Estonia', 'Viro', 'Estland'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('FIN', 'Finland', 'Suomi', 'Finland'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('FRA', 'France', 'Ranska', 'Frankrike'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GEO', 'Georgia', 'Georgia', 'Georgien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('DEU', 'Germany', 'Saksa', 'Tyskland'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GRC', 'Greece', 'Kreikka', 'Grekland'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ISL', 'Iceland', 'Islanti', 'Island'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('IRL', 'Ireland', 'Irlanti', 'Irland'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ISR', 'Israel', 'Israel', 'Israel'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ITA', 'Italy', 'Italia', 'Italien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LVA', 'Latvia', 'Latvia', 'Lettland'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LTU', 'Lithuania', 'Liettua', 'Litauen'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LUX', 'Luxembourg', 'Luxemburg', 'Luxemburg'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('MLT', 'Malta', 'Malta', 'Malta'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('MNE', 'Montenegro', 'Montenegro', 'Montenegro'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('NLD', 'Netherlands', 'Alankomaat', 'Nederländerna'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('NOR', 'Norway', 'Norja', 'Norge'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('POL', 'Poland', 'Puola', 'Polen'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('PRT', 'Portugal', 'Portugali', 'Portugal'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SMR', 'San Marino', 'San Marino', 'San Marino'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SRB', 'Serbia', 'Serbia', 'Serbien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SVN', 'Slovenia', 'Slovenia', 'Slovenien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ESP', 'Spain', 'Espanja', 'Spanien'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SWE', 'Sweden', 'Ruotsi', 'Sverige'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CHE', 'Switzerland', 'Sveitsi', 'Schweiz'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('UKR', 'Ukraine', 'Ukraina', 'Ukraina'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GBR', 'United Kingdom', 'Englanti', 'Storbritannien'); -- TODO: Clean this next year. Its now here just to get the show running INSERT INTO Contest (year, finals_date) VALUES (2025, '2025-05-16'); -- ============================================================================= -- Views (Optional - Not Created Here Based on Documentation Decision) -- ============================================================================= -- The documentation suggests performing result aggregations in the application -- layer. No CREATE VIEW statements are included. -- ============================================================================= -- End of Schema Definition