Cat W

S. D. F.

Пароль S. D. F.

* А, понял, примитивный пароль, кнопочки под левой рукой.

- Пароль не распознан. В доступе отказано…

* Ладно, вторая попытка, помнится есть расширение sdf – вроде это база данных?

- Уже теплее…

* Третья попытка, третья… сдаюсь.

- Вообще-то пароль SQL Delphi Firebird.

- Поставлена задача: написать программу для исполнения SQL запросов. Программа пишется в среде Delphi, а база будет функционировать под управлением СУДБ Firebird.

* Я знаю пароль, я вижу ориентир…

Есть ли у вас план…

- Давай поиграем с SQL.

* Поиграем? Ну, давай… А почему с SQL?

- Ну, у меня возникла очередная задача – упорядочивание моей библиотеки книг FB2.

* Но эта задача, сто лет как решена, есть масса программ для работы с библиотеками.

- Вот это - не надо… Чукча не пользователь, чукча – программист.

* Может быть, чукча, написал свой офис и заодно операционку?

- Конечно, заманчиво… и в истории есть примеры,… но пока давай займемся SQL.

* Ну, кое-что я помню, меня этому учили…

- Как я тебя понимаю, мы все учились понемногу и главное как-нибудь.

* Так, с помощью, какой базы мы будем писать базу, погоди, я запутался…

- Я понял, ты имеешь в виду СУДБ (систему управления базами данных). Теоретически это не имеет значения SQL и в Африке SQL. Но, я предлагаю Firebird.

* Законный вопрос, а почему именно эта птичка?

- Как говорит Википедия: Firebird (FirebirdSQL) — компактная, кроссплатформенная, свободная система управления базами данных (СУБД), работающая на Linux, Microsoft Windows и разнообразных Unix платформах. Для меня большое значение имеет то, что она компактна и бесплатна (Firebird - является полностью свободным от лицензионных отчислений даже для коммерческого использования.).

* Есть ли у вас план, мистер Фикс?

- Есть ли у меня план? Да у меня целых три плана!

- Во-первых, написать программу для выполнения запросов SQL.

- Второе, проработать SQL с помощью книги Мартина Грубера «Понимание SQL»

- А потом – писать базу для FB2, но это уже за рамками этой книги.

* Ну вот. Так хорошо начиналось «Играть с SQL», а теперь опять, писать программу…

- Во-первых, отсылаю тебя к 5ой строке этого текста, а потом…

* Но есть же масса подобных программ…

- Есть. И одной из них я постоянно пользуюсь – это IBExpert. Отличная программа, и переплюнуть ее не берусь. Но есть неприятный момент, который меня постоянно раздражает каждый раз каждый новый вариант базы надо регистрировать,… да это всего несколько минут, но как я уже говорил, если выполнение рутинных действий (или ожидание) занимает более 10 секунд – для пользователя это непереносимо.

* А почему они так сделали, ведь не дураки там сидят?

- Это расплата за универсализм, повторюсь, отличная программа, но универсальная. Вспомни, как относились истинные меломаны к музыкальным комбайнам…

* Ну, а второй пункт, почему именно эта книга.

- Да надо, что-то выбрать, why not – книга то хорошая, и примеров там много.

* Хорошо, техническое задание написано - поехали!

Браузер

- Как я уже сказал, меня достала IBExpert, и года два назад я написал обозреватель баз данных формата Firebird, про себя я его называю браузер баз данных. Ну, назови БДБ или ДББ или ББД – только в печь не сажай.

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

Рис 1. Главная форма

Рис 2. Форма TDataModule

- В этом модуле IBTransaction1 должна быть привязана к  IBDatabase1 и соответственно IBSQL1 привязывается к IBTransaction1 и IBDatabase1.

* Как это «привязаны»? Что ты имеешь в виду.

- Я имею в виду, что не буду по-шагово все разжевывать, давай-ка поработай сам, понять все не трудно… (я настаиваю на мысли – если дать готовые рецепты – учащийся ничего не запомнит, да, сейчас он позлится, «его заставили потрудится» - и эта эмоция запишет в память нужные знания)

Первая задача: создать новую базу данных.

Рис 2.

010 procedure TFMain.NewDB1Click(Sender: TObject);

020 var

030 fn : string;

040 begin

050 fn := InputBox('File Name',' Введите имя файла новой базы данных','');

060    if fn = '' then exit;

070    if UpperCase(ExtractFileExt(fn)) <> '.FDB'

080       then fn := fn + '.FDB';

090      with DataM do

100      begin

110       IBDatabase1.Params.Clear;

120       IBDatabase1.Params.Add('USER SYSDBA');

130       IBDatabase1.Params.Add('PASSWORD masterkey');

140       IBDatabase1.Params.Add('PAGE_SIZE 4096');

150       IBDatabase1.DatabaseName:= fn;

160       IBDatabase1.SQLDialect := 3;

170       IBDatabase1.CreateDatabase;

180       IBDatabase1.Connected:=True;

190       IBTransaction1.Active:=True;

200     end;

210 end;

- В 50 строке запрашивается имя для файла новой базы данных, если мы не ответим, то в 60 строке выход из процедуры.

- В 70 строке проверяется, ввели ли мы расширение в имени файла, и если мы ленивы, то 80 строка таковое расширение добавляет.

