C++ Builder
| Главная | Уроки | Статьи | FAQ | Форум | Downloads | Литература | Ссылки | RXLib | Диски |

стр.: (2) < [1] 2 >
Ключевое поле, дублирование
Лена
Отправлено: 19.10.2006, 11:04


Мастер участка

Группа: Участник
Сообщений: 501



Когда пользователь вводит в ключевую колонку значение, которое там уже есть, то возникает исключение из-за дублирования.
Как правильно его обработать в OnBeforePost компонента ClientDataSet.
Сейчас у меня в этом обработчике есть код, который заставляет пользователя заполнить нужные колонки:
CODE

void __fastcall TDataModule2::ClientDataSetKeyBeforePost(TDataSet *DataSet)
{

 if(Form1->DBGrid1->Fields[0]->Value.IsNull() || Form1->DBGrid1->Fields[2]->Value.IsNull()
     || Form1->DBGrid1->Fields[3]->Value.IsNull())
   {
    ShowMessage("Поля \"Имя\", \"Фамилия\", и \"Отчество\" должны быть заполнены");
    Abort();
   }
}

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



Присоединить изображение

Присоединить изображение

AVC
Отправлено: 19.10.2006, 11:33


Ветеран

Группа: Модератор
Сообщений: 1583



1. PK это святое и незачем его давать на растерзание пользователю. Слишком много головной боли даже при поддерживаемых и правильно настроенных каскадах.

2. Как вы собираетесь проверять на дублирование? Для этого нужно иметь все значения всех запсей на клиенте. За время анализа другая станция может создать запись с таким значением. Тупик.

Ловите ошибку после сохранения и делайте выводы.
Лена
Отправлено: 19.10.2006, 11:43


Мастер участка

Группа: Участник
Сообщений: 501



>незачем его давать на растерзание пользователю

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

beginner
Отправлено: 19.10.2006, 11:51


Дежурный стрелочник

Группа: Участник
Сообщений: 44



Можно использовать Lookup.

Если успешный, значит дублируется значение.
olegenty
Отправлено: 19.10.2006, 12:01


Ветеран

Группа: Модератор
Сообщений: 2412



тогда отправляй запрос на сервер о том, а не exists ли там уже такой код. однако, поддерживаю AVC, — прочти статью "естественные ключи против искусственных" и введи искусственный ключ, а по своему уникальному полю построй уникальный индекс. и, до кучи, в идеале, проверка должна делаться на серверной, а не клиентской стороне. так спокойнее. пусть сервер тебе сформирует сообщение о том, что что-то не так...
ИМХО — плохо спроектированную базу всё равно надо перепроектировать.
AVC
Отправлено: 19.10.2006, 12:16


Ветеран

Группа: Модератор
Сообщений: 1583



QUOTE (Лена @ 19.10.2006, 11:43)
База создана не мной и требование, чтобы это поле было видимо пользователю.

"Видимое это вам не изменяемое"

QUOTE

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

См. пункт 2. Грамотно только ПОСЛЕ сервера.
Лена
Отправлено: 19.10.2006, 14:49


Мастер участка

Группа: Участник
Сообщений: 501



QUOTE

"Видимое это вам не изменяемое"


Изменяемое, так хочет заказчик. smile.gif
Это первичный ключ, который можно менять в гриде, но надо отслеживать возможность дублирования и отменять ее. Многопользовательского доступа не будет.
Скажите, а почему не срабатывает обработчик OnPostError?
Почему не работает?:
CODE

void __fastcall TDataModule2::ADOQueryKeyPostError(TDataSet *DataSet,
EDatabaseError *E, TDataAction &Action)
{
if(E->Message=="Key violation")
{
ShowMessage("Нельзя дублировать");
Abort();
}
}


Отредактировано Лена — 19.10.2006, 15:50
beginner
Отправлено: 19.10.2006, 15:02


Дежурный стрелочник

Группа: Участник
Сообщений: 44



