wiki:DatabaseProgramming

Version 2 (modified by 231071, 13 days ago) ( diff )

--

Фаза 4 — Функции, Процедури, Тригери

Тригери

1. trg_validate_ballot

Тип: BEFORE INSERT на табела ballot

Се активира пред секое внесување на гласачко ливче и ги проверува три услови: дали изборот е активен (status=1), дали гласачкото место е назначено на тој избор, и дали entity_id учествувал во изборот. Ја имплементира бизнис логиката за интегритет на гласање — спречува невалидни или фалсификувани ливчиња да влезат во системот.

CREATE OR REPLACE FUNCTION public.fn_validate_ballot()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_status        SMALLINT;
    v_station_ok    BOOLEAN;
    v_entity_ok     BOOLEAN;
BEGIN

    -- Check (a): election must be active
    SELECT status
    INTO   v_status
    FROM   election
    WHERE  election_id = NEW.election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION
            'Election % does not exist', NEW.election_id;
    END IF;

    IF v_status <> 1 THEN
        RAISE EXCEPTION
            'Election % is not active (status=%). Ballots cannot be cast.',
            NEW.election_id, v_status;
    END IF;

    -- Check (b): station must be in this election
    SELECT EXISTS (
        SELECT 1
        FROM   station_election
        WHERE  station_id  = NEW.station_id
        AND    election_id = NEW.election_id
    ) INTO v_station_ok;

    IF NOT v_station_ok THEN
        RAISE EXCEPTION
            'Polling station % is not assigned to election %.',
            NEW.station_id, NEW.election_id;
    END IF;

    -- Check (c/d): entity validation for valid ballots
    IF NEW.is_valid = TRUE THEN

        IF NEW.entity_id IS NULL AND NEW.candidate_id IS NULL THEN
            RAISE EXCEPTION
                'A valid ballot must have either an entity_id or candidate_id.';
        END IF;

        IF NEW.entity_id IS NOT NULL THEN
            SELECT EXISTS (
                SELECT 1
                FROM   election_participant ep
                JOIN   electoral_district   ed
                       ON ed.district_id  = ep.district_id
                WHERE  ep.entity_id   = NEW.entity_id
                AND    ed.election_id  = NEW.election_id
            ) INTO v_entity_ok;

            IF NOT v_entity_ok THEN
                RAISE EXCEPTION
                    'Entity % did not participate in election %.',
                    NEW.entity_id, NEW.election_id;
            END IF;
        END IF;

    END IF;

    RETURN NEW;

END;
$function$;

-- Trigger definition
CREATE TRIGGER trg_validate_ballot
BEFORE INSERT ON public.ballot
FOR EACH ROW EXECUTE FUNCTION fn_validate_ballot();

2. trg_after_insert_ballot_live_count

Тип: AFTER INSERT на табела ballot

Се активира по секое внесување на валидно ливче и автоматски го ажурира бројачот во vote_result преку UPSERT — ако записот постои го зголемува за 1, инаку создава нов. Ја имплементира логиката за live бројање на гласови во реално време, без потреба од рачна агрегација на резултати.

CREATE OR REPLACE FUNCTION public.fn_trigger_live_vote_counter()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_result_id_exists BIGINT;
    v_max_id           BIGINT;
BEGIN
    IF NEW.is_valid = TRUE THEN

        SELECT result_id INTO v_result_id_exists
        FROM public.vote_result
        WHERE election_id  = NEW.election_id
          AND station_id   = NEW.station_id
          AND entity_id    = NEW.entity_id
          AND (candidate_id = NEW.candidate_id
               OR (candidate_id IS NULL AND NEW.candidate_id IS NULL));

        IF v_result_id_exists IS NOT NULL THEN
            UPDATE public.vote_result
            SET votes = votes + 1
            WHERE result_id = v_result_id_exists;
        ELSE
            SELECT COALESCE(MAX(result_id), 0) + 1
            INTO   v_max_id
            FROM   public.vote_result;

            INSERT INTO public.vote_result
                (result_id, election_id, station_id, entity_id, candidate_id, votes)
            VALUES
                (v_max_id, NEW.election_id, NEW.station_id, NEW.entity_id, NEW.candidate_id, 1);
        END IF;

    END IF;

    RETURN NEW;
END;
$function$;