- А вот это, важно! В строках 120 и 130 вводится имя пользователя и пароль. Во всех руководствах по Firebird говорится, что приведенные пароль и пользователь, указанны для образца, а использовать надлежит свои.

- Посему, меняем строки на:

120       IBDatabase1.Params.Add('USER Cats');

130       IBDatabase1.Params.Add('PASSWORD forever');

* А причем здесь коты?

- Ну, у себя ты введешь свои пароли.

* О, у себя, я как всегда напишу…

- Стоп, стоп, стоп! И ни другу, не врагу, никогда не …

* Ладно, я понял.

- * -

* Шеф, усе пропало!!! т.е. не получилось. Появилось угрожающее окно Debugger Exception Notification, со страшной надписью Project Project1.exe raised exception class EIBInterBaseError with message 'unavailable database'. Process stopped. Use Step or Run to continue..

- Спокойствие, только спокойствие... бывало и у меня такое.

- Для комфортной работы Firebird хочет иметь доступ к своим dll а именно:

gds32.dll

icudt30.dll

icuuc30.dll

и желательно:

firebird.msg

Конечно, лучше всего поместить эту компанию, в папочку WINDOWS\system или WINDOWS\system32, в этом случае, в дальнейшем ни о чем не надо беспокоится, НО если тебе надо показать твою гениальную программу заказчику, (а на его месте я бы не разрешил лазить тебе в системные папки) лучше держать эту свору в демонстрационной папке.

- * -

* У меня проблемы стал копировать dll, а система и говорит файл gds32.dll мол у меня есть только постарее да поменьше, можно ли заменить, ну я слов то не померил, и системе той поверил. А эта Редиска и говорит что она занята, т.е. файл занят.

- Ну, занят файл, значит, занят, мы то ящик покупаем не для собственного удовольствия, а для удовлетворения запросов системы, ну и ладно…

- Ибо сказано: Если у тебя есть фонтан – заткни его, и компьютеру надо отдохнуть…

- Все выключай, и иди погуляй.

«Продолжение следует»

ББД продолжение

- Извини, выключать комп было совершенно не нужно. Достаточно было закрыть Delphi, именно он занимал файл.

* Ладно, проехали. У меня тут как раз возник вопрос. Все сработало, файл базы создался, но на фейсе программы ничего не изменилось.

- Да, вот тут, ты прав. Недоработка. Но, я тоже обдумал… надо ввести изменения.

165 try

170    IBDatabase1.CreateDatabase;

180    IBDatabase1.Connected:=True;

190    IBTransaction1.Active:=True;

192 except

194   ShowMessage('Не нахожу dll');

195   exit;

196  end;   // try

187  FMain.Caption := fn;

198       ShowTables;

200 end;

- Во-первых давай позаботимся о том, чтобы исключительное положение, которое у нас возникло, было бы хоть как ни будь обработано ( строки 165, 192-196).

- Строка 198 выдаст название новой базы в заголовке программы и сообщит что таблиц, в этой базе пока нет.

procedure ShowTables;

begin

with FMain.ListBox1 do

begin

Clear;

Items.Add('Empty');]

end;

end;

- Сразу скажу, что процедуру ShowTables мы очень скоро будем существенно менять, посему не буду нумеровать строки…

- * -

* Все сработало, но…

- Да, хвалится пока еще нечем. Надо делать следующий шаг.

Задача: открыть существующую базу данных.

010 procedure TFMain.OpenDB1Click(Sender: TObject);

020 begin

030    with OpenDialog1 do

040    begin

050     Filter  :=  'DB|*.fdb';

060     DefaultExt := 'fdb';

070     if Execute then

080      with DataModule2 do

090       begin

100       IBDatabase1.DatabaseName:= FileName;

110       IBDatabase1.Params.Clear;

120       IBDatabase1.Params.Add('USER Cats');

130       IBDatabase1.Params.Add('PASSWORD forever');

140           try

150           IBDatabase1.Connected:=True;

160           except

170            ShowMessage('Не могу работать!');

180            exit;

190           end; // try

200       IBTransaction1.Active:=True;

210      FMain.Caption := FileName;

220       ShowTables;

230      end; // DataModule2

240    end; // OpenDialog1

250 end;

- Открываем диалог загрузки файла – стр. 30

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

- Если в стр. 70 файл базы найден, вводим параметры базы строки 100-130 (обрати внимание пароль и пользователь должен быть тот же, что и при создании базы).

- Наученные опытом в строках 140-190 пытаемся соединится с базой. (обрати внимание на стр. 190 – желательно кончики программных блоков помечать – легче будет разбираться).

* А почему ты изменил 170 строку.

- Искл. состояние может возникнуть не только из-за dll, но и в случае если база будет не родная (не совпадает пароль и др.) т.е. это место надо переписать более тщательно, сделаем это позже.

* Нет ничего более постоянного …

- Согласен, давай изменим строку 160

160           except // в дальнейшем обработать 1. отсутствие dll. 2. несовпадение пароля 3. другие случаи

- Теперь пришла пора для вкусненького.

* Погоди, сбегаю за кетчупом.

- Боюсь, ни клаве ни монитору кетчуп не понравится.

- Открою тебе великую тайну:

- Когда мы создали пустую базу. База там уже есть.

* Я что-то подобное подозревал, файл то не очень маленький.