CODE
void __fastcall TDataModule2::ClientDataSetKeyBeforePost(TDataSet *DataSet)
{
 if(Form1->DBGrid1->Fields[0]->Value.IsNull() || Form1->DBGrid1->Fields[2]->Value.IsNull()
    || Form1->DBGrid1->Fields[3]->Value.IsNull())
 {
   ShowMessage("Поля \"Имя\", \"Фамилия\", и \"Отчество\" должны быть заполнены");
   Abort();
 }
 Variant locvalues[1];
 locvalues[0] = Form1->DBGrid1->Fields[Номер_ключ_поля]->AsVariant;
 Variant LookupResults = ClientDataSetKey->Lookup("Имя_ключ_поля", VarArrayOf(locvalues, 1), "Имя_Ключевого_поля");
 if ! (VarType(LookupResults) == varNull)
 {
   ShowMessage("Дублирование ключевого поля!");
   Abort();
 }
}
Лена
Отправлено: 19.10.2006, 15:36


Мастер участка

Группа: Участник
Сообщений: 501



Получаю исключение. Сам тип этого поля String.
CODE

Variant locvalues[1];
locvalues[0] = Form1->DBGrid1->Fields[0]->AsVariant;
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues, 1), "code");
if (!VarType(LookupResults) == varNull)
{
ShowMessage("Дублирование в этом поле запрещено!");
Abort();
}

Если написать так:
locvalues[0] = Form1->DBGrid1->Fields[0]->AsString;
тоже самое:

Отредактировано Лена — 19.10.2006, 16:37

Присоединить изображение

Присоединить изображение

olegenty
Отправлено: 19.10.2006, 15:48


Ветеран

Группа: Модератор
Сообщений: 2412



ну так присваивать надо было не как AsVariant, а как AsString

Лен, это в любом случае в корне кривое решение. ты прикинь, что у этой записи есть подчинённые записи... и ты их тоже плотно будешь апдейтить? (даже если это за тебя СУБД сделает — нафиг надо-то?)
beginner
Отправлено: 19.10.2006, 15:48


Дежурный стрелочник

Группа: Участник
Сообщений: 44



Странно.
Можно, тогда попробовать

locvalues[0] = Variant(Form1->DBGrid1->Fields[Номер_ключ_поля]->AsString);
Лена
Отправлено: 19.10.2006, 15:58


Мастер участка

Группа: Участник
Сообщений: 501



CODE
Variant locvalues[1];
locvalues[0] = Variant(Form1->DBGrid1->Fields[0]->AsString);
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues, 1), "code");
if(!VarType(LookupResults) == varNull)
{
  ShowMessage("Дублирование запрещено!");
  Abort();
}


Ошибка та же на этой строке:
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues, 1), "code");

>это в любом случае в корне кривое решение

Покажу результат руководству, а они путсть решают оставить так, или базу изменить. smile.gif
Лена
Отправлено: 19.10.2006, 16:07


Мастер участка

Группа: Участник
Сообщений: 501



Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(OPENARRAY(Variant,(locvalues, 1))), "code");

так тоже получаю исключение AV но уже другое. ohmy.gif



А если так:
CODE

String locvalues;
locvalues = Form1->DBGrid1->Fields[0]->AsString;
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(OPENARRAY(Variant,(locvalues))), "code");
if(!VarType(LookupResults) == varNull)
{
  ShowMessage("vvvvvvvvvvvvvvvvvvvv!");
  Abort();
}


Пишет стак оверфлоу. rolleyes.gif

Отредактировано Лена — 19.10.2006, 17:10
olegenty
Отправлено: 19.10.2006, 16:08


Ветеран

Группа: Модератор
Сообщений: 2412



QUOTE

Покажу результат руководству, а они путсть решают оставить так, или базу изменить.


будет весело, если наш директор завод начнёт решать, КАК изменить базу данных, чтобы нечто иметь на выходе. и как он в этом случае предложит сопровождать эту базу...
beginner
Отправлено: 19.10.2006, 16:11


Дежурный стрелочник

Группа: Участник
Сообщений: 44



Это моя ошибка.
Нужен
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues, 0), "code");

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

Отредактировано beginner — 19.10.2006, 17:11
Лена
Отправлено: 19.10.2006, 16:16


Мастер участка

Группа: Участник
Сообщений: 501



CODE

