Работа с данными

HomeFAQРабота с данными
Выбрать из дочерней таблицы записи с максимальной датой

Задача

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

Решение

Исходные данные

  
 * Главная таблица  
  CREATE CURSOR tabMain (tabMainID I, NickName C(50))    
  INSERT INTO tabMain (tabMainID, NickName) VALUES (1, "Первая запись главной таблицы")    
  INSERT INTO tabMain (tabMainID, NickName) VALUES (2, "Вторая запись главной таблицы")    
  INSERT INTO tabMain (tabMainID, NickName) VALUES (3, "Третья запись главной таблицы")    
    
 * Подчиненная таблица  
 * Для первой записи главной таблицы есть дублирующее значение по максимальной дате  
 * Для второй записи главной таблицы вообще нет значений в подчиненной таблице  
  CREATE CURSOR tabChild (tabChildID I, tabMainID I, CurDate D, NickName C(50))    
  INSERT INTO tabChild (tabChildID, tabMainID, CurDate, NickName) ;  
  	VALUES (1, 1, DATE(2005,6,15), "Первая запись дочерней первой записи главной")  
  INSERT INTO tabChild (tabChildID, tabMainID, CurDate, NickName) ;  
  	VALUES (2, 1, DATE(2005,6,15), "Вторая запись дочерней первой записи главной")  
  INSERT INTO tabChild (tabChildID, tabMainID, CurDate, NickName) ;  
  	VALUES (3, 1, DATE(2005,6,10), "Третья запись дочерней первой записи главной")  
  INSERT INTO tabChild (tabChildID, tabMainID, CurDate, NickName) ;  
  	VALUES (4, 3, DATE(2005,6,13), "Первая запись дочерней третьей записи главной")  
  INSERT INTO tabChild (tabChildID, tabMainID, CurDate, NickName) ;  
  	VALUES (5, 3, DATE(2005,6,14), "Первая запись дочерней третьей записи главной")

У функции DATE() можно задавать параметры, начиная с версии Visual FoxPro 6. Для младших версий укажите значение даты другим способом.

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

  
  SELECT tabMain.*, tabChild.* ;  
  FROM tabMain ;  
  INNER JOIN tabChild ON tabMain.tabMainID=tabChild.tabMainID ;  
  WHERE tabChild.CurDate IN ;  
  	(SELECT MAX(CurDate) FROM tabChild WHERE tabMainID = tabMain.tabMainID)

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

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

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

  
  SELECT tabMain.*, tab2.* ;  
  FROM tabMain ;  
  INNER JOIN tabChild tab2 ON tabMain.tabMainID=tab2.tabMainID ;  
  WHERE NOT EXISTS(SELECT 'x' FROM tabChild ;  
  		WHERE tab2.tabMainID = tabChild.tabMainID ;  
  			AND tab2.CurDate < tabChild.CurDate) ;  
  AND NOT EXISTS(SELECT 'x' FROM tabChild ;  
  		WHERE tab2.tabMainID = tabChild.tabMainID ;  
  			AND tab2.CurDate = tabChild.CurDate ;  
  			AND tab2.tabChildId < tabChild.tabChildId)

Разберем этот запрос подробнее.

Первое объединение по INNER JOIN просто связывает главную и подчиненную таблицу. Получим все записи подчиненной таблицы, соответствующие записям главной таблицы.

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

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

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

К сожалению, в Visual FoxPro невозможно объединить эти два подзапроса в один. Впрочем, в таком виде запрос выглядит более наглядно. А если вы уверены, что в вашей таблице не может быть записей с одинаковым значением даты, то просто не включайте второй подзапрос.

В принципе, тот же самый алгоритм можно использовать, если надо найти не максимальную дату, а скажем, максимальную цену. Или максимальную сумму.

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

===================================================================================

Есть еще одно, довольно экзотическое, решение. Оно заведомо не оптимизируемо, поэтому выполняется достаточно медленно.