-- Trigger definition
CREATE TRIGGER trg_after_insert_ballot_live_count
AFTER INSERT ON public.ballot
FOR EACH ROW EXECUTE FUNCTION fn_trigger_live_vote_counter();

3. trg_before_insert_voter_age

Тип: BEFORE INSERT на табела voter

Се активира пред регистрација на нов гласач и проверува дали лицето е полнолетно (≥18 години), фрлајќи детален исклучок со името на лицето ако не е. Директно ја имплементира законската забрана за гласање на малолетни лица на ниво на базата.

CREATE OR REPLACE FUNCTION public.fn_check_voter_age_limit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_birth_date DATE;
    v_full_name  TEXT;
BEGIN
    SELECT date_of_birth, (name || ' ' || surname)
    INTO   v_birth_date, v_full_name
    FROM   public.person
    WHERE  person_id = NEW.person_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION
            'Грешка: Лицето со ID % не постои во матичната евиденција (табела person).',
            NEW.person_id;
    END IF;

    IF v_birth_date > CURRENT_DATE - INTERVAL '18 years' THEN
        RAISE EXCEPTION
            'Критична грешка: Лицето "%" (ID: %) има % години и е малолетно. '
            'Регистрацијата во базата на гласачи е ОДБИЕНА.',
            v_full_name,
            NEW.person_id,
            EXTRACT(YEAR FROM AGE(CURRENT_DATE, v_birth_date));
    END IF;

    RETURN NEW;
END;
$function$;

-- Trigger definition
CREATE TRIGGER trg_before_insert_voter_age
BEFORE INSERT ON public.voter
FOR EACH ROW EXECUTE FUNCTION fn_check_voter_age_limit();

4. trg_after_voter_change_sync_stats

Тип: AFTER INSERT OR DELETE на табела voter

Автоматски го ажурира registered_voter бројачот во polling_station при секоја промена на гласачкиот список — +1 при регистрација, -1 при бришење. Ја одржува конзистентноста на статистиките за регистрирани гласачи без рачно одржување.

CREATE OR REPLACE FUNCTION public.fn_trigger_sync_registered_voters()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE public.polling_station
        SET    registered_voter = COALESCE(registered_voter, 0) + 1
        WHERE  station_id = NEW.station_id;

    ELSIF TG_OP = 'DELETE' THEN
        UPDATE public.polling_station
        SET    registered_voter = COALESCE(registered_voter, 0) - 1
        WHERE  station_id = OLD.station_id;
    END IF;

    RETURN NULL;
END;
$function$;

-- Trigger definition
CREATE TRIGGER trg_after_voter_change_sync_stats
AFTER INSERT OR DELETE ON public.voter
FOR EACH ROW EXECUTE FUNCTION fn_trigger_sync_registered_voters();

5. trg_prevent_duplicate_voter_checkin

Тип: BEFORE INSERT на табела voter_election

Проверува три услови: изборот е активен, гласачот не се пријавил веќе на тој избор, и check-in е на неговата матична станица. Ја имплементира основната изборна контрола против двојно гласање и гласање надвор од матичното место.

CREATE OR REPLACE FUNCTION public.fn_prevent_duplicate_voter_checkin()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_registered_station BIGINT;
    v_election_status    SMALLINT;
    v_election_name      VARCHAR;
    v_voter_name         TEXT;
BEGIN
    -- (1) Изборот мора да биде активен
    SELECT e.status, e.name
    INTO   v_election_status, v_election_name
    FROM   election e
    WHERE  e.election_id = NEW.election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Election % does not exist.', NEW.election_id;
    END IF;

    IF v_election_status <> 1 THEN
        RAISE EXCEPTION
            'Election "%" is not active. Voter check-in is not allowed.',
            v_election_name;
    END IF;

    -- (2) Гласачот не смее двапати да се пријави
    IF EXISTS (
        SELECT 1
        FROM   voter_election ve
        WHERE  ve.voter_id    = NEW.voter_id
        AND    ve.election_id = NEW.election_id
    ) THEN
        SELECT (p.name || ' ' || p.surname)
        INTO   v_voter_name
        FROM   voter  v
        JOIN   person p ON p.person_id = v.person_id
        WHERE  v.voter_id = NEW.voter_id;

        RAISE EXCEPTION
            'Voter "%" (id: %) has already checked in for election "%".',
            v_voter_name, NEW.voter_id, v_election_name;
    END IF;

    -- (3) Check-in мора да биде на матичната станица
    SELECT v.station_id
    INTO   v_registered_station
    FROM   voter v
    WHERE  v.voter_id = NEW.voter_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Voter % does not exist.', NEW.voter_id;
    END IF;

    IF v_registered_station <> NEW.station_id THEN
        RAISE EXCEPTION
            'Voter % must check in at their registered station (%). '
            'Attempted check-in at station %.',
            NEW.voter_id, v_registered_station, NEW.station_id;
    END IF;

    RETURN NEW;