- Да, в этом файле есть служебная база в которой будут хранится сведения о нашей базе.

* База о базе. Логично. Ведь логичное же, что директор завода Жигулей ездит на Мерседесе.

- Переписываем ShowTables:

-

procedure ShowTables(fn:string);

begin

FMain.Caption := fn;

with FMain.ListBox1 do

begin

Clear;

PrintList('select DISTINCT R.RDB$RELATION_NAME '+

'from RDB$RELATION_FIELDS R '+

'where R.RDB$SYSTEM_FLAG = 0 order by R.RDB$RELATION_NAME');

end;

end;

* Опять нет нумерации, значит это тоже временно.

- Совершенно точно. А теперь функция PrintList:

010 function PrintList( iSQL:string):boolean;

020 begin

030 with DataModule2.IBSQL1 do

040  begin

050     result := false;

060     Close;

070     SQL.Clear;

080     SQL.Add(iSQL);

090     ExecQuery;

100     if RecordCount = 0 then

110       begin

120         FMain.ListBox1.Items.Add('Empty');

130         result := true;

140         exit;

150       end;

160     while not EOF do

170      begin

180       FMain.ListBox1.Items.Add( trim(Fields[0].AsString));

190       Next;

200      end; // while

210  end;

220 end;

- PrintList выполняет (стр. 90) наш первый SQL запрос (стр. 80). В случае, когда осмысленного ответа нет (стр. 100-150) выдается надпись - 'Empty' т.е. базы нет, а иначе (стр. 160-200) выдаем список таблиц в нашей базе.

* А что за страшную строку получает PrintList получает как параметр.

- Это тот самый запрос и есть. Пока разбирать его не будем.

- * -

(уточнение, “- * -“ равняется банке пива)

- Ну как, чего ты такой…

* Опять, не работает.

- Привыкай к тяготам лишениям нашей службы…

- Ну что там у нас

* Operation cancelled at user's request

- Н-да, и что характерно, каждый раз на этом месте…

- Так, у компонента IBDatabase1 измени свойство LoginPrompt на False. И будет тебе счастье.

- * -

* Ну, что, сразу нельзя было сказать?

- Забыл, бывает…

- Как, заработало?

* Да, но гора родила мышь. Ввели много дополнительных слов, а результат тот же.

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

* Не п*ди, что будем делать дальше?

- Дальше?

Готовимся выполнять SQL запросы.

- Для начала, что попроще.

- На закладке History помести TMemo – во всю ее ширину и высоту, да, назови это дело His.

- А на закладке SQL надо расположить тоже TMemo (у меня оно названо Memo1) и для начала пару кнопок “Run SQL” и “Clear SQL”.

- Теперь делаем процедуры кнопок:

procedure TFMain.Button1Click(Sender: TObject);

begin  // кнопка run SQL

RunSQL( MakeSQL);

end;

* Опять временно?

- Ну, не хочу тебя сразу пугать, все по очереди.

- Обрати внимание на комментарий к слову begin – рекомендую так подписывать процедуру, если название ее мало информативно.

* А вторая кнопка?

- Ну, даже неудобно…

procedure TFMain.Button2Click(Sender: TObject);

begin

Memo1.Clear;

end;

* Да, проще некуда. Так теперь по очереди RunSQL( MakeSQL);

- Недавно переделал, довольно много повкалывал с этими 30 строками…

010 function RunSQL( S:string):boolean;

020 var

030  b : boolean; // отслеживание ошибки

040  Mistake : string; // сообщение ошибки

050 begin

060 with DataModule2.IBSQL1 do

070  begin

080     Close;

090     SQL.Clear;

100     SQL.Add(S);

110    try

120     ExecQuery;  // попытка выполнения запроса

130     b := true;  // ошибки нет

140    except    // Обработка ошибки

150      on E: Exception do

160         begin

170           b := false; // к сожалению, ошибка

180           Mistake := E.ClassName+' raised exception: '+E.Message;

190         end;

200    end; // try

210  end;  // with DataModule2

220   result := not b;

230  if b

240   then

250    begin // запрос выполнен

260      Hi.Lines.Add('ok');

270      Memo1.Clear; // и переход на закладку History

280      if DataModule2.IBSQL1.SQLType = SQLSelect

290       then PrintSELECT(S)  // распечатка результата запроса SELECT

300       else PageControl1.ActivePageIndex := 3;

310    end

320   else

330    begin // была ошибка

340     ShowMessage(Mistake); // сообщение об ошибке

350     Hi.Lines.Add('Error');

360     Hi.Lines.Add(Mistake); // запись в историю

370    end;

380  Hi.Lines.Add('------------');

390 end;

- Самое интересное происходит, когда запрос НЕ выполняется.

* Прочитал, все понятно, и над чем тут было биться?

- Все понятно? Отлично, объясни тогда строки 280, 290.

* Так, интересно. Определяется тип запроса и если это SELECT. Нечестно, ты еще ничего не сказал о PrintSELECT.

- Давай, это исправим:

010 procedure PrintSELECT(S:string);

020 var

030  i : integer;

040  a : string;

050  LHTML : TStringList;

060 begin

070  LHTML := TStringList.Create;

080   HTMLHead(LHTML);

090   LHTML.Add('<center><p> '+ s + '</p>');

100 with DataModule2.IBSQL1 do

110 if RecordCount > 0 then

120   BEGIN

