Alexander Mylnikov

15Nov/140

CSC Бызы данных, 9 дз

Вам нужно посчитать некоторую статистику о багах в багтрекере. Статистику вы хотите видеть в представлениях, соответственно основной задачей является написание запросов, формирующих представления.

Во-первых, вам хочется для каждого проекта найти суммарное количество багов каждого из имеющихся у вас статусов. Хочется видеть примерно такое:

SELECT * FROM BusSummaryPerProject;

project_id  | new  | assigned | fixed | verified | not reproducible
1001          45     24         3       0          10
1002          56     10         37      104        15

Для упрощения можете считать, что множество статусов известно и не собирается меняться.

Во-вторых, для каждого статуса хочется видеть компоненту с максимальным, минимальным и медианным количеством багов с таким статусом. Например, если у нас 7 компонент и количество багов со статусом “new” в них распределено так:

C1 | 10
C2 | 50
C3 | 15
C4 | 30
C5 | 20
C6 | 25
C7 | 35

то в строке представления для статуса new должно быть:

SELECT * FROM StatisticsPerStatus WHERE status='new';

status | max_component | max_value | min_component | min_value | median_component | median_value
new      C2              50          C1              10          C6                 25

Если число компонент четное, то считайте медианой большее из двух возможных значений. Если медианное значение принадлежит нескольким компонентам, то добавьте лексикографическую сортировку по названию компоненты (так, если бы в примере выше в компоненте C4 было бы 25 багов, то медианой стала бы она, если бы в компоненте C5 было 25 багов, то медианой осталась бы C6, а если бы и в C4 и в C5 было бы 25 багов, то медианой стала бы C5)

Собсвтенно решение:

Пунт 1

Для вывода информаци по кадому проекту я построил впомогательно представление которое получает отношение "проект  - статус  - количество"

-- View: statustoproject

-- DROP VIEW statustoproject;

CREATE OR REPLACE VIEW statustoproject AS 
 SELECT count(bug.status_id) AS count, bug.status_id AS status, component.project_id AS project
   FROM bug
   JOIN bugcomponent ON bug.num = bugcomponent.bug_num
   JOIN component ON bugcomponent.component_id = component.id
  GROUP BY component.project_id, bug.status_id
  ORDER BY component.project_id, bug.status_id;

ALTER TABLE statustoproject
  OWNER TO postgres;

Далее, собственно, выбираю из вспомогательной выбоки и строю нужное отношение.

-- View: bussummaryperproject

-- DROP VIEW bussummaryperproject;

CREATE OR REPLACE VIEW bussummaryperproject AS 
 SELECT project.id AS project_id, COALESCE(( SELECT statustoproject.count
           FROM statustoproject
          WHERE statustoproject.project = project.id AND statustoproject.status = 1), 0::bigint) AS new, COALESCE(( SELECT statustoproject.count
           FROM statustoproject
          WHERE statustoproject.project = project.id AND statustoproject.status = 2), 0::bigint) AS assigned, COALESCE(( SELECT statustoproject.count
           FROM statustoproject
          WHERE statustoproject.project = project.id AND statustoproject.status = 3), 0::bigint) AS fixed, COALESCE(( SELECT statustoproject.count
           FROM statustoproject
          WHERE statustoproject.project = project.id AND statustoproject.status = 4), 0::bigint) AS verified, COALESCE(( SELECT statustoproject.count
           FROM statustoproject
          WHERE statustoproject.project = project.id AND statustoproject.status = 5), 0::bigint) AS "not reproducible"
   FROM project;

ALTER TABLE bussummaryperproject
  OWNER TO postgres;

Пункт 2

Самым сложным для меня было создать функцию медианы

CREATE OR REPLACE FUNCTION array_median(numeric[])
  RETURNS numeric AS
$$
    SELECT CASE WHEN array_upper($1,1) = 0 THEN null ELSE asorted[ceiling(array_upper(asorted,1)/2.0)] END
    FROM (SELECT ARRAY(SELECT ($1)[n] FROM
generate_series(1, array_upper($1, 1)) AS n
    WHERE ($1)[n] IS NOT NULL
            ORDER BY ($1)[n]
) As asorted) As foo ;
$$
  LANGUAGE 'sql' IMMUTABLE;
CREATE  AGGREGATE median(numeric) (
                  SFUNC=array_append,
                  STYPE=numeric[],
                  FINALFUNC=array_median
                );

Далее как и в первом примере я строю вспомогательное представление.

-- View: statustocomponent

-- DROP VIEW statustocomponent;

CREATE OR REPLACE VIEW statustocomponent AS 
 SELECT DISTINCT count(bug.status_id) AS count, bug.status_id AS status, component.id, component.title AS componentname
   FROM bug
   JOIN bugcomponent ON bug.num = bugcomponent.bug_num
   JOIN component ON bugcomponent.component_id = component.id
  GROUP BY component.id, bug.status_id
  ORDER BY component.id, bug.status_id;

ALTER TABLE statustocomponent
  OWNER TO postgres;

Далее строю нужно отношение.

-- View: statisticsperstatus

-- DROP VIEW statisticsperstatus;

