Советы начинающим Часть II

В этой статье :

Таблица

Собственно работа с ключевыми полями таблицы

ТАБЛИЦА

Это термин, который очень часто употребляется в FoxPro и также часто он употребляется совсем не в том смысле, как его понимают новички. Можно сказать, что у этого термина есть два определения (как и у термина "база данных")

Таблица - это файл с расширением DBF и связанные с ним файлы с тем же именем, но с расширением FPT (файл для хранения полей типа Memo и General) и с расширением CDX (структурный индексный файл)

Формально, это абсолютно правильное определение. Проблема только в том, что в подавляющем большинстве случаев, когда в FoxPro употребляют термин "таблица", то под этим подразумевают вовсе не это. Точнее, не совсем это.

Таблица - это некий образ файла с расширением DBF открытый в указанной сессии данных и в указанной рабочей области.

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

Алиас (alias) - это "псевдоним" файла DBF открытого в какой-либо рабочей области. Как правило, алиас совпадает с именем файла DBF. Однако это не всегда так. Дело в том, что в одном сеансе данных не может быть двух одинаковых алиасов. Поэтому, если алиас таблицы не указан явно при ее открытии (опция ALIAS в команде USE, или каким-либо другим способом), то FoxPro самостоятельно назначит уникальный алиас для таблицы, начав разумееется с алиаса совпадающего с именем файла DBF если это возможно.

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

В версиях FoxPro 2.x в том смысле, в котором используется термин "таблица" использовался термин "база данных" (видимо отсюда пошло расширение - первые буквы английской фразы DataBase File), поскольку в тех версиях еще не существовало файла DBC. Соответственно, когда программисты переходят на версию Visual FoxPro, то их бывает достаточно трудно понять из-за этой путаницы с терминами.

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

Рабочая область (Work area) - это некий числовой идентификатор, который может быть присвоен таблице. Если у Вас был опыт программирования в других языках, то можно сказать, что рабочая область - это "хэндл" или "дескриптор" таблицы внутри среды FoxPro

Одновременно одной рабочей области может соответствовать только одна таблица, в то время как одной таблице может соответствовать несколько рабочих областей. Другими словами, одну и ту же таблицу можно одновременно открыть в нескольких рабочих областях, но в одной конкретной рабочей области может быть открыта только одна таблица.

До тех пор, пока таблица не будет открыта в какой-либо рабочей области она для FoxPro как бы не существует. Точнее с ней нельзя производить никакие операции по чтению/модификации данных используя штатные команды и функции.

Кроме понятия "рабочая область" в FoxPro введено понятие "сеанс данных" (DataSession)

Сеанс данных (DataSession) - это некоторая динамическая копия среды FoxPro. Открывая среду FoxPro Вы автоматически открываете сеанс данных, который автоматически же и завершается при выходе из FoxPro. Но внутри собственно FoxPro Вы имеете возможность сделать как бы копию среды FoxPro используя так называемые "частные сеансы данных" (Private DataSession)

Что собственно дает эти "частные сеансы данных"? А дает это возможность симулировать внутри одного приложения FoxPro работу нескольких, независимых друг от друга пользователей.

В результате таблицы открытые в одном сеансе данных "не видны" в другом сеансе данных. И соответственно манипуляции (не все) производимые с таблицами в одном сеансе данных не влияют на другие сеансы данных.

Самый распростарненный случай применения Private DataSession - это одновременное открытие нескольких форм, рассматривающих один и тот же документ с разных сторон. Как правило, для этой цели между таблицами устанавливается связь по SET RELATION. Если открыть подобные формы в одном сеансе данных, то зачастую это становится большой проблемой, поскольку в одной форме требуется наложить на таблицы одни индексы и связи, а в другой - другие. И переключение из одной формы в другую приводит к непредсказуемым изменениям содержимого.

НАЗВАНИЕ ТАБЛИЦЫ