END;
$function$;

-- Trigger definition
CREATE TRIGGER trg_prevent_duplicate_voter_checkin
BEFORE INSERT ON public.voter_election
FOR EACH ROW EXECUTE FUNCTION fn_prevent_duplicate_voter_checkin();

Функции

6. fn_calculate_dhondt

Сигнатура: fn_calculate_dhondt(p_election_id bigint, p_district_id bigint)

Враќа: TABLE(round, entity_id, entity_name, votes, divisor, quotient, seat_awarded)

Ја пресметува распределбата на мандати по D'Hondt метод за даден избор и изборна единица — генерира табела со сите квоциенти, дивизори, позиции и флаг дали е доделен мандат. Ја имплементира пропорционалната изборна математика која е законски задолжителна за парламентарни избори во Македонија и Србија, со вграден 5% праг на влез.

CREATE OR REPLACE FUNCTION public.fn_calculate_dhondt(p_election_id bigint, p_district_id bigint)
 RETURNS TABLE(round bigint, entity_id bigint, entity_name character varying,
               votes bigint, divisor integer, quotient numeric, seat_awarded boolean)
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_seats         INTEGER;
    v_method_id     BIGINT;
    v_election_name VARCHAR;
    v_region_name   VARCHAR;
BEGIN

    SELECT e.winner_method_id, e.name
    INTO   v_method_id, v_election_name
    FROM   election e
    WHERE  e.election_id = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Election % does not exist', p_election_id;
    END IF;

    IF v_method_id <> 8 THEN
        RAISE EXCEPTION
            'Election "%" does not use D''Hondt method. '
            'This function only works for proportional elections.',
            v_election_name;
    END IF;

    SELECT ed.seats_available, r.name
    INTO   v_seats, v_region_name
    FROM   electoral_district ed
    JOIN   region             r  ON r.region_id = ed.region_id
    WHERE  ed.district_id  = p_district_id
    AND    ed.election_id  = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION
            'District % does not exist for election %',
            p_district_id, p_election_id;
    END IF;

    IF v_seats IS NULL OR v_seats = 0 THEN
        RAISE EXCEPTION 'District % has no seats defined', p_district_id;
    END IF;

    RAISE NOTICE 'Calculating D''Hondt for: % — % (% seats)',
        v_election_name, v_region_name, v_seats;

    RETURN QUERY
    WITH
    -- Чекор 1: гласови по ентитет во дистриктот
    raw_votes AS (
        SELECT
            COALESCE(pe2.entity_id, vr.entity_id) AS entity_id,
            SUM(vr.votes)                          AS district_votes
        FROM        vote_result      vr
        JOIN        polling_station  ps  ON ps.station_id   = vr.station_id
        JOIN        region           r2  ON r2.region_id    = ps.municipality_id
        JOIN        political_entity pe  ON pe.entity_id    = vr.entity_id
        LEFT JOIN   coalition_member cm  ON cm.party_id     = pe.party_id
        LEFT JOIN   party_coalition  pc  ON pc.coalition_id = cm.coalition_id
                                        AND pc.election_id  = vr.election_id
        LEFT JOIN   political_entity pe2 ON pe2.coalition_id = pc.coalition_id
        WHERE       vr.election_id  = p_election_id
        AND         vr.candidate_id IS NULL
        AND (
            r2.region_id        = (SELECT region_id FROM electoral_district
                                   WHERE district_id = p_district_id)
            OR
            r2.parent_region_id = (SELECT region_id FROM electoral_district
                                   WHERE district_id = p_district_id)
        )
        GROUP BY COALESCE(pe2.entity_id, vr.entity_id)
    ),
    -- Чекор 2: филтрирање на ентитети над 5% праг
    district_total AS (
        SELECT SUM(district_votes) AS total FROM raw_votes
    ),
    eligible AS (
        SELECT rv.entity_id, rv.district_votes
        FROM   raw_votes rv
        CROSS JOIN district_total dt
        WHERE  rv.district_votes * 100.0 / NULLIF(dt.total, 0) >= 5.0
    ),
    -- Чекор 3: генерирање на сите квоциенти
    quotients AS (
        SELECT
            e.entity_id,
            e.district_votes,
            d.divisor,
            ROUND(e.district_votes::NUMERIC / d.divisor, 4) AS quotient
        FROM       eligible e
        CROSS JOIN generate_series(1, v_seats) AS d(divisor)
    ),
    -- Чекор 4: рангирање на сите квоциенти
    ranked AS (
        SELECT
            q.*,
            RANK() OVER (ORDER BY q.quotient DESC) AS global_rank
        FROM quotients q
    )
    -- Чекор 5: враќање со флаг за доделен мандат
    SELECT
        r.global_rank                  AS round,
        r.entity_id,
        pe.name                        AS entity_name,
        r.district_votes               AS votes,
        r.divisor,
        r.quotient,
        (r.global_rank <= v_seats)     AS seat_awarded
    FROM   ranked           r
    JOIN   political_entity pe ON pe.entity_id = r.entity_id
    ORDER BY r.global_rank, r.entity_id;