Variant locvalues[1];
locvalues[0] = Form1->DBGrid1->Fields[0]->AsVariant;
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues, 0), "code");
if (!VarType(LookupResults) == varNull)
{
ShowMessage("Äóáëèðîâàíèå êëþ÷åâîãî ïîëÿ!");
Abort();
}


или так:
CODE

String locvalues;
locvalues = Form1->DBGrid1->Fields[0]->AsString;
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(OPENARRAY(Variant,(locvalues))), "code");
if (!VarType(LookupResults) == varNull)
{
  ShowMessage("Äóáëèðîâàíèå êëþ÷åâîãî ïîëÿ!");
  Abort();
}

Получаю это исключение на рисунке.



Отредактировано Лена — 19.10.2006, 17:38

Присоединить изображение

Присоединить изображение

beginner
Отправлено: 19.10.2006, 17:07


Дежурный стрелочник

Группа: Участник
Сообщений: 44



Наверно здесь баг:

if (!(VarType(LookupResults) == varNull))
olegenty
Отправлено: 19.10.2006, 17:26


Ветеран

Группа: Модератор
Сообщений: 2412



так проверять Variant нельзя. надо так
CODE

if (!LookupResults.IsNull() && !LookupResults.IsEmpty())
{
   ...
}
Admin
Отправлено: 19.10.2006, 20:47


Владимир

Группа: Администратор
Сообщений: 1190



Может это конечно не по теме, но если у вас будет два
человека с одниковым ФИО ? Не такая уж редкость если на
предприятии больше 300 человек. Каким образом вы их введете
в базу данных и будете отличать ?

Делайте autoinc поле на ключ, при добавлении записи -
триггер или хранимую процедуру на увеличение значения поля.
Лена
Отправлено: 23.10.2006, 09:44


Мастер участка

Группа: Участник
Сообщений: 501



CODE

Variant locvalues[1];
locvalues[0] = Form1->DBGrid1->Fields[0]->AsString;
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues,0), "code");
if ((!LookupResults.IsNull() && !LookupResults.IsEmpty())
)
{
  ShowMessage("Дублирование запрещено!");
  Abort();
}


Ошибка переполнения стека на строке:
Variant LookupResults = ClientDataSetKey->Lookup("code", VarArrayOf(locvalues,0), "code");

Что касается этого поля (code), то это первичный ключ не связанный с другими таблицами. В него вводятся индефикационные номара, например 1234SE и т.д. Эти номера присуствуют только в таблице keys.

CREATE TABLE keys
(
code varchar(32) NOT NULL,
description varchar(50),
enabled bool NOT NULL,
person_id int4,
CONSTRAINT pk_keys PRIMARY KEY (code)
)
WITHOUT OIDS;
ALTER TABLE keys OWNER TO postgres;

sad.gif
Admin
Отправлено: 23.10.2006, 11:56


Владимир

Группа: Администратор
Сообщений: 1190



CODE

void __fastcall TForm1::Button2Click(TObject *Sender)
{


Variant Rez = ADODataSet1->Lookup("Area", Edit1->Text.ToInt(), "Area");
if(!Rez.IsNull())
ShowMessage("Такое значение уже есть !");
else ShowMessage("Такого значения нет !");
}
//---------------------------------------------------------------------------


для тектсового поля соответственно

CODE
Variant Rez = ClientDataSetKey->Lookup("code", Form1->DBGrid1->Fields[0]->AsString, "code");
if(!Rez.IsNull())
ShowMessage("Такое значение уже есть !");
else ShowMessage("Такого значения нет !");

}
//---------------------------------------------------------------------------
Лена
Отправлено: 23.10.2006, 12:12


Мастер участка

Группа: Участник
Сообщений: 501



Спасибо.
Работает в этом варианте:
CODE

ADOQueryKey->Open();
Variant Rez = ADOQueryKey->Lookup("code", Form1->DBGrid1->Fields[0]->AsString, "code");
if(!Rez.IsNull())
{
ShowMessage("Такое значение уже есть!");
Abort();
}

Причем строка ADOQueryKey->Open(); обязательна.
Скажите, почему не работает так: ClientDataSetKey->Lookup... (переполнение стека) и еше нужно ли в конце этого кода писать:
ADOQueryKey->Close(); ?

Отредактировано Лена — 23.10.2006, 13:14
Admin
Отправлено: 23.10.2006, 12:19


