«Различающиеся» объекты из реляционной базы данных - PullRequest
9 голосов
/ 11 мая 2009

Наше приложение win32 собирает объекты из данных в нескольких таблицах в реляционной базе данных MySQL. Для такого объекта несколько ревизий хранятся в базе данных.

При хранении нескольких ревизий чего-либо, рано или поздно вы зададите себе вопрос, сможете ли вы визуализировать различия между двумя ревизиями :) Итак, мой вопрос: что было бы хорошим способом для "сравнения" двух такие объекты базы данных?

  • Сделали бы вы сравнение на уровне базы данных? (Звучит не очень хорошая идея: слишком низкий уровень и слишком чувствительный к схеме).
  • Не могли бы вы сравнить объекты?
    • Не могли бы вы написать функцию, которая "вручную" сравнивает свойства и поля двух объектов?
    • Как бы вы сохранили различия? В отдельном родовом объекте "TDiff"?
    • Какие-либо общие рекомендации о том, как визуализировать такие вещи в пользовательском интерфейсе?

Советы или истории о вашем собственном опыте с этим, очень приветствуются; спасибо большое!

Дополнительная информация о прецеденте (20090515)

В ответ на комментарий Антония: это специальное приложение используется для составления расписания учебных курсов, проводимых командами учителей. Расписание учителя хранится в различных таблицах базы данных и содержит такую ​​информацию, как «куда она должна идти в какой день», «кто ее коллеги по команде» и т. Д. Эта информация разбросана по нескольким таблицы.

Время от времени мы «публикуем» расписание, чтобы учителя могли видеть его на веб-странице. Каждая «публикация» представляет собой ревизию, и мы хотели бы иметь возможность показать пользователям (а затем и учителям), что изменилось между двумя публикациями - если что.

Надеюсь, что это делает сценарий более осязаемым:)

Несколько заключительных замечаний

Ну, щедрость подошла к концу, поэтому я принял ответ. Если бы можно было как-то вырезать пару лишних сотен из моего представителя и дать его другим ответам, я бы сделал это без колебаний. Вся помощь твоих парней была отличной, и я очень благодарен! ~ Onno 20090519

Ответы [ 12 ]

8 голосов
/ 13 мая 2009

Просто идея, но стоит ли вам преобразовывать две сравниваемые версии объекта в некоторый текстовый формат, а затем сравнивать эти текстовые объекты, используя существующую программу сравнения, например diff? Есть много хороших программ сравнения, которые могут предложить хорошие визуальные представления и т. Д.

Так например

Текстовая версия Объекта 1:

first_name: Harry
last_name: Lime
address: Wien
version: 0.1

Текстовая версия Объекта 2:

first_name: Harry
last_name: Lime
address: Vienna
version: 0.2

Разница будет что-то вроде:

3,4c3,4
< address: Wien
< version: 0.1
---
> address: Vienna
> version: 0.2
4 голосов
/ 16 мая 2009

Предположим, что у класса есть 5 известных свойств - дата, время, тема, контур, местоположение. Когда я смотрю на свое расписание, меня больше всего интересует самая последняя (то есть текущая / точная) версия этих свойств. Также было бы полезно узнать, что изменилось. (В качестве примечания: если дата, время или место изменились, я также ожидал, что мне будет отправлено электронное письмо / смс с уведомлением, если я не проверю обновленное расписание: -))

Я бы предположил, что 'diff' выполняется во время изменения графика. Итак, когда создается версия 2 класса, запишите, какие значения изменились, и сохраните это в двух полях «changelog» объекта версии 2 (уже должна быть одна родительская таблица, расположенная поверх всех ваших таблиц - используйте эту! ). Одним из полей журнала изменений является «читаемый человеком текст», например «Дата изменена с понедельника 1 мая на вторник 2 мая, время изменено с 10:00 до 10:30». Второе поле журнала изменений представляет собой разделенный список измененных полей, например, «дата, время». Для этого перед сохранением необходимо выполнить циклическое переключение значений, представленных пользователем, сравнить их с текущими значениями базы данных и объединить 2 строки, одну для чтения человеком, одну список имен полей. Затем обновите данные и установите свои сцепленные строки в качестве значений 'changelog'.