END;
$function$;

7. fn_get_candidate_list_for_entity

Сигнатура: fn_get_candidate_list_for_entity(p_entity_id bigint, p_election_id bigint)

Враќа: TABLE(list_position, candidate_id, full_name, date_of_birth, gender, municipality, party_name, entity_name, election_name)

Ја враќа целосната листа на кандидати за даден политички ентитет на даден избор, подредена по позиција, со лични податоци, партиска припадност и општина. Ја имплементира логиката за јавна транспарентност — граѓаните и медиумите можат да видат кој е на каква позиција на изборната листа.

CREATE OR REPLACE FUNCTION public.fn_get_candidate_list_for_entity(p_entity_id bigint, p_election_id bigint)
 RETURNS TABLE(list_position integer, candidate_id bigint, full_name text,
               date_of_birth date, gender character, municipality character varying,
               party_name character varying, entity_name character varying,
               election_name character varying)
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_entity_name   VARCHAR;
    v_election_name VARCHAR;
    v_list_id       BIGINT;
BEGIN
    SELECT pe.name INTO v_entity_name
    FROM   political_entity pe
    WHERE  pe.entity_id = p_entity_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Political entity % does not exist.', p_entity_id;
    END IF;

    SELECT e.name INTO v_election_name
    FROM   election e
    WHERE  e.election_id = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Election % does not exist.', p_election_id;
    END IF;

    SELECT cl.list_id INTO v_list_id
    FROM   candidate_list cl
    WHERE  cl.entity_id   = p_entity_id
    AND    cl.election_id = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION
            'No candidate list found for entity "%" on election "%".',
            v_entity_name, v_election_name;
    END IF;

    RAISE NOTICE 'Candidate list for "%" — Election: "%" (list_id: %)',
        v_entity_name, v_election_name, v_list_id;

    RETURN QUERY
    SELECT
        cli.position                                AS list_position,
        c.candidate_id,
        (p.name || ' ' || p.surname)::TEXT          AS full_name,
        p.date_of_birth,
        p.gender,
        COALESCE(r.name, 'No municipality data')    AS municipality,
        COALESCE(
            (
                SELECT pp.name
                FROM   candidate_party cp
                JOIN   political_party  pp ON pp.party_id = cp.party_id
                WHERE  cp.candidate_id = c.candidate_id
                AND    cp.election_id  = p_election_id
                ORDER BY cp.candidate_party_id
                LIMIT  1
            ),
            v_entity_name
        )                                           AS party_name,
        v_entity_name                               AS entity_name,
        v_election_name                             AS election_name
    FROM        candidate_list_item cli
    JOIN        candidate           c   ON c.candidate_id = cli.candidate_id
    JOIN        person              p   ON p.person_id    = c.person_id
    LEFT JOIN   region              r   ON r.region_id    = p.municipality_id
    WHERE       cli.list_id = v_list_id
    ORDER BY    cli.position ASC;
END;
$function$;

8. fn_station_anomaly_score

