3.7. Обеспечение ссылочной целостности данных

Вернемся к рис. 2.1. Важной особенностью Microsoft Visual FoxPro является автоматическое обеспечение ссылочной целостности данных. Если на связь между таблицами наложены условия ссылочной целостности, то добавление в связанную таблицу записи, для которой нет соответствующих записей в главной таблице, становится невозможным.

Проверка целостности данных может осуществляться и программными средствами. Например, при добавлении в таблицу Building описания нового здания, вы можете проверить, имеется ли в таблице District район, в котором расположено это здание. Однако более правильным является определение условия целостности данных на уровне базы данных, так как в этом случае ни одно приложение не может нарушить целостность данных. С базой данных может работать несколько приложений, в том числе и не только ваших.

Несколько дополнительных возможностей. Откройте окно базы данных. В главном меню Visual FoxPro щелкните пункт File и выберите команду Open. В открывшемся окне в ячейке Тип файлов выберите пункт Database (*.dbc). Перейдите в папку DBF. Выберите нашу базу данных Real Estate.dbc. Обязательно поставьте флажок  в ячейке Open exclusive. Если этого не сделать, то вам будут недоступны опции по работе с базой данных, которые мы сейчас рассмотрим. Щелкните по кнопке OK. Появится окно базы данных. В главном меню Visual FoxPro выберите пункт Database, а в открывшемся подменю пункт Edit Referential Integrity. Появится окно конструктора ссылочной целостности (рис. 3.16).

 


Рис. 3.16. Конструктор ссылочной целостности базы данных

 

В нижней части конструктора перечислены все связи между таблицами (каждая на отдельной строке). В первых двух столбцах приводятся названия родительской и дочерней таблиц. В следующих трех - Update (Обновить), Delete (Удалить) и Insert (Вставить) - указаны правила соблюдения целостности. В начале работы все эти три столбца содержат элемент Ignore (Игнорировать). Однако вы можете сами определить правила поведения для каждой связи и выполняемого действия. Наконец, в последних двух столбцах определены родительский и дочерний индексы, участвующие в отношении.

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

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

·       Cascade (Последовательно выполнить). При выборе этой опции обновляются все "дочерние" записи в соответствии с новым значением ключа в родительской таблице, если у них совпадало старое значение "родительского" ключа.

·       Restrict (Ограничить). Если в дочерней таблице есть связанные записи (т.е. существуют записи с текущим значением родительского ключа), то FoxPro запрещает обновление родительского ключа.

·       Ignore (Игнорировать). При выборе этой опции система прекращает следить за соблюдением правил ссылочной целостности и разрешает обновление родительского ключа независимо от наличия связанных записей в дочерних таблицах.

Как видите, все эти опции объединены в группу переключателей. При щелчке на одном из переключателей изменяется правило целостности для выделенной связи между таблицами в соответствии с выбранной опцией переключателя. Итак, у вас есть два способа выбора правил ссылочной целостности: с помощью группы переключателей в верхней части окна построителя и с помощью меню в нижней его части.

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

·       Restrict. Запрещается вставка дочерней записи, если нет родительской записи с таким же значением ключа.

·       Ignore. He выполняется никаких проверок в целях сохранения ссылочной целостности, т.е. вставка заведомо разрешается.

Определив правила ссылочной целостности для каждой связи между таблицами и действия, щелкните на кнопке ОК, чтобы выйти из конструктора. Что касается нашего примера, то, скорее всего, нам стоит разрешить последовательное выполнение обновлений ключа в таблице Flat после того, как он обновится в таблице Building. С другой стороны, следует запретить удаление записей таблицы Building, если существуют связанные записи в таблице Flat. И наконец, имеет смысл запретить вставку записи в таблицу Flat, если в таблице Building нет записи с таким же значением ключа.

При щелчке на кнопке ОК отображается диалоговое окно с вопросом о сохранении внесенных изменений и генерируется код ссылочной целостности, после чего построитель завершает свою работу. При этом в базе данных создается набор триггеров и хранимых процедур. Если в базе данных ранее были определены триггеры или хранимые процедуры, то перед их перезаписью создается резервная копия. Она помещается в файл Risp.old в текущую папку. Хранимые процедуры, созданные ранее для других целей (например, для соблюдения правил контроля данных), придется вручную скопировать из резервной копии.

После того как построитель ссылочной целостности завершит свою работу, можно открыть окно Table Designer (рис. 3.14) и выбрать в нем третью вкладку Table, чтобы просмотреть добавленные триггеры. В качестве альтернативного варианта можно просмотреть хранимые процедуры, щелкнув на кнопке Edit Stored Procedure (Редактировать хранимую процедуру), расположенной на панели инструментов Database Designer в окне конструктора баз данных. Приведем текст хранимой в базе данных процедуры _ri_update_flat(). Она будет запущена на выполнение автоматически при изменении значения первичного ключа.

 

procedure __RI_UPDATE_flat

** "Referential integrity update trigger for" flat

LOCAL llRetVal

llRetVal = .t.

PRIVATE pcParentDBF,pnParentRec,pcChildDBF,pnChildRec,;

        pcParentID,pcChildID

PRIVATE pcParentExpr,pcChildExpr

STORE "" TO pcParentDBF,pcChildDBF,pcParentID,pcChildID,;

            pcParentExpr,pcChildExpr

STORE 0 TO pnParentRec,pnChildRec