При отображении расписания загружайте текущую версию по умолчанию. Переберите поля в списке полей журнала изменений и аннотируйте отображение, чтобы показать, что значение изменилось (* или выделение и т. Д.). Затем на отдельной панели отобразится удобочитаемый журнал изменений.

Если в расписание вносятся изменения более одного раза, вы, вероятно, захотите объединить журналы изменений между версиями 1 и 2 и 2 и 3. Скажите, что в версии 3 изменился только план курса - если это был единственный журнал изменений, который вы имели, когда при отображении расписания изменения даты и времени отображаться не будут.

Обратите внимание, что этот денормализованный подход не будет подходить для анализа - например, для определения, в каком конкретном месте всегда есть классы, из которых оно изменилось, - но вы можете расширить его, используя модель E-A-V для хранения журнала изменений.

2 голосов
/ 17 мая 2009

Сравнение на уровне базы данных было бы хорошо, если бы вы заботились об изменениях в базе данных. Это имеет смысл, если вы пытаетесь создать слой общей функциональности поверх самой базы данных.

Выполнение сравнения на уровне объекта было бы хорошо, если вы заботитесь об изменениях данных. Например, если данные были входными данными для программы, и вам было интересно посмотреть на изменения во входных данных, чтобы убедиться, что изменения в выходных данных были правильными.

Ваш вариант использования не соответствует ни одному из них. Вы, кажется, заботитесь о результатах и ​​хотите различия с этой точки зрения. Если это так, я бы делал различия в выходном отчете (или в его текстовой версии) вместо базовых данных. Вы можете сделать это с помощью любого стандартного инструмента сравнения. Чтобы упростить работу конечных пользователей, вы можете проанализировать результаты сравнения и отобразить их в виде HTML. Здесь есть много вариантов: бок о бок с цветовой кодировкой для обозначения изменений, один документ с разметкой для изменений (например, красный зачеркнутый для удалений и зеленый для добавлений), возможно, просто выделите измененные области и используйте всплывающие подсказки для предыдущие / текущие значения по запросу.

Я думал о сравнении баз данных, но никогда не пытался это реализовать. Как вы заметили, любые такие попытки тесно связаны со схемой.

Я провел сравнение на уровне объекта. Общий алгоритм был такой:

  1. Выполнить сравнение наборов в списках идентификаторов объектов. Это создает три группы результатов: добавленные объекты, удаленные объекты и объекты, которые живут в обоих наборах.
  2. Сообщить об удалении.
  3. Сообщить о дополнениях.
  4. Для вещей в обоих наборах выполните сравнение по атрибутам.
  5. Если обнаружены какие-либо различия, сообщите идентификатор объекта, отличающиеся атрибуты и соответствующие значения. При необходимости выделите часть значения атрибута, которая изменилась.

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

1 голос
/ 15 ноября 2010

Вы смотрели на Open Source DiffKit?

www.diffkit.org

Я думаю, что он делает то, что вы хотите.

1 голос
/ 19 мая 2009

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

Для пользовательского метода вам нужно создать класс, который принимает два экземпляра классов для сравнения. Затем он возвращает различия;

 public class Person
 {
     public string name;
 }

 public class PersonComparer
 {
     public PersonComparer(Person old, Person new)
     {
        ....         
     }

     public bool NameIsDifferent() { return old.Name != new.Name; }
     public string NameDifferentText() { return NameIsDifferent() ? "Name changed from " + old.Name + " to " + new.Name : ""; }
 }

Таким образом, вы можете использовать объект NameComparer для создания вашего GUI.

Герерический подход был бы почти таким же, просто вы обобщаете вызовы и используете поиск объектов (вызов getObjectProperty ниже), чтобы найти различия;

 public class ObjectComparer()
 {
    public ObjectComparer(object old, object new)
    {
        ...
    }

    public bool PropertyIsDifferent(string propertyName) { return getObjectProperty(old, propertyName) != getObjectProperty(new, propertyName) };

     public string PropertyDifferentText(string propertyName) { return PropertyIsDifferent(propertyName) ? propertyName + " " + changed from " + getObjectProperty(old, propertyName) + " to " + getObjectProperty(new, propertyName): ""; }
 }
}