Сигнатура: fn_station_anomaly_score(p_station_id bigint, p_election_id bigint)

Враќа: TABLE(station_id, station_name, election_name, registered_voters, ballots_cast, valid_ballots, invalid_ballots, turnout_pct, invalid_pct, total_votes_vr, anomaly_score, flag_high_turnout, flag_impossible_turnout, flag_high_invalid, flag_no_ballots, flag_impossible_votes, recommendation)

Ја анализира статистиката на гласачко место и пресметува anomaly_score врз основа на 5 флагови: висока излезност (>95%), невозможна излезност (>100%), многу невалидни ливчиња (>10%), нула ливчиња, и гласови поголеми од регистрираните гласачи. Ја имплементира автоматската детекција на потенцијални изборни измами или грешки во внесот со конкретна препорака за акција.

Anomaly Score Препорака
0 OK — No anomalies detected.
1–2 MINOR — Monitor this station.
3–5 WARNING — Manual review recommended.
>5 CRITICAL — Immediate investigation required.
CREATE OR REPLACE FUNCTION public.fn_station_anomaly_score(p_station_id bigint, p_election_id bigint)
 RETURNS TABLE(station_id bigint, station_name character varying, election_name character varying,
               registered_voters integer, ballots_cast bigint, valid_ballots bigint,
               invalid_ballots bigint, turnout_pct numeric, invalid_pct numeric,
               total_votes_vr bigint, anomaly_score integer,
               flag_high_turnout boolean, flag_impossible_turnout boolean,
               flag_high_invalid boolean, flag_no_ballots boolean,
               flag_impossible_votes boolean, recommendation text)
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_station_name    VARCHAR;
    v_election_name   VARCHAR;
    v_registered      INT;
    v_ballots         BIGINT;
    v_valid           BIGINT;
    v_invalid         BIGINT;
    v_votes_vr        BIGINT;
    v_turnout         NUMERIC;
    v_invalid_pct     NUMERIC;
    v_score           INT     := 0;
    v_flag_turnout    BOOLEAN := FALSE;
    v_flag_impossible BOOLEAN := FALSE;
    v_flag_invalid    BOOLEAN := FALSE;
    v_flag_no_ballot  BOOLEAN := FALSE;
    v_flag_imp_votes  BOOLEAN := FALSE;
    v_recommendation  TEXT;
BEGIN
    SELECT ps.name, ps.registered_voter
    INTO   v_station_name, v_registered
    FROM   polling_station ps
    WHERE  ps.station_id = p_station_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Polling station % does not exist.', p_station_id;
    END IF;

    SELECT e.name INTO v_election_name
    FROM   election e
    WHERE  e.election_id = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Election % does not exist.', p_election_id;
    END IF;

    IF NOT EXISTS (
        SELECT 1 FROM station_election se
        WHERE  se.station_id  = p_station_id
        AND    se.election_id = p_election_id
    ) THEN
        RAISE EXCEPTION 'Station % is not assigned to election %.',
            p_station_id, p_election_id;
    END IF;

    SELECT
        COUNT(*),
        COUNT(*) FILTER (WHERE b.is_valid = TRUE),
        COUNT(*) FILTER (WHERE b.is_valid = FALSE)
    INTO v_ballots, v_valid, v_invalid
    FROM ballot b
    WHERE b.station_id  = p_station_id
    AND   b.election_id = p_election_id;

    SELECT COALESCE(SUM(vr.votes), 0)
    INTO   v_votes_vr
    FROM   vote_result vr
    WHERE  vr.station_id  = p_station_id
    AND    vr.election_id = p_election_id;

    v_turnout := ROUND(
        v_ballots::NUMERIC * 100.0 / NULLIF(COALESCE(v_registered, 0), 0), 2);

    v_invalid_pct := ROUND(
        v_invalid::NUMERIC * 100.0 / NULLIF(v_ballots, 0), 2);

    -- Flag 1: Излезност > 95%
    IF v_turnout > 95.0 THEN
        v_flag_turnout := TRUE;
        v_score        := v_score + 3;
    END IF;

    -- Flag 2: Излезност > 100% (невозможно)
    IF v_turnout > 100.0 THEN
        v_flag_impossible := TRUE;
        v_score           := v_score + 10;
    END IF;

    -- Flag 3: Невалидни > 10%
    IF v_invalid_pct > 10.0 THEN
        v_flag_invalid := TRUE;
        v_score        := v_score + 2;
    END IF;

    -- Flag 4: Нула балоти
    IF v_ballots = 0 THEN
        v_flag_no_ballot := TRUE;
        v_score          := v_score + 2;
    END IF;

    -- Flag 5: Гласови во vote_result > регистрирани гласачи
    IF v_votes_vr > COALESCE(v_registered, 0) THEN
        v_flag_imp_votes := TRUE;
        v_score          := v_score + 5;
    END IF;

    v_recommendation := CASE
        WHEN v_score = 0             THEN 'OK — No anomalies detected.'
        WHEN v_score BETWEEN 1 AND 2 THEN 'MINOR — Monitor this station.'
        WHEN v_score BETWEEN 3 AND 5 THEN 'WARNING — Manual review recommended.'
        ELSE                              'CRITICAL — Immediate investigation required.'
    END;

    RETURN QUERY
    SELECT
        p_station_id, v_station_name, v_election_name,
        v_registered, v_ballots, v_valid, v_invalid,
        COALESCE(v_turnout, 0), COALESCE(v_invalid_pct, 0),
        v_votes_vr, v_score,
        v_flag_turnout, v_flag_impossible, v_flag_invalid,
        v_flag_no_ballot, v_flag_imp_votes, v_recommendation;
