wiki:AdvancedDatabaseDevelopment

Version 1 (modified by 231136, 3 weeks ago) ( diff )

--

Напреден развој на базата

Процедури

Најпопуларна песна, албум и артист од изминатиот ден

CREATE OR REPLACE PROCEDURE yesterdays_most_popular()
    language plpgsql
as
$$
DECLARE
    yesterday TIMESTAMP;
BEGIN
    yesterday := CURRENT_DATE - 1;

    IF EXISTS (
        SELECT 1
        FROM listens
        WHERE timestamp BETWEEN yesterday AND CURRENT_DATE
    ) THEN
        INSERT INTO daily_top_songs (day, song_id, play_count)
        SELECT CURRENT_DATE - 1, s.id, COUNT(*)
        FROM songs s
        JOIN listens l ON l.song_id = s.id
        WHERE l.timestamp BETWEEN yesterday AND NOW()
        GROUP BY s.id
        ORDER BY COUNT(*) DESC
        LIMIT 1;

        INSERT INTO daily_top_albums(day, album_id, play_count)
        SELECT CURRENT_DATE - 1, a.id, COUNT(*)
        FROM albums a
        JOIN songs s on s.album_id = a.id
        JOIN listens l on l.song_id = s.id
        WHERE l.timestamp BETWEEN yesterday AND NOW()
        GROUP BY a.id
        ORDER BY COUNT(*) DESC
        LIMIT 1;

        INSERT INTO daily_top_artists(day, artist_id, play_count)
        SELECT CURRENT_DATE - 1, a.user_id, COUNT(*)
        FROM artists a
        JOIN musical_entities me on me.released_by = a.user_id
        JOIN listens l on l.song_id = me.id
        WHERE l.timestamp BETWEEN yesterday AND NOW()
        GROUP BY a.user_id
        ORDER BY COUNT(*) DESC
        LIMIT 1;

    ELSE
        INSERT INTO daily_top_songs (day, song_id, play_count)
        VALUES (CURRENT_DATE - 1, NULL, 0);

        INSERT INTO daily_top_albums (day, album_id, play_count)
        VALUES (CURRENT_DATE - 1, NULL, 0);

        INSERT INTO daily_top_artists (day, artist_id, play_count)
        VALUES (CURRENT_DATE - 1, NULL, 0);
    END IF;
END;
$$;

Известување на артисти кои треба да настапат на настан во наредните 7 дена

Прво ја додаваме табелата каде ќе ги чуваме нотификациите:

CREATE TABLE event_notifications(
    event_id BIGINT REFERENCES events(event_id),
    artist_id BIGINT REFERENCES artists(user_id),
    artist_has_read BOOLEAN,
    message TEXT,

    CONSTRAINT pk_event_notifications PRIMARY KEY (event_id, artist_id)
);

Потоа, процедурата:

CREATE OR REPLACE PROCEDURE notify_event_performers()
    language plpgsql
AS
$$
DECLARE
    to_notify RECORD;
    next_seven_days DATE;
BEGIN
    next_seven_days := CURRENT_DATE + 7;

    FOR to_notify IN SELECT
                         e.event_id,
                         a.user_id,
                         e.name,
                         e.venue,
                         e.location,
                         e.DATE - current_date AS days_until
    FROM events e
    JOIN performs_at pa ON pa.event_id=e.event_id
    JOIN artists a ON pa.artist_id=a.user_id
    WHERE e.DATE BETWEEN current_date AND next_seven_days AND
    NOT EXISTS(
        SELECT 1
        FROM event_notifications en
        WHERE en.event_id=e.event_id AND en.artist_id=pa.artist_id
    )

    LOOP
        INSERT INTO event_notifications (event_id, artist_id, artist_has_read, message)
        VALUES(
               to_notify.event_id,
               to_notify.user_id,
               FALSE,
               format(
                  'You have an event at %s, %s IN %s days.',
                   to_notify.venue,
                   to_notify.location,
                   to_notify.days_until
               )
            );
    END LOOP;

END;
$$;

Известување артистите доколку нивна песна достигне одреден број на слушања

Прво ги додаваме потребните табели:

CREATE TABLE song_play_counts (
    song_id     BIGINT PRIMARY KEY REFERENCES songs(id),
    play_count  BIGINT NOT NULL DEFAULT 0
);

CREATE TABLE song_play_milestones (
    song_id  BIGINT PRIMARY KEY REFERENCES songs(id),
    last_milestone BIGINT NOT NULL DEFAULT 0
);