Способ решения отличается для версии Visual FoxPro 9 и младших версий, поскольку в 9 версии значительно расширены возможности команды Select-SQL

  
 * Для версии младше Visual FoxPro 9  
    
  SELECT tabMain.*, tabChild.* ;  
  FROM tabMain ;  
  INNER JOIN tabChild ON tabMain.tabMainID=tabChild.tabMainID ;  
  WHERE tabChild.tabChildID IN ;  
  	(SELECT CTOBIN(RIGHT(MAX(DTOS(CurDate)+BINTOC(tabChildID)),4)) ;  
  			FROM tabChild GROUP BY tabMainID) ;  
  UNION ALL ;  
  SELECT tabMain.*, 0, 0, {}, '' ;  
  FROM tabMain ;  
  WHERE tabMain.tabMainID NOT IN (SELECT tabMainID FROM tabChild)  
    
 * Для Visual FoxPro 9  
    
  SELECT tabMain.*, tabChild.* ;  
  FROM tabMain ;  
  LEFT JOIN tabChild ON tabMain.tabMainID=tabChild.tabMainID ;  
  WHERE tabChild.tabChildID IS NULL ;  
  	OR tabChild.tabChildID IN ;  
  		(SELECT CTOBIN(RIGHT(MAX(DTOS(CurDate)+BINTOC(tabChildID)),4)) ;  
  			FROM tabChild GROUP BY tabMainID)

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

Для корректного определения максимального значения дата конвертируется в строку при помощи функции DTOS(). Т.е. это представления даты в виде "ГГГГММДД". Другими словами дата "15 июня 2005 года" будет выглядеть как "20050615".

В данном примере, ключевое поле дочерней таблицы имеет тип Integer. Для конвертации его в строку использована функция BINTOC() просто для того, чтобы получить как можно меньший размер. В данном случае получается строка длиной в 4 символа. А в случае стандартного преобразования через STR() пришлось бы выделить 10 символов. Отсечь ведущие пробелы в данном случае нельзя.

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

После того, как будет найдено максимальное значение для такого "синтетического" ключа из него выделяются последние 4 символа и преобразуются к типу Integer. По сути, получаем значение кода записи дочерней таблицы для максимального значения даты.

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

Выбрать записи с повторяющимися (дублирующими) значениями поля

Задача

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

Решение

  
 * Исходная таблица    
  CREATE CURSOR tabMain (tabMainID I, NickName C(50), curDate D, Chet L)  
  INSERT INTO tabMain (tabMainID, NickName, curDate, Chet) VALUES (1, "Первая запись", Date(2005,6,1), .F.)  
  INSERT INTO tabMain (tabMainID, NickName, curDate, Chet) VALUES (2, "Вторая запись", Date(2005,6,2), .T.)  
  INSERT INTO tabMain (tabMainID, NickName, curDate, Chet) VALUES (3, "Третья запись", Date(2005,6,3), .F.)  
  INSERT INTO tabMain (tabMainID, NickName, curDate, Chet) VALUES (4, "Четвертая запись", Date(2005,6,1), .T.)  
  INSERT INTO tabMain (tabMainID, NickName, curDate, Chet) VALUES (5, "Пятая запись", Date(2005,6,2), .F.)  
  INSERT INTO tabMain (tabMainID, NickName, curDate, Chet) VALUES (6, "Шестая запись", Date(2005,6,1), .T.)

У функции DATE() можно задавать параметры, начиная с версии Visual FoxPro 6. Для младших версий укажите значение даты другим способом.

Находим все записи, у которых повторяется значение поля curDate

  
  SELECT DISTINCT tabMain.* ;    
  FROM tabMain ;    
  INNER JOIN tabMain tabDouble ON tabMain.curDate = tabDouble.curDate  ;    
    			AND tabMain.tabMainID <> tabDouble.tabMainID ;  
  ORDER BY tabMain.curDate

Следует иметь в виду, что данное решение опирается на тот факт, что у таблицы есть уникальное (не повторяющееся) ключевое поле tabMainID.

Кроме того, данное решение легко расширить для случая, если требуется отобрать записи по повторяющемуся значению в нескольких полях. Достаточно просто добавить дополнительное условие через AND в условие объединения таблицы.