END;
$function$;

Процедури

9. sp_close_election

Сигнатура: sp_close_election(IN p_election_id bigint)

Ги верифицира резултатите, го наоѓа победникот со процент на гласови, го затвора изборот (status=0) и логира детален извештај. Ја имплементира официјалната процедура за завршување на изборен циклус — само избори со внесени резултати можат да се затворат.

CREATE OR REPLACE PROCEDURE public.sp_close_election(IN p_election_id bigint)
 LANGUAGE plpgsql
AS $procedure$
DECLARE
    v_election_name  VARCHAR;
    v_election_type  VARCHAR;
    v_status         SMALLINT;
    v_station_count  INT;
    v_total_votes    BIGINT;
    v_winner_name    VARCHAR;
    v_winner_votes   BIGINT;
    v_winner_pct     NUMERIC;
BEGIN
    -- (1) Провери дали изборот постои
    SELECT e.name, e.status, et.type_name
    INTO   v_election_name, v_status, v_election_type
    FROM   election e
    JOIN   election_type et ON et.election_type_id = e.election_type_id
    WHERE  e.election_id = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Election % does not exist.', p_election_id;
    END IF;

    -- (2) Провери дали е активен
    IF v_status = 0 THEN
        RAISE EXCEPTION 'Election "%" is already closed.', v_election_name;
    END IF;

    -- (3) Провери дали постојат vote_result записи
    SELECT COUNT(DISTINCT vr.station_id), SUM(vr.votes)
    INTO   v_station_count, v_total_votes
    FROM   vote_result vr
    WHERE  vr.election_id  = p_election_id
    AND    vr.candidate_id IS NULL;

    IF v_total_votes IS NULL OR v_total_votes = 0 THEN
        RAISE EXCEPTION
            'Election "%" has no vote results. Cannot close an election without results.',
            v_election_name;
    END IF;

    -- Најди победник
    SELECT pe.name, SUM(vr.votes),
           ROUND(SUM(vr.votes)::NUMERIC * 100.0 / NULLIF(v_total_votes, 0), 2)
    INTO   v_winner_name, v_winner_votes, v_winner_pct
    FROM   vote_result vr
    JOIN   political_entity pe ON pe.entity_id = vr.entity_id
    WHERE  vr.election_id  = p_election_id
    AND    vr.candidate_id IS NULL
    GROUP BY pe.name
    ORDER BY SUM(vr.votes) DESC
    LIMIT 1;

    -- Затвори го изборот
    UPDATE election SET status = 0 WHERE election_id = p_election_id;

    RAISE NOTICE '════════════════════════════════════════';
    RAISE NOTICE 'Election closed: % (%)', v_election_name, v_election_type;
    RAISE NOTICE 'Stations reported : %', v_station_count;
    RAISE NOTICE 'Total votes        : %', v_total_votes;
    RAISE NOTICE 'Winner             : %', v_winner_name;
    RAISE NOTICE 'Winner votes       : %', v_winner_votes;
    RAISE NOTICE 'Winner share       : % pct', v_winner_pct;
    RAISE NOTICE '════════════════════════════════════════';