Я бы пошел на секунду, поскольку это действительно облегчает изменение графического интерфейса при необходимости. Графический интерфейс Я бы попытался «пожелтеть» различия, чтобы их было легко увидеть, но это зависит от того, как вы хотите показать различия.

Получение объекта для сравнения будет означать загрузку вашего объекта с начальной и последней ревизиями.

Мои 2 цента ... Не так много, как в базе данных, сравнивайте уже здесь.

1 голос
/ 19 мая 2009

Я перебиваю то, что предложил Гарри Лайм: выведите свои свойства в текстовый формат, затем хешируйте результаты. Таким образом, вы можете сравнить значения хеш-функции и легко пометить данные, которые были изменены. Таким образом, вы получаете лучшее из обоих миров, поскольку вы можете визуально видеть различия, но программно выявлять различия. Имея has, у вас будет хороший источник для индекса, если вы хотите сохранить и извлечь дельты.

1 голос
/ 19 мая 2009

Это на самом деле не ответ на заданный вами вопрос, а попытка переосмыслить проблему. Рассматриваете ли вы изменение базы данных и объектной модели для хранения совокупного корня и серии дельт? То есть моделировать и хранить RevisionSets, которые являются коллекциями Revision; Редакция - это свойство объекта в сочетании со значением. В некотором смысле это привносит структуру ревизии в вашу архитектуру, которую другие авторы предлагают вам добавить к тому, что у вас уже есть, через «логи».

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

Благодарность за основную идею принадлежит Грегу Янгу и его работе с потоками финансовых данных, но она неизбежно применима к вашей проблеме.

1 голос
/ 14 мая 2009

В вашей ситуации в PostgreSQL я использовал таблицы разностей со схемой:

history_columns (
    column_id smallint primary key,
    column_name text not null,
    table_name text not null,
    unique (table_name, column_name)
);
create temporary sequence column_id_seq;
insert into history_columns
select nextval('column_id_seq'), column_name, table_name
    from information_schema.columns
    where
        table_name in ('table1','table2','table3')
        and table_schema=current_schema() and table_catalog=current_database();

create table history (
    column_id smallint not null references history_columns,
    id int not null,
    change_time timestamp with time zone not null
        constraint change_time_full_second -- only one change allowed per second
            check (date_trunc('second',change_time)=change_time),
    primary key (column_id,id,change_time),
    value text
);

И на столах я использовал триггер, подобный этому:

create or replace function save_history() returns trigger as
$$
    if (tg_op = 'DELETE') then
        insert into historia values (
            find_column_id('id',tg_relname), OLD.id,
            date_trunc('second',current_timestamp),
            OLD.id );
        [for each column_name] {
            if (char_length(OLD.column_name)>0) then
                insert into history values (
                    find_column_id(column_name,tg_relname), OLD.id,
                    OLD.change_time, OLD.column_name
                )
        }
    elsif (tg_op = 'UPDATE') then
        [for each column_name] {
            if (OLD.column_name is distinct from NEW.column_name) then
                insert into history values (
                    find_column_id(column_name,tg_relname), OLD.id,
                    OLD.change_time, OLD.column_name
                );
            end if;
        }
    end if;
$$ language plpgsql volatile;

create trigger save_history_table1
    before update or delete on table1
    for each row execute procedure save_history();
1 голос
/ 13 мая 2009

Я бы подумал о каком-то типичном текстовом представлении объектов и позволил бы сравнить тексты с существующим инструментом сравнения, таким как WinMerge.

Я не вижу необходимости придумывать саму себя, потому что уже есть много хороших инструментов, которые я могу использовать.

1 голос
/ 13 мая 2009

Мой dbscript приложения сравнивает иерархические данные (схемы базы данных) в хранимой процедуре, которая, конечно, должна сравнивать каждое поле / свойство каждого объекта с его аналогом. Я думаю, что вы не сможете обойти этот шаг (если у вас нет общей модели описания объекта)

Что касается пользовательского интерфейса вашего вопроса, взгляните на скриншоты, чтобы просмотреть и выбрать различия.

...