Файл таблицы, как и любой другой файл в системе Windows может содержать до 128 символов, содержать пробелы, русские символы, цифры и некоторые спец.символы. Однако для упрощения работы в FoxPro я бы порекомендовал следующие ограничения в наименовании файла таблицы

  • Не использовать в названии русские символы - причина этой рекомендации в том, что FoxPro разрабатывался прежде всего для англоязычных пользователей и использование в нем символов другого языка - это уже последующее дополнение. Как следствие, велик риск, что чего-то, где-то недосмотрели и при определенных ситуациях русские буквы в имени вызовут неожиданные глюки
  • Не использовать в названии пробелы - в принципе, ошибок использование пробелов не вызовет, но несколько усложнит сам процесс программирования, поскольку имена и пути доступа, содержащие пробелы необходимо заключать в кавычки. Просто добавит лишней заботы - не забывать кавычки. А зачем усложнять себе жизнь, когда без этого легко можно обойтись.
  • Ограничивайте длину названия 8 символами и не используйте в названии цифр и спец.символов - в отличии от аналогичной рекомендации в отношении наименования файла база данных этому есть причина. Причина не настолько явная, чтобы ее описать в двух словах. Но цепочка рассуждений приведшая к этой рекомендации основана на рекомендации по наименованию ключевых полей таблиц и наименований индексных тэгов. Прочтя эти разделы Вам станет понятна причина этой рекомендации.
  • Не называйте таблицу также как и одно из его полей - разумеется ошибки это не вызовет, но усложнит понимание написанного кода самим программистом. Не всегда с ходу можно однозначно определить, что речь идет именно о таблице, а не о поле таблицы. А если еще и их названия совпадают, то совсем тяжело становится.
  • Не используйте для названия одно из зарезервированных в FoxPro слов - опять же, ошибки это не вызовет, но резко снизит "читабельность" кода. Ведь зарезервированные слова автоматически подсвечиваются опеределенным цветом (если Вы используете стандартный текстовый редактор FoxPro) и с ходу становится проблематично отличить опцию или команду от имени таблицы
  • Не используйте псевдонимы таблицы внутри базы данных - в данном случае речь идет о том, что внутри базы данных можно присвоить таблице псевдоним, отличный от имени файла DBF. Речь  не идет об опции ALIAS в команде USE. Это несколько другое. Если Вы войдете в режим модификации структуры таблицы, и перейдете на закладку "Table", то увидите, что в опции "Name" стоит имя, совпадающее с именем файла DBF. Вот это-то имя и можно изменить, присвоив таблице некоторый "псевдоним" по которому и будут обращаться к указанной таблице. Лично я не рекомендовал бы новичкам использование подобных псевдонимов, поскольку это может внести путаницу в сам процесс программирования.

РАСПОЛОЖЕНИЕ ТАБЛИЦЫ

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

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

  • Желательно файл базы данных располагать в той же папке, где и включенные в него файлы DBF

    По большому счету, конечно можно держать файл базы данных в одной директории, а включенные в него файлы DBF в другой. Но это усложняет сам процесс разработки проекта. Например, создание резервной копии в простейшем случе заключается в простом копировании директории с рабочими файлами в другое место. Если же рабочие файлы отделены от файла базы данных, то потребуется уже копирование 2-х директорий. Соответственно усложнится код.

    Кроме того, в этом случае несколько увеличится размер файла базы данных (точнее файла DCT - мемо-файла), поскольку непосредственно в нем хранится относительный путь к включенным в него таблицам. Если таблицы расположены в той же директории, что и сам файл базы данных, то этого относительного пути просто нет. Справедливости ради, следует заметить, что относительный путь к базе данных хранится и в заголовках таблиц, которые в эту базу данных включены. Но на размер таблиц это не влияет, поскольку под этот относительный путь всегда выделяется фиксированное место вне зависимости от факта его наличия.

    СОБСТВЕННО РАБОТА С ТАБЛИЦАМИ

У объекта Grid при указании значения для RecordSourceType под термином "таблица" (Table) подразумевают как раз-таки именно файл DBF, а под термином "алиас" (Alias) - образ файла. Что вызывает огромное количество проблем у новичков с этим объектом. Ни в коем случае не используйте в качестве RecordSourceType указание "Table" - это приведет к непредсказуемому поведению данного объекта. Оставьте значение по-умолчанию "Alias"

[li]Как уже было замечено выше, таблицы всегда открываются в конкретной рабочей области и для перехода в нужную рабочую область есть 2 способа адресации: либо по номеру этой рабочей области, либо по имени алиаса (alias) таблицы открытой в этой рабочей области.