CREATE OR REPLACE VIEW statisticsperstatus AS 
 SELECT bugstatus.value AS status, COALESCE(( SELECT statustocomponent.componentname
           FROM statustocomponent
          WHERE statustocomponent.status = bugstatus.id
          ORDER BY statustocomponent.count DESC, statustocomponent.componentname
         LIMIT 1), 'null'::character varying) AS max_component, COALESCE(( SELECT max(statustocomponent.count) AS max
           FROM statustocomponent
          WHERE statustocomponent.status = bugstatus.id), 0::bigint) AS max_value, COALESCE(( SELECT statustocomponent.componentname
           FROM statustocomponent
          WHERE statustocomponent.status = bugstatus.id
          ORDER BY statustocomponent.count, statustocomponent.componentname
         LIMIT 1), 'null'::character varying) AS min_component, COALESCE(( SELECT min(statustocomponent.count) AS min
           FROM statustocomponent
          WHERE statustocomponent.status = bugstatus.id), 0::bigint) AS min_value, COALESCE(( SELECT statustocomponent.componentname
           FROM statustocomponent
          WHERE statustocomponent.status = bugstatus.id AND statustocomponent.count::numeric = (( SELECT median(statustocomponent.count::numeric) AS median
                   FROM statustocomponent
                  WHERE statustocomponent.status = bugstatus.id))
          ORDER BY statustocomponent.componentname
         LIMIT 1), 'null'::character varying) AS median_component, COALESCE(( SELECT median(statustocomponent.count::numeric) AS median
           FROM statustocomponent
          WHERE statustocomponent.status = bugstatus.id), 0::numeric) AS median_value
   FROM bugstatus;

ALTER TABLE statisticsperstatus
  OWNER TO postgres;

Работет все это достаточно шутсро, поэтому решиние претендует быть верным.
Вомозможно я неверно лексиграфически упрорячиваю названия компонент

Дамп базы dbhw9.sql

Filed under: CSC No Comments
9Nov/140

CSC Базы данных, 8 дз

У нас в багтрекере появилось новое требование: мы хотим хранить историю изменений статуса бага. Мы хотим иметь возможность видеть отчет типа

Баг №1: Improve overall performance дата | статус 01.09.2014 new 02.09.2014 assigned 25.09.2014 fixed 26.09.2014 assigned 06.10.2014 fixed 07.10.2014 verified

Мы не планируем выполнять такие отчеты часто, нам все же в основном будет интересен актуальный статус всех багов. Поэтому идея вносить дополнительный атрибут “дата” в таблицу Bug, или же наоборот, убирать атрибут status_id из таблицы Bug кажется не очень хорошей (но вы можете попробовать это сделать и оценить, насколько она в действительности плоха. Оценить можно написав, например, запрос, выдающий актуальные статусы всех багов)

с точки зрения клиента, в идеале, процедура изменения статуса не должна измениться вообще, и должно быть достаточно выполнить запрос вида

UPDATE Bug SET status_id = 2 WHERE num=1

Поиск актуальных статусов, в идеале, тоже должен остаться прежним:

SELECT * FROM Bug

А историю клиенту хочется получать запросом вида

SELECT * FROM BugHistory WHERE num=1

где BugHistory — это отдельная таблица, или представление.

Задание решается достаточно просто.
А имеено: с помощью триггреров. Наша задача при добавлении бага начинать его историю, и при каждом UPDATE если статус был изменен вносить строку в таблицу истории. Далее приведены функции.

CREATE OR REPLACE FUNCTION log_update()
  RETURNS trigger AS
$BODY$
BEGIN
    IF NEW.status_id <> OLD.status_id THEN
        INSERT INTO BugHistory(num, date, status_id)
        VALUES (OLD.num, now()::date, NEW.status_id)    
    END IF;    
    RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER update_log_status
BEFORE UPDATE
ON bug
FOR EACH ROW
EXECUTE PROCEDURE log_update();
CREATE OR REPLACE FUNCTION log_insert()
  RETURNS trigger AS
$BODY$
BEGIN
    INSERT INTO bughistory(num, date, status_id)
    VALUES (NEW.num, now()::date, NEW.status_id);
    RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER insert_log_status
AFTER INSERT
ON bug
FOR EACH ROW
EXECUTE PROCEDURE log_insert();

 

Структура созданной таблицы bughistory

CREATE TABLE bughistory
(
  num integer,
  date date,
  status_id integer,
  id serial NOT NULL,
  CONSTRAINT pk_bhis PRIMARY KEY (id ),
  CONSTRAINT status_fk FOREIGN KEY (status_id)
      REFERENCES bugstatus (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT to_bug_table FOREIGN KEY (num)
      REFERENCES bug (num) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE
);
ALTER TABLE bughistory
  OWNER TO postgres;

-- Index: fki_status_fk

-- DROP INDEX fki_status_fk;

CREATE INDEX fki_status_fk
  ON bughistory
  USING btree
  (status_id );

-- Index: fki_to_bug_table

-- DROP INDEX fki_to_bug_table;

CREATE INDEX fki_to_bug_table
  ON bughistory
  USING btree
  (num );

Так же прилагаю дамп базы данных dbhw8

Filed under: CSC No Comments