Например, найти все записи, у которых повторяется как значение поля curDate, так и значение поля Chet.

  
  SELECT DISTINCT tabMain.* ;    
  FROM tabMain ;    
  INNER JOIN tabMain tabDouble ON tabMain.curDate = tabDouble.curDate  ;    
    			AND tabMain.Chet = tabDouble.Chet ;  
    			AND tabMain.tabMainID <> tabDouble.tabMainID ;  
  ORDER BY tabMain.curDate, tabMain.Chet

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

Определяю значение поля curDate, которое встречается более одного раза

  
  SELECT tabMain.curDate  ;    
  FROM tabMain ;    
  GROUP BY tabMain.curDate ;  
  HAVING count(*) > 1
Как выполнить восстановление поврежденных индексов

Вопрос

Как выполнить восстановление поврежденных индексов

Ответ

В версии FoxPro 2.x стандартным советом было: удалите все индексы и создайте их заново.

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

  1. Предварительно Вам необходимо создать резервную копию структуры Вашей базы данных. Т.е. вообще все файлы DBF, CDX, FPT, DBC, DCT, DCX, но без собственно данных. Пустые таблицы. В данном случае также подойдет резервная копия Вашей базы данных. Т.е. заполненные данные за предыдущий период. Главное, чтобы эта копия имела не поврежденные индексные файлы.
  2. В случае повреждения индексов, все файлы CDX просто удаляются из рабочей базы данных. На их место копируются одноименные файлы из резервной копии.
  3. Поскольку структура файлов CDX в резервной копии не повреждена, то Вы сможете открыть Ваши таблицы в эксклюзивном (единоличном) режиме без проблем. Т.е. без сообщений об ошибках.
    USE MyTab.dbf EXCLUSIVE
  4. Теперь остается только привести в соответствие содержимое индексных файлов и собственно таблиц. Для этого следует использовать команду
    REINDEX
Как изменить значения в одной таблице данными из другой

Задача

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

Решение

Исходные данные

  
 * Главная таблица    
  CREATE CURSOR tabMain (tabMainID I, Summa N(18,2))  
  INDEX ON tabMainID TAG tabMainID  
  SET ORDER TO 0  
  INSERT INTO tabMain (tabMainID, Summa) VALUES (1, 0)  
  INSERT INTO tabMain (tabMainID, Summa) VALUES (2, 0)  
  INSERT INTO tabMain (tabMainID, Summa) VALUES (3, 0)  
    
 * Подчиненная таблица    
  CREATE CURSOR tabChild (tabChildID I, tabMainID I, Qty I, Price N(18,2))  
  INDEX ON tabMainID TAG tabMainID  
  SET ORDER TO 0  
  INSERT INTO tabChild (tabChildID, tabMainID, Qty, Price) VALUES (1, 1, 10, 0.25)  
  INSERT INTO tabChild (tabChildID, tabMainID, Qty, Price) VALUES (2, 2, 15, 10.05)  
  INSERT INTO tabChild (tabChildID, tabMainID, Qty, Price) VALUES (3, 3, 30, 5.00)

Способ решения отличается для версии Visual FoxPro 9 и младших версий, поскольку в 9 версии значительно расширены возможности команды Select-SQL

  
 * Для версии младше Visual FoxPro 9  
  SELECT tabMain  
  REPLACE FOR SEEK(tabMain.tabMainID,"tabChild","tabMainID") ;  
  	Summa WITH tabChild.Qty*tabChild.Price  
    
 * Или через команду UPDATE  
  UPDATE tabMain SET Summa = tabChild.Qty*tabChild.Price ;  
  	WHERE SEEK(tabMain.tabMainID,"tabChild","tabMainID")   
    
 * Для Visual FoxPro 9  
  UPDATE tabMain ;  
  SET Summa =tabChild.Qty*tabChild.Price ;  
  FROM tabChild ;  
  WHERE tabMain.tabMainID=tabChild.tabMainID

Если дочерняя таблица не имеет нужного индекса, то для версии младше Visual FoxPro 9 остается только сканирование главной таблицы с поиском нужной записи в подчиненной таблице через команду LOCATE