Так вот, я бы рекомендовал всегда адресоваться к рабочей области именно по имени алиаса (alias) таблицы, а не по номеру.

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

Крайне нежелательно динамически в процессе работы модифицировать таблицы или добавлять/удалять таблицы в базе данных. Это сопряжено с большими проблемами и значительным усложнением программного кода.

РАБОЧАЯ ОБЛАСТЬ С НОМЕРОМ 0

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

Такая адресация позволяет делать несколько вещей

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

     USE MyTable IN 0

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

Другое применение нулевой рабочей области - это настройки по умолчанию.

Например, Вы вероятно уже знаете, что любое View по-умолчанию открывается в режиме оптимистической буферизации строк (3), но если Вам необходимо использовать его в режиме оптимистической буферизации таблиц (5), то необходимо после его открытия сделать это переключение используя функцию CursorSetProp() примерно так

   USE MyView IN 0  
  =CursorSetProp('buffering',5,'MyView') 

Однако используя нулевую рабочую область можно сделать глобальную настройку для открытия всех талиц в 5 режиме буферизации примерно так:

   =CursorSetProp('buffering',5,0)  
  USE MyView IN 0 

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

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

БОЛЬШАЯ ТАБЛИЦА

Очень часто в конференциях проскакивает словосочетание "большая таблица".

Я бы сказал, что понятие "большая таблица" весьма субъективно. По моим наблюдениям, в большинстве случаев, под "большой" понимают такую таблицу, выполнение операций чтения/записи с которой занимает ощутимо заметное время. Заметное для пользователя.

Одним из важнейших критериев, влияющих на время выполнения любой операции с таблицей является количество записей в этой таблице. Но тогда встает вопрос: большая таблица - это сколько записей?

Предельно допустимое количество записей, которое может содержаться в одной таблице - это 1 миллиард записей (1 billion). Т.е. единица и девять нулей. Так вот, "большой" можно считать таблицу содержащую не менее нескольких миллионов записей.

Но опять-таки термин "большая" всплывает не тогда, когда оценивают просто количество записей, а когда оцениваю время выполнения некоторых операций с такими таблицами. Но ведь есть множество причин влияющих на время чтения/записи. Соответственно, та таблица, которая считается "большой" в одном случае в другом уже не такая "большая".

КУРСОР

В очередной раз вспоминается лень американцев и их стремление все сокращать (впрочем русские здесь ушли еще дальше - могут сказать почти все используя только несколько специфических слов). Термин "курсор" употребляется сразу в нескольких смыслах в зависимости от контекста.

Курсор - это образ файла DBF открытого в одной из рабочих областей

Курсор - это временная таблица являющаяся результатом выполнения команды Select-SQL

Курсор - это указатель положения индикатора ввода текста с клавиатуры

Ну, последнее определение не очень-то интересно. В том смысле, что здесь все ясно, кроме того, почему этот термин был использован еще и для временных таблиц, ведь слово "cursor" собственно и переводится как "указатель".

КУРСОР КАК ОБРАЗ ФАЙЛ DBF

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

Замечу еще, что курсор, как объект, используется не только как образ файлов DBF, но и как образ View. А View и таблица - это не одно и то же.

КУРСОР КАК ВРЕМЕННАЯ ТАБЛИЦА

Вот это наиболее употребительное использование данного термина. Собственно есть 2 способа создания таких курсоров

Первый способ - это использование команды CREATE CURSOR. Созданный таким способом курсор будет редактируемым. И это будет именно временная таблица, т.е. она будет автоматически уничтожена в момент закрытия. Ну про этот способ сказать особо нечего. Здесь нет каких-то проблем и особенностей

Второй способ - это использование опции CURSOR в команде SELECT-SQL. Примерно в следующем синтаксисе

   SELECT * FROM MyTable INTO CURSOR TmpTable

Вот этот-то TmpTable и есть курсор

В зависимости от различных условий этот курсор может иметь разное физическое "воплощение" и разные свойства