130    LHTML.Add('<h2>'+UniqueRelationName+'</h2>');

140    LHTML.Add('<center><table cellspacing=0 cellpadding=0 ><tr>');

150    S := ''; // формирование заголовка таблицы

160       for i := 0 to Current.Count-1 do

170           S := S + '<th>'+ Fields[i].Name +'</th>';

180     S := S + '</tr>'; // конец заголовка таблицы

190    LHTML.Add(S); // печать заголовка таблицы

200     while not EOF do

210      begin

220       S := '<tr>';   //

230       for i := 0 to Current.Count-1 do

240         begin // формирование строки данных

250          a := Fields[i].AsString;

260          if a = '' then a := ' - ';

270          S := S + '<td>'+a+'</td>';

280         end; // for

290       Next;

300       S := S + '</tr>';

310      LHTML.Add(S);

320      end; // while

330   LHTML.Add('</table></center>');

340  END

350  ELSE

360  LHTML.Add('<h3> EMPTY</h3>');

370  LHTML.Add('</body>');

380  LHTML.SaveToFile(Path+'~.htm');

390    Web.Navigate(Path+'~.htm');

400    PageControl1.ActivePageIndex := 2;

410   LHTML.Free;

420 end;

* Не маленький кусочек.

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

* Да ладно, расхвастался, к делу. В начале готовится заголовок HTML.

- Ну это просто:

procedure HTMLHead(LHTML : TStringList);

begin

LHTML.add('<html><head>');

LHTML.add('<meta http-equiv=<Content-Type< content=<text/html; charset=windows-1251<>');

LHTML.add('<style>');

LHTML.add('table {border:1px solid #c3c3c3; border-collapse:collapse;');

LHTML.add(' text-align: center; width:90%;}');

LHTML.add('table th {background-color:#e5eecc; border:1px solid #c3c3c3;');

LHTML.add('padding:3px; vertical-align:top; }');

LHTML.add('table td {border:1px solid #c3c3c3; padding:3px; vertical-align:top;');

LHTML.add('</style></head><body>');

end;

- Тут, я ничего объяснять не буду HTML сегодня не наша тема.

- В цикле стр 200 – 320 выдается результат предшествующего запроса. Остальное все просто.

* Опять, таки не вижу, чему тут гордится.

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

* Хорошо! Спорим, что я напишу лучше!

- Вполне возможно. Я буду только рад. Выиграет только вселенский разум.

* Ладно, успокоились, что у нас еще есть неясного.

* А, что такое MakeSQL – по смыслу делается строка запроса?

- Да, это очень простая функция, и возможно это надо еще упростить…

010 function MakeSQL:string;

020 var

030  i : integer;

040 begin

050  result := '';

060  with FMain.Memo1 do

070  for i := 0 to Lines.Count - 1 do

080   if trim(Lines[i]) <> '' then

090    begin

100    FMain.His.Lines.Add(Lines[i]);

110   result := result + Lines[i] + ' ';

120  end;

130 result := trim(result);

140 end;

- Все, что находится в Memo1 записывается в одну строку(стр. 110) и одновременно сохраняется в истории(стр. 100).

* Так значит мы можем теперь выполнять SQL запросы?

- И да, и нет.

* ??

- Выполнять запросы можем, но не всегда увидим результат. Ну, еще не все готово.

* Так давай! Беги, покупай, эти тр-ан-зис-то-ры!

- Не спеши. Выполним, те SQL, что запланированы.

- Но сначала еще одна мелочь. Сделай обработчик для кнопки Show DB это на самой первой закладке.

procedure TFMain.Button3Click(Sender: TObject);

begin // Show DB

ShowTables;

end;

???????

Первые запросы

- Ну, что, давай попробуем.

- Открой или сделай новую базу и выполни следующий запрос:

CREATE TABLE Salespeople

( snum integer,

sname char (10),

city char (10),

comm decimal );

* Говорит что все OK.

- Перейди на первую закладку и нажми кнопку Show DB.

* Ура!! Заработало!!

- Рано кричать, закрой программу, включи опять и открой твою базу.

* Есть, таблица сразу отобразилась.

* Есть вопросик, для чего у нас на форме CheckListBox1 и закладка Result.

- Ну, с итоговой закладкой погоди, а вот по первому пункту поболтаем.

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

* А как?

- Начинай читать книги по SQL. Я предлагал только играться, (и сделать инструмент для игры) а правила игры вспоминай сам.

- А мне, пора возвращаться к своим, ко… нет к баранам.

Продолжаем клепать ББД

- Итак закладка Structure DB показывает имеющиеся в сей базе таблицы.

- Но, интересно узнать из каких полей состоит таблица.

- Именно эту информацию будет показывать CheckListBox1

* Но, кроме имен полей, хочется знать их типы, размеры и прочее.

- В числе прочей, эта информация появится на накладке Result, но как сказал классик «в очередь, с*ны дети, в очередь».

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

unit gv; // Глобальные переменные

interface

var

Path : string; // Путь к исполняемой программе

implementation

end.

* Да, знатный модуль.

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

- Второе: делаем обработчик создания формы:

10 procedure TFMain.FormCreate(Sender: TObject);

20 begin

30  Path := ExtractFileDir(ParamStr(0)) + '\';

40  OpenDialog1.InitialDir := Path;