IF _triggerlevel=1

  BEGIN TRANSACTION

  PRIVATE pcRIcursors,pcRIwkareas,pcRIolderror,pnerror,;

  pcOldDele,pcOldExact,pcOldTalk,pcOldCompat,PcOldDBC

  pcOldTalk=SET("TALK")

  SET TALK OFF

  pcOldDele=SET("DELETED")

  pcOldExact=SET("EXACT")

  pcOldCompat=SET("COMPATIBLE")

  SET COMPATIBLE OFF

  SET DELETED ON

  SET EXACT OFF

  pcRIcursors=""

  pcRIwkareas=""

  pcRIolderror=ON("error")

  pnerror=0

  ON ERROR pnerror=rierror(ERROR(),message(),message(1),program())

  IF TYPE('gaErrors(1)')<>"U"

    release gaErrors

  ENDIF

  PUBLIC gaErrors(1,12)

  pcOldDBC=DBC()

  SET DATA TO ("REAL ESTATE")

ENDIF first trigger

LOCAL lcParentID && parent's value to be sought in child

LOCAL lcOldParentID && previous parent id value

LOCAL lcChildWkArea && child work area handle returned by riopen

LOCAL lcChildID && child's value to be sought in parent

LOCAL lcOldChildID && old child id value

LOCAL lcParentWkArea && parentwork area handle returned by riopen

LOCAL lcStartArea

lcStartArea=select()

llRetVal=.t.

lcParentWkArea=select()

SELECT (lcParentWkArea)

pcParentDBF=dbf()

pnParentRec=recno()

lcOldParentID=OLDVAL("ACCOUNT")

pcParentID=lcOldParentID

pcParentExpr="ACCOUNT"

lcParentID=ACCOUNT

IF lcParentID<>lcOldParentID

  lcChildWkArea=riopen("account")

  IF lcChildWkArea<=0

    IF _triggerlevel=1

      DO riend WITH .F.

    ENDIF at the end of the highest trigger level

    SELECT (lcStartArea)

    RETURN .F.

  ENDIF not able to open the child work area

  pcChildDBF=dbf(lcChildWkArea)

  SELECT (lcChildWkArea)

  SCAN FOR ACCOUNT=lcOldParentID

    pnChildRec=recno()

    pcChildID=ACCOUNT

    pcChildExpr="ACCOUNT"

    IF NOT llRetVal

      EXIT

    ENDIF && not llretval

    llRetVal=riupdate("ACCOUNT",lcParentID,"FLAT")

  ENDSCAN get all of the account records

  =rireuse("account",lcChildWkArea)

  IF NOT llRetVal

    IF _triggerlevel=1

      DO riend WITH llRetVal

    ENDIF at the end of the highest trigger level

    SELECT (lcStartArea)

    RETURN llRetVal

  ENDIF

ENDIF this parent id changed

SELECT (lcParentWkArea)

pcParentDBF=dbf()

pnParentRec=recno()

lcOldParentID=OLDVAL("STR(STREET)+HOUSE+STR(FLAT)")

pcParentID=lcOldParentID

pcParentExpr="STR(STREET)+HOUSE+STR(FLAT)"

lcParentID=STR(STREET)+HOUSE+STR(FLAT)

IF lcParentID<>lcOldParentID

  lcChildWkArea=riopen("owners")

  IF lcChildWkArea<=0

    IF _triggerlevel=1

      DO riend WITH .F.

    ENDIF at the end of the highest trigger level

    SELECT (lcStartArea)

    RETURN .F.

  ENDIF not able to open the child work area

  pcChildDBF=dbf(lcChildWkArea)

  SELECT (lcChildWkArea)

  SCAN FOR STR(STREET)+HOUSE+STR(FLAT)=lcOldParentID

    pnChildRec=recno()

    pcChildID=STR(STREET)+HOUSE+STR(FLAT)

    pcChildExpr="STR(STREET)+HOUSE+STR(FLAT)"

    llRetVal=riupdate("STREET",VAL(substr(lcParentID,1,10)),;

            "flat")

    IF NOT llRetVal

      EXIT

    ENDIF && not llretval

    llRetVal=riupdate("HOUSE",substr(lcParentID,11,4),"flat")

    IF NOT llRetVal

      EXIT

    ENDIF && not llretval

    llRetVal=riupdate("FLAT",VAL(substr(lcParentID,15,10)),"flat")

    IF NOT llRetVal

      EXIT

    ENDIF && not llretval

  ENDSCAN get all of the owners records

  =rireuse("owners",lcChildWkArea)

  IF NOT llRetVal

    IF _triggerlevel=1

      DO riend WITH llRetVal

    ENDIF at the end of the highest trigger level

    SELECT (lcStartArea)

    RETURN llRetVal

  ENDIF

ENDIF this parent id changed

IF _triggerlevel=1

  do riend with llRetVal

ENDIF at the end of the highest trigger level

SELECT (lcStartArea)

RETURN llRetVal

** "End of Referential integrity Update trigger for" flat

 

Мы очень резко окунулись в код Microsoft Visual FoxPro. Не пугайтесь! Разработчики Microsoft так далеко обогнали нас, прикладников, что даже не смешно. И все-таки со временем вы разберетесь в том, что делает этот код, а сейчас - примите его к сведению. При изменении любой из таблиц, влияющих на состояние ссылочной целостности базы данных, их индексы (или постоянные отношения) перезапускают построитель ссылочной целостности. Это приводит к пересмотру кода в соответствии с выполненными изменениями.

Как видите, с помощью построителя Referential Integrity Builder можно быстро и легко добавлять общие правила ссылочной целостности к отношениям между табли­цами в базе данных, не особенно беспокоясь о том, как это реализуется.