Если SQL-запрос полностью оптимизируем, то вместо создания нового файла будет просто открыта та же самая таблица с наложенным на нее фильтром. Зачастую это очень неприятная неожиданность. Проверить, чем же физически является сформированный курсор, можно используя функцию DBF()

   SELECT * FROM MyTable INTO CURSOR TmpTable  
  ?DBF('TmpTable')

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

Если Вы хотите при любых запросах быть уверенными, что курсор - это именно временная таблица, а не исходная таблица с наложенным фильтром, то Вам следует добавить опцию NOFILTER

   SELECT * FROM MyTable INTO CURSOR TmpTable NOFILTER

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

Однако если курсор - это временная таблица, то это еще не значит, что эта временная таблица будет непременно физически расположена на диске. Вполне возможно, что вся временная таблица целиком поместится в оперативную память. Т.е. функция DBF('TmpTable') будет исправно показывать некий временный файл, но попытка найти его физически на диске окончится неудачей и функция FILE(DBF('TmpTable')) вернет .F.

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

Еще один немаловажный вопрос связан с тем, что полученные таким образом курсоры нельзя редактировать. Они доступны только на чтение. Начиная с 7 версии Visual FoxPro для решения этой проблемы появилась специальная опция ReadWrite

   SELECT * FROM MyTable INTO CURSOR TmpTable NOFILTER READWRITE

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

   SELECT * FROM MyTable INTO CURSOR TmpReadTable NOFILTER  
  USE (DBF('TmpReadTable')) IN 0 AGAIN ALIAS TmpWriteTable  
  USE IN TmpReadTable

Переоткрытый таким образом курсор TmpWriteTable уже можно будет редактировать

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

Все созданные таким образом курсоры автоматически удаляются с диска (если временный файл физически был создан на диске) в момент их закрытия. Если Вы создали для такого курсора структурный индексный файл, то этот файл также будет автоматически удален в момент закрытия курсора.

ФОРМИРОВАНИЕ ИМЕНИ КУРСОРА В КОМАНДЕ SELECT-SQL

Это не такой простой вопрос, как может показаться. Проблема здесь в том, что имя курсора - это фактически алиас (alias) временной таблицы. Но в FoxPro в одном сеансе данных не может быть открыто 2 таблиц с одинаковыми алиасам.

Курсор всегда создается на машине клиента, поэтому конфликтов с другими пользователями можно не опасаться. Более того, даже если запущен дважды один и тот же проект на одной машине все-равно не будет конфликат связанного с одинаковыми именами курсоров, поскольку они открыты в разных сеансах данных. Конфликт возможен если Вы создаете несколько курсоров с одним и тем же именем в одном сеансе данных

Ну например, Вы открыли 2 формы использующих Default DataSession и в обоих создали курсор с одинаковым именем. В этом случае, курсор созданный позднее затрет курсор созданный ранее. При этом настройка SET SAFETY не играет никакой роли. Курсор будет пересоздан молча. Без каких-либо дополнительных запросов.