Запрос с GROUP BY выдает сообщение о синтаксической ошибке

Проблема

Я выполняю примерно такой запрос

SELECT company, country FROM Customer GROUP BY country

И в версии FoxPro начиная с 8 и выше, получаю сообщение вроде

Цитата:

SQL: GROUP BY clause is invalid (Error 1807)

Причем в младших версиях FoxPro подобный запрос работал без проблем.

Причина

Начиная с версии Visual FoxPro 7.0, были ужесточены требования к корректности конструкции SQL-запросов.

В данном случае запрос содержит неоднозначность: Какое именно значение поля company надо взять из таблицы, если для одного и того же значения country их может существовать несколько?

В младших версиях FoxPro в этом случае использовалось первое попавшееся значение. В старших версиях FoxPro такая конструкция воспринимается как синтаксически некорректная.

Решение

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

SELECT company, country FROM Customer GROUP BY company, country

или добавить любую агрегирующую функцию к тем полям, которые не перечислены в конструкции GROUP BY

SELECT MAX(company) as company, country FROM Customer GROUP BY country

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

SET ENGINEBEHAVIOR 70

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

При работе в сети иногда не открывается таблица

Проблема

В сетевом приложении при одновременной работе нескольких пользователей иногда не открываются таблицы. Возникает сообщение вроде  "Attempting to lock... Press Esc to cancel"

Причина

Это связано с ужесточением контроля структуры таблиц в момент их открытия, начиная с версии Visual FoxPro 8. Варианты этого контроля регулируются настройкой

SET TABLEVALIDATE

На момент проведения такого контроля (при открытии таблицы) требуется заблокировать заголовок таблицы. Если это невозможно (заголовок блокирован другим пользователем), то и возникает данное сообщение.

Решение

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

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

Блокировка должна осуществляться только в момент сохранения внесенных изменений и в этот момент недопустимо ожидание реакции пользователя.

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

Как получить программный код создания структуры базы данных

Вопрос
Как я могу получить (посмотреть) программный код создания структуры базы данных

Ответ

Вместе с FoxPro поставляется утилита (программа) по генерации программного кода структуры существующей базы данных. Она называется GenDBC.prg

В командном окне FoxPro дайте команду

DO (HOME()+"Tools\GenDBC\GenDBC.prg")

Скобки в данном случае обязательны

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

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

В результате работы данной утилиты, Вы получите файл PRG, который можно просмотреть из среды FoxPro через стандартную команду

MODIFY COMMAND

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

Как восстановить поврежденную таблицу

Вопрос

Таблица нормально открывалась. Однако после сбоя питания (не корректного выключения компьютера) при открытии таблицы возникает сообщение об ошибке

 

Цитата:

"file" is not a table (Error 15)

Как можно починить таблицу?

Причина

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

Что и где должно находится, так сказать, "оглавление", записывается в самом начале файла. В так называемой "заголовочной части". Это просто самое начало файла. После которого идет само содержание файла. Данные.

Естественно "оглавление" должно соответствовать определенному "стандарту", зависящему от версии FoxPro, а также должно соответствовать "содержанию". И некоторые из таких соответствий можно проверить. Т.е. посмотреть действительно ли в указанном месте находится соответствующие данные? Что, собственно, и делает FoxPro при открытии файлов DBF.

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

Решение

Прежде чем попытаться "исправить" файл DBF, не важно, каким способом, желательно сделать резервную копию. Пусть и поврежденного файла. Как бы "лечение" не сделало хуже!

Помните, что таблица - это совокупность трех файлов с одинковым именем и с расширениями

DBF - собственно таблица
FPT - содержимое мемо-полей
CDX - структурный индексный файл

Это значит, что делать резервную копию надо всех трех файлов. Разумеется, если они есть.

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

Table File Structure (.dbc, .dbf, .frx, .lbx, .mnx, .pjx, .scx, .vcx)

А для мемо-полей, которые хранятся в отдельном файле с расширением FPT это статья с названием

Memo File Structure (.FPT)

Для проверки можно открыть таблицу любым низкоуровневым текстовым редактором. Например, Norton Commander, Disco Commander, FAR-manager и т.п. Далее проанализировать значения в заголовочной части и в области с данными.

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