50   PageControl1.ActivePage := TabSheet1;

60 end;

- Строка 30 – инициализация пути к запускаемой программе.

* А для чего?

- Ну для начала изменим procedure TFMain.NewDB1Click:

150       IBDatabase1.DatabaseName:=  Path + fn;

* А зачем? И так все хорошо работало.

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

* И для этого заводить специальный модуль.

- Не ворчи, сказано, это моя привычка.

- Строка 40 Инициализация текущей папки для диалога открытия файла.

- По умолчанию диалог откроется с Моих Документов, что часто очень не удобно…

- Строка 50 Делаем активной первую закладку нашей программы, независимо от того, на которой мы остались при запуске компиляции.

* Ух, что-то я за… работался.

- Ладно, давай прервемся.

«Продолжение следует»

А вот и продолжение

- Ну, с новыми силами…

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

procedure TFMain.ListBox1DblClick(Sender: TObject);

var

kod : integer;

begin

ShowFields;

end;

010 procedure ShowFields;

020 var

030  n   : integer;

040 begin

050  CLBox1.Clear;

060 with DataModule2.IBSQL1 do

070  begin

080     Close;

090     SQL.Clear;

100 SQL.Add('select R.RDB$FIELD_NAME from RDB$FIELDS F, RDB$RELATION_FIELDS R '+

110 'where F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE and R.RDB$SYSTEM_FLAG = 0 '+

120  'and RDB$RELATION_NAME = ''' + LBox1.Items[ LBox1.ItemIndex]+''' '+

130 'order by R.RDB$RELATION_NAME, R.RDB$FIELD_POSITION');

140     ExecQuery;

150     while not EOF do

160      begin

170       n := CLBox1.Items.Add( trim(Fields[0].AsString));

180       CLBox1.Checked[n] := true;

190       Next;

200      end;

210    Close;

220  end; //  with DataModule2

230 end;

* Душераздирающее зрелище — вот как это называется!

- Ничего, потренируешься с SQL и будешь его щелкать…

- Как видишь служебная (внутренняя) база содержит несколько таблиц:

RDB$RELATIONS

RDB$FIELDS

RDB$RELATION_FIELDS

и т.д.

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

- В строках 100-130 формируется запрос, в стр. 140 он выполняется, а  затем цикл в строках 150-200 сохраняет имена полей таблицы в пунктах CheckListBox1, по команде стр. 190 переход на следующую запись… вот вкратце.

* Да, объяснения ты печешь как горячие пирожки.

- Подумаешь, пироги! Я еще вышивать умею и на машинке шить. А ты думал, что мы только мясо умеем воровать из кастрюль и на крышах кричать.

* Вызывает интерес, и еще такой разрез. Где ты взял эту информацию?

- Пилите, Шура хелпы, пилите, они золотые.

* А почему мы используем CheckListBox зачем нам эти галочки.

- Вот тут мы подходим к следующей задаче. У нас на файсе есть еще кнопка “Show Data”.

- Подробности о содержимое таблицы будем показывать в зависимости от установленных галочек в этом самом CheckListBox.

- Но, давай по очереди.

Show Structure

- Когда я планировал ББД, и дошел до этого пункта, перебрал несколько вариантов: можно выдавать результат в отдельный файл, можно в Memo, можно StringGrid или подобное, но это все не то. Т.е. хочется, чтобы была видна табличная форма таблицы ( возгласы с мест «* Невнятная тавтология»). А потом сообразил – Браузер, конечно же Браузер. Именно HTML дает широкие и гибкие возможности отображения информации.

- Итак на закладке Result ставим компонент Web типа TwebBrowser.

- А для кнопки “Show Structure” обработчик нажатия.

010 procedure TFMain.Button3Click(Sender: TObject);

020 var

030  i : integer;

040  LHTML : TStringList;

050 begin // кнопка Show Structure

060  if ListBox1.Items.Count = 0 then exit;

070  LHTML := TStringList.Create;

080  HTMLHead(LHTML);

090  with ListBox1 do

100   for i := 0 to Items.Count - 1 do

110     PrintFields(Items[i], LHTML);

120   LHTML.SaveToFile(Path+’~.htm’);

130   Web.Navigate(Path+’~.htm’);

140   PageControl1.ActivePage := TabSheet2;

150   LHTML.Free;

160 end;

- Стр. 70 создаем листочек HTML

- Стр. 80 записываем заголовочную часть HTML

- 100-110 циклически заносим в HTML нужную информацию

- Стр. 120 Вот тут можно было пойти разными путями, я пошел простейшим сохраняю HTML файл в рабочей папке.

- Стр. 130 Отображаю готовый файл в навигаторе.

* Хорошо, копаем глубже…

- Пожалуйста:

010 procedure PrintFields(table:string; LHTML : TStringList);

020 var

030  S : string;

040 begin

050  LHTML.Add('<h2>'+table+'</h2>');

060 with DataM.IBSQL1 do

070  begin

080     Close;

090     SQL.Clear;

100      SQL.Add('select R.RDB$FIELD_POSITION, R.RDB$FIELD_NAME, '+

110 'F.RDB$FIELD_LENGTH, F.RDB$FIELD_TYPE '+

120 'from RDB$FIELDS F, RDB$RELATION_FIELDS R '+

130 'where F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE and R.RDB$SYSTEM_FLAG = 0 '+

140  'and RDB$RELATION_NAME = ''' + table +''' '+

150 'order by R.RDB$RELATION_NAME, R.RDB$FIELD_POSITION');

160     ExecQuery;

170     LHTML.Add('<center><table><tr>');

180     LHTML.Add('<th>Position</th>');

190     LHTML.Add('<th>Name</th>');

200     LHTML.Add('<th>Length</th>');

210     LHTML.Add('<th>Type</th>');

220     LHTML.Add('</tr>');

230     while not EOF do

240      begin

250       S := '<tr><td>' +Fields[0].AsString + '</td>'+

260       '<td>'+ trim(Fields[1].AsString)+'</td>'+

270       '<td>'+ trim(Fields[2].AsString) +'</td>'+

280       '<td>'+ FieadType(Fields[3].AsInteger)+'</td></tr>';

290       LHTML.Add(s);

300       Next;

310      end;

320     LHTML.Add('</table></center>');

330  end;

340 end;

* А я стал привыкать к этим крокозябрам.

- Тем более, что текст во многом повторяется.

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

* Погоди! А что это в 280 строке?

- О, кота то я, и не заметил.

function FieadType(n:integer):string;

begin

case n of

7  : result := 'smallint';

8  : result := 'integer';

9  : result := 'quad';

10 : result := 'float';

11 : result := 'd_float';

12 : result := 'date';

13 : result := 'time';

14 : result := 'char';

27 : result := 'double';

35 : result := 'date';

37 : result := 'varchar';

261 : result := 'blob';

else  result := IntToStr(n)+' ?';

end; // case

end;

В результате отобразится, нечто такое:

- * -

- Ну, и чего так долго, конечно, пива много не бывает, но…

* В общем-то, получилось, но никак не могу скопировать получившееся.

- Да, есть такая задача. Вставь в самом конце (перед end.) в файле Unit1:

initialization

OleInitialize(nil);

finalization

OleUninitialize;

А в самом начале этого файла вставь ActiveX в разделе uses.

* Так, просто! Да что же это такое. Просто хочется рвать и метать!!

- Это Windows.

* Ну, что теперь отображаем данные?

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

«Продолжение следует»

Последний? Рывок к ББД

- Предлагаю вернуться к выполнению SQL запроса.

- Бывают запросы SELECT и все остальные. Результат выполнения SELECT – будем выдавать в закладку result,  а остальные просто выполнять.

- Переделываем Button1Click:

010 procedure TFMain.Button1Click(Sender: TObject);

020 begin  // кнопка run SQL

030    RunSQL(MakeSQL);  // выполение команды SQL

040 end;

- Пойдем дальше.

- Теперь можно заняться кнопочкой «Show Data»

010 procedure TFMain.Button5Click(Sender: TObject);

020 var

030  i : integer;

040  S : string;

050 begin  // Show Data

060  with CheckListBox1 do

070   begin // составляем список полей

080    if Items.Count = 0 then exit;  // если список пуст - уходим

090     S := 'SELECT ';

100    for i := 0 to Items.Count - 1 do // список полей вставляем в запрос

110      if  Checked[i] then

120       S := S + LowerCase(Items[i]) + ', ';

130    end; // with CheckListBox1

140  delete(S, length(S)-1, 2);

150  Memo1.Clear;

160  Memo1.Lines.Add(S);

170  S := 'FROM '+ LowerCase( ListBox1.Items[ ListBox1.ItemIndex] );

180  Memo1.Lines.Add(S);

190  RunSQL( MakeSQL);

200  end;

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

* Ну, как теперь-то все готово.

- Вот же шило в одном месте. Ладно, следующий пункт:

Тестирование

- Как ты помнишь, тренироваться мы будем по книге Мартина Грубера «Понимание SQL»[1], при потребности будем поглядывать в «Firebird. Руководство разработчика баз данных» Хелен Борри[2].

* Ну и в интернете найдется масса материала.

- Для нашей игры понадобится база с определенными таблицами, полюбуйся на них приложении к [1].

* Не сказать, что 3 таблицы это очень много, но…

- Я так понимаю, что тебе лень, а лень двигатель прогресса.

- В данном случае, я тебе помогу…

- На закладке SQL ставим еще одну кнопку «Run SQL by List».

- Будут выполнятся запросы записанные в текстовом файле.

- Каждый запрос должен заканчиваться «;», допускается перенос запроса на несколько строк, не допускается несколько запросов в одной строке.

- Не отображается результат запросов SELECT

- При ошибочном запросе, выполнение списка прекращается.

010 procedure TFMain.Button6Click(Sender: TObject);

020 var

030  i : integer;

040  L : TStringList;

050  S : string;

060 begin  //  Run SQL by List

070    with OpenDialog1 do

080    begin // подготавливаем диалог

090     Filter  :=  'File with SQL|*.txt';

100     DefaultExt := 'txt';

110     if Execute then

120      with DataModule2 do

130       begin

140         L := TStringList.Create;

150         L.LoadFromFile(FileName); // открываем файл

160         His.Lines.Add('Run SQL by List '+FileName);// запись в историю

170         S := '';

180         for i := 0 to L.Count - 1 do

190          begin

200           S := S + ' ' + L[i]; // складываем строки

210           if pos(';', S) > 0 then // пока в строке не появится ';'

220            begin

230             S := trim(S); // ??

240             His.Lines.Add(S); // записываем запрос в историю

250             if RunSQL(S) then break; // если запрос не выполнился выходим.

260             S := '';

270            end; // if

280          end; // for

290          L.Free;

300        end; //  if Execute

310     end;  // OpenDialog1

320 end;

* В общем все понятно…

* Что за безобразие, хочу чтобы SELECT тоже выполнялись списком.

- Надо подумать. Минуточку. «Раз, два... Меркурий во втором доме... луна ушла... шесть – несчастье... вечер – семь...»

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

* Да?!

- Да.

- Возвращаюсь к нашей теме.

- Сейчас я тебе дам пару файлов. Желательно, чтобы ты поступил так:

- 1. Создай новую базу.

- 2. Сделай из нижеследующего файл и выполни его.

«начало кода»

CREATE TABLE Salespeople

( snum integer,

sname char (10),

city char (10),

comm decimal );

CREATE TABLE Customers

( cnum integer,

cname char (10),

city char (10),

rating integer,

snum integer );

CREATE TABLE Orders

( onum integer,

amt decimal,

odate date,

cnum integer,

snum integer );

«конец кода»

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

- 4. Сделай следующий файл и выполни:

«начало кода»

INSERT INTO Salespeople (snum,sname,city ,comm)

VALUES (1001,'Peel','London',.12);

INSERT INTO Salespeople (snum,sname,city ,comm)

VALUES (1002,'Serres','San Jose',.13);

INSERT INTO Salespeople (snum,sname,city ,comm)

VALUES (1004,'Motika','London',.11);

INSERT INTO Salespeople (snum,sname,city ,comm)

VALUES (1007,'Rifkin','Barcelona',.15);

INSERT INTO Salespeople (snum,sname,city ,comm)

VALUES (1003,'Axelrod','New York',.10);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2001,'Hoffman','London',100,1001);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2002,'Giovanni','Rome',200,1003);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2003,'Liu','San Jose',200,1002);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2004,'Grass','Berlin',300,1002);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2006,'Clemens','London',100,1001);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2008,'Cisneros','San Jose',300,1007);

INSERT INTO Customers (cnum,cname,city,rating,snum)

VALUES (2007,'Pereira','Rome',100,1004);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3001,18.69,'10/03/1990',2008,1007);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3003,767.19,'10/03/1990',2001,1001);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3002,1900.10,'10/03/1990',2007,1004);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3005,5160.45,'10/03/1990',2003,1002);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3006,1098.16,'10/03/1990',2008,1007);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3009,1713.23,'10/04/1990',2002,1003);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3007,75.75,'10/04/1990',2004,1002);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3008,4723.00,'10/05/1990',2006,1001);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3010,1309.95,'10/06/1990',2004,1002);

INSERT INTO Orders (onum,amt,odate,cnum,snum)

VALUES (3011,9891.88,'10/06/1990',2006,1001);

«конец кода»

- Все должно пройти хорошо.

* Брюки превращаются, превращаются брюки… в элегантную базу данных. Все ОК.

- Теперь заготовка базы у нас есть, и мы можем выполнять запросы из книги [1] выполнятся будут 3 задачи:

- 1. Самообучение SQL.

- 2. Вычитывание книги (с исправлением опечаток)

- 3. Тестирование программы.

* Значит теперь, со всеми переделками, программа сможет выполнить любой запрос?

- Это предстоит проверить.

Прошло три дня.

- Нет, ребята, пулемета я вам не дам.

- В книге оказалось, очень много ошибок. Ну, очень много ошибок, и все они полезные.

* Догадываюсь, задумано издевательство.

- Точно! Мне так понравилась идея с ошибками, что я задумался, не наделать их и в этом тексте.

* Вот этого, не надо!

- Хорошо, но ошибки неизбежны, где-то что-то я упущу, в этом случае я сделаю вид, что все так и задумано…

* Ну, ты и жук!

- Учись, студент. Учись, как сдавать программу заказчику, главное держать невозмутимое выражение морды лица: “Как, при запуске программы включилось форматирование диска? Ну, это у вас устаревший Windows, давайте я его переставлю, это будет стоить… что, при этом утеряны данные за 3 года работы. Но, это же хорошо (хорошо-то хорошо, да ничего хорошего – что бы еще соврать) – Слава, Богу, это был только сон…”

* Хе-хе, ну у тебя и шуточки. Давай все-таки вернемся к книге [1].

- Да! Как я уже раньше думал сказать, если бы все примеры в книге были бы без ошибок, пользы от нее было бы намного меньше. Копируй запросы, выполняй, проверяй результат… скучно. А тут, наполненная описками жизнь бурлит и хлещет.

* И какие там ошибки?

- Например:

SELECT sname, sity

FROM Salespeople;

WHERE city=LONDON;

или еще:

SELECT * FROM Order WHERE NOT

((odate=10/03/1990 AND cnum < > 1002)OR amt > 2000.00);

- Тут пару ошибок достаточно очевидны (лишний символ “;” и нет апострофов вокруг даты ну и прочее, и прочее.

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

- Посему, изменяем RunSQL

В разделе переменных добавляем:

LPos : integer;  // позиция ошибки

- Между строками 330 и 340 добавляем текст:

330   begin // была ошибка

331  Memo1.Clear;

332  Memo1.Lines.Add(S); // перезапись запроса одной строкой

333  LPos := GetSelStart(Mistake); // поиск места ошибки

334  if LPos > -1 then  begin

335     Memo1.SelStart := LPos-1; // вкл индикатора

336     Memo1.SelLength := 1;

337     Memo1.SetFocus;

338      Panel5.Caption := IntToStr(LPos); // дополнительная информ.

339     end;

340  ShowMessage(Mistake); // сообщение об ошибке

- Как видишь, GetSelStart получает строку с описанием ошибки и пытается найти там информация о ее позиции:

function GetSelStart(S: string):integer;

var

i, n : integer;

ss : string;

begin

result := -1;

n := pos('column ',S);

if n > 0

then begin

delete(S,1,n+6);

ss := '';

for i := 1 to length(s) do

if S[i] in ['0'..'9']

then ss := ss + s[i]

else break;

if ss <> '' then

result := StrToInt(ss);

// ShowMessage(S);

end;

end;

- Согласись, тут все довольно просто. Но результат, впечатляет.

- Да, как всегда, что-то да упустишь, во-первых, в компоненте Memo1 измени свойство HideSelection на false – очень помогает, и второе под этим Memo поставь панельку (у меня это Panel5), которая будет показывать текущее положение курсора в строке, для этого вставь обработчик:

procedure TFMain.Memo1Click(Sender: TObject);

begin

Panel5.Caption := IntToStr(  Memo1.SelStart );

end;

* Ну, ты загонял, то тут добавить, то там исправить.

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

- Ну, что, теперь, твоя очередь тестировать программу.

- * -

- * -

- Ну, как успехи. Выходит?

* И входит, и выходит. Вообще-то мне нравится, но я обнаружил бревно в твоем глазу.

* Как-то раз вылезло такое сообщение:

“Cannot perform operation - DB is currently open”

- Так, так, в каких обстоятельствах появилась ошибка.

* Я отрывал базу.

- И чё сея строчка значит?

* Грит не может выполнять работу – база сейчас открыта.

- Объясни понятнее.

* Т.е. не может открыть базу, потому, что она уже открыта.

- Не обязательно она, но какая-то база открыта.

* Ну, и чего делать?

- Конечно, исправлять.

* Как?

- Думай.

* Ну, если какая-то база открыта, надо ее закрыть.

- Отлично, вот тебе две строчки, вставь их в двух местах… где сам найдешь.

if IBDatabase1.Connected then

IBDatabase1.Connected := false;

* Ты хочешь сказать, что все было запланировано?

- J

- Еще, есть целый ряд ситуаций в книге [1] которые формально не являются ошибкой, но…

* Ну-ка, ну-ка.

- Например такой запрос:

SELECT first.cname, second.cname, first.rating

FROM Customers first, Customers second

WHERE first.rating=second.rating;

* И в чем прикол?

- Попробуй.

* Останавливается на слове second.

- Я тоже не сразу понял, открой в книге [2] список зарезервированных слов.

* Да, не все йогурты одинаковы.

- Как и реализации SQL, но, все они полезны.

* Еще я заметил, что в некоторых случаях запросы выполняется, но порядок строк, не такой как в книге [1].

- Это, та же ситуация. Разные реализации SQL имеют разные установки (по умолчанию) для сортировки, т.е. если будет явно задан порядок, тогда таблица должны 100% совпасть.

* Так, что, задача выполнена?

- Нет, не совсем.

* И в чем дело, какие-то исправления?

- Поправки? Да, так, по мелочи, SetFocus для Memo1 при сбросах.

* И что дает?

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

* А куда дальше, мне кажется, что у меня появилось понимание SQL. Правда, хочется еще раз внимательно перечитать книгу [1].

- Это хорошо, но требуется еще понимание Firebird.

* ?

- Ну, вот сейчас, мы выяснили, что требуется знать зарезервированные слова Firebird. Но в этой системе есть и много вкусненького, что грех не использовать.

* Например?

- Мне очень нравится организация функции AutoIncrement.

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

- Знакомься:

CREATE GENERATOR gl;

SET GENERATOR gl TO 1;

CREATE TRIGGER Persons FOR Persons

ACTIVE BEFORE INSERT POSITION 0

AS

BEGIN

IF (NEW.P_Id IS NULL) THEN

NEW.P_Id = GEN_ID(gl, 1);

END

// (- * -) умножить примерно на 1024.

* Продолжение следует??

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

* Не отбрешешься!

- Да, помнится, были задумки, но чтобы продолжить надо опять сосредоточится в нужное состояние, лень! А стоит ли!

* А я?!

- Ну, давай прикинем, сколько это будет «я». Скачают, сей файл раз 100 – 200, если отфильтровать скачки по ошибке, удаленные по прочтении двух предложений, прочтенные, но не понятые, и тд и тп, то останется 0.75 читателя, пусть из вежливости будет 1.

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

А пока, прощаюсь. Успехов!

W Cat.

Content

Cat W S. D. F.

Пароль S. D. F.

Есть ли у вас план…

Браузер

Первая задача: создать новую базу данных.

ББД продолжение

Задача: открыть существующую базу данных.

Готовимся выполнять SQL запросы.

Первые запросы

Продолжаем клепать ББД

А вот и продолжение

Show Structure

Последний? Рывок к ББД

Тестирование

* Продолжение следует??