Избежать подобных конфликтов можно несколькими способами

  • Открывать формы и отчеты только в Private DataSession
  • Самостоятельно следить за уникальностью имен курсоров
  • Использовать функцию для генерации уникальных имен файлов
    Последний вариант кажется наиболее предпочтительным. Однако тут следует быть осторожным. Дело в том, что в описании к FoxPro для генерации уникальных имен файлов предлагается использовать следующую функцию
       lcCursorName=SubStr(SYS(2015),3,10)

    Проблема в том, что функция SYS(2015) может содержать в возвращаемом значении как буквы, так и цифры. Это значит, что при использовании выделения строки по SubStr() Вы вполне можете получить первым символом цифру. А использование в качестве имени переменной цифры в синтаксисе FoxPro недопустимо и Вы неожиданно получите сообщение о синтаксической ошибке. Чтобы этого избежать следует либо принудительно подмешать букву

       lcCursorName='t'+SubStr(SYS(2015),3,10)

    Либо вообще не выделять строку

       lcCursorName=SYS(2015)

    Соответсвенно выполнение запроса станет выглядеть так:

       LOCAL lcCursorName  
      lcCursorName=SYS(2015)  
      SELECT * FROM MyTable INTO CURSOR &lcCursorName NOFILTER

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

    ПОЛЯ ТАБЛИЦЫ

    Поле таблицы - это столбец который Вы видите каждый раз открывая таблицу на просмотр.

    НАЗВАНИЕ ПОЛЕЙ ТАБЛИЦЫ

    Поле таблицы, включенной в {базу данных} может содержать до 128 символов, содержать русские символы, цифры и некоторые спец.символы. Однако для упрощения работы в FoxPro я бы порекомендовал следующие ограничения в наименовании файла таблицы

  • Не использовать в названии русские символы - причина этой рекомендации в том, что FoxPro разрабатывался прежде всего для англоязычных пользователей и использование в нем символов другого языка - это уже последующее дополнение. Как следствие, велик риск, что чего-то, где-то недосмотрели и при определенных ситуациях русские буквы в имени вызовут неожиданные глюки.
  • Не использовать в названии цифры и спец.символы - в принципе, ошибок использование цифр и спец.символов не вызовет. Причина этой рекомендации в повышении сложности восприятия названия.

    Ну, например, если в таблице необходимо указать сумму платежа и сумму налога, то Вы конечно можете создать 2 поля Platezh1 и Platezh2. Но в этом случае, вам каждый раз придется вспоминать, что собственно обозначает цифра 1, а что цифра 2. Если Вы работаете постоянно только с одним проектом, то это не страшно, как-нибудь да запомните. Но если Вы отложили этот проект и вернулсь к нему через несколько месяцев, то напряженные размышления на тему - а что это собственно такое? - Вам обеспечены. Гораздо разумнее дать значимые имена: Platezh и Nalog

  • Если по данному полю Вы создаете простой индексный тэг, выражение которого состоит только из имени поля, то ограничивайте название поля 10 символами - причина здесь в том, что количество символов в названии индексного тэга не может быть больше 10. Это значит, что Вам придется указать в качестве названия индексного тэга что-то отличное от названия поля, по которому этот индекс построен. В принципе, ничего страшного. Однако если имя тэга и имя поля совпадают, то это сильно упрощает процесс программирования и позволяет в некоторых случаях создавать универсальный программный код не зависящий от использования конкретной таблицы.
  • Не называйте таблицу также как и одно из его полей - разумеется ошибки это не вызовет, но усложнит понимание написанного кода самим программистом. Не всегда с ходу можно однозначно определить, что речь идет именно о таблице, а не о поле таблицы. А если еще и их названия совпадают, то совсем тяжело становится.
  • Не используйте для названия одно из зарезервированных в FoxPro слов, в особенности те слова, которые используются в команде Select-SQL - это может вызвать сообщение о синтаксической ошибке, для подавления которой придется сильно усложнить программный код. Кроме того, это снизит "читабельность" кода. Ведь зарезервированные слова автоматически подсвечиваются опеределенным цветом (если Вы используете стандартный текстовый редактор FoxPro) и с ходу становится проблематично отличить опцию или команду от имени поля таблицы.

    Если воображение Вам напрочь отказывает и Вы не знаете как по другому назвать поле кроме как например "Order" или "Group", то добавьте в качестве первого символа букву, обозначающую тип данных, используемых в данном поле. Например, "nOrder" или "cGroup"

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

    СОБСТВЕННО РАБОТА С ПОЛЯМИ ТАБЛИЦЫ

  • Крайне нежелательно динамически в процессе работы модифицировать поля таблицы или добавлять/удалять поля. Это сопряжено с большими проблемами и значительным усложнением программного кода.
  • Желательно при использовании полей таблиц всегда дописывать алиас таблицы для их однозначной идентификации. Разумеется, кроме случаев, когда отсутствие алиаса обусловлено логикой программы.

    Дело в том, что если алиас не указан, то FoxPro предполагает, что речь идет о поле таблицы, расположенной в текущей рабочей области. А если у таблицы в текущей рабочей области нет такого поля, то о переменной памяти с тем же именем. Но при написании относительно сложной программы далеко не всегда можно с уверенностью сказать, что мы находимся в нужной рабочей области. Явное указание алиаса таблицы снимает эту проблему.

  • Заполняйте раздел "Comment" для всех полей таблицы в дезайнере таблиц. Написание комментариев, хотя бы минимальное, в любом случае очень полезная вещь. Этот текст автоматически отображается в окне самого проекта (Project), когда указатель встает на соответствующее поле таблицы. Да и документирование базы данных упрощается. Можно использовать этот текст (через функцию DBGetProp()) при выдаче сообщений об ошибках.

    КЛЮЧЕВОЕ ПОЛЕ

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

    Ключевое поле - это такое поле (или набор полей) по содержимому которого можно однозначно идентифицировать запись таблицы. Т.е. по значению ключевого поля всегда однозначно можно сказать о какой записи идет речь и не может существовать 2 записей с одинаковыми значениями ключевого поля.

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

    Начну сразу с вывода:
    Для большинства задач в FoxPro удобно использовать в качестве ключевого поля суррогатный ключ типа Integer. Ключевое поле желательно вводить для всех без исключения таблиц базы данных

    Ну а теперь рассмотрим как же я дошел до выводов таких

    ЕСТЕССТВЕННЫЕ ИЛИ СУРРОГАТНЫЕ КЛЮЧИ

    До сих пор не утихают споры о том, что лучше использовать: суррогатные или естесственные ключи.

    Естесственный ключ - это поле или набор полей, которые имеют некий физический смысл. Ну например, табельный номер, номер паспорта, и т.п.

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

    Рассмотрим какие преимущества и недостатки имеют естесственные и суррогатные ключи

    Фактически самый главный и определяющий критерий по которому можно судить о том, пригодно ли данное поле для использования его в качестве ключевого или нет - это однозначность идентификации записи. Здесь имеется в виду, действительно ли значение выбранного поля для каждой записи уникально?

    Ну с суррогатным ключем все понятно - мы сами формируем его значение без участия со стороны пользователя, поэтому можем проследить за уникальностью, а вот как тут у естесственного ключа?

    На первый взгляд кажется, что тоже все в порядке. Разве может номер паспорта быть не уникальным? Или табельный номер? Оказывается еще как может!

    Во-первых, любая информация вводимая пользователем заведомо может содержать ошибки. Ну не могут люди не ошибаться. Кто-то ошибается чаще, кто-то реже, но ошибаются все. Часть ошибок безусловно можно отловить программно, но далеко не все.

    Например, если речь идет о некотором цифро-буквенном номере (номер паспорта) и пользователь ввел "123 АБВ", а надо было "423 АБВ", то синтаксически это правильно, но по содержанию - это ошибка. Если впоследствии окажется что теперь надо ввести новый номер паспорта, но уже "123 АБВ", то программа откажется это сделать, поскольку такой номер уже есть. А на что его исправить неизвестно, поскольку документов, с которых он был введен уже нет.

    Во-вторых (и это пожалуй более существенно), информация имеет тенденцию меняться. Например, не так давно был произведена замена паспортов в Российской Федерации и если бы программа опиралась в качестве идентификатора на его номер, то теперь все пришло в большой беспорядок в связи с этой заменой

    Опять же, не так давно в Российской Федерации были введены так называемые "Идентификационные Номера Налогоплательщика". Так называемые "ИНН". Ну казалось бы, ну вот он уникальный идентификатор контрагента. Но наша родная бюрократия и здесь сумела все испортить. Оказалось, что ИНН нарушает все критерии уникальности, в связи с некоторыми особенностями его формирования

    В одной из конференций, один из посетителей выразился по этому поводу достаточно эмоционально

       1. ИНН может быть одинаковым у разных организаций. Не верите? Я тоже не верил...   
      2. ИНН может быть разным у одной и той же организации.   
      3. У одного и того же человека может быть два разных номера паспорта.   
      4. У одного и того же человека могут быть разные фамилии.   
      5. У одной и той же организации могут быть разные наименования (в разное время года :) )   
      6. Количество детей, конечностей, зубов и даже папилярные линии... МОГУТ ИЗМЕНЯТЬСЯ!!!

    Я отдаю предпочтение СК просто потому, что СК предоставляет возможность пользователю решать, Иванов И.И. и Петров И.И. - один и тот же человек или разные. Может быть, он сменил фамилию, может быть даже пол, может быть он сменил и тип паспорта, и его номер. Может быть, он сменил гражданство, вернулся с войны без рук, ног и головы, наплодил детей, сделал пластическую операцию по изменению веса... Но от этого он не перестал быть самим собой.

    Таким образом, в общем случае, естесственный ключ не обеспечивает главного - однозначности идентификации записи по значению ключевого поля. А если все-таки обеспечивает, то он перестает быть естесственным в первоначальном смысле этого слова, поскольку вынуждает пользователя приспосабливаться и изворачиваться.
    Так почему же, несмотря на столь явные аргументы против, все равно есть достаточно большое количество приверженцев использования естесственного ключа?

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

    Дело в том, что главным аргументом приверженцев естесственного ключа является его "понятность" пользователю. Т.е. предполагается, что пользователь что-то ищет опираясь на значение ключевого поля. Иными словами, пользователь сам вводит, исправляет и просматирвает значение ключевого поля.

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

       1 - Получено  
      2 - Отложено  
      3 - Утверждено

    Есть очень большое искушение не делать дополнительную таблицу с этими 3 записями, а просто использовать коды 1,2,3 Ну действительно, ну не может же пользователь изменить количество и название состояний. Вот тут приверженцы естесственного ключа и поднимают голос - дескать, что говорят программисту какие-то цифирки, пускай уж сразу слова пишет

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

    КАКОЙ ТИП ДАННЫХ ИСПОЛЬЗОВАТЬ: CHARACTER ИЛИ INTEGER

    Раньше, когда для хранения числовых данных в FoxPro существовали только поля типа Numeric перевес в аргументации склонялся в пользу использования полей типа Character, но с появлением полей типа Integer все стало не так однозначно

    При сравнении способа хранения ключевого поля в символьном или числовом формате выдвигаются 3 аргумента

  • В числовых полях легче формировать новое значение
  • Символьные поля "экономичнее", т.е. можно уместить больше значений в том же размере
  • Символьные поля "универсальнее" при так назваемых задачах репликации, т.е. объединении не связанных между собой баз данных

    Числовые поля легче формировать

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

    Замечу еще, что не стоит для генерации нового значения ключа использовать именно определение максимального значения в текущей таблице. Это хорошо в однопользовательском режиме, но в многопользовательском Вы рискуете получить 2 одинаковых значения ключа при одновременном добавлении новой записи двумя пользователями одновременно. Обычно используют специальную служебную таблицу, хранящуюю значение последнего использованного (или первого не использованного) значения ключа. Примеры использования такой таблицы приведены в стандартных проектах примеров FoxPro: Solution.pjx (форма NewID.scx) и TasTrade.pjx. А в 8 версии FoxPro появились автоинкрементные поля, которые совсем упростили данную задачу.

    Символьные поля "экономичнее", т.е. можно уместить больше значений в том же размере

    Начнем с вопроса, а сколько вообще необходимо значений для идентификации вообщех всех записей таблицы?

    Из системных ограничений известно, что предельно допустимое количество записей в DBF-таблице - это 1 миллиард записей (1 billion). Т.е. это единица и девять нулей

    Поле типа Integer может принимать знаячение в диапазоне от -2,147,483,647 до 2,147,483,647. Ну, отрицательные значения как правило не используются, но и положительные значения в 2 раза больше, чем максимально возможное количество записей. С учетом возможного удаления записей - в самый раз.

    Поскольку поле типа Integer физически занимает 4 байта (4 символа), то посмотрим, сколько же значений может быть присвоено при таком же размере для символьного поля

    В одном байте может быть записано 256 значений, т.е. это составит 256**4=4,294,967,296. Но поскольку некоторые значения нельзя использовать по ряду соображений (например, символ перевода строки, Esc и т.п.), то получается, что в смысле количества значений поля типа Integer ничуть не уступает полю типа Caracter(4), даже пожалуй несколько превосходит

    Замечание

    Следует заметить, что в FoxPro поля типа Numeric храняться как символьные поля, т.е. для хранения каждой цифры нужен один байт. Это значит, что если предполагаемое количество значений в данной таблице не превышает тысячи (меньше 4 символов), то возникает искушение "сэкономить" и вместо типа Integer ввести скажем поле типа N(2)

    Так вот, я бы не советовал этого делать по следующим соображениям:

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

    Символьные поля "универсальнее" при так назваемых задачах репликации

    Репликация - это объединение информации из двух не связанных между собой баз данных, например, из двух филиалов одной организации территориально удаленных друг от друга

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

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

  • генерация нового значения числового ключа проще, чем символьного
  • количество значений ключа для типа Integer и Chracter(4) практически одинаково
  • при решении сложных задач репликации удобнее пользоваться символьными ключевыми полями
    Но поскольку большинству программистов не придется сталкиваться с задачами репликации, то можно смело использовать для ключевых полей тип Integer.

    Надо ли использовать ключевое поле во всех без исключения таблицах

    На первый взгляд, вопрос может показаться странным. Разве можно без ключевого поля? Оказывается, в некоторых случаях можно.

    Например, для организации связи много-ко-многим стандартным способом является создание таблицы-посредника. Что имеется в виду?

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

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

    Однако я настоятельно рекомендовал бы Вам вводить-таки собственное ключевое поле для всех таблиц базы данных. Почему? Ну потому, что любая программа имеет "привычку" развиваться.

    Если вернуться к примеру с контрагентами и банками, то легко заметить, что я обошел вниманием тот момент, что один контрагент может иметь несколько счетов в одном и том же банке. Этот самый реквизит (счет контрагента в банке) невозможно прицепить ни к банку, ни к контрагенту. Но он очень удачно подходит к этой таблице-посреднику. Если бы для идентификации записи в этой таблице Вы опирались бы на пару: код банка - код контрагента, то Вам пришлось бы серьезно переделывать значительную часть программы. А в случае существования собственного кода записи никаких проблем. Ну добавили еще одно поле, ну и что?

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

    НАЗВАНИЕ КЛЮЧЕВОГО ПОЛЯ

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

  • Образуйте название ключевого поля таблицы добавив к имени таблицы 2 буквы "ID" (от слова identifier - идентификатор), отбросив букву "s" если имя таблицы - это слово во множественном числе - Например, если вы назвали таблицу контрагентов "Partners", то ключевое поле будет назваться "PartnerID". При таком способе наименования однозначно можно сказать к какой таблице относится ключевое. Но не стоит назвать ключевое поле также как и собственно таблицу, поскольку в некоторых случаях станет весьма проблематично сходу определить о чем идет речь - о поле или собственно о таблице
  • Называйте внешние ключи также как и соответствующие ключевые поля - такой способ наименования внешних ключей очень облегчает понимание о чем собственно идет речь при написании программы.
  • Ограничивайте название ключевого поля 10 символами - причина здесь в том, что количество символов в названии индексного тэга не может быть больше 10. А по ключевому полю обязательно следует построить индекс. Если количество символов в ключевом поле будет больше 10, то название индексного тэга будет отличаться от названия ключевого поля. А это не очень хорошо в том смысле, что серьезно затруднит программирование, поскольку использовать этот индекс Вы будете сплошь и рядом.

    Кстати, из этой рекомендации вместе с самой первой рекомендацией по наименованию ключевых полей вытекает, что количество символов в имени таблицы не должно превышать 8 (или 9, если название таблицы - это множественное число)

    СОБСТВЕННО РАБОТА С КЛЮЧЕВЫМИ ПОЛЯМИ ТАБЛИЦЫ

  • Значение ключевого поля должно присваиваться один раз в момент создания записи и не меняться все время существования этой записи. Крайне нежелательно модифицировать значение ключевого поля. Лучше строить программу таким образом, чтобы не возникало необходимости в подобной модификации. Более того, я бы не рекомендовал использовать значения ключевых полей удаленных записей (умерла, так умерла).

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

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

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

    Дело в том, что любая информация, которую вводит пользователь по определению является не надежной и как бы Вы ни защищали информацию, пользователь найдет как ее обойти или просто заставит Вас это сделать! А то, о чем он не подозревает и испортить не может.

  • Не пытайтесь навесить на ключевые поля какие-либо еще функции кроме однозначной идентификации записи.

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

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

Автор публикации

не в сети 24 года

Владимир Максимов

Комментарии: 0Публикации: 65Регистрация: 02-09-2000
Материалы по теме
Оставить комментарий
//////////////// ///////////////
Авторизация
*
*
Генерация пароля