Тем не менее, просмотреть поврежденный файл в низкоуровневом редакторе бывает полезно. Как правило, при этом можно сделать некоторые общие выводы. Например, а есть ли в файле вообще какая-либо информация. Не заполнен ли файл каким-либо "мусором".

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

-------------------------------------------------------------------------------------

Есть некоторые способы, позволяющие в простых случаях "привести в чувство" испорченную таблицу.

Если таблицу все-таки удалось открыть, например, отключив проверку структуры настройкой

SET TABLEVALIDATE TO 0

Или же открыв таблицу в младшей версии Visual FoxPro. То можно попробовать выполнить следующие действия:

  1. При помощи команды COPY TO скопировать содержимое таблицы в другой файл. Команда COPY TO создаст таблицу корректной структуры.
  2. Очистить содержимое испорченной таблицы при помощи команды ZAP или просто пересоздать таблицу заново, если ничто не помогает
  3. Заполнить пустую таблицу командой APPEND FROM данными, сохраненными ранее в копии, при помощи команды COPY TO.

Иногда, помогает использование команды PACK.

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

--------------------------------------------------------------------------------

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

Если в старших версиях FoxPro это рассогласование вызовет сообщение об ошибке в момент открытия, то в младших версиях FoxPro это соответствие не проверяется. Как следствие, при открытии таблицы Вы увидите меньше записей, чем есть на самом деле. Или больше. Но в последнем случае, эти "лишние" записи будут заполнены "мусором".

Собственно, довольно просто проверить это соответствие самостоятельно. Достаточно посмотреть, как именно формируется "оглавление" в файле DBF. Т.е. посмотреть в HELP структуру файла DBF. Или поискать в интернете различные "фиксеры", которые исправляют некоторые повреждения структуры файла DBF.

Только следует с осторожностью использовать "фиксеры" найденные в интернете. Учитывая тот факт, что есть отличия в структурах файлов DBF для FoxPro 2.x и Visual FoxPro. Отличия в структуре файлов DBF разных версий Visual FoxPro можно считать не существенным с точки зрения исправления возможных повреждений структуры. Впрочем, и большинство "фиксеров" разработанных для FoxPro 2.x могут успешно исправить и повреждение таблиц Visual FoxPro.

Еще раз напомню, прежде чем попытаться "исправить" файл DBF при помощи сторонней утилиты, желательно сделать резервную копию. Пусть и поврежденного файла. Как бы "лечение" не сделало хуже!

-------------------------------------------------------------------------