CREATE TABLE milestone_notifications(
    user_id BIGINT REFERENCES users(user_id),
    song_id BIGINT REFERENCES songs(id),
    milestone BIGINT ,
    message TEXT,
    artist_has_read BOOLEAN,

    CONSTRAINT pk_milestone_notifications PRIMARY KEY (user_id,song_id,milestone)
);

CREATE TABLE last_processed_listen(
    last_timestamp TIMESTAMP
);

Иницијално поставуваме last_timestamp на -infinity за при првиот внес сите слушања да се земат предвид.

INSERT INTO last_processed_listen (last_timestamp)
VALUES ('-infinity');

Потребна ни е процедура што периодично ќе ги земе сите нови слушања и соодветно ќе ги зголеми бројачите во song_play_counts.

CREATE OR REPLACE PROCEDURE update_song_play_counts()
    language plpgsql
AS
$$
DECLARE

BEGIN
    WITH new_listens AS (
        SELECT l.song_id, COUNT(*) AS cnt
        FROM listens l
        WHERE l.TIMESTAMP > (
            SELECT last_timestamp FROM last_processed_listen
        )
        GROUP BY l.song_id
    )
    INSERT INTO song_play_counts (song_id, play_count)
    SELECT song_id, cnt
    FROM new_listens
    ON CONFLICT (song_id)
        DO UPDATE
        SET play_count = song_play_counts.play_count + EXCLUDED.play_count;

    UPDATE last_processed_listen
    SET last_timestamp = NOW();
END;
$$;

Потоа, процедура што ги наоѓа песните кои постигнале одреден успех - во нашиот случај, бидејќи базата е со малку податоци, сметаме дека 10 слушања се доволно голем успех за да го известиме артистот за истите. Откога песната ќе добие 10 слушања и артистот ќе биде известен, понатаму ќе биде известуван на 20, 30, 40 итн.

CREATE OR REPLACE PROCEDURE notify_artists_on_reached_milestone()
    language plpgsql
AS
$$
DECLARE

BEGIN

    INSERT INTO milestone_notifications (user_id, song_id, milestone, message, artist_has_read)
    SELECT
        u.user_id,
        s.id,
        spm.last_milestone+10,
        format(
            'Milestone reached! Song %s reached %s listens!',
             me.title,
             spm.last_milestone + 10
        ),
        FALSE
    FROM song_play_counts spc
    LEFT JOIN song_play_milestones spm ON spc.song_id = spm.song_id
    LEFT JOIN songs s ON spc.song_id = s.id
    LEFT JOIN musical_entities me ON s.id = me.id
    LEFT JOIN users u ON u.user_id=me.released_by
    WHERE spc.play_count >= coalesce(spm.last_milestone,0)+10
    ON CONFLICT DO NOTHING;

    INSERT INTO song_play_milestones (song_id, last_milestone)
    SELECT
        song_id,
        MAX(milestone)
    FROM milestone_notifications
    GROUP BY song_id
    ON CONFLICT (song_id)
        DO UPDATE SET last_milestone = EXCLUDED.last_milestone;

END;
$$;

Тригери

Тригер што проверува дали дали албум или песна се слушани од корисник пред да бидат оценети.

Не сакаме во базата да имаме оценки за песни или албуми коишто не биле слушани, но не може тоа да го спречиме со едноставен constraint, па воведуваме тригер при нови записи во базата.

CREATE OR REPLACE FUNCTION check_has_listened()
    RETURNS TRIGGER
AS $$
BEGIN
    IF EXISTS(
        SELECT 1 FROM listens l
        WHERE l.song_id = NEW.musical_entity_id
          AND l.listener_id = NEW.listener_id
    )
        OR EXISTS(
            SELECT 1 FROM albums a
            JOIN songs s ON s.album_id = a.id
            JOIN listens l ON s.id = l.song_id
            WHERE a.id = NEW.musical_entity_id AND l.listener_id = NEW.listener_id
        )
    THEN RETURN NEW;
    ELSE RAISE EXCEPTION 'listener_id=% has NOT listened to musical_entity_id=%',
        NEW.listener_id,
        NEW.musical_entity_id;
    END IF;
END;
$$
    LANGUAGE plpgsql;

CREATE OR REPLACE TRIGGER has_listened_before_review
    BEFORE INSERT ON reviews
    FOR EACH ROW
EXECUTE FUNCTION check_has_listened();
Note: See TracWiki for help on using the wiki.