Владимир

Группа: Администратор
Сообщений: 1190



QUOTE
Скажите, почему не работает так: ClientDataSetKey->Lookup... (переполнение стека) и еше нужно ли в конце этого кода писать:
ADOQueryKey->Close(); ?


Это надо смотреть по коду, так ничего не могу сказать.
ADOQueryKey->Close(); лучше явно закрыть, осободить ресурсы,
если он больше не нужен.
Admin
Отправлено: 23.10.2006, 12:26


Владимир

Группа: Администратор
Сообщений: 1190



Но возникает вопрос — а что сидит в ADOQueryKey ?
Если там Select * from keys
и если таблица keys будет очень большой — wink.gif
вам будет возвращены все записи и поля таблицы.

смотрите, может лучше это организовать это по другому,
отдельным запросом, что-то типа
Select code from keys where code = параметр из Form1->DBGrid1->Fields[0]->AsString,
тогда и возвращено будет только одно значение code или NULL.

Но это уже вам виднее.
beginner
Отправлено: 23.10.2006, 12:29


Дежурный стрелочник

Группа: Участник
Сообщений: 44



QUOTE (Лена @ 23.10.2006, 13:12)
Спасибо.
Работает в этом варианте:
Скажите, почему не работает так: ClientDataSetKey->Lookup... (переполнение стека) ?

Согласно help, конструкция с VarArrayOf применяется, когда индекс состоит из нескольких полей. Видимо, ClientDataset lookup с массивом единичного размера работать не может.
beginner
Отправлено: 23.10.2006, 12:29


Дежурный стрелочник

Группа: Участник
Сообщений: 44



...

Отредактировано beginner — 23.10.2006, 13:33
Лена
Отправлено: 23.10.2006, 12:33


Мастер участка

Группа: Участник
Сообщений: 501



>Но возникает вопрос — а что сидит в ADOQueryKey

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

select keys.code, keys.description, keys.enabled, keys.person_id, (visitors.lastname || ' ' || visitors.firstname || ' ' || visitors.patronymic || ' ' || visitors.post) as man from keys
join visitors on keys.person_id = visitors.id order by man

Отредактировано Лена — 23.10.2006, 13:34
Лена
Отправлено: 23.10.2006, 13:28


Мастер участка

Группа: Участник
Сообщений: 501



Проблема продолжается: wizard.gif
Теперь попытка поменять в соседнем булевом поле значение с "Да" на "Нет" приводит к коду:
ShowMessage("Такое значение уже есть!");
Abort();
да и попытка в любом поле ввести значение которое там есть приводит к этому коду. Как ограничить действие этого кода только на первый столбец грида?


Отредактировано Лена — 23.10.2006, 14:54
Admin
Отправлено: 23.10.2006, 14:10


Владимир

Группа: Администратор
Сообщений: 1190



Это нужно смотреть код. Например, без кода непонятно, зачем используется Abort();
Лена
Отправлено: 23.10.2006, 14:24


Мастер участка

Группа: Участник
Сообщений: 501



Abort() чтобы пользователь исправил запись. Весь код:
Весь код
CODE

void __fastcall TDataModule2::ClientDataSetKeyBeforePost(TDataSet *DataSet)
{
//некоторые колонки не могут быть пустыми
 if(Form1->DBGrid1->Fields[0]->Value.IsNull() || Form1->DBGrid1->Fields[2]->Value.IsNull()
     || Form1->DBGrid1->Fields[3]->Value.IsNull())
   {
    ShowMessage("Поля \"Код\", \"Доступ\", и \"Владелец\" должны быть заполнены");
    Abort();
   }

//первая колонка не должна содержать дубликаты
  ADOQueryKey->Open();
  //DataModule2->ClientDataSetKey->FieldByName("code")->AsString;
  Variant Rez = ADOQueryKey->Lookup("code", Form1->DBGrid1->Fields[0]->AsString, "code");
  if(!Rez.IsNull())
            {
            ShowMessage("Такое значение уже есть !");
            Abort();
            }
  ADOQueryKey->Close();

}
стр.: (2) < [1] 2 >
Вернуться в Работа с базами данных в C++Builder