Ниже приведен один из вариантов исправления

  
 *********************************************************************  
 * Назначение : 	Приведение в соответствие значение количества записей   
 *		в заголовке таблице и реального количества записей  
 * Автор : 	Владимир Максимов  
 * Дата : 	31.08.2008  
 * Версия FoxPro: Visual FoxPro с 3 по 9  
 *		Возможно, этот код будет работать и в версиях FoxPro 2.x  
 *		если убрать объявления LOCAL и MessageBox()   
 *		Но это не проверялось  
 *********************************************************************  
    
 * Пользователь выбирает файл для проверки  
 * 5 параметр в функции GetFile() был введен только в версии Visual FoxPro 6  
 * для младших версий FoxPro его надо удалить  
  Local lcFileName  
  lcFileName = GetFile("DBF","","",0,"Выбор файла для проверки количества записей")  
  If Empty(m.lcFileName)  
 	* Файл не был выбран  
  	Return  
  EndIf  
    
 * Открываю файл и запоминаю его дескриптор (идентификатор)  
  Local lnFD  
  lnFD = Fopen(m.lcFileName,12)  
  If m.lnFD < 0  
  	MessageBox("Не удалось открыть файл" + Chr(13) + ;  
  		m.lcFileName + Chr(13) + ;  
  		"Возможно, он открыт другим приложением")  
  EndIf  
    
 * Теоретически, здесь хорошо бы добавить проверку на тот факт,   
 * что данный файл - это DBF-таблица (анализ первого байта)  
 * Но в данном случае предполагается, что пользователь понимает, что он делает  
 * Если, тем не менее, Вы хотите создать универсальную утилиту, то  
 * посмотрите код программы CPZERO.PRG из поставки FoxPro.  
 * Эта программа находится в каталоге TOOLS\CPZERO корневой папки FoxPro  
    
 * Определяю количество записей записанное в заголовке файла  
 * байты с 4 по 7  
  LOCAL lnReccount  
  =FSEEK(m.lnFD,4)  
  lnReccount =	ASC(FREAD(m.lnFD,1)) + ;  
  		ASC(FREAD(m.lnFD,1)) * 256 + ;  
  		ASC(FREAD(m.lnFD,1)) * 256 * 256 + ;  
  		ASC(FREAD(m.lnFD,1)) * 256 * 256 * 256  
  			  
 * Определяю позицию, с которой начинается собственно данные (первая запись)  
 * байты с 8 по 9  
  LOCAL lnDataStart  
  lnDataStart = 	ASC(FREAD(m.lnFD,1)) + ;  
  		ASC(FREAD(m.lnFD,1)) * 256  
    
 * Определяю количество символов в одной записи, включая метку на удаление  
 * байты с 10 по 11  
  LOCAL lnRecLength  
  lnRecLength =	ASC(FREAD(m.lnFD,1)) + ;  
  		ASC(FREAD(m.lnFD,1)) * 256  
    
 * Определяю общий размер файла, через порядковый номер последнего байта  
  LOCAL lnFileSize  
  =FSEEK(m.lnFD,0,0)  
  lnFileSize = FSEEK(m.lnFD,0,2)  
    
 * Определяю значение самого последнего байта файла  
  LOCAL lnEndByte  
  =FSEEK(m.lnFD,-1,1)  
  lnEndByte = ASC(FREAD(m.lnFD,1))  
    
 * И реальное количество записей в файле  
  LOCAL lnFaktCount  
  DO CASE  
  CASE m.lnFileSize = m.lnDataStart  
 	* Т.е. в таблице вообще нет информации  
  	lnFaktCount = 0  
  CASE m.lnEndByte = 26  
 	* Если последний байт файла имеет ASCII код 26 (0x1A),   
 	* то общее число информационных байтов надо уменьшить на 1  
  	lnFaktCount = (m.lnFileSize - m.lnDataStart - 1) / m.lnRecLength  
  OTHERWISE  
  	lnFaktCount = (m.lnFileSize - m.lnDataStart) / m.lnRecLength  
  ENDCASE  
    
    
  LOCAL lnResult  
 * Анализ полученных результатов  
  DO CASE  
  CASE INT(m.lnFaktCount) <> m.lnFaktCount  
    
 	* Есть дробная часть. Т.е. выделить целое количество записей невозможно  
 	* повреждения более существенные, чем разница в количестве записей  
 	* лучше ничего не трогать  
  	lnResult = -1  
  	=Fclose(m.lnFD)  
  	  
  	MessageBox("Выделить целое количество записей невозможно"+Chr(13) + ;  
  				"Повреждения более существенные, чем разница в количестве записей")  
    
  CASE m.lnFaktCount <> m.lnReccount  
    
 	* Записанное и фактическое количество записей отличаются  
 	* корректирую записанное количество записей  
  	=FSEEK(m.lnFD,4,0)  
  	FOR lnI=1 TO 4  
  		=FWRITE(m.lnFD,CHR(MOD(INT(m.lnFaktCount / 256**(m.lnI-1)),256)))  
  	ENDFOR  
    
  	lnResult = 1  
  	=Fclose(m.lnFD)  
    
  	MessageBox("Значение количества записей исправлено")  
    
  OTHERWISE  
    
 	* Ошибки в определении количества записей не обнаружено  
  	lnResult=0  
  	=Fclose(m.lnFD)  
    
  	MessageBox("Ошибки в количестве записей не обнаружено")  
    
  ENDCASE  
    
 * Результат возвращается на случай, если есть необходимость в подобном анализе  
  RETURN m.lnResult
Авторизация
*
*
Генерация пароля