END;
$procedure$;

10. sp_open_election

Сигнатура: sp_open_election(IN p_election_id bigint)

Го активира изборот (status=1) по верификација дека не постои веќе активен избор од ист тип во системот. Ја имплементира бизнис правилото дека не можат да се одржуваат два истовидни избори истовремено.

CREATE OR REPLACE PROCEDURE public.sp_open_election(IN p_election_id bigint)
 LANGUAGE plpgsql
AS $procedure$
DECLARE
    v_election_name    VARCHAR;
    v_type_id          BIGINT;
    v_status           SMALLINT;
    v_same_type_active BOOLEAN;
BEGIN
    SELECT name, status, election_type_id
    INTO   v_election_name, v_status, v_type_id
    FROM   public.election
    WHERE  election_id = p_election_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Грешка: Изборот со ID % не постои.', p_election_id;
    END IF;

    IF v_status = 1 THEN
        RAISE EXCEPTION
            'Забелешка: Изборот "%" е веќе отворен и активен.', v_election_name;
    END IF;

    SELECT EXISTS (
        SELECT 1 FROM public.election
        WHERE  status = 1 AND election_type_id = v_type_id
    ) INTO v_same_type_active;

    IF v_same_type_active THEN
        RAISE EXCEPTION
            'Предупредување: Веќе постојат активни избори од овој тип во системот. '
            'Мора прво да ги затворите претходните од истата категорија.';
    END IF;

    UPDATE public.election SET status = 1 WHERE election_id = p_election_id;

    RAISE NOTICE '========================================';
    RAISE NOTICE 'Изборот "%" е УСПЕШНО ОТВОРЕН!', v_election_name;
    RAISE NOTICE 'Овозможено е истовремено гласање со други активни различни типови избори.';
    RAISE NOTICE '========================================';

END;
$procedure$;

11. sp_register_newly_eligible_voters

Сигнатура: sp_register_newly_eligible_voters()

Ги регистрира сите лица кои наполниле 18 години а сè уште не се во гласачкиот список, и ги распоредува на најмалку оптоварената станица во batch-ови од 10,000. Ја автоматизира периодичната ажурирање на гласачкиот список која државните институции ја вршат пред секои избори.

CREATE OR REPLACE PROCEDURE public.sp_register_newly_eligible_voters()
 LANGUAGE plpgsql
AS $procedure$
DECLARE
    v_count        INT := 0;
    v_max_voter_id BIGINT;
    v_station_id   BIGINT;
    v_region_id    BIGINT;
    rec            RECORD;
BEGIN
    SELECT COALESCE(MAX(voter_id), 0)
    INTO   v_max_voter_id
    FROM   voter;

    -- Најди најмалку оптоварена станица
    SELECT ps.station_id, ps.municipality_id
    INTO   v_station_id, v_region_id
    FROM   polling_station ps
    LEFT JOIN voter v ON v.station_id = ps.station_id
    GROUP BY ps.station_id, ps.municipality_id, ps.registered_voter
    ORDER BY COUNT(v.voter_id) * 1.0 / NULLIF(ps.registered_voter, 1) ASC
    LIMIT 1;

    IF v_station_id IS NULL THEN
        RAISE EXCEPTION 'No polling stations found in the system';
    END IF;

    FOR rec IN
        SELECT p.person_id
        FROM   person p
        WHERE  p.date_of_birth <= CURRENT_DATE - INTERVAL '18 years'
        AND    p.person_id NOT IN (SELECT person_id FROM voter)
        ORDER BY p.person_id
    LOOP
        v_max_voter_id := v_max_voter_id + 1;

        INSERT INTO voter (voter_id, person_id, region_id, station_id)
        VALUES (v_max_voter_id, rec.person_id, v_region_id, v_station_id)
        ON CONFLICT (person_id) DO NOTHING;

        v_count := v_count + 1;

        -- Commit во batch-ови од 10,000
        IF v_count % 10000 = 0 THEN
            RAISE NOTICE 'Registered % voters so far...', v_count;
        END IF;

    END LOOP;

    RAISE NOTICE 'Done. Total newly registered voters: %', v_count;

END;
$procedure$;
Note: See TracWiki for help on using the wiki.