Создание базданных в Delphi
Урок1:Настройка BDE
Содержаниеурока 1:
Обзор2
СущностьBDE2
Алиасы2
Системнаяинформацияутилиты настройкиBDE 4
Заключение5
На этом урокемы познакомимсяс ядром базданных компанииБорланд - BorlandDatabase Engine (BDE), а такженаучимся создаватьи редактироватьалиасы - механизм,облегчающийсвязь с базамиданных. Крометого, мы изучим,как конфигурироватьODBC драйверы.
Мощность игибкость Delphi приработе с базамиданных основанана низкоуровневомядре - процессоребаз данныхBorland Database Engine (BDE). Его интерфейсс прикладнымипрограммаминазываетсяIntegrated Database Application Programming Interface (IDAPI). Впринципе, сейчасне различаютэти два названия(BDE и IDAPI) и считаютих синонимами.BDE позволяетосуществлятьдоступ к даннымкак с использованиемтрадиционногоrecord-ориентированного(навигационного)подхода, таки с использованиемset-ориентированногоподхода, используемогов SQL-серверахбаз данных.Кроме BDE, Delphi позволяетосуществлятьдоступ к базамданных, используятехнологию(и, соответственно,драйверы) OpenDataBase Connectivity (ODBC) фирмыMicrosoft. Но, как показываетпрактика,производительностьсистем с использованиемBDE гораздо выше,чем оных прииспользованииODBC. ODBC драйвераработают черезспециальный“ODBC socket”, которыйпозволяетвстраиватьих в BDE.
Все инструментальныесредства базданных Borland- Paradox, dBase, Database Desktop - используютBDE. Все особенности,имеющиеся вParadox или dBase, “наследуются”BDE, и поэтому этимиже особенностямиобладает иDelphi.
Таблицы сохраняютсяв базе данных.Некоторые СУБДсохраняют базуданных в виденесколькихотдельныхфайлов, представляющихсобой таблицы(в основном,все локальныеСУБД), в то времякак другиесостоят изодного файла,который содержитв себе все таблицыи индексы(InterBase). Например,таблицы dBase иParadox всегда сохраняютсяв отдельныхфайлах на диске.Каталог, содержащийdBase .DBF файлы илиParadox .DB файлы,рассматриваетсякак база данных.Другими словами,любой каталог,содержащийфайлы в форматеParadox или dBase, рассматриваетсяDelphi как единаябаза данных.Для переключенияна другую базуданных нужнопросто переключитьсяна другой каталог.Как уже былоуказано выше,InterBase сохраняетвсе таблицыв одном файле,имеющем расширение.GDB, поэтому этотфайл и естьбаза данныхInterBase.
Удобно не простоуказывать путьдоступа к таблицамбазы данных,а использоватьдля этого некийзаменитель- псевдоним,называемыйалиасом. Онсохраняетсяв отдельномконфигурационномфайле в произвольномместе на дискеи позволяетисключить изпрограммыпрямое указаниепути доступак базе данных.Такой подходдает возможностьрасполагатьданные в любомместе, не перекомпилируяпри этом программу.Кроме путидоступа, в алиасеуказываютсятип базы данных,языковый драйвери много другойуправляющейинформации.Поэтому использованиеалиасов позволяетлегко переходитьот локальныхбаз данных кSQL-сервернымбазам (естественно,при выполнениитребованийразделенияприложенияна клиентскуюи сервернуючасти).
Для созданияалиаса запуститеутилиту конфигурацииBDE (программуbdeadmin.exe), находящуюсяв каталоге, вкотором располагаютсядинамическиебиблиотекиBDE.
Рис. 1: Главноеокно утилитыконфигурацииBDE
Рис.2: В диалоговомокне добавлениянового алиасаможно указатьтип базы данных
После созданиянового алиасаследует датьему имя. Этоможно сделатьс помощью подпункта“Rename” меню“Object”. Однакопросто создатьалиас не достаточно.Вам нужно указатьдополнительнуюинформацию,содержаниекоторой зависитот типа выбраннойбазы данных.Например, длябаз данныхParadox и dBase (STANDARD) требуетсяуказать лишьпуть доступак данным, имядрайвера и флагENABLE BCD, которыйопределяет,транслируетли BDE числав двоично-десятичномформате (значениядвоично-десятичногокода устраняютошибки округления):TYPE | STANDARD |
DEFAULTDRIVER | PARADOX |
ENABLEBCD | FALSE |
PATH | c:\users\data |
SQL-сервер InterBase идругие типыбаз данныхтребуют заданиябольшого количествапараметров,многие из которыхможно оставитьустановленнымипо умолчанию.
Итак, мы познакомилисьс наиболееважной возможностьюутилиты настройкиBDE - созданиеми редактированиемалиасов, определяющихпараметрыдоступа к базамданных. Однако,утилита настройкиBDE позволяетспецифицироватьне только алиасы,но и драйверыдля доступак базам данных,а также различнуюсистемнуюинформацию,составляющуюоперационноеокружение этихсамых алиасов.
Рассмотрим,например, системнуюинформациюдрайвера PARADOX:
NET DIR. Параметрсодержитрасположениекаталога сетевогоуправляющегофайла. Он нужендля того, чтобыобратитьсяк таблице PARADOXна сетевомдиске.
VERSION. Номерверсии драйвера.
TYPE. Тип драйвера.
LANGDRIVER. Языковойдрайвер, определяющиймножестводопустимыхсимволов.
BLOCK SIZE. Размерблока на диске,используемогодля запоминанияодной записи.
FILL FACTOR. Содержитпроцент отблока на текущемдиске. Параметрнужен для созданияиндексныхфайлов.
LEVEL. Параметропределяеттип формататаблицы, используемойдля созданиявременныхтаблиц.
STRICTINTEGRTY. Параметриспользованияссылочнойцелостности.Если он равенTRUE, то вы не можетеизменить таблицус ссылочнойцелостностью,а если FALSE, то можете,но рискуетенарушить целостностьданных.
Как уже отмечалосьвыше, утилитанастройки BDEсохраняет всюконфигурационнуюинформациюв файле IDAPI.CFG. Этотфайл с предустановленнымиссылками надрайверы инекоторымистандартнымиалиасами создаетсяпри установкеDelphi. Кроме того,он создаетсяпри установкефайлов редистрибуцииBDE (т.е. когда ВыпереноситеBDE и SQL Links на другиекомпьютеры).
Итак, на данномуроке мы постаралисьпонять для, чтотакое BDE,изучили оченьважное дляработы с базамиданных понятие- алиас, а такженаучилисьнастраиватьего параметрыдля корректнойработы программына примередрайвера PARADOX.
Урок 1:НастройкаBDE
Создание базданных в Delphi
Урок 2:Создание таблицс помощью DatabaseDesktop
Содержаниеурока 2:
Обзор2
УтилитаDatabase Desktop2
Заключение9
На данном урокемы изучим, каксоздаватьтаблицы базыданных с помощьюутилиты Database Desktop,входящей впоставку Delphi. Хотядля созданиятаблиц можноиспользоватьразличныесредства (SQL -компонентTQueryи компонентTTable),применениеэтой утилитыпозволяетсоздаватьтаблицы винтерактивномрежиме и сразуже просмотретьих содержимое- и все это длябольшого числаформатов. Этоособенно удобнодля локальныхбаз данных, вчастностиParadox и dBase.
Database Desktop - это утилита,во многом похожаяна Paradox, котораяпоставляетсявместе с Delphi дляинтерактивнойработы с таблицамиразличныхформатов локальныхбаз данных -Paradox и dBase, а такжеSQL-серверныхбаз данныхInterBase, Oracle, Informix, Sybase (с использованиемSQL Links). Исполняемыйфайл утилитыназываетсяDBD32.EXE. Для запускаDatabase Desktop просто дваждыщелкните поее иконке.
Рис. 1: Выпадающийсписок в диалоговомокне Table Type позволяетвыбрать типсоздаваемойтаблицы
После выборатипа таблицыDatabase Desktop представитВам диалоговоеокно, специфичноедля каждогоформата, в которомВы сможетеопределитьполя таблицыи их тип, какпоказано нарис.2.
Рис. 2: Database Desktop позволяетзадать именаи типы полейв таблице
Имя поля в таблицеформата Paradoxпредставляетсобой строку,написаниекоторой подчиняетсяследующимправилам:
Имя должнобыть не длиннее25 символов.
Имя не должноначинатьсяс пробела, однакоможет содержатьпробелы. Однако,если Вы предполагаетев будущем переноситьбазу данныхв другие форматы,разумнее будетизбегать включенияпробелов вназвание поля.Фактически,в целях переносимостилучше ограничитьсядевятью символамив названииполя, не включаяв него пробелы.
Имя не должносодержатьквадратные,круглые илифигурные скобки[], ()или {},тире, а такжекомбинациюсимволов “тире”и “больше”(->).
Имя не должнобыть толькосимволом #,хотя этот символможет присутствоватьв имени средидругих символов.Хотя Paradox поддерживаетточку (.)в названииполя, лучше ееизбегать, посколькуточка зарезервированав Delphi для другихцелей.
Имя поля в таблицеформата dBaseпредставляетсобой строку,написаниекоторой подчиняетсяправилам, отличнымот Paradox:
Имя должнобыть не длиннее10 символов.
Пробелы в именинедопустимы.
Таким образом,Вы видите, чтоимена полейв формате dBaseподчиняютсягораздо болеестрогим правилам,нежели таковыев формате Paradox.Однако, мы ещераз хотимподчеркнуть,что если передВами когда-либовстанут вопросысовместимости,то лучше сразузакладыватьэту совместимость- давать полямимена, подчиняющиесяболее строгимправилам.
Укажем ещеправила, которымподчиняетсянаписание именполей в форматеInterBase.
Имя должнобыть не длиннее31 символа.
Имя должноначинатьсяс букв A-Z,a-z.
Имя поля можетсодержатьбуквы (A-Z,a-z),цифры, знак $и символ подчеркивания(_).
Пробелы в именинедопустимы.
Для имен таблицзапрещаетсяиспользоватьзарезервированныеслова InterBase.
Следующий(после выбораимени поля) шагсостоит в заданиитипа поля. Типыполей оченьсильно различаютсядруг от друга,в зависимостиот формататаблицы. Дляполучениясписка типовполей перейдитек столбцу “Type”,а затем нажмитепробел илищелкните правойкнопкой мышки.Приведем спискитипов полей,характерныедля форматовParadox, dBase и InterBase.
Итак, поля таблицформата Paradox могутиметь следующийтип (для вводатипа поля можнонабрать толькоподчеркнутыебуквы или цифры):
Табл.A: Типы полейформата Paradox
Alpha | строкадлиной 1-255 байт,содержащаялюбые печатаемыесимволы |
Number | числовоеполе длиной8 байт, значениекоторого можетбыть положительными отрицательным.Диапазон чисел- от 10-308до 10308с 15 значащимицифрами |
$(Money) | числовоеполе, значениекоторого можетбыть положительными отрицательным.По умолчанию,являетсяформатированнымдля отображениядесятичнойточки и денежногознака |
Short | числовоеполе длиной2 байта, котороеможет содержатьтолько целыечисла в диапазонеот -32768 до 32767 |
LongInteger | числовоеполе длиной4 байта, котороеможет содержатьцелые числав диапазонеот -2147483648 до 2147483648 |
#(BCD) | числовоеполе, содержащееданные в форматеBCD (Binary Coded Decimal). Скоростьвычисленийнемного меньше,чем в другихчисловых форматах,однако точность- гораздо выше.Может иметь0-32 цифр последесятичнойточки |
Date | поледаты длиной4 байта, котороеможет содержатьдату от 1 января9999 г. до нашейэры - до 31 декабря9999 г. нашей эры.Корректнообрабатываетвисокосныегода и имеетвстроенныймеханизм проверкиправильностидаты |
Time | полевремени длиной4 байта, содержитвремя в миллисекундахот полуночии ограничено24 часами |
@(Timestamp) | обобщенноеполе даты длиной8 байт - содержити дату и время |
Memo | поледля хранениясимволов,суммарнаядлина которыхболее 255 байт.Может иметьлюбую длину.При этом размер,указываемыйпри созданиитаблицы, означаетколичествосимволов,сохраняемыхв таблице (1-240)- остальныесимволы сохраняютсяв отдельномфайле с расширением.MB |
FormattedMemo | поле,аналогичноеMemo, с добавлениемвозможностизадавать шрифттекста. Такжеможет иметьлюбую длину.При этом размер,указываемыйпри созданиитаблицы, означаетколичествосимволов,сохраняемыхв таблице (0-240)- остальныесимволы сохраняютсяв отдельномфайле с расширением.MB. Однако, Delphi встандартнойпоставке необладаетвозможностьюработать сполями типаFormatted Memo |
Graphic | поле,содержащееграфическуюинформацию.Может иметьлюбую длину.Смысл размера- такой же, каки в Formatted Memo. Database Desktop “умеет”создаватьполя типа Graphic,однако наполнятьих можно тольков приложении |
OLE | поле,содержащееOLE-данные (Object Linking andEmbedding) - образы, звук,видео, документы- которые длясвоей обработкивызывают создавшееих приложение.Может иметьлюбую длину.Смысл размера- такой же, каки в Formatted Memo. Database Desktop “умеет”создаватьполя типа OLE,однако наполнятьих можно тольков приложении.Delphi “напрямую”не умеет работатьс OLE-полями, ноэто легкообходитсяпутем использованияпотоков |
Logical | поледлиной 1 байт,которое можетсодержатьтолько двазначения - T(true, истина) илиF(false, ложь). Допускаютсястрочные ипрописныебуквы |
+(Autoincrement) | поледлиной 4 байта,содержащеенередактируемое(read-only) значениетипа longinteger.Значение этогополя автоматическиувеличивается(начиная с 1) сшагом 1 - этоочень удобнодля созданияуникальногоидентификаторазаписи (физическийномер записине может служитьее идентификатором,поскольку вПарадоксетаковой отсутствует.В InterBase также отсутствуютфизическиеномера записей,но отсутствуети поле Autoincrement.Его с успехомзаменяетвстроеннаяфункция Gen_id,которую удобнейвсего применятьв триггерах) |
Binary | поле,содержащеелюбую двоичнуюинформацию.Может иметьлюбую длину.При этом размер,указываемыйпри созданиитаблицы, означаетколичествосимволов,сохраняемыхв таблице (0-240)- остальныесимволы сохраняютсяв отдельномфайле с расширением.MB. Это полнейшийаналог поляBLOb в InterBase |
Bytes | строкацифр длиной1-255 байт, содержащаялюбые данные |
Поля таблицформата dBase могутиметь следующийтип (для вводатипа поля можнонабрать толькоподчеркнутыебуквы или цифры):
Табл.B: Типы полейформата dBase
Character(alpha) | строкадлиной 1-254 байт,содержащаялюбые печатаемыесимволы |
Float(numeric) | числовоеполе размером1-20 байт в форматес плавающейточкой, значениекоторого можетбыть положительными отрицательным.Может содержатьочень большиевеличины, однакоследует иметьв виду постоянныеошибки округленияпри работе сполем такоготипа. Числоцифр последесятичнойточки (параметрDec в DBD) должно бытьпо крайнеймере на 2 меньше,чем размервсего поля,поскольку вобщий размервключаютсясама десятичнаяточка и знак |
Number(BCD) | числовоеполе размером1-20 байт, содержащееданные в форматеBCD (Binary Coded Decimal). Скоростьвычисленийнемного меньше,чем в другихчисловых форматах,однако точность- гораздо выше.Число цифрпосле десятичнойточки (параметрDec в DBD) также должнобыть по крайнеймере на 2 меньше,чем размервсего поля,поскольку вобщий размервключаютсясама десятичнаяточка и знак |
Date | поледаты длиной8 байт. По умолчанию,используетсяформат короткойдаты (ShortDateFormat) |
Logical | поледлиной 1 байт,которое можетсодержатьтолько значения“истина” или“ложь” - T,t,Y,y(true, истина) илиF,f,N,n(false, ложь). Допускаютсястрочные ипрописныебуквы. Такимобразом, в отличиеот Парадокса,допускаютсябуквы “Y” и “N”(сокращениеот Yes и No) |
Memo | поледля хранениясимволов,суммарнаядлина которыхболее 255 байт.Может иметьлюбую длину.Это поле хранитсяв отдельномфайле. Database Desktop неимеет возможностивставлятьданные в полетипа Memo |
OLE | поле,содержащееOLE-данные (Object Linking andEmbedding) - образы, звук,видео, документы- которые длясвоей обработкивызывают создавшееих приложение.Может иметьлюбую длину.Это поле такжесохраняетсяв отдельномфайле. Database Desktop “умеет”создаватьполя типа OLE,однако наполнятьих можно тольков приложении.Delphi “напрямую”не умеет работатьс OLE-полями, ноэто легкообходитсяпутем использованияпотоков |
Binary | поле,содержащеелюбую двоичнуюинформацию.Может иметьлюбую длину.Данное полесохраняетсяв отдельномфайле с расширением.DBT. Это полнейшийаналог поляBLOb в InterBase |
Поля таблицформата InterBase могутиметь следующийтип:
Табл.C: Типы полейформата InterBase
SHORT | числовоеполе длиной2 байта, котороеможет содержатьтолько целыечисла в диапазонеот -32768 до 32767 |
LONG | числовоеполе длиной4 байта, котороеможет содержатьцелые числав диапазонеот -2147483648 до 2147483648 |
FLOAT | числовоеполе длиной4 байта, значениекоторого можетбыть положительными отрицательным.Диапазон чисел- от 3.4*10-38до 3.4*1038с 7 значащимицифрами |
DOUBLE | числовоеполе длиной8 байт (длиназависит отплатформы),значение которогоможет бытьположительными отрицательным.Диапазон чисел- от 1.7*10-308до 1.7*10308с 15 значащимицифрами |
CHAR | строкасимволовфиксированнойдлины (0-32767 байт),содержащаялюбые печатаемыесимволы. Числосимволов зависитот Character Set, установленногов InterBase для данногополя или длявсей базы данных(например, длясимволов вкодировкеUnicode число символовбудет в двараза меньшедлины строки) |
VARCHAR | строкасимволовпеременнойдлины (0-32767 байт),содержащаялюбые печатаемыесимволы. Числосимволов такжезависит отCharacter Set, установленногов InterBase для данногополя или длявсей базы данных |
DATE | поледаты длиной8 байт, значениекоторого можетбыть от 1 января100 года до 11 декабря5941 года (времятакже содержится) |
BLOB | поле,содержащеелюбую двоичнуюинформацию.Может иметьлюбую длину.Database Desktop не имеетвозможностиредактироватьполя типа BLOB |
ARRAY | поле,содержащеемассивы данных.InterBase позволяетопределятьмассивы, имеющиеразмерность16. Поле можетиметь любуюдлину. Однако,Database Desktop не имеетвозможностине толькоредактироватьполя типа ARRAY,но и создаватьих |
TEXTBLOB | подтипBLOB-поля, содержащеетолько текстовуюинформацию.Может иметьлюбую длину.Database Desktop не имеетвозможностиредактироватьполя типа TEXTBLOB |
Типы полеймогут отличатьсяот приведенныхвыше. Это зависитот версии драйверабазы данных.
Итак, мы изучиливсе типы полей,являющиеся“родными” дляDelphi.
После этогодля таблицParadox мы можемопределитьполя, составляющиепервичный ключ,причем все онидолжны бытьв начале записи,а первое поле,входящее включ, должнобыть первымполем в записи.Для этого достаточнопо ней дваждыщелкнуть мышкойили нажатьлюбую клавишу.
После созданиятаблицы, с нейможно связатьнекоторыесвойства, переченькоторых зависитот формататаблицы. Так,для таблицформата Paradox можнозадать:
Validity Checks (проверкаправильности)- относится кполю записии определяетминимальноеи максимальноезначение, атакже значениепо умолчанию.Кроме того,позволяетзадать маскуввода
Table Lookup (таблицадля “подсматривания”)- позволяетвводить значениев таблицу, используяуже существующеезначение вдругой таблице
Secondary Indexes (вторичныеиндексы) - позволяютдоступатьсяк данным в порядке,отличном отпорядка, задаваемогопервичнымключом
Referential Integrity (ссылочнаяцелостность)- позволяетзадать связимежду таблицамии поддерживатьэти связи науровне ядра.Обычно задаетсяпосле созданиявсех таблицв базе данных
Password Security (парольнаязащита) - позволяетзакрыть таблицупаролем
Table Language (язык таблицы)- позволяетзадать длятаблицы языковыйдрайвер.
В таблицахdBase не существуетпервичныхключей. Однако,это обстоятельствоможно преодолетьпутем определенияуникальных(Unique) и поддерживаемых(Maintained) индексов(Indexes). Кроме того,для таблицdBase можно определитьи язык таблицы(Table Language) - языковыйдрайвер, управляющийсортировкойи отображениемсимвольныхданных.
Определениядополнительныхсвойств таблицвсех форматовдоступны черезкнопку “Define” (длятаблиц InterBase даннаякнопка называется“Define Index...” и позволяетопределятьлишь толькоиндекс, но непервичный ключ)в правой верхнейчасти окна(группа Table Properties).Причем, все этидействия можнопроделыватьне только присоздании таблицы,но и тогда, когдаона уже существует.Для этогоиспользуетсякоманда Table|RestructureTable (для открытойв данный моменттаблицы) илиUtilities|Restructure (с возможностьювыбора таблицы).Однако, еслиВы желаетеизменить структуруили добавитьновые свойствадля таблицы,которая в данныймомент ужеиспользуетсядругим приложением,Database Desktop откажетВам в этом, посколькуданная операциятребует монопольногодоступа к таблице.Но зато всепроизведенныев структуреизменения сразуже начинают“работать”- например, еслиВы определитессылочнуюцелостностьдля пары таблиц,то при попыткевставить вдочернюю таблицуданные, отсутствующиев родительскойтаблице, в Delphiвозникнетисключительноесостояние.
В заключениеотметим ещечасто используемуюочень полезнуювозможностьDatabase Desktop. Создаватьтаблицу любогоформата можноне только “счистого листа”,но и путемкопированияструктуры ужесуществующейтаблицы. Дляэтого достаточновоспользоватьсякнопкой “Borrow”,имеющейся влевом нижнемуглу окна.Появляющеесядиалоговоеокно позволитВам выбратьсуществующуютаблицу ивключить/выключитьдополнительныеопции, совпадающиес уже перечисленнымисвойствамитаблиц. Этонаиболее легкийспособ созданиятаблиц.
Итак, на данномуроке мы познакомилисьсо штатнойутилитой,используемойдля интерактивногосоздания имодификациитаблиц различнойструктуры. Ихотя управлениетаблицами можноосуществлятьс помощью различныхсредств (компонентTTable,компонентTQuery),данная утилитапозволяетделать это винтерактивномрежиме наиболеепростым способом.
Урок 2:Создание таблицс помощью DatabaseDesktop
Созданиебаз данных вDelphi
Урок 3:Создание таблицс помощьюSQL-запросов
Содержаниеурока 3:
Обзор2
Созданиетаблиц с помощьюSQL2
Заключение6
На данномуроке мы познакомимсяеще с однойвозможностьюсоздания таблиц- через посылкуSQL-запросов. КакВы, наверное,могли заметитьна предыдущемуроке, Database Desktop необладает всемивозможностямипо управлениюSQL-сервернымибазами данных.Поэтому с помощьюDatabase Desktop удобно создаватьили локальныебазы данныхили толькопростейшиеSQL-серверныебазы данных,состоящие изнебольшогочисла таблиц,не очень сильносвязанных другс другом. Еслиже Вам необходимосоздать базуданных, состоящуюиз большогочисла таблиц,имеющих сложныевзаимосвязи,можно воспользоватьсяязыком SQL. Приэтом можновоспользоватьсякомпонентомQueryв Delphi, каждый разпосылая поодному SQL-запросу,а можно записатьвсю последовательностьSQL-предложенийв один так называемыйскрипти послать егона выполнение.Конечно, дляэтого нужнохорошо знатьязык SQL, но, уверяюВас, сложногов этом ничегонет! Конкретныереализацииязыка SQL незначительноотличаютсяв различныхSQL-серверах, однакобазовые предложенияостаются одинаковымидля всех реализаций.
Если Вы хотитевоспользоватьсякомпонентомTQuery,сначала поместитеего на форму.После этогонастройтесвойство DatabaseNameна нужный Вамалиас. Послеэтого можноввести SQL-предложениев свойство SQL.Для выполнениязапроса, изменяющегоструктуру,вставляющегоили обновляющегоданные на сервере,нужно вызватьметод ExecSQLкомпонентаTQuery.Для выполнениязапроса, получающегоданные с сервера(т.е. запроса,в котором основнымявляется операторSELECT), нужно вызватьметод OpenкомпонентаTQuery.Это связанос тем, что BDE припосылке запросатипа SELECT открываеттак называемыйкурсор,с помощью которогоосуществляетсянавигация повыборке данных(подробней обэтом см. в уроке,посвященномTQuery).
ПриведемупрощенныйсинтаксисSQL-предложениядля созданиятаблицы наSQL-сервере InterBase(более полныйсинтаксис можнопосмотретьв online-справочникепо SQL, поставляемомс локальнымInterBase):
CREATE TABLE table
(
где
table- имя создаваемойтаблицы,
Описаниеполя состоитиз наименованияполя и типаполя (или домена- см. урок 9), а такжедополнительныхограничений,накладываемыхна поле:
[DEFAULT{literal | NULL | USER}]
[NOT NULL][
[COLLATEcollation]
Здесь
col- имя поля;
datatype- любой правильныйтип SQL-сервера(для InterBase такимитипами являютсяSMALLINT,INTEGER,FLOAT,DOUBLEPRECISION, DECIMAL,NUMERIC,DATE,CHAR,VARCHAR,NCHAR,BLOB),символьныетипы могутиметь CHARACTER SET - наборсимволов,определяющийязык страны.Для русскогоязыка следуетзадать наборсимволов WIN1251;
COMPUTEDBY (
domain- имя домена(обобщенноготипа), определенногов базе данных;
DEFAULT- конструкция,определяющаязначение поляпо умолчанию;
NOTNULL - конструкция,указывающаяна то, что полене может бытьпустым;
COLLATE- предложение,определяющеепорядок сортировкидля выбранногонабора символов(для поля типаBLOB не применяется).Русский наборсимволов WIN1251имеет 2 порядкасортировки- WIN1251 и PXW_CYRL. Для правильнойсортировки,включающейбольшие буквы,следует выбратьпорядок PXW_CYRL.
Описаниеограниченийи/или ключейвключает в себяпредложенияCONSTRAINTили предложения,описывающиеуникальныеполя, первичные,внешние ключи,а также ограниченияCHECK(такие конструкциимогут определятьсякак на уровнеполя, так и науровне таблицыв целом, еслиони затрагиваютнесколькополей):
Здесь
| CHECK(
{
|
|
|
col [
| NULL | USER |RDB$DB_KEY } [COLLATE collation]
COUNT (* | [ALL]
| SUM ([ALL]
|AVG ([ALL]
| MAX ([ALL]
| MIN ([ALL]
| CAST (
| UPPER (
| GEN_ID(generator,
}
Приведенногонеполногосинтаксисадостаточнодля большинствазадач, решаемыхв различныхпредметныхобластях. Прощевсего синтаксисSQL можно понятьиз примеров.Поэтому мыприведем несколькопримеров созданиятаблиц с помощьюSQL.
ПримерA: Простая таблицас конструкциейPRIMARY KEYна уровне поля
CREATE TABLE REGION(
REGIONREGION_NAME NOT NULL PRIMARY KEY,
POPULATIONINTEGERNOT NULL);
Предполагается,что в базе данныхопределен доменREGION_NAME,например, следующимобразом:
CREATE DOMAINREGION_NAME
AS VARCHAR(40)CHARACTER SET WIN1251 COLLATE PXW_CYRL;
ПримерB: Таблица спредложениемUNIQUEкак на уровнеполя, так и науровне таблицы
CREATE TABLE GOODS (
MODEL SMALLINT NOTNULL UNIQUE,
NAMECHAR(10) NOTNULL,
ITEMID INTEGER NOTNULL, CONSTRAINT MOD_UNIQUE
UNIQUE (NAME,ITEMID));
ПримерC: Таблица сопределениемпервичногоключа, внешнегоключа и конструкцииCHECK,а также символьныхмассивов
CREATE TABLE JOB(
JOB_CODE JOBCODENOT NULL,
JOB_GRADEJOBGRADENOT NULL,
JOB_REGIONREGION_NAME NOT NULL,
JOB_TITLEVARCHAR(25) CHARACTER SET WIN1251 COLLATE PXW_CYRL NOT NULL,
MIN_SALARY SALARYNOT NULL,
MAX_SALARY SALARYNOT NULL,
JOB_REQ BLOB(400,1)CHARACTER SET WIN1251,
LANGUAGE_REQVARCHAR(15) [5],
PRIMARY KEY(JOB_CODE, JOB_GRADE, JOB_REGION),
FOREIGN KEY(JOB_REGION) REFERENCES REGION (REGION),
CHECK (MIN_SALARY Данный примерсоздает таблицу,содержащуюинформациюо работах(профессиях).Типы полейоснованы надоменах JOBCODE,JOBGRADE,REGION_NAMEи SALARY.Определенмассив LANGUAGE_REQ,состоящий из5 элементовтипа VARCHAR(15).Кроме того,введено полеJOB_REQ,имеющее типBLOBс подтипом 1(текстовыйблоб) и размеромсегмента 400.Для таблицыопределенпервичный ключ,состоящий изтрех полейJOB_CODE,JOB_GRADEи JOB_REGION.Далее, определенвнешний ключ(JOB_REGION),ссылающийсяна поле REGIONтаблицы REGION.И, наконец, включенопредложениеCHECK,позволяющеепроизводитьпроверку соотношениядля двух полейи вызыватьисключительноесостояние принарушениитакого соотношения. ПримерD: Таблица свычисляемымполем CREATETABLE SALARY_HISTORY ( EMP_NO EMPNO NOTNULL, CHANGE_DATE DATEDEFAULT "NOW" NOT NULL, UPDATER_IDVARCHAR(20) NOT NULL, OLD_SALARY SALARYNOT NULL, PERC_CHANGEDOUBLEPRECISION DEFAULT 0 NOT NULL CHECK (PERC_CHANGEBETWEEN -50 AND 50), NEW_SALARY COMPUTEDBY (OLD_SALARY +OLD_SALARY * PERC_CHANGE / 100), PRIMARY KEY (EMP_NO,CHANGE_DATE, UPDATER_ID), FOREIGN KEY (EMP_NO)REFERENCES EMPLOYEE (EMP_NO)); Данный примерсоздает таблицу,где среди другихполей имеетсявычисляемое(физически несуществующее)поле NEW_SALARY,значение котороговычисляетсяпо значениямдвух другихполей (OLD_SALARYи PERC_CHANGE). Итак, мырассмотрели,как создаватьтаблицы с помощьюSQL-выражений.Этот процесс,хотя и не стольудобен, какинтерактивноесредство DatabaseDesktop, однако обладаетнаиболее гибкимивозможностямипо настройкеВашей системыи управленияее связями. Урок 3: Созданиетаблиц с помощьюSQL запросов Создание базданных в Delphi Урок4: ОбъектTTable Содержаниеурока 4:1 КлассTDataSet2 Открытиеи закрытиеDataSet4 Поля10 Работас Данными14 ИспользованиеSetKey для поискав таблице17 Использованиефильтров дляограничениячисла записейв DataSet20 Обновление(Refresh)22 Закладки(Bookmarks)23 СозданиеСвязанныхКурсоров (Linkedcursors)23 Основныепонятия оTDataSource26 ИспользованиеTDataSource для проверкисостоянияБД:27 ОтслеживаниесостоянияDataSet 31 Обзор Статья содержитвсестороннийобзор основныхфактов которыеВы должны знать,прежде чемначать писатьпрограммы,работающиес Базами Данных(БД). Прочитавэту статью, Выдолжны понятьбольшинствомеханизмовдоступа к данным,которые естьв Delphi. Болееподробно здесьрассказываетсяо TTable и TDataSource. Имеются несколькоосновныхкомпонент(объектов),которые Выбудете использоватьпостоянно длядоступа к БД.Эти объектымогут бытьразделены натри группы: невизуальные:TTable, TQuery, TDataSet, TField визуальные:TDBGrid, TDBEdit связующие:TDataSource Первая группавключает невизуальныеклассы, которыеиспользуютсядля управлениятаблицами изапросами. Этагруппа сосредотачиваетсявокруг компоненттипа TTable, TQuery, TDataSet и TField.В Палитре Компонентэти объектырасположенына страницеData Access. Вторая важнаягруппа классов- визуальные,которые показываютданные пользователю,и позволяютему просматриватьи модифицироватьих. Эта группаклассов включаеткомпонентытипа TDBGrid, TDBEdit, TDBImage иTDBComboBox. В ПалитреКомпонент эти объекты расположенына страницеData Controls. Имеется и третийтип, которыйиспользуетсядля того, чтобы связать предыдущиедва типа объектов.К третьему типуотноситсятолько невизуальныйкомпонентTDataSource. TDataSet класс - одиниз наиболееважных объектовБД. Чтобы начатьработать с ним,Вы должны взглянутьна следующуюиерархию: TDataSet | TDBDataSet | |-- TTable |-- TQuery |-- TStoredProc TDataSet содержитабстрактныеметоды там, гдедолжно бытьнепосредственноеуправлениеданными. TDBDataSet знает,как обращатьсяс паролями ито, что нужносделать, чтобыприсоединитьВас к определеннойтаблице. TTable знает(т.е. уже всеабстрактныеметоды переписаны),как обращатьсяс таблицей, ееиндексами ит.д. КакВы увидите вдалее, TQuery имеетопределенныеметоды дляобработки SQLзапросов. TDataSet - инструмент,который Выбудете использоватьчтобы открытьтаблицу, иперемещатьсяпо ней. Конечно,Вы никогда небудете непосредственносоздаватьобъект типаTDataSet. Вместо этого,Вы будетеиспользоватьTTable, TQuery или другихпотомков TDataSet(например, TQBE).Полное пониманиеработы системы,и точное значениеTDataSet, будут становитьсявсе более яснымипо мере прочтенияэтой главы. Нанаиболеефундаментальномуровне, Dataset этопросто наборзаписей, какизображенона рис.1Заключение
Содержаниеурока 4:
КлассTDataSet
Рис.1: Любойdataset состоит изряда записей(каждая содержитN полей) и указательна текущуюзапись.
В большинствеслучаев dataset будетиметь a прямое,один к одному,соответствиес физическойтаблицей, котораясуществуетна диске. Однако,в других случаяхВы можете исполнятьзапрос илидругое действие,возвращающиеdataset, который содержитлибо любоеподмножествозаписей однойтаблицы, либообъединение(join) между несколькимитаблицами. Втексте будутиногда использоватьсятермины DataSet иTTable как синонимы.
Обычно в программеиспользуютсяобъекты типаTTable или TQuery, поэтомув следующихнесколькихглавах будетпредполагатьсясуществованиеобъекта типаTTable называемогоTable1.
Итак, самоевремя начатьисследованиеTDataSet. Как толькоВы познакомитесьс его возможностями,Вы начнетепонимать, какиеметоды используетDelphi для доступак данным, хранящимсяна диске в видеБД. Ключевоймомент здесь- не забывать,что почти всякийраз, когдапрограммистна Delphi открываеттаблицу, онбудет использоватьTTable или TQuery, которыеявляются простонекоторойнадстройкойнад TDataSet.
В этой главеВы узнаетенекоторые фактыоб открытиии закрытииDataSet.
Если Вы используетеTTable для доступак таблице, топри открытииданной таблицызаполняютсянекоторыесвойства TTable(количествозаписей RecordCount,описание структурытаблицы и т.д.).
Прежде всего,Вы должны поместитьво время дизайнана форму объектTTable и указать, скакой таблицейхотите работать.Для этого нужнозаполнить вИнспектореобъектов свойстваDatabaseName и TableName. В DatabaseName можнолибо указатьдиректорию,в которой лежаттаблицы в форматеdBase или Paradox (например,C:\DELPHI\DEMOS\DATA), либо выбратьиз списка псевдонимбазы данных(DBDEMOS). Теперь, еслисвойство Activeустановитьв True, то при запускеприложениятаблица будетоткрыватьсяавтоматически.
Имеются дваразличныхспособа открытьтаблицу вовремя выполненияпрограммы. Выможете написатьследующуюстроку кода:
Table1.Open;
Или, если Выпредпочитаете,то можете установитьсвойство Activeравное True:
Table1.Active:= True;
Нет никакогоразличия междурезультатомпроизводимымэтими двумяоперациями.Метод Open, однако,сам заканчиваетсяустановкойсвойства Active вTrue, так что можетбыть даже чутьболее эффективноиспользоватьсвойство Activeнапрямую.
Также, какимеются дваспособа открытьa таблицу, таки есть два способазакрыть ее.Самый простойспособ простовызывать Close:
Table1.Close;
Или,если Вы желаете,Вы можете написать:
Table1.Active:= False;
Еще раз повторим,что нет никакойсущественнойразницы междудвумя этимиспособами. Выдолжны толькопомнить, чтоOpen и Close это методы(процедуры), аActive - свойство.
Навигация(Перемещениепо записям)
После открытиятаблицы, следующимшагом Вы должныузнать какперемещатьсяпо записямвнутри него.
Следующийобширный наборметодов и свойстваTDataSet обеспечиваетвсе , что Вамнужно для доступак любой конкретнойзаписи внутритаблицы:
procedureFirst;
procedureLast;
procedureNext;
procedurePrior;
propertyBOF: Boolean read FBOF;
propertyEOF: Boolean read FEOF;
procedureMoveBy(Distance: Integer);
Дадимкраткий обзорих функциональныхвозможностей:
ВызовTable1.First перемещаетВас к первойзаписи в таблице.
Table1.LastперемещаетВас к последнейзаписи.
Table1.NextперемещаетВас на однузапись вперед.
Table1.PriorперемещаетВас на однузапись Назад.
Выможете проверятьсвойства BOF илиEOF, чтобы понять,находитесьли Вы в началеили в концетаблицы.
ПроцедураMoveBy перемещаетВас на N записейвперед илиназад в таблице.Нет никакогофункциональногоразличия междузапросом Table1.Nextи вызовомTable1.MoveBy(1). Аналогично,вызов Table1.Prior имееттот же самыйрезультат, чтои вызов Table1.MoveBy(-1).
Чтобы начатьиспользоватьэти навигационныеметоды, Вы должныпоместитьTTable, TDataSource и TDBGrid на форму,также, как Выделали это впредыдущемуроке. ПрисоединитеDBGrid1 к DataSource1, и DataSource1 к Table1.Затем установитесвойства таблицы:
вDatabaseName имя подкаталога,где находятсядемонстрационныетаблицы (илипсевдонимDBDEMOS);
вTableName установитеимя таблицыCUSTOMER.
Если Вы запустилипрограмму,которая содержитвидимый элементTDBGrid, то увидите,что можноперемещатьсяпо записямтаблицы с помощьюполос прокрутки(scrollbar) на нижнейи правой сторонахDBGrid.
Однако, иногданужно перемещатьсяпо таблице“программнымпутем”, безиспользованиявозможностей,встроенныхв визуальныекомпоненты.В следующихнесколькихабзацах объясняетсякак можно этосделать.
Поместите двекнопки на формуи назовите ихNext и Prior, как показанона рис.2.
Рис.2 : Next и Prior кнопкипозволяют Вамперемещатьсяпо БД.
Дваждыщелкните накнопке Next - появитсязаготовкаобработчикасобытия:
procedureTForm1.NextClick(Sender: TObject);
begin
end;
Теперьдобавьте однустрочку кодатак, чтобы процедуравыглядела так:
procedureTForm1.NextClick(Sender: TObject);
begin
Table1.Next;
end;
Повторитете же самыедействия скнопкой Prior, так,чтобы функциясвязанная сней выгляделатак:
procedureTForm1.PriorClick(Sender: TObject);
begin
Table1.Prior;
end;
Теперь запуститепрограмму, инажмите накнопки. Вы увидите,что они легкопозволяют Вамперемещатьсяпо записям втаблице.
Теперьдобавьте ещедве кнопки иназовите ихFirst и Last, как показанона рис.3
Рис.3: Программасо всеми четырьмякнопками.
Сделайтето же самое дляновых кнопок.
procedureTForm1.FirstClick(Sender: TObject);
begin
Table1.First;
end;
procedureTForm1.LastClick(Sender: TObject);
begin
Table1.Last;
end;
Нет ничегоболее простогочем эти навигационныефункции. First перемещаетВас в началотаблицы, Last перемещаетВас в конецтаблицы, а Next иPrior перемещаютВас на однузапись впередили назад.
TDataSet.BOF - read-only Boolean свойство,используетсядля проверки,находитесьли Вы в началетаблицы. СвойстваBOF возвращаетtrue в трех случаях:
Послетого, как Выоткрыли файл;
Послетого, как ВывызывалиTDataSet.First;
Послетого, как вызовTDataSet.Prior не выполняется.
Первые двапункта - очевидны.Когда Вы открываететаблицу, Delphi помещаетВас на первуюзапись; когдаВы вызываетеметод First, Delphi такжеперемещаетВас в началотаблицы. Третийпункт, однако,требует небольшогопояснения:после того, какВы вызывалиметод Prior многораз, Вы могли добраться доначала таблицы,и следующийвызов Prior будетнеудачным -после этогоBOF и будет возвращатьTrue.
Следующий кодпоказываетсамый общийпример использованияPrior, когда Вы попадаетек началу a файла:
whilenot Table.Bof do begin
DoSomething;
Table1.Prior;
end;
В коде, показанномздесь, гипотетическаяфункция DoSomething будетвызвана спервана текущейзаписи и затемна каждой следующейзаписи (от текущейи до началатаблицы). Циклбудет продолжатьсядо тех пор, покавызов Table1.Prior несможет большепереместитьВас на предыдущуюзапись в таблице.В этот моментBOF вернет True и программавыйдет из цикла.(Чтобы оптимизироватьвышеприведенныйкод, установитеDataSource1.Enabled в False передначалом цикла,и верните егов True после окончанияцикла.)
ВсесказанноеотносительноBOF также применимои к EOF. Другимисловами, код,приведенныйниже показываетпростой способпробежать повсем записямв a dataset:
Table1.First;
whilenot Table1.EOF do begin
DoSomething;
Table1.Next;
end;
Классическаяошибка в случаях,подобных этому:Вы входите вцикл while или repeat, нозабываетевызывать Table1.Next:
Table1.First;
repeat
DoSomething;
untilTable1.EOF;
ЕслиВы случайнонаписали такойкод, то вашамашина зависнет.Также, этот кодмог бы вызватьпроблемы, еслиВы открылипустую таблицу.Так как здесьиспользуетсяцикл repeat,DoSomething был бы вызванодин раз, дажеесли бы нечегобыло обрабатывать.Поэтому, лучшеиспользоватьцикл whileвместо repeatв ситуацияхподобных этой.
EOFвозвращаетTrue в следующихтрех случаях:
Послетого, как Выоткрыли пустойфайл;
Послетого, как ВывызывалиTDataSet.Last;
Послетого, как вызовTDataSet.Next не выполняется.
Единственнаянавигационнаяпроцедура,которая ещене упоминалась- MoveBy, которая позволяетВам переместитьсяна N записей вперед илиназад в таблице.Если Вы хотитепереместитьсяна две записивперед, то напишите:
MoveBy(2);
Иесли Вы хотите переместитьсяна две записиназад, то:
MoveBy(-2);
При использованииэтой функцииВы должны всегдапомнить, чтоDataSet - это изменяющиесяобъекты, и запись,которая былапятой по счетув предыдущиймомент, теперьможет бытьчетвертой илишестой иливообще можетбыть удалена...
Prior и Next - это простыефункции, которыевызывают MoveBy.
В большинствеслучаев, когдаВы хотите получитьдоступ из программык индивидуальныеполям записи,Вы можетеиспользоватьодно из следующихсвойств илиметодов, каждыйиз которыхпринадлежатTDataSet:
propertyFields[Index: Integer];
functionFieldByName(const FieldName: string): TField;
propertyFieldCount;
СвойствоFieldCount возвращаетчисло полейв текущей структурезаписи. ЕслиВы хотите программнымпутем прочитатьимена полей,то используйтесвойство Fieldsдля доступак ним:
var
S:String;
begin
S:= Fields[0].FieldName;
end;
Если Вы работалис записью вкоторой первоеполе называетсяCustNo, тогда кодпоказанныйвыше поместитстроку “CustNo” впеременнуюS. Если Вы хотитеполучить доступк имени второгополя в вышеупомянутомпримере, тогдаВы могли бынаписать:
S:= Fields[1].FieldName;
Корочеговоря, индекспередаваемыйв Fields(начинающийсяс нуля), и определяетномер поля ккоторому Выполучите доступ,т.е. первое поле- ноль, второеодин, и так далее.
ЕслиВы хотите прочитатьтекущее содержаниеконкретногополя конкретнойзаписи, то Выможете использоватьсвойство Fieldsили методFieldsByName.Для того, чтобынайти значениепервого полязаписи, прочитайтепервый элементмассива Fields:
S:= Fields[0].AsString;
Предположим,что первое полев записи содержитномер заказчика,тогда код, показанныйвыше, возвратилбы строку типа“1021”, “1031” или “2058”.Если Вы хотелиполучить доступк этот переменный,как к числовойвеличине, тогдаВы могли быиспользоватьAsInteger вместо AsString.Аналогично,свойство Fieldsвключают AsBoolean,AsFloat и AsDate.
Еслихотите, Вы можетеиспользоватьфункцию FieldsByNameвместо свойстваFields:
S:= FieldsByName(‘CustNo’).AsString;
Как показанов примерахвыше, и FieldsByName,и Fieldsвозвращаютте же самыеданные. Дваразличныхсинтаксисаиспользуютсяисключительнодля того, чтобыобеспечитьпрограммистовгибким и удобнымнабором инструментовдля программногодоступа к содержимомуDataSet.
Давайте посмотримна простомпримере, какможно использоватьдоступ к полямтаблицы вовремя выполненияпрограммы.Создайте новыйпроект, положитена форму объектTTable, два объектаListBox и две кнопки- “Fields” и “Values” (смрис.4).
Соединитеобъект TTable с таблицейCUSTOMER, котораяпоставляетсявместе с Delphi (DBDEMOS),не забудьтеоткрыть таблицу(Active = True).
Рис.4: ПрограммаFLDS показывает,как использоватьсвойство Fields.
СделайтеDouble click на кнопкеFields и создайтеa метод которыйвыглядит так:
procedureTForm1.FieldsClick(Sender: TObject);
var
i:Integer;
begin
ListBox1.Clear;
fori := 0 to Table1.FieldCount - 1 do
ListBox1.Items.Add(Table1.Fields[i].FieldName);
end;
Обработчиксобытия начинаетсяс очистки первогоListBox1, затем он проходитчерез все поля,добавляя ихимена один задругим в ListBox1.Заметьте, чтоцикл показанныйздесь пробегаетот 0 до FieldCount - 1. ЕслиВы забудетевычесть единицуиз FieldCount, то Вы получитеошибку “List Index Out ofBounds”, так как Выбудете пытатьсяпрочесть имяполя котороене существует.
Предположим,что Вы ввеликод правильно,и заполнилиListBox1 именами всехполей в текущейструктурезаписи.
В Delphi существуюти другие средствакоторые позволяютВам получитьту же самуюинформацию,но это самыйпростой способдоступа к именамполей в Run Time.
Свойство Fieldsпозволяет Вамполучить доступне только именамполей записи,но также и ксодержимомуполей. В нашемпримере, длявторой кнопкинапишем:
procedureTForm1.ValuesClick(Sender: TObject);
var
i:Integer;
begin
ListBox2.Clear;
fori := 0 to Table1.FieldCount - 1 do
ListBox2.Items.Add(Table1.Fields[i].AsString);
end;
Этот код добавляетсодержимоекаждого изполей во второйlistbox. Обратитевнимание, чтовновь счетчикизменяетсяот нуля до FieldCount- 1.
Свойство Fieldsпозволяет Вамвыбрать типрезультатанаписав Fields[N].AsString.Этот и несколькосвязанныхметодов обеспечиваютa простой и гибкийспособ доступак данным, связаннымис конкретнымполем. Вот списокдоступныхметодов которыйВы можете найтив описаниикласса TField:
propertyAsBoolean
propertyAsFloat
propertyAsInteger
propertyAsString
propertyAsDateTime
Всякий раз(когда это имеетсмысл), Delphi сможетсделать преобразования.Например, Delphiможет преобразовыватьполе Boolean к Integer илиFloat, или поле Integer кString. Но не будетпреобразовыватьString к Integer, хотя и можетпреобразовыватьFloat к Integer. BLOB и Memo поля- специальныеслучаи, и мы ихрассмотримпозже. Если Выхотите работатьс полями Date илиDateTime, то можетеиспользовать AsString и AsFloat для доступак ним.
Как было объясненовыше, свойствоFieldByName позволяетВам получитьдоступ к содержимомуопределенногополя простоуказав имяэтого поля:
S:= Table1.FieldByName(‘CustNo’).AsString;
Это - удобнаятехнология,которая имеетнесколькопреимуществ,когда используетсясоответствующимобразом. Например,если Вы не увереныв местонахожденииполя, или еслиВы думаете, чтоструктуразаписи, с которойВы работаетемогла измениться,и следовательно,местонахождениеполя не определено.
Следующиеметоды позволяютВам изменитьданные, связанныес TTable:
procedureAppend;
procedureInsert;
procedureCancel;
procedureDelete;
procedureEdit;
procedurePost;
Все эти методы- часть TDataSet, ониунаследованыи используютсяTTable и TQuery.
Всякий раз,когда Вы хотитеизменить данные,Вы должны сначалаперевестиDataSet в режим редактирования.Как Вы увидите,большинствовизуальныхкомпонентделают этоавтоматически,и когда Выиспользуетеих, то совершенноне будете обэтом заботиться.Однако, еслиВы хотите изменитьTTable программно,Вам придетсяиспользоватьвышеупомянутыефункции.
Имеется a типичнаяпоследовательность,которую Вымогли бы использоватьпри измененииполя текущейзаписи:
Table1.Edit;
Table1.FieldByName(‘CustName’).AsString:= ‘Fred’;
Table1.Post;
Первая строкапереводит БДв режим редактирования.Следующаястрока присваиваетзначение ‘Fred’полю ‘CustName’. Наконец,данные записываютсяна диск, когдаВы вызываетеPost.
При использованиитакого подхода,Вы всегда работаетес записями. Самфакт перемещенияк следующейзаписи автоматическисохраняет вашиданные на диск.Например, следующийкод будет иметьтот же самыйэффект, что икод показанныйвыше, плюс этомубудет перемещатьВас на следующуюзапись:
Table1.Edit;
Table1.FieldByName(‘CustNo’).AsInteger:= 1234;
Table1.Next;
Общее правило,которому нужноследовать -всякий раз,когда Вы сдвигаетесьс текущей записи,введенные Вамиданные будутзаписаныавтоматически.Это означает,что вызовыFirst,Next,Prior иLastвсегда выполняютPost, еслиВы находилисьв режиме редактирования.Если Вы работаетес данными насервере итранзакциями,тогда правила,приведенныездесь, не применяются.Однако, транзакции- это отдельныйвопрос с ихсобственнымиспециальнымиправилами, Выувидите это,когда прочитаетео них в следующихуроках.
Темне менее, дажеесли Вы не работаетесо транзакциями,Вы можете всеже отменитьрезультатывашего редактированияв любое время,до тех пор, покане вызвалинапрямую иликосвенно методPost.Например, еслиВы перевелитаблицу в режимредактирования,и изменилиданные в одномили более полей,Вы можете всегдавернуть записьв исходноесостояниевызовом методаCancel.
Существуютдва метода,названныеAppend иInsert,который Выможете использоватьвсякий раз,когда Вы хотитедобавить новуюзапись в DataSet. Очевидноимеет большесмысла использоватьAppend дляDataSets которые неиндексированы,но Delphi не будетгенерироватьexception если Вы используетеAppend наиндексированнойтаблице. Фактически,всегда можноиспользоватьи Append,и Insert.
Продемонстрируемработу методовна простомпримере. Чтобысоздать программу,используйтеTTable, TDataSource и TdbGrid. Открытьтаблицу COUNTRY. Затемразместитедве кнопки наформе и назовитеих ‘Insert’ и ‘Delete’. Когда Вы всесделаете, то должна получитьсяпрограмма,показаннаяна рис.5
Рис.5: Программаможет вставлятьи удалять записьиз таблицыCOUNTRY.
Следующимшагом Вы долженсвязать кодс кнопкамиInsert и Delete:
procedureTForm1.InsertClick(Sender: TObject);
begin
Table1.Insert;
Table1.FieldByName('Name').AsString:= 'Russia';
Table1.FieldByName('Capital').AsString:= 'Moscow';
Table1.Post;
end;
procedureTForm1.DeleteClick(Sender: TObject);
begin
Table1.Delete;
end;
Процедурапоказаннаяздесь сначалапереводиттаблицу в режимвставки (новаязапись с незаполненнымиполями вставляетсяв текущую позициюdataset). После вставкипустой записи,следующимэтапом нужноназначитьзначения одномуили большемуколичествуполей. Существует,конечно, несколькоразличных путейприсвоить этизначения. Внашей программеВы могли быпросто ввестиинформациюв новую записьчерез DBGrid. Или Вымогли бы разместитьна форме стандартнуюстроку ввода(TEdit) и затем установитькаждое полеравным значению,которое пользовательнапечатал вэтой строке:
Table1.FieldByName(‘Name’).AsString:= Edit1.Text;
Можно было быиспользоватькомпоненты,специальнопредназначенныедля работы сданными в DataSet.
Назначениеэтой главы,однако, состоитв том, чтобы показать, каквводить данныеиз программы.Поэтому, в примеревводимая информацияскомпилированапрямо в кодпрограммы:
Table1.FieldByName('Name').AsString:= 'Russia';
Один из интересныхмоментов в этомпримере этото, что нажатиекнопки Insert дваждыподряд автоматическивызывает exception‘Key Violation’. Чтобыисправить этуситуацию, Выдолжны либоудалить текущуюзапись, илиизменять поляName и Capital вновь созданнойзаписи.
Просматриваякод показанныйвыше, Вы увидите,что простовставка записии заполненияее полей недостаточнодля того, чтобы изменить физическиеданные на диске.Если Вы хотите,чтобы информациязаписаласьна диск, Вы должнывызывать Post.
Если послевызова Insert, Вырешаете отказатьсяот вставкиновой записи,то Вы можетевызвать Cancel. ЕслиВы сделаетеэто прежде, чемВы вызоветеPost, то все что Выввели послевызова Insert будетотменено, иdataset будет находитьсяв состоянии,которое былодо вызова Insert.
Однодополнительноесвойство, котороеВы должны иметьв виду называетсяCanModify.Если CanModify возвращаетFalse, то TTable находитьсяв состоянииReadOnly. В противномслучае CanModify возвращаетTrue и Вы можете редактироватьили добавлятьзаписи в неепо желанию.CanModify - само по себе‘read only’ свойство.Если Вы хотитеустановитьDataSet в состояниетолько на чтение(Read Only), то Вы должныиспользоватьсвойство ReadOnly,не CanModify.
Для того,чтобы найтинекоторуювеличину втаблице, программистна Delphi можетиспользоватьдве процедурыSetKey иGotoKey.Обе эти процедурыпредполагают,что поле покоторому Выищете индексировано.Delphi поставляетсяс демонстрационнойпрограммойSEARCH, которая показывает,как использоватьэти запросы.
Чтобы создатьпрограммуSEARCH, поместитеTTable, TDataSource, TDBGrid, TButton, TLabel и TEdit наформу, и расположитеих как показанона рис.6. Назовитекнопку Search, и затемсоединитекомпонентыБД так, чтобыВы видели вDBGrid1 таблицу Customer.
Рис.6: ПрограммаSEARCH позволяетВам ввестиномер заказчикаи затем найтиего по нажатиюкнопки.
Вся функциональностьпрограммыSEARCH скрыта вединственномметоде, которыйприсоединенк кнопке Search. Этафункция считываетстроку, введеннуюв окно редактора,и ищет ее в колонкеCustNo, и наконецпомещает фокусна найденнойзаписи. В простейшемварианте, кодприсоединенныйк кнопке Search выглядиттак:
procedureTSearchDemo.SearchClick(Sender: TObject);
begin
Table1.SetKey;
Table1.FieldByName(’CustNo’).AsString:= Edit1.Text;
Table1.GotoKey;
end;
Первый вызовв этой процедуреустановитTable1 в режим поиска.Delphi должен знать,что Вы переключилисьв режим поискапросто потому,что свойствоFields используетсяпо другому вэтом режиме.Далее, нужноприсвоитьсвойству Fieldsзначение, котороеВы хотите найти.Для фактическоговыполненияпоиска нужнопросто вызыватьTable1.GotoKey.
Если Вы ищетене по первичномуиндексу файла,тогда Вы должныопределитьимя индекса,который Выиспользуетев свойствеIndexName. Например,если таблицаCustomer имеет вторичныйиндекс по полюCity, тогда Вы должныустановитьсвойство IndexNameравным именииндекса. КогдаВы будете искатьпо этому полю,Вы должны написать:
Table1.IndexName:= ’CityIndex’;
Table1.Active:= True;
Table1.SetKey;
Table1.FieldByName(’City’).AsString:= Edit1.Text;
Table1.GotoKey;
Запомните:поиск не будетвыполняться,если Вы не назначитеправильноиндекс (св-воIndexName). Кроме того,Вы должны обратитьвнимание, чтоIndexName - это свойствоTTable, и не присутствуетв других прямыхпотомках TDataSet илиTDBDataSet.
Когда Вы ищетенекотороезначение в БД, всегда существуетвероятностьтого, что поискокажется неудачным.В таком случаеDelphi будет автоматическивызывать exception,но если Вы хотитеобработать ошибку сами,то могли бынаписать примернотакой код:
procedureTSearchDemo.SearchClick(Sender: TObject);
begin
Cust.SetKey;
Cust.FieldByName('CustNo').AsString:=CustNoEdit.Text;
ifnot Cust.GotoKey then
raiseException.CreateFmt('Cannot find CustNo %g',
[CustNo]);
end;
Вкоде, показанномвыше, либо неверноеприсвоениеномера, либонеудача поискаавтоматическиприведут ксообщению обошибке ‘Cannotfind CustNo %g’.
Иногдатребуется найтине точно совпадающеезначение, аблизкое к нему,для этого следуетвместо GotoKeyпользоватьсяметодом GotoNearest.
ПроцедураApplyRangeпозволяет Вамустановитьфильтр, которыйограничиваетдиапазонпросматриваемыхзаписей. Например,в БД Customers, поле CustNoимеет диапазонот 1,000 до 10,000. ЕслиВы хотите видетьтолько те записи,которые имеютномер заказчикамежду 2000 и 3000, тоВы должныиспользоватьметод ApplyRange,и еще два связанныхс ним метода.Данные методыработают толькос индексированнымполем.
Вотпроцедуры,которые Выбудете чащевсего использоватьпри установкефильтров:
procedureSetRangeStart;
procedureSetRangeEnd;
procedureApplyRange;
procedureCancelRange;
Крометого, у TTable естьдополнительныеметоды дляуправленияфильтрами:
procedureEditRangeStart;
procedureEditRangeEnd;
procedureSetRange;
Дляиспользованияэтих процедурнеобходимо:
Сначалавызвать SetRangeStartи использоватьсвойство Fields дляопределенияначала диапазона.
Затемвызвать SetRangeEndи вновь использоватьсвойство Fields дляопределенияконца диапазона.
Первыедва шага подготавливаютфильтр, и теперьвсе что Вамнеобходимо,это вызватьApplyRange,и новый фильтрвступит в силу.
Когданужно прекратитьдействие фильтра- вызовитеCancelRange.
ПрограммаRANGE, которая естьсреди примеровDelphi, показывает,как использоватьэти процедуры.Чтобы создатьпрограмму,поместитеTTable, TDataSource и TdbGrid на форму.Соедините ихтак, чтобы Вывидеть таблицуCUSTOMERS из подкаталогаDEMOS. Затем поместитедва объектаTLabel на форму иназовите их‘Start Range’ и ‘End Range’. Затемположите наформу два объектаTEdit. Наконец, добавьтекнопки ‘ApplyRange’ и‘CancelRange’. Когда Вывсе выполните,форма имеетвид, как на рис.7
Рис.7: ПрограммаRANGE показываеткак ограничиватьчисло записейтаблицы дляпросмотра.
ПроцедурыSetRangeStartи SetRangeEndпозволяют Вамуказать первоеи последнеезначения вдиапазонезаписей, которыеВы хотите видеть.Чтобы начатьиспользоватьэти процедуры,сначала выполнитеdouble-click на кнопкеApplyRange,и создайтепроцедуру,которая выглядиттак:
procedureTForm1.ApplyRangeBtnClick(Sender: TObject);
begin
Table1.SetRangeStart;
ifRangeStart.Text '' then
Table1.Fields[0].AsString := RangeStart.Text;
Table1.SetRangeEnd;
ifRangeEnd.Text '' then
Table1.Fields[0].AsString:= RangeEnd.Text;
Table1.ApplyRange;
end;
СначалавызываетсяпроцедураSetRangeStart,которая переводиттаблицу в режимдиапазона(range mode). Затем Выдолжны определитьначало и конецдиапазона.Обратите внимание,что Вы используетесвойство Fields дляопределениядиапазона:
Table1.Fields[0].AsString:= RangeStart.Text;
Такое использованиесвойства Fields - этоспециальныйслучай, так каксинтаксис,показанныйздесь, обычноиспользуетсядля установкизначения поля.Этот специальныйслучай имеетместо толькопосле того, какВы перевелитаблицу в режимдиапазона,вызвав SetRangeStart.
Заключительныйшаг в процедурепоказаннойвыше - вызовApplyRange.Этот вызовфактическиприводит вашзапрос в действие.После вызоваApplyRange, TTable больше нев находитсяв режиме диапазона,и свойстваFields функционируеткак обычно.
Обработчиксобытия нажатиякнопки ‘CancelRange’:
procedureTForm1.CancelRangeBtnClick(Sender: TObject);
begin
Table1.CancelRange;
end;
Как Вы уже знаете,любая таблица,которую Выоткрываетевсегда “подверженаизменению”.Короче говоря,Вы должны расценитьтаблицу скореекак меняющуюся,чем как статическуюсущность. Дажеесли Вы - единственноелицо, использующееданную TTable, и дажеесли Вы не работаетев сети, всегдасуществуетвозможностьтого, что программас которой Выработаете,может иметьдва различныхпути измененияданных в таблице.В результате,Вы должны всегдазнать, необходимоли Вам обновитьвид таблицына экране.
ФункцияRefreshсвязана с функциейOpen, втом смысле чтоона считываетданные, илинекоторую частьданных, связанныхс данной таблицей.Например, когдаВы открываететаблицу, Delphi считываетданные непосредственноиз файла БД.Аналогично,когда Вы Регенерируететаблицу, Delphi считываетданные напрямуюиз таблицы.Поэтому Выможете использоватьэту функцию,чтобы перепрочитатьтаблицу, еслиВы думаете чтоона могла измениться.Быстрее иэффективнее,вызывать Refresh,чем вызыватьClose изатем Open.
Имейте ввиду,однако, чтообновлениеTTable может иногдапривести кнеожиданнымрезультатам.Например, еслиa пользовательрассматриваетзапись, котораяуже была удалена,то она исчезнетс экрана в тотмомент, когдабудет вызванRefresh. Аналогично,если некийдругой пользовательредактировалданные, то вызовRefresh приведет кдинамическомуизменениюданных. Конечномаловероятно,что один пользовательбудет изменятьили удалятьзапись в товремя, как другойпросматриваетее, но это возможно.
Частобывает полезноотметить текущееместоположениев таблице так,чтобы можнобыло быстровозвратитьсяк этому местув дальнейшем.Delphi обеспечиваетэту функциональнуювозможностьпосредствомтрех методов,которые используютпонятие закладки.
functionGetBookmark: TBookmark;
(устанавливаетзакладку втаблице)
procedureGotoBookmark(Bookmark: TBookmark);
(переходитна закладку)
procedureFreeBookmark(Bookmark: TBookmark);
(освобождаетпамять)
КакВы можете видеть,вызов GetBookmarkвозвращаетпеременную типа TBookmark. TBookmark содержитдостаточноеколичествоинформации,чтобы Delphi могнайти местоположениек которомуотносится этотTBookmark. Поэтому Выможете простопередаватьэтот TBookmark функцииGotoBookmark, и будетенемедленновозвращенык местоположению,связанномус этой закладкой.
Обратитевнимание, чтовызов GetBookmarkраспределяетпамять дляTBookmark, так что Выдолжны вызыватьFreeBookmarkдо окончаниявашей программы,и перед каждойпопыткой повторногоиспользованияTbookmark (в GetBookMark).
Связанныекурсоры позволяютпрограммистамопределитьотношение одинко многим(one-to-many relationship). Например,иногда полезносвязать таблицыCUSTOMER и ORDERS так, чтобыкаждый раз,когда пользовательвыбирает имязаказчика, тоон видит списокзаказов связанныхс этим заказчиком.Иначе говоря,когда пользовательвыбирает записьо заказчике,то он можетпросматриватьтолько заказы,сделанные этимзаказчиком.
ПрограммаLINKTBL демонстрирует,как создатьпрограммукоторая используетсвязанныекурсоры. Чтобысоздать программузаново, поместитедва TTable, два TDataSources идва TDBGrid на форму.Присоединитепервый набортаблице CUSTOMER, авторой к таблицеORDERS. Программав этой стадииимеет вид, показанныйна рис.8
Рис.8:ПрограммаLINKTBL показывает,как определитьотношения междудвумя таблицами.
Следующий шагдолжен связатьтаблицу ORDERS стаблицей CUSTOMERтак, чтобы Вывидели толькоте заказы, которыесвязанные стекущей записьюв таблице заказчиков.В первой таблицезаказчик однозначноидентифицируетсясвоим номером- поле CustNo. Во второйтаблице принадлежностьзаказа определяетсятакже номеромзаказчика вполе CustNo. Следовательно,таблицы нужносвязывать пополю CustNo в обоихтаблицах (полямогут иметьразличноеназвание, нодолжны бытьсовместимыпо типу). Дляэтого, Вы должнысделать тришага, каждыйиз которыхтребует некоторогопояснения:
УстановитьсвойствоTable2.MasterSource = DataSource1
УстановитьсвойствоTable2.MasterField = CustNo
УстановитьсвойствоTable2.IndexName = CustNo
Если Вы теперьзапуститепрограмму, тоувидите, чтообе таблицысвязаны вместе,и всякий раз,когда Вы перемещаетесьна новую записьв таблице CUSTOMER,Вы будете видетьтолько те записив таблице ORDERS,которые принадлежатэтому заказчику.
СвойствоMasterSource в Table2 определяетDataSource от которогоTable2 может получитьинформацию.То есть, онопозволяеттаблице ORDERS знать,какая записьв настоящеевремя являетсятекущей в таблицеCUSTOMERS.
Но тогда возникаетвопрос: Какаяеще информациянужна Table2 длятого, чтобыдолжным образомотфильтроватьсодержимоетаблицы ORDERS? Ответсостоит из двухчастей:
Требуетсяимя поля покоторому связанныдве таблицы.
Требуетсяиндекс по этомуполю в таблицеORDERS (в таблице‘многих записей’),которая будетсвязыватьсяс таблицейCUSTOMER(таблице вкоторой выбирается‘одна запись’).
Чтобы правильновоспользоватьсяинформациейописаннойздесь, Вы должнысначала проверить,что таблицаORDERS имеет нужныеиндексы. Еслиэтот индекспервичный,тогда не нужнодополнительноуказывать егов поле IndexName, и поэтомуВы можете оставитьэто поле незаполненнымв таблице TTable2(ORDERS). Однако, еслитаблица связанас другой черезвторичныйиндекс, то Выдолжны явноопределятьэтот индексв поле IndexName связаннойтаблицы.
В примере показанномздесь таблицаORDERS не имеет первичногоиндекса по полюCustNo, так что Выдолжны явнозадать в свойствеIndexName индекс CustNo.
Недостаточно,однако, простоyпомянуть имяиндекса, которыйВы хотитеиспользовать.Некоторыеиндексы могутсодержатьнесколькополей, так чтоВы должны явнозадать имяполя, по которомуВы хотите связатьдве таблицы.Вы должны ввестиимя ‘CustNo’ в свойствоTable2.MasterFields. Если Выхотите связатьдве таблицыбольше чем поодному полю,Вы должны внестив список всеполя, помещаясимвол ‘|’ междукаждым:
Table1.MasterFields:= ‘CustNo | SaleData | ShipDate’;
В данном конкретномслучае, выражение,показанноездесь, не имеетсмысла, так какхотя поля SaleData иShipDate также индексированы,но не дублируютсяв таблице CUSTOMER.Поэтому Выдолжны ввеститолько полеCustNo в свойствеMasterFields. Вы можетеопределитьэто непосредственнов редакторесвойств, илинаписать кодподобно показанномувыше. Крометого, поле (илиполя) связиможно получить,вызвав редакторсвязей - в ИнспектореОбъектов дваждыщелкните насвойство MasterFields(рис.10)
Рис.10:Редактор связейдля построениясвязанныхкурсоров.
Важно подчеркнуть,что даннаяглава охватилатолько одиниз несколькихпутей, которымВы можете создатьсвязанныекурсоры в Delphi. Вглаве о запросахбудет описанвторой метод,который будетобращен к темкто знаком сSQL.
Класс TDataSource используетсяв качествепроводникамежду TTable илиTQuery и компонентами,визуализирующимиданные, типаTDBGrid, TDBEdit и TDBComboBox (data-aware components). Вбольшинствеслучаев, все,что нужно сделатьс DataSource - это указатьв свойствеDataSet соответствующийTTable или TQuery. Затем,у data-aware компонентав свойствеDataSource указываетсяTDataSource, которыйиспользуетсяв настоящеевремя.
TDataSource также имеетсвойство Enabled, ионо может бытьполезно всякийраз, когда Выхотите временноотсоединить,например, DBGrid оттаблицы илизапроса. Этитребуется,например, еслинужно программнопройти черезвсе записи втаблице. Ведь,если таблицасвязана с визуальнымикомпонентами(DBGrid, DBEdit и т.п.), то каждыйраз, когда Вывызываете методTTable.Next, визуальныекомпонентыбудут перерисовываться.Даже если самосканированиев таблице двухили трех тысячзаписей незаймет многовремени, томожет потребоватьсязначительнобольше времени,чтобы столькоже раз перерисоватьвизуальныекомпоненты.В случаях подобныхэтому, лучшевсего установитьполе DataSource.Eabled в False. Этопозволит Вампросканироватьзаписи безперерисовкивизуальныхкомпонент. Этоединственнаяоперация можетувеличитьскорость внекоторыхслучаях нанесколько тысячпроцентов.
СвойствоTDataSource.AutoEdit указывает,переходит лиDataSet автоматическив режим редактированияпри вводе текстав data-aware объекте.
TDataSource имеет триключевых события,связанных ссостояниемБД
OnDataChange
OnStateChange
OnUpdateData
OnDataChangeпроисходитвсякий раз,когда Вы переходитена новую запись,или состояниеDataSet сменилосьс dsInactiveна другое, илиначато редактирование.Другими словами,если Вы вызываетеNext, Previous, Insert, или любойдругой запрос,который долженпривести кизменениюданных, связанныхс текущей записью,то произойдетсобытие OnDataChange. Еслив программенужно определитьмомент, когдапроисходитпереход надругую запись,то это можносделать в обработчикесобытия OnDataChange:
procedureTForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
ifDataSource1.DataSet.State = dsBrowse then begin
DoSomething;
end;
end;
СобытиеOnStateChangeсобытие происходитвсякий раз,когда изменяетсятекущее состояниеDataSet. DataSet всегда знает,в каком состояниион находится.Если Вы вызываетеEdit, Append или Insert, то TTableзнает, что онтеперь находитсяв режиме редактирования(dsEdit или dsInsert). Аналогично,после того, какВы делаетеPost, то TTable знает чтоданные большене редактируется,и переключаетсяобратно в режимпросмотра(dsBrowse).
Dataset имеет шестьразличныхвозможныхсостояний,каждое из которыхвключено вследующемперечисляемомтипе:
TDataSetState= (dsInactive, dsBrowse, dsEdit, dsInsert,
dsSetKey,dsCalcFields);
В течение обычногосеанса работы,БД часто меняетсвое состояниемежду Browse, Edit, Insert идругими режимами.Если Вы хотитеотслеживатьэти изменения,то Вы можетереагироватьна них написавпримерно такойкод:
procedureTForm1.DataSource1StateChange(Sender: TObject);
var
S:String;
begin
caseTable1.State of
dsInactive:S := 'Inactive';
dsBrowse:S := 'Browse';
dsEdit:S := 'Edit';
dsInsert:S := 'Insert';
dsSetKey:S := 'SetKey';
dsCalcFields:S := 'CalcFields';
end;
Label1.Caption:= S;
end;
OnUpdateDataсобытие происходитперед тем, какданные в текущейзаписи будутобновлены.Например,OnUpdateEvent будет происходитьмежду вызовомPost и фактическимобновлениеминформациина диске.
События, генерируемыеTDataSource могут бытьочень полезны.Иллюстрациейэтого служитследующийпример. Этапрограммаработает стаблицей COUNTRY, ивключает TTable,TDataSource, пять TEdit, шестьTLlabel, восемь кнопоки панель. Действительноерасположениеэлементовпоказано нарис.11. Обратитевнимание, чтошестой TLabel расположенна панели внизуглавной формы.
Рис.11: ПрограммаSTATE показывает,как отслеживатьтекущее состояниетаблицы.
Длявсех кнопокнапишите обработчики,вроде:
procedureTForm1.FirstClick(Sender: TObject);
begin
Table1.First;
end;
В данной программеесть одна маленькаяхитрость, которуюВы должны понять,если хотитеузнать, какработает программа.Так как естьпять отдельныхредакторовTEdit на главнойформе, то хотелосьбы иметь некоторыйспособ обращатьсяк ним быстрои легко. Одинпростой способсостоит в том,чтобы объявитьмассив редакторов:
Edits:array[1..5] of TEdit;
Чтобы заполнитьмассив, Вы можетев событии OnCreateглавной формынаписать:
procedureTForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 5 do
Edits[i] :=TEdit(FindComponent('Edit' + IntToStr(i)));
Table1.Open;
end;
Код показанныйздесь предполагает,что первыйредактор, которыйВы будетеиспользоватьназовем Edit1, второйEdit2, и т.д. Существованиеэтого массивапозволяет оченьпросто использоватьсобытие OnDataChange, чтобысинхронизироватьсодержаниеобъектов TEdit ссодержимомтекущей записив DataSet:
procedureTForm1.DataSource1DataChange(Sender: TObject;
Field:TField);
var
i:Integer;
begin
fori := 1 to 5 do
Edits[i].Text:= Table1.Fields[i - 1].AsString;
end;
Всякий раз,когда вызываетсяTable1.Next, или любойдругой изнавигационныхметодов, тобудет вызванапроцедурапоказаннаявыше. Это обеспечиваетто, что все редакторывсегда содержатданные из текущейзаписи.
Всякий раз,когда вызываетсяPost, нужно выполнитьпротивоположноедействие, тоесть взятьинформациюиз редакторови поместитьее в текущуюзапись. Выполнитьэто действие,проще всегов обработчикесобытияTDataSource.OnUpdateData, котороепроисходитвсякий раз,когда вызываетсяPost:
procedureTForm1.DataSource1UpdateData(Sender: TObject);
var
i:Integer;
begin
fori := 1 to 5 do
Table1.Fields[i- 1].AsString := Edits[i].Text;
end;
Программабудет автоматическипереключатсяв режим редактированиякаждый раз,когда Вы вводитечто-либо в одномиз редакторов.Это делаетсяв обработчикесобытия OnKeyDown (укажитеэтот обработчикко всем редакторам):
procedureTForm1.Edit1KeyDown(Sender: TObject;
varKey: Word; Shift: TShiftState);
begin
ifDataSource1.State dsEdit then
Table1.Edit;
end;
Этоткод показывает,как Вы можетеиспользоватьсв-во StateDataSource, чтобы определитьтекущий режимDataSet.
Обновлениеметки в статуснойпанели происходитпри изменениисостояниятаблицы:
procedureTForm1.DataSource1StateChange(Sender: TObject);
var
s : String;
begin
caseDataSource1.State of
dsInactive: s:='Inactive';
dsBrowse :s:='Browse';
dsEdit :s:='Edit';
dsInsert :s:='Insert';
dsSetKey :s:='SetKey';
dsCalcFields :s:='CalcFields';
end;
Label6.Caption:=s;
end;
Данная программаявляетсядемонстрационнойи ту же задачуможно решитьгораздо проще,если использоватьобъекты TDBEdit.
В предыдущейчасти Вы узнали,как использоватьTDataSource, чтобы узнатьтекущее состоянииTDataSet. ИспользованиеDataSource - это простойпуть выполненияданной задачи.Однако, еслиВы хотите отслеживатьэти событиябез использованияDataSource, то можетенаписать своиобработчикисобытий TTable иTQuery:
propertyOnOpen
propertyOnClose
propertyBeforeInsert
propertyAfterInsert
propertyBeforeEdit
propertyAfterEdit
propertyBeforePost
propertyAfterPost
propertyOnCancel
propertyOnDelete
propertyOnNewRecord
Большинствоэтих свойствочевидны. СобытиеBeforePost функциональноподобно событиюTDataSource.OnUpdateData, котороеобъяснено выше.Другими словами,программа STATEработала быточно также, если бы Вы отвечалине на DataSource1.OnUpdateData а наTable1.BeforePost. Конечно,в первом случаеВы должен иметь TDataSource на форме, вто время, какво втором этогоне требуется.
Создание базданных в Delphi
Урок 5: КомпонентTTable. Созданиетаблиц
с помощьюкомпонентаTTable
Содержаниеурока 5:
Созданиетаблиц с помощьюкомпонентаTTable2
Заключение6
Обзор
Наэтом небольшомуроке мы завершимизучение возможностейсоздания таблиц.Как Вы помните,мы уже освоилидва способасоздания таблиц- с помощью утилитыDatabase Desktop, входящейв поставкуDelphi и с помощьюSQL-запросов, которыеможно использоватькак в WISQL (Windows Interactive SQL -клиентскаячасть Local InterBase), таки в компонентеTQuery.Теперь мы рассмотрим,как можно создаватьлокальныетаблицы в режимевыполненияс помощью компонентаTTable.
Длясоздания таблицкомпонентTTableимеет методCreateTable.Этот методсоздает новуюпустую таблицузаданной структуры.Данный метод(процедура)может создаватьтолько локальныетаблицы форматаdBase или Paradox.
КомпонентTTableможно поместитьна форму в режимепроектированияили создатьдинамическиво время выполнения.В последнемслучае передиспользованиемего необходимосоздать, например,с помощью следующейконструкции:
var
Table1: TTable;
...
Table1:=TTable.Create(nil);
...
Передвызовом методаCreateTableнеобходимоустановитьзначения свойств
TableType-тип таблицы
DatabaseName-база данных
TableName-имя таблицы
FieldDefs-массив описанийполей
IndexDefs-массив описанийиндексов.
СвойствоTableTypeимеет тип TTableTypeи определяеттип таблицыв базе данных.Если это свойствоустановленов ttDefault,тип таблицыопределяетсяпо расширениюфайла, содержащегоэту таблицу:
Расширение.DBили без расширения:таблица Paradox
Расширение.DBF: таблица dBASE
Расширение.TXT: таблица ASCII (текстовыйфайл).
Еслизначение свойстваTableTypeне равно ttDefault,создаваемаятаблица всегдабудет иметьустановленныйтип, вне зависимостиот расширения:
ttASCII:текстовый файл
ttDBase:таблица dBASE
ttParadox:таблица Paradox.
СвойствоDatabaseNameопределяетбазу данных,в которой находитсятаблица. Этосвойство можетсодержать:
BDE алиас
директорийдля локальныхБД
директорийи имя файлабазы данныхдля Local InterBase
локальныйалиас, определенныйчерез компонентTDatabase.
СвойствоTableNameопределяетимя таблицыбазы данных.
СвойствоFieldDefs(имеющее типTFieldDefs)для существующейтаблицы содержитинформациюобо всех поляхтаблицы. Этаинформациядоступна тольков режиме выполненияи хранится ввиде массиваэкземпляровкласса TFieldDef,хранящих данныео физическихполях таблицы(т.о. вычисляемыена уровне клиентаполя не имеютсвоего объектаTFieldDef).Число полейопределяетсясвойствомCount,а доступ к элементаммассива осуществляетсячерез свойствоItems:
propertyItems[Index: Integer]: TFieldDef;
При созданиитаблицы, передвызовом методаCreateTable,нужно сформироватьэти элементы.Для этого укласса TFieldDefsимеется методAdd:
procedureAdd(const Name: string; DataType: TFieldType; Size: Word;Required: Boolean);
Параметр Name,имеющий типstring,определяетимя поля. ПараметрDataType(тип TFieldType)обозначаеттип поля. Онможет иметьодно из следующихзначений, смыслкоторых ясениз их наименования:
TFieldType= (ftUnknown, ftString, ftSmallint, ftInteger, ftWord, ftBoolean,ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes,ftVarBytes, ftBlob, ftMemo,
ftGraphic);
Параметр Size(тип word)представляетсобой размерполя. Этотпараметр имеетсмысл толькодля полей типаftString,ftBytes,ftVarBytes,ftBlob,ftMemo,ftGraphic,размер которыхможет сильноварьироваться.Поля остальныхтипов всегдаимеют строгофиксированныйразмер, так чтоданный параметрдля них непринимаетсяво внимание.Четвертыйпараметр - Required- определяет,может ли полеиметь пустоезначение призаписи в базуданных. Еслизначение этогопараметра -true,то поле является“требуемым”,т.е. не можетиметь пустогозначения. Впротивномслучае полене является“требуемым”и, следовательно,допускаетзапись значенияNULL. Отметим, чтов документациипо Delphi и online-справочникедопущена ошибка- там отсутствуетупоминаниео четвертомпараметре дляметода Add.
ЕслиВы желаетеиндексироватьтаблицу поодному илинесколькимполям, используйтеметод Addдля свойстваIndexDefs,которое, какможно догадаться,также являетсяобъектом, т.е.экземпляромкласса TIndexDefs.Свойство IndexDefsдля существующейтаблицы содержитинформациюобо всех индексахтаблицы. Этаинформациядоступна тольков режиме выполненияи хранится ввиде массиваэкземпляровкласса TIndexDef,хранящих данныеоб индексахтаблицы. Числоиндексов определяетсясвойствомCount,а доступ к элементаммассива осуществляетсячерез свойствоItems:
propertyItems[Index: Integer]: TIndexDef;
Метод Addкласса TIndexDefsимеет следующийвид:
procedure Add(constName, Fields: string;
Options: TIndexOptions);
Параметр Name,имеющий типstring,определяетимя индекса.Параметр Fields(также имеющийтип string)обозначаетимя поля, котороедолжно бытьиндексировано,т.е. имя индексируемогополя. Составнойиндекс, использующийнесколькополей, можетбыть задансписком именполей, разделенныхточкой с запятой“;”,например:‘Field1;Field2;Field4’.Последнийпараметр - Options- определяеттип индекса.Он может иметьнабор значений,описываемыхтипом TIndexOptions:
TIndexOptions = setof (ixPrimary, ixUnique, ixDescending,
ixCaseInsensitive, ixExpression);
Поясним этизначения. ixPrimaryобозначаетпервичный ключ,ixUnique- уникальныйиндекс, ixDescending- индекс, отсортированныйпо уменьшениюзначений (длястрок - в порядке,обратномалфавитному),ixCaseInsensitive- индекс, “нечувствительный”к региструбукв, ixExpression- индекс повыражению.Отметим, чтоупоминаниео последнемзначении такжеотсутствуетв документациии online-справочнике.Опция ixExpressionпозволяет длятаблиц форматаdBase создаватьиндекс по выражению.Для этого достаточнов параметреFieldsуказать желаемоевыражение,например:'Field1*Field2+Field3'.Вообще говоря,не все опциииндексов применимыко всем форматамтаблиц. Нижемы приведемсписок допустимыхзначений длятаблиц dBase и Paradox:
Опциииндексов dBASE Paradox
---------------------------------------
ixPrimary
ixUnique
ixDescending
ixCaseInsensitive
ixExpression
Необходимопридерживатьсяуказанногопорядка примененияопций индексовво избежаниенекорректнойработы. Следуетотметить, чтодля форматаParadox опция ixUniqueможет использоватьсятолько вместес опцией ixPrimary(см. пример надиске - Рис. 1).
Итак,после заполнениявсех указанныхвыше свойстви вызова методовAddдля FieldDefsи IndexDefsнеобходимовызвать методкласса TTable- CreateTable:
with Table1 do
begin
DatabaseName:='dbdemos';
TableName:='mytest';
TableType:=ttParadox;
{Создатьполя}
with FieldDefsdo
begin
Add('Surname',ftString, 30, true);
Add('Name',ftString, 25, true);
Add('Patronymic',ftString, 25, true);
Add('Age',ftInteger, 0, false);
Add('Weight',ftFloat, 0, false);
end;
{Сгенерироватьиндексы}
with IndexDefsdo
begin
Add('I_Name','Surname;Name;Patronymic',
[ixPrimary, ixUnique]);
Add('I_Age','Age', [ixCaseInsensitive]);
end;
CreateTable;
end;
Рис. 1: ПрограммаCREATABL демонстрирует технику созданиятаблиц во времявыполнения
Индексыможно сгенерироватьи не только присоздании таблицы.Для того чтобысгенерироватьиндексы длясуществующейтаблицы, нужновызвать методAddIndexкласса TTable,набор параметровкоторого полностьюповторяет наборпараметровдля метода Addкласса TIndexDefs:
procedureAddIndex(const Name, Fields: string;
Options:TIndexOptions);
При этом дляметода AddIndexсправедливывсе замечанияпо поводу записиполей и опцийиндексов, сделанныевыше.
Итак,мы познакомилисьс еще однимспособом созданиятаблиц - способом,использующимметод CreateTableкласса TTable.Использованиеданного способапридаст Вашемуприложениюмаксимальнуюгибкость, и Высможете строитьлокальныетаблицы “налету”. Сопутствующимметодом являетсяметод AddIndexкласса TTable,позволяющийсоздаватьиндексы дляуже существующейтаблицы. Подчеркнемеще раз, что данный способприменим толькодля локальныхтаблиц.
Урок5: Создание таблицс помощьюкомпонентаTTable
Создание базданных в Delphi
Урок6: Объект TQuery
СодержаниеУрока 6:1
КраткийОбзор2
Основныепонятия о TQuery2
СвойствоSQL3
TQuery и Параметры6
Передачапараметровчерез TDataSource 10
Выполнениесоединениянесколькихтаблиц.12
Open илиExecSQL?14
Специальныесвойства TQuery15
В этой главеВы узнаетенекоторыеосновные понятияо запросах(queries) и транзакциях.Это достаточноширокие понятия,поэтому обсуждениеразбито наследующиеосновные части:
ОбъектTQuery.
ИспользованиеSQL с локальными удаленнымсерверами(Select, Update, Delete и Insert).
ИспользованиеSQL для созданияобъединения(joins), связанныхкурсоров (linkedcursors) и программ,которые ведутпоиск заданныхзаписей.
СокращениеSQL означаетStructured Query Language - ЯзыкСтруктурированныхЗапросов, иобычно произноситьсялибо как "Sequel"либо " Ess Qu El”. Однако,как бы Вы егони произносили,SQL - это мощныйязык БД, которыйлегко доступениз Delphi, но которыйотличаетсяот родногоязыка Delphi. Delphi можетиспользоватьутвержденияSQL для просмотра таблиц, выполнятьобъединениетаблиц, создаватьотношенияодин-ко-многим,или исполнитьпочти любоедействие, котороемогут сделатьваши основныеинструментыБД. Delphi поставляетсяс Local SQL, так что Выможете выполнятьзапросы SQL приработе с локальнымитаблицами, бездоступа к SQLсерверу.
Delphi обеспечиваетподдержку “passthrough SQL”, это означаетто, что Вы можетесоставлятьпредложенияSQL и посылатьих непосредственносерверам Oracle,Sybase, Inrterbase и другим.“Pass through SQL” - это мощныймеханизм подвум причинам:
Большинствосерверов могутобрабатыватьSQL запросы оченьбыстро, а этоозначает, чтоиспользуя SQLдля удаленныхданных, Вы получитеответ оченьбыстро.
Есть возможностьсоставлятьSQL запросы, которыезаставят серверисполнитьспециализированныезадачи, недоступныечерез роднойязык Delphi.
Перед чтениемэтой статьиВы должны иметь,по крайнеймере, элементарноепонятие о серверахи различияхмежду локальнымии удаленными(remote) данными.
ПредыдущийУрок был, в основном,посвящен объектуTTable, который служитдля доступак данным. ПрииспользованииTTable, возможендоступ ко всемунабору записейиз одной таблицы.В отличие отTTable, TQuery позволяетпроизвольнымобразом (в рамкахSQL) выбрать наборданных дляработы с ним.Во многом, методикаработы с объектомTQuery похожа наметодику работыс TTable, однако естьсвои особенности.
Вы может создатьSQL запрос используякомпонентTQuery следующимспособом:
НазначитеПсевдоним(Alias) DatabaseName.
Используйтесвойство SQL чтобыввести SQL запростипа
“Select * from Country”.
Установитесвойство Active вTrue
Если обращениеидет к локальнымданным, то вместопсевдонимаможно указатьполный путьк каталогу, гденаходятсятаблицы.
Две основныхвещи, которыеВы должны понятьпрежде, чемперейти дальше:
Этот урок неявляется учебникомдля начинающихпо SQL, а, скорее,описаниемобъекта TQuery иосновных задач,которые Выможете решитьс его помощью.Если Вы не знаетеничто об SQL, Вывсе же сможетевоспользоватьсяэтой статьей,и, в конце концов,приобрететенекотороепониманиеоснов SQL. Однако,для полногоизучения языка,Вы должны обратитьсяк любой из большогоколичествакниг и документов,доступных поэтому предмету.
Delphi используетpass through SQL, поэтомудля разных SQLсерверов синтаксисможет бытьнесколькоразным. ВерсияSQL для локальныхтаблиц (Local SQL) оченьсильно урезан,по сравнениюсо стандартом.Чтобы узнатьо его возможностях,Вы должны прочитатьне только этустатью, но такжефайл LOCALSQL.HLP.
Вы увидите,что объектTQuery один из наиболееполезных игибких компонентов,доступных вDelphi. С ним Вы сможетевоспользоватьсявсей мощью,предоставляемойлидерами средипромышленныхSQL серверов, вродеInrterBase, Oracle или Sybase.
Свойство SQL -вероятно, самаяважная частьTQuery. Доступ к этомусвойству происходитлибо черезИнспекторОбъектов вовремя конструированияпроекта (design time), илипрограммново время выполненияпрограммы (runtime).
Интересней,конечно, получитьдоступ к свойствуSQL во время выполнения,чтобы динамически изменять запрос.Например, еслитребуетсявыполнить триSQL запроса, тоне надо размещатьтри компонентаTQuery на форме. Вместоэтого можноразместитьодин и простоизменять свойствоSQL три раза. Наиболееэффективный,простой и мощныйспособ - сделатьэто черезпараметризованныезапросы, которыебудут объясненыв следующейчасти. Однако,сначала исследуемосновные особенностисвойства SQL, апотом рассмотримболее сложныетемы, типа запросовс параметрами.
Свойство SQL имееттип TStrings, которыйозначает чтоэто ряд строк,сохраняемыхв списке. Списокдействуеттакже, как имассив, но,фактически,это специальныйкласс с собственнымиуникальнымивозможностями.В следующихнесколькихабзацах будутрассмотренынаиболее частоиспользуемыесвойства.
При программномиспользованииTQuery, рекомендуетсясначала закрытьтекущий запроси очиститьсписок строкв свойстве SQL:
Query1.Close;
Query1.SQL.Clear;
Обратите внимание,что всегдаможно “безопасно”вызвать Close. Дажев том случае,если запросуже закрыт,исключительнаяситуациягенерироватьсяне будет.
Следующий шаг- добавлениеновых строкв запрос:
Query1.SQL.Add(‘Select* from Country’);
Query1.SQL.Add(‘whereName = ’’Argentina’’’);
Метод Add используетсядля добавленияодной или несколькихстрок к запросуSQL. Общий объемограничентолько количествомпамяти на вашеймашине.
Чтобы Delphi отработалзапрос и возвратилкурсор, содержащийрезультат ввиде таблицы,можно вызватьметод:
Query1.Open;
ДемонстрационнаяпрограммаTHREESQL показываетэтот процесс(см Рис.1)
Рис.1: ПрограммаTHREESQL показывает,как сделатьнесколькозапросов спомощью единственногообъекта TQuery.
ПрограммаTHREESQL используетособенностьлокальногоSQL, который позволяетиспользоватьшаблоны поискабез учета регистра(case insensitive). Например,следующий SQLзапрос:
Select* form Country where Name like ’C%’
возвращаетDataSet, содержащийвсе записи, гдеполе Name начинаетсяс буквы ‘C’.Следующийзапрос позволитувидеть всестраны, в названиикоторых встречаетсябуква ‘C’:
Select* from Country where Name like ‘%C%’;
Вот запрос,которое находитвсе страны,название которыхзаканчиваетсяна ‘ia’:
Select* from Country where Name like ‘%ia’;
Одна из полезныхособенностейсвойства SQL - этоспособностьчитать файлы,содержащиетекст запросанепосредственнос диска. Этаособенностьпоказана впрограммеTHREESQL.
Вот как этоработает. Вдиректориис примерамик данному урокуесть файл срасширениемSQL. Он содержаттекст SQL запроса.ПрограммаTHREESQL имеет кнопкус названиемLoad, которая позволяетВам выбратьодин из этихфайлов и выполнятьSQL запрос, сохраненныйв этом файле.
КнопкаLoad имеет следующийметод для событияOnClick:
procedureTForm1.LoadClick(Sender: TObject);
begin
ifOpenDialog1.Execute then
withQuery1 do begin
Close;
SQL.LoadFromFile(OpenDialog1.FileName);
Open;
end;
end;
Метод LoadClick сначалазагружаеткомпонентуOpenDialog и позволяетпользователювыбрать файлс расширениемSQL. Если файлвыбран, текущийзапрос закрывается,выбраный файлзагружаетсяс диска в св-воSQL, запрос выполняетсяи результатпоказываетсяпользователю.
Delphi позволяетсоставить“гибкую” формузапроса, называемуюпараметризованнымзапросом. Такиезапросы позволяютподставитьзначение переменнойвместо отдельныхслов в выражениях“where” или “insert”. Этапеременнаяможет бытьизменена практическив любое время.(Если используетсялокальный SQL,то можно сделатьзамену почтилюбого словав утвержденииSQL, но при этомта же самаявозможностьне поддерживаетсябольшинствомсерверов.)
Перед тем, какначать использоватьпараметризованныезапросы, рассмотримснова одно изпростых вышеупомянутыхпредложенийSQL:
Select* from Country where Name like ’C%’
Можно превратитьэто утверждениев параметризованныйзапрос заменивправую частьпеременнойNameStr:
select* from County where Name like :NameStr
В этом предложенииSQL, NameStr не являетсяпредопределеннойконстантойи может изменятьсялибо во времядизайна, либово время выполнения.SQL parser (программа,которая разбираеттекст запроса)понимает, чтоон имеет делос параметром,а не константойпотому, чтопараметрупредшествуетдвоеточие":NameStr". Это двоеточиесообщает Delphi онеобходимостизаменить переменнуюNameStr некоторойвеличиной,которая будетизвестна позже.
Обратите внимание, слово NameStr быловыбрано абсолютнослучайно.Использоватьможно любоедопустимоеимя переменной, точно также,как выбираетсяидентификаторпеременнойв программе.
Есть два путиприсвоитьзначение переменнойв параметризованномзапросе SQL. Одинспособ состоитв том, чтобыиспользоватьсвойство Paramsобъекта TQuery. Второй- использоватьсвойство DataSourceдля полученияинформациииз другогоDataSet. Вот ключевыесвойства длядостиженияэтих целей:
propertyParams[Index: Word];
functionParamByName(const Value: string);
propertyDataSource;
Если подставлятьзначение параметрав параметризованныйзапрос черезсвойство Params, тообычно нужносделать четырешага:
ЗакрытьTQuery
Подготовитьобъект TQuery, вызвавметод Prepare
Присвоитьнеобходимыезначения свойствуParams
ОткрытьTQuery
Второй шагвыполняетсяв том случае,если данныйтекст запросавыполняетсявпервые, в дальнейшемего можно опустить.
Вот фрагменткода, показывающийкак это можетбыть выполненопрактически:
Query1.Close;
Query1.Prepare;
Query1.Params[0].AsString:= ‘Argentina’;
Query1.Open;
Этот код можетпоказатьсянемного таинственным.Чтобы понятьего, требуетсявнимательныйпострочныйанализ. Прощевсего начатьс третьей строки,так как свойствоParams является“сердцем” этогопроцесса.
Params - это индексированноесвойство, котороеимеет синтаксискак у свойстваFields для TDataSet. Например,можно получитьдоступ к первойпеременнойв SQL запросе,адресуя нулевойэлемент в массивеParams:
Params[0].AsString:= ‘”Argentina”’;
ЕслипараметризованныйSQL запрос выглядиттак:
select* from Country where Name = :NameStr
токонечный результат(т.е. то, что выполнитсяна самом деле)- это следующеепредложениеSQL:
select* from Country where Name = “Argentina”
Все, что произошло,это переменной:NameStr было присвоено значение "Аргентина"через свойствоParams. Таким образом,Вы закончилипостроениепростого утвержденияSQL.
Еслив запросе содержитсяболее одногопараметра, тодоступатьсяк ним можноизменяя индекс у свойстваParams
Params[1].AsString:= ‘SomeValue’;
либоиспользуядоступ по именипараметра
ParamByName(‘NameStr’).AsString:=’”Argentina”’;
Итак, параметризованныеSQL запросы используютпеременные,которые всегданачинаютсяс двоеточия,определяяместа, кудабудут переданызначения параметров.
Прежде, чемиспользоватьпеременнуюParams, сначала можновызвать Prepare. Этотвызов заставляетDelphi разобратьваш SQL запроси подготовитьсвойство Params так,чтобы оно "былоготово принять”соответствующееколичествопеременных.Можно присвоитьзначение переменнойParams без предварительноговызова Prepare, ноэто будет работатьнесколькомедленнее.
После того,как Вы вызывалиPrepare, и после того,как присвоилинеобходимыезначения переменнойParams, Вы должнывызвать Open, чтобызакончитьпривязку переменныхи получитьжелаемый DataSet. Внашем случае,DataSet должен включатьзаписи где вполе “Name” стоит“Argentina”.
Рассмотримработу с параметрамина примере(программаPARAMS.DPR). Для созданияпрограммы,разместитена форме компонентыTQuery, TDataSource, TDBGrid и TTabSet. Соединитекомпонентыи установитев свойствеTQuery.DatabaseName псевдонимDBDEMOS. См. рис.2
Рис.2: ПрограммаPARAMS во время дизайна.
В обработчикесобытия дляформы OnCreate напишемкод, заполняющийзакладки дляTTabSet, кроме того,здесь подготавливаетсязапрос:
procedureTForm1.FormCreate(Sender: TObject);
var
i: Byte;
begin
Query1.Prepare;
fori:=0 to 25 do
TabSet1.Tabs.Add(Chr(Byte('A')+i));
end;
ТекстSQL запроса вкомпонентеQuery1:
select* from employee where LastName like :LastNameStr
Запрос выбираетзаписи из таблицыEMPLOYEE, в которыхполе LastName похоже(like) на значениепараметра:LastNameStr. Параметрбудет передаватьсяв момент переключениязакладок:
procedureTForm1.TabSet1Change(Sender: TObject;
NewTab: Integer;
var AllowChange:Boolean);
begin
with Query1 dobegin
Close;
Params[0].AsString:=
'"'+TabSet1.Tabs.Strings[NewTab]+'%"';
Open;
end;
end;
Рис.3:ПрограммаPARAMS во время выполнения.
В предыдущемУроке Вы виделиспособ созданияотношенияоднин-ко-многиммежду двумятаблицами.Теперь речьпойдет о выполнениитого же самогодействия сиспользованиемобъекта TQuery. Этотспособ болеегибок в томотношении, чтоон не требуетиндексациипо полям связи.
Объект TQuery имеетсвойство DataSource,которое можетиспользоватьсядля того, чтобысоздать связьс другим DataSet. Неимеет значения,является лидругой DataSet объектомTTable, TQuery, или некоторыйдругим потомкомTDataSet. Все что нужнодля установлениясоединения- это удостовериться,что у того DataSetесть связанныйс ним DataSource.
Предположим,что Вы хотитесоздать связьмежду таблицамиORDERS и CUSTOMERS так, чтокаждый раз,когда Вы просматриваетеконкретнуюзапись о заказчике,будут виднытолько заказы,связанные сним.
Рассмотритеследующийпараметризованныйзапрос:
select* from Orders where CustNo = :CustNo
В этом запросе:CustNo - связывающаяпеременная,которой должнобыть присвоенозначение изнекоторогоисточника.Delphi позволяетиспользоватьполе TQuery.DataSource чтобы указать другойDataSet, которыйпредоставитэту информациюавтоматически.Другими словами,вместо того,чтобы использоватьсвойство Params и“вручную”присваиватьзначения переменной,эти значенияпеременноймогут бытьпросто взятыавтоматическииз другой таблицы. Кроме того,Delphi всегда сначалапытается выполнитьпараметризованныйзапрос используясвойство DataSource,и только потом(если не былонайдено какое-тозначение параметра)будет пытатьсяполучить значениепеременнойиз свойстваParams. При полученииданных из DataSourceсчитается, чтопосле двоеточиястоит имя поляиз DataSource. При изменениитекущей записив главном DataSetзапрос будетавтоматическипересчитываться.
Давайте переделаемпример из прошлогоурока (LINKTBL - связываниедвух таблиц).Создайте новыйпроект, положитена форму одиннабор TTable, TDataSource иTDBGrid. Привяжитеего к таблицеCUSTOMER. Положите наформу второйнабор - TQuery, TDataSource иTDBGrid и свяжитеобъекты междусобой. (см рис.4).
В свойстве SQLнаберите текстзапроса:
select* from Orders where CustNo = :CustNo
Всвойстве DatabaseNameдля Query1 укажитеDBDEMOS.
Всвойстве DataSourceдля Query1 укажитеDataSource1.
ПоставьтеActive = True и запуститепрограмму.
Рис.4:ПрограммаLINKQRY - связанныекурсоры с помощьюSQL
Вы видели чтотаблицы CUSTOMERS иORDERS связаны вотношенииодин-ко-многим,основанномуна поле CustNo. ТаблицыORDERS и ITEMS также связаныотношенииодин-ко-многим,только черезполе OrderNo.
Более конкретно,каждый заказкоторый существуетв таблице ORDERSбудет иметьнесколькозаписей в таблицеITEMS, связанныхс этим заказом.Записи из таблицыITEMS определяюттип и количествоизделий, связанныхс этим заказом.
Пример.
НектоИванов Ф.П. 1 мая1995г. заказалследующее:
Гайка4х-угольная - 50 штук
Вентиль- 1 штука
Анекто СидорчукЮ.Г. 8 декабря1994г. заказал:
М/схемаКР580 ИК80 - 10 штук
ТранзисторКТ315 - 15 штук
Мотокпровода - 1 штука
В ситуацииподобной этой,иногда прощевсего "соединить"данные из таблицORDERS и ITEMS так, чтобырезультирующийDataSet содержалинформациюиз обеих таблиц:
ИвановФ.П. 1 мая 1995г Гайка4х-угольная50штук
ИвановФ.П. 1 мая 1995г Вентиль 1 штука
СидорчукЮ.Г. 8 декабря1994гМ/схема КР580ИК80 10 штук
СидорчукЮ.Г. 8 декабря1994г ТранзисторКТ315 15 штук
СидорчукЮ.Г. 8 декабря1994гМоток провода1 штука
Слияние этихдвух таблицназывается"соединение"и это одно изфундаментальныхдействий, которыеВы можете выполнитьна наборе двухили большетаблиц.
Взяв таблицыORDERS и ITEMS из подкаталогаDEMOS\DATA, их можносоединить ихтаким путем,что поля CustNo, OrderNo иSaleDate из таблицыORDERS будут “слиты”с полями PartNo и Qtyиз таблицыITEMS и сформируютновый DataSet, содержащийвсе пять полей.Grid содержащийрезультирующийDataSet показан нарис.5
Рис.5: Соединениетаблиц ORDERS и ITEMS можетбыть сделанотак, что формируетсяновый DataSet содержащийполя из каждойтаблицы.
Имеется существенноеразличие междусвязаннымикурсорами исоединеннымитаблицами.Однако ониимеют две общиечерты:
Ите, и другиеиспользуютдве или болеетаблиц
Каждыйтаблица связанас другой поодному илиболее одинаковыхполей.
Соединениетаблиц ORDERS и ITEMS можетбыть выполненоединственнымSQL запросом, которыйвыглядит так:
select
O.CustNo, O.OrderNo,O.SaleDate, I.PartNo, I.Qty
fromOrders O, Items I
whereO.OrderNo = I.OrderNo
Этотзапрос состоитиз четырехразличныхчастей:
ВыражениеSelect определяет,что Вы хотитеполучить - курсор, содержащийнекоторуюформу DataSet.
Затем идетсписок полейкоторые Выхотите включитьв dataset. Этот списоквключает поляCustNo, OrderNo, SaleDate, PartNo и Qty. Первыетри поля изтаблицы ORDERS, адва других -из таблицыITEMS.
Выражениеfromобъявляет, чтоВы работаетес двумя таблицами,одна называетсяORDERS, а другая ITEMS.Для краткости,в запросеиспользуетсяособенностьSQL, которая позволяетВам ссылатьсяна таблицуORDERS буквой O, а натаблицу ITEMS буквойI.
Выражениеwhereжизненно важнопотому, чтооно определяетполя связи длядвух таблиц.Некоторыесерверы могутвернуть DataSet, дажеесли Вы не включитевыражениеwhere взапрос, но почтивсегда результирующийнабор записейбудет не тем,что Вы хотеливидеть. Чтобыполучить нужныйрезультат,убедитесь чтоВы включиливыражениеwhere.
После того,как составленSQL запрос, естьдва различныхспособа выполнитьего. Если Выхотите получитькурсор, то нужновызывать Open. Есливыражение SQLне подразумеваетвозвращениекурсора, тонужно вызыватьExecSQL. Например,если происходитвставка, удалениеили обновлениеданных (т.е. SQLзапросы INSERT, DELETE,UPDATE), то нужно вызыватьExecSQL. Тоже самоеможно сказатьпо-другому:Open вызываетсяпри запросетипа SELECT, а ExecSQL - вовсех остальныхслучаях.
Воттипичный SQL запрос,который используетсядля удалениязаписи из таблицы:
deletefrom Country where Name = ‘Argentina’;
Этот запросудалил бы любуюзапись из таблицыCOUNTRY, которая имеетзначение "Argentina"в поле Имя.
Не трудно заметить,что это тотслучай, когдаудобно использоватьпараметризованныйзапрос. Например,неплохо былобы менять имястраны, которуютребуетсяудалить:
deletefrom Country where Name = :CountryName
Вэтом случаепеременная:CountryName может бытьизменена вовремя выполнения:
Query2.Prepare;
Query2.Params[0]:= ‘Argentina’;
Query2.ExecSQL;
Код сначалавызывает Prepare,чтобы сообщитьDelphi что он долженразобрать SQLзапрос и подготовитьсвойство Params.Следующим шагомприсваиваетсязначение свойствуParams и затем выполняетсяподготовленныйSQL запрос. Обратитевнимание, чтоон выполняетсячерез ExecSQL, а неOpen.
ПрограммаINSQUERY из примеровDelphi демонстрируетэту технику(проектC:\DELPHI\DEMOS\DB\INSQUERY.DPR)
Есть несколькосвойств, принадлежащихTQuery, которые ещене упоминались:
propertyUniDirectional: Boolean;
propertyHandle: HDBICur;
propertyStmtHandle: HDBIStmt;
propertyDBHandle: HDBIDB;
СвойствоUniDirectional используетсядля того, чтобыоптимизироватьдоступ к таблице.Если Вы установитеUniDirectional в True, то Вы можетеперемещатьсяпо таблицеболее быстро,но Вы сможетедвигатьсятолько вперед.
Свойство StmtHandleсвязано сосвойствомHandle TDataSet. То есть, оновключеноисключительнодля того, чтоВы могли делатьвызовы Borland Database Engineнапрямую. Принормальныхобстоятельствах,нет никакойнеобходимостииспользоватьэто свойство,так как компонентыDelphi могут удовлетворитьпотребностямибольшинствапрограммистов.Однако, еслиВы знакомы сBorland Database Engine, и если Вызнаете чтосуществуютнекоторыевозможностине поддерживаемыев VCL, то Вы можетеиспользоватьTQuery.StmtHandle, или TQuery. Handle, чтобысделать вызовнапрямую вengine.
Следующийфрагмент кода показываетдва запросак BDE:
var
Name: array[0..100] of Char;
Records: Integer;
begin
dbiGetNetUserName(Name);
dbiGetRecordCount(Query1.Handle,Records);
end;
Создание базданных в Delphi
Урок7: Редактор DataSet,Вычисляемыеполя1
СодержаниеУрока 7:1
Обзор2
РедакторDataSet2
ВычисляемыеПоля5
УправлениеTDBGrid во времявыполнения9
В этой статьевы узнаете оРедактореDataSet и о способахуправлениякомпонентомTDBGrid во время выполненияпрограммы.Здесь же будутрассмотренывычисляемыеполя - весьмаценная особенностьРедактораDataSet.
Примеры, которыевы увидите вэтой статье,продемонстрируют основные способы,которыми пользуютсябольшинствопрограммистовдля показатаблиц БДпользователям.Для пониманиябольшей частиматериалатребуется общеезнание средыи языка Delphi.
Редактор DataSetможет бытьвызван с помощьюобъектов TTable илиTQuery. Чтобы начатьработать с ним,положите объектTQuery на форму, установитепсевдонимDBDEMOS, введите SQL запрос"select * from customer" и активизируйтеего (установивсв-во Active в True).
Откройте комбобокс“Object Selector” вверхуИнспектораОбъектов - внастоящее времятам имеетсядва компонента:TForm и TQuery.
Нажмите правуюкнопку мышина объектеTQuery и в контекстномменю выберитепункт “Fields Editor”.Нажмите кнопкуAdd - появитьсядиалог Add Fields, какпоказано нарис.1
Рис.1: ДиалогAdd Fields РедактораDataSet.
По-умолчанию,все поля в диалогевыбраны. Нажмитена кнопку OK, чтобывыбрать всеполя, и закройтередактор. Сновазагляните в“Object Selector”, теперьздесь появилосьнесколько новыхобъектов, (см.рис.2)
Рис.2: Object Selectorпоказываетв списке всеобъекты созданныев РедактореDataSet. Вы можететакже найтиэтот списокв определениикласса TForm1.
Эти новые объектыбудут использоватьсядля визуальногопредставления таблицы CUSTOMERпользователю.
Вот полныйсписок объектов,которые толькочто созданы:
Query1CustNo:TFloatField;
Query1Company:TStringField;
Query1Addr1:TStringField;
Query1Addr2:TStringField;
Query1City:TStringField;
Query1State:TStringField;
Query1Zip:TStringField;
Query1Country:TStringField;
Query1Phone:TStringField;
Query1FAX:TStringField;
Query1TaxRate:TFloatField;
Query1Contact:TStringField;
Query1LastInvoiceDate:TDateTimeField;
Я вырезал ивставил этотсписок из определениякласса TForm1, котороеможно найтив окне Редактораисходноготекста. Происхождениеимен показанныхздесь, должнобыть достаточноочевидно. Часть"Query1" беретсяпо-умолчаниюот имени объектаTQuery, а вторая половинаот имени поляв таблице Customer.Если бы мы сейчаспереименовалиобъект Query1 в Customer,то получилибы такие имена:
CustomerCustNo
CustomerCompany
Это соглашениеможет бытьочень полезно,когда Вы работаетес несколькими таблицами, исразу хотитезнать, на полекакой таблицы ссылаетсяданная переменная.
Любой объект,созданный вредактореDataSet являетсянаследникомкласса TField. Точныйтип потомказависит от типаданных в конкретномполе. Например,поле CustNo имееттип TFloatField, а полеQuery1City имеет типTStringField. Это два типаполей, которыеВы будете встречатьнаиболее часто.Другие типывключают типTDateTimeField, которыйпредставленполем Query1LastInvoiceDate, иTIntegerField, который невстречаетсяв этой таблице.
Чтобы понять,что можно делатьс потомкамиTField, откройтеBrowser, выключитепросмотр полейPrivate и Protected, и просмотритесвойства иметоды Public и Publishedсоответствующихклассов.
Наиболее важноесвойство называетсяValue. Вы можетеполучить доступк нему так:
procedureTForm1.Button1Click(Sender: TObject);
var
d:Double;
S:string;
begin
d:= Query1CustNo.Value;
S:= Query1Company.Value;
d:=d+1;
S:= 'Zoo';
Query1CustNo.Value:= d;
Query1Company.Value:= S;
end;
В коде, показанномздесь, сначалаприсваиваютсязначения переменнымd и S. Следующиедве строкиизменяют этизначения, апоследний двеприсваиваютновые значенияобъектам. Неимеет большогосмысла писатькод, подобныйэтому, в программе,но этот кодслужит лишьдля того, чтобыпродемонстрироватьсинтаксис,используемыйс потомкамиTField.
Свойство Valueвсегда соответствуеттипу поля, ккоторому оноотносится.Например уTStringFields - string, TCurrencyFields - double. Однако,если вы отображаетеполе типаTCurrencyField с помощьюкомпонент,“чувствительныхк данным”(data-aware: TDBEdit, TDBGrid etc.), то онобудет представленастрокой типа:"$5.00".
Это могло бызаставить васдумать, что уDelphi внезапноотключилсястрогий контрольтипов. ВедьTCurrencyField.Value объявленакак Double, и если Выпробуете присвоитьему строку, Выполучите ошибку“type mismatch” (несоответствиетипа). Вышеупомянутыйпример демонстрируетна самом делесвойства объектоввизуализацииданных, а неослаблениепроверки типов.(Однако, естьвозможностьполучить значениеполя уже преобразованноек другому типу.Для этого уTField и его потомковимеется наборметодов типаAsString или AsFloat. Конечно,преобразованиепроисходиттолько тогда,когда имеетсмысл.)
Если нужнополучить именаполей в текущемDataSet, то для этогоиспользуетсясвойство FieldNameодним из двухспособов, показанныхниже:
S:= Query1.Fields[0].FieldName;
S:= Query1CustNo.FieldName;
Если вы хотитеполучить имяобъекта, связанногос полем, то выдолжны использоватьсвойство Name:
S:= Query1.Fields[0].Name;
S:= Query1CustNo.Name;
Для таблицыCUSTOMER, первый примервернет строку"CustNo", а любая изстрок второгопримера строку"Query1CustNo".
Создание вычисляемыхполей - одно изнаиболее ценныхсвойств РедактораDataSet. Вы можетеиспользоватьэти поля дляразличныхцелей, но дваслучая выделяютсяособо:
выполнениевычисленийпо двум илиболее полямв DataSet, и отображениерезультатавычисленийв третьем поле.
имитация соединениядвух таблицс возможностьюредактироватьрезультатсоединения.
ПрограммаCALC_SUM.DPR из примеровк данному урокуиллюстрируетпервый случайиспользованиявычисляемыхполей.
Эта программасвязывает тритаблицы в отношенииодин ко многим.В частности,ORDERS и ITEMS связаныпо полю OrderNo, а ITEMS иPARTS связаны пополю PartNo. (В таблицеORDERS хранятся всезаказы; в таблицеITEMS - предметы,указанные взаказах; PARTS - справочникпредметов). Впрограмме можноперемещатьсяпо таблицеORDERS и видеть связанныйс текущим заказомсписок включенныхв него предметов.ПрограммаCALC_SUM достаточносложная, нохорошо иллюстрируетмощность вычисляемыхполей.
Последовательностьсоздания проектаCALC_SUM:
Создайте новыйпроект (File|New Project) иудалите изнего форму (вМенеджереПроекта View|ProjectManager)
Выберите экспертаформ БД из менюHelp.
На первом экране,выберите "Createa master/detail form" и "Create a form using TQueryObjects".
Нажмите кнопкуNext и выберитетаблицу ORDERS.DB изпсевдонимаБД DBDEMOS.
Нажмите Next ивыберите поляOrderNo, CustNo, SaleDate, ShipDate и ItemsTotal изтаблицы ORDERS.DB.
Нажмите Next ивыберите"Horizontal" из расстановкикомпонентовdbEdit на форме.
Нажмите Next ивыберите таблицуITEMS.DB.
В двух следующихэкранах выберитевсе поля изтаблицы и поместитеих в grid.
Нажмите Next ивыберите полеOrderNo из Master и Detail ListBoxes, иНажмите кнопкуAdd.
Нажмите Next исгенерируйтеформу.
Требуетсямного слов длятого, чтобыописать процесспоказанныйвыше, но, фактически,выполнениекоманд в Экспертеформ БД легкои интуитивно.
Выделите первыйиз двух объектовTQuery и установятсвойство Active вTrue. Для Query2 в свойствеSQL напишите текстзапроса:
select * from Items I, Parts P
where (I.OrderNo =:OrderNo)and
(I.PartNo=P.PartNo)
Активизируйтеобъект Query2 (Active установитев True) и вызовитередактор DataSet(Fields Editor) для него.Вызовите диалогAdd Fields и добавьтеполя OrderNo, PartNo, Qty и ListPrice.
Нажмите Define иведите словоTotal в поле FieldName. УстановитеField Type в CurrencyField. Проверьтечто Calculated CheckBox отмечен.Нажмите Ok и закройтередактор DataSet.
Простой процессописанный впредыдущемабзаце, показываеткак создатьвычисляемоеполе. Если посмотретьв DBGrid, то можновидеть, что тамтеперь естьеще одно пустоеполе. Для того,чтобы поместитьзначение в этополе, откройтев ИнспектореОбъектов страницусобытий дляобъекта Query2 исделайте двойнойщелчок наOnCalcFields. Заполнитесозданный методтак:
procedureTForm2.Query2CalcFields(DataSet: TDataSet);
begin
Query2NewTotalInvoice.Value:= 23.0;
end;
После запускапрограммы полеTotal будет содержитстроку $23.00.
Это показывает,насколькопросто создатьвычисляемоеполе, котороепоказываетправильносформатированныеданные. На самомделе это поледолжно показыватьнечто другое- произведениеполей Qty (количество)и ListPrice (цена). Дляэтого вышеприведенныйкод для событияOnCalcFields нужно изменитьследующимобразом:
procedureTForm1.Query2CalcFields(DataSet: TDataset);
begin
Query2Total.Value:=Query2Qty.Value*Query2ListPrice.Value;
end;
Если теперьзапуститьпрограмму, тополе Total будетсодержатьтребуемоезначение.
В обработчикесобытия OnCalcFields можновыполнять иболее сложныевычисления(это будет показанопозже), однакоследует помнить,что это вызываетсоответствующеезамедлениескорости работыпрограммы.
Теперь давайтедобавим вычисляемоеполе для первойтаблицы (Query1, ORDERS),которое будетотображатьсумму значенийиз поля Total второйтаблицы (Query2) дляданного заказа.Вызовите редакторDataSet для объектаQuery1 и добавьтевычисляемоеполе NewItemsTotal типаCurrencyField. В обработчикесобытия OnCalcFields дляQuery1 нужно подсчитатьсумму и присвоитьее полю NewItemsTotal:
procedureTForm1.Query1CalcFields(DataSet: TDataset);
var
R : Double;
begin
R:=0;
with Query2 dobegin
DisableControls;
Close;
Open;
repeat
R:=R+Query2Total.Value;
Next;
until EOF;
First;
EnableControls;
end;
Query1NewItemsTotal.Value:=R;
end;
Вданном примересумма подсчитываетсяс помощью простогоперебора записей,это не самыйоптимальныйвариант - можно,например, дляподсчета суммыиспользоватьдополнительныйобъект типаTQuery. Метод DisableControlsвызываетсядля того, чтобыотменить перерисовкуDBGrid при сканированиитаблицы. ЗапросQuery2 переоткрываетсядля уверенностив том, что еготекущий наборзаписей соответствуеттекущему заказу.
Поместите наформу еще одинэлемент DBEdit ипривяжите егок Query1, полю NewItemsTotal. Запуститепрограмму, еепримерный видпоказан нарис.3
Рис.3: ПрограммаCALC_SUM
Как видно изпрограммы,наличие поляItemsTotal в таблицеORDERS для данногопримера необязательнои его можнобыло бы удалить(однако, ононеобходимов других случаях).
Объект DBGrid можетбыть полностьюреконфигурированво время выполненияпрограммы. Выможете прятатьи показыватьколонки, изменятьпорядок показаколонок и ихширину.
Вы можетеиспользоватьсвойство Optionsобъекта DBGrid, чтобыизменить еепредставление.Свойство Optionsможет приниматьследующиевозможныезначения:
dgEditing | Установленпо-умолчаниюв true, позволяетпользователюредактироватьgrid. Вы можететакже установитьсвойство ReadOnlygrid в True или False. |
dgAlwaysShowEditor | Всегдапоказыватьредактор. |
dgTitles | Показыватьназвания колонок. |
dgIndicator | Показыватьнебольшиеиконки слева. |
dgColumnResize | Можетли пользовательменять размерколонки. |
dgColLines | Показыватьлинии междуколонками. |
dgRowLines | Показыватьлинии междустроками. |
dgTabs | Можетли пользовательиспользоватьtab и shift-tab для переключениямежду колонками. |
dgRowSelect | Выделятьвсю записьцеликом. |
dgAlwaysShowSelection | Всегдапоказыватьвыбранныезаписи. |
dgConfirmDelete | Подтверждатьудаление. |
dgCancelOnExit | Отменаизмененийпри выходеиз DBGrid. |
dgMultiSelect | Одновременноможет бытьвыделена большечем одна запись. |
Как объявленов этой структуре:
DBGridOption= (dgEditing, dgAlwaysShowEditor, dgTitles, dgIndicator,dgColumnResize, dgColLines, dgRowLines, dgTabs,dgRowSelect,dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit,dgMultiSelect);
Например Выможете установитьопции в Runtime написавтакой код:
DBGrid1.Options:= [dgTitles, dgIndicator];
Если Вы хотитевключать ивыключатьопции, это можносделать с помощьюлогическихопераций. Например,следующий кодбудет добавлятьdgTitles к текущемунабору параметров:
DBGrid1.Options:= DBGrid1.Options + [dgTitles];
Пусть естьпеременнаяShowTitles типа Boolean, тогдаследующий кодпозволяютвключать ивыключатьпараметр однойкнопкой:
procedureTForm1.Button3Click(Sender: TObject);
begin
ifShowTitles then
DBGrid1.Options :=DBGrid1.Options + [dgTitles]
else
DBGrid1.Options :=DBGrid1.Options - [dgTitles];
ShowTitles:= not ShowTitles;
end;
Если Вы хотитескрыть полев run-time, то можетеустановитьсвойство visible вfalse:
Query1.FieldByName(‘CustNo’).Visible:= False;
Query1CustNo.Visible:= False;
Обе строки кодавыполняютидентичнуюзадачу. Чтобыпоказать полеснова, установитевидимый в true:
Query1.FieldByName(‘CustNo’).Visible:= True;
Query1CustNo.Visible:= True;
Если Вы хотитеизменить положениеколонки в Runtime,можете простоизменить индекс,(первое полев записи имеетиндекс нуль):
Query1.FieldByName(‘CustNo’).Index:= 1;
Query1CustNo.Index:= 2;
По-умолчанию,поле CustNo в таблицеCustomer являетсяпервым. Код впервой строкеперемещаетэто поле вовторую позицию,а следующаястрока перемещаетего в третьюпозицию. Помните,что нумерацияполей начинаетсяс нуля, такприсвоениесвойству Index 1делает полевторым в записи.Первое полеимеет Index 0.
Когда Вы изменяетеиндекс поля,индексы другихполей в записиизменяютсяавтоматически.
Если Вы хотитеизменить ширинуколонки в Runtime,только изменитесвойство DisplayWidthсоответствующегоTField.
Query1.FieldByName(‘CustNo’).DisplayWidth:= 12;
Query1CustNo.DisplayWidth:= 12;
Величина 12 относитсяк числу символов,которые могутбыть показаныв видимом элементе.
ПрограммаDBGR_RT показываеткак работатьс DBGrid в Runtime. Программадостаточнопроста, кромедвух небольшихчастей, которыеописаны ниже.Первая частьпоказывает,как создатьcheck box в Runtime, а втораяпоказывает,как изменитьпорядок пунктовв listbox в Runtime.
При созданииформы (событиеOnCreate) ListBox заполняетсяименами полей,далее создаетсямассив объектовCheckBox, соответствующийполям в таблице.Сперва всеCheckBox’ы выбраныи все поля втаблице видимы.Программаузнает черезTTable1 имена полейи присваиваетих свойствуCaption соответствующегоCheckBox. Кроме того,обработчикусобытия OnClick всехCheckBox’ов присваиваетсяпроцедураChBClick, которая ивключает/выключаетполя в DBGrid.
procedureTForm1.FormCreate(Sender: TObject);
var
i : Word;
R: Array[0..49] of TCheckBox;
begin
{Fill ListBox}
ListBox1.Clear;
for i:=0 toTable1.FieldCount-1 do
ListBox1.Items.Add(Table1.Fields[i].FieldName);
{Make CheckBoxes}
for i:=0 toTable1.FieldCount-1 do begin
R[I] :=TCheckBox.Create(Self);
R[I].Parent :=ScrollBox1;
R[I].Caption :=Table1.Fields[i].FieldName;
R[I].Left := 10;
R[I].Top := I *CheckBox1.Height + 5;
R[I].Width :=200;
R[I].Checked :=True;
R[I].OnClick :=ChBClick;
end;
end;
Большая частькода в этомпримере выполняетотносительнопростые задачи,типа назначенияимен и положенийcheck boxes. Вот две ключевыхстроки:
R[I]:= TCheckBox.Create(Self);
R[I].Parent :=ScrollBox1;
Первая строкисоздает CheckBox сзаданным Owner(Владельцем).Вторая строкиназначаетParent (Родителя) дляCheckBox. Чтобы понятьразличия междуРодителем иВладельцем,посмотритесоответствующиесвойства вonline-help.
Программасодержит ListBox,который показываеттекущий порядокполей в DataSet. Дляизмененияпорядка полейв DataSet (а, следовательно,в DBGrid) используютсядве кнопки. Принажатии на однуиз кнопок, выбранноев ListBox’е полеперемещаетсяна одну позициювверх или вниз.Синхронно сэтим меняетсяи порядок полейв DBGrid. Код, показанныйниже, изменяетIndex поля для Table1,изменяя, такимобразом, позициюполя в DBGrid. Этиизменениякасаются тольковизуальногопредставленияDataSet. Физическиданные на дискене изменяются.
procedureTForm1.downButtonClick(Sender: TObject);
var
i: Integer;
begin
with ListBox1 do
if(ItemIndex
i := ItemIndex;
{move ListBoxitem}
Items.Move(i,i+1);
ItemIndex :=i+1;
{move Field}
Table1.Fields[i].Index:=i+1;
end;
end;
Последняястрока в примерекак раз та, котораяфактическиизменяет индексколонки, которуюпользовательхочет переместить.Две строки коданепосредственноперед ней перемещаюттекущую строкув ListBox на новуюпозицию.
Внешнийвид программыDBGR_RT показан нарис.4
Рис.4: ПрограммаDBGR_RT
Урок 7 :РедакторDataSet, вычисляемыеполя
Созданиебаз данных вDelphi
Урок 8:Управлениесоединениемс базой данных(класс TDataBase, объектSession)
Содержаниеурока 8:
Обзор2
КлассTDataBase2
ОбъектSession7
Указаниесетевого протоколапри соединениис БД7
В даннойстатье рассказываетсяоб управлениисоединениемс базой данныхпри помощикомпонентыTDataBase и объектаTSession, который создаетсяв программеавтоматически.Описываютсяпроцедурысоздания локальногопсевдонимабазы данныхи доступа ктаблицам Paradox попаролю.
Объект типаTDataBase не являетсяобязательнымпри работе сбазами данных,однако онпредоставляетряд дополнительныхвозможностейпо управлениюсоединениемс базой данных.TDataBase служит для:
Созданияпостоянногосоединенияс базой данных
Определениясобственногодиалога присоединениис базой данных(опрос пароля)
Созданиялокальногопсевдонимабазы данных
Измененияпараметровпри соединении
Управлениятранзакциями
TDataBaseявляется невидимымво время выполненияобъектом. Оннаходится настранице “DataAccess” ПалитрыКомпонент. Длявключения впроект TDataBase нужно“положить”его на главноеокно вашейпрограммы.
Созданиепостоянногосоединенияс базой данных
Есливы работаетес базой данных,то перед началомработы выполняетсяпроцедурасоединенияс этой базой.В процедурусоединения,кроме прочего,входит опросимени и пароляпользователя(кроме случаяработы с локальнымитаблицамиParadox и dBase через IDAPI).Если в программене используетсяTDataBase, то процедурасоединениявыполняетсяпри открытиипервой таблицыиз базы данных.Соединениес базой данныхобрывается,когда в программезакрываетсяпоследняятаблицы из этойбазы (это происходитв том случае,если свойствоKeepConnectionsобъекта Sessionустановленов False, но об этомчуть позже).Теперь, еслиснова открытьтаблицу, топроцедураустановкисоединенияповторитсяи это можетбыть достаточнонеудобно дляпользователя.Чтобы соединениене обрывалосьдаже в том случае,когда нет открытыхтаблиц даннойбазы, можноиспользоватькомпонент типаTDataBase. В свойствеAliasName укажите псевдонимбазы данных,с которой работаетпрограмма; всвойстве DatabaseName- любое имя(псевдоним БД),на котороебудут ссылатьсятаблицы вместостарого псевдонимабазы. СвойствоConnected установитев True - процедурасоединенияс базой будетвыполнятьсяпри запускепрограммы. И,наконец, свойствоKeepConnection нужно установитьв True (см. рис.1).
Рис.A:Свойства TDataBase вИнспектореобъектов
Внашем примере,после заданиясвойств DataBase1 нужноу всех таблиц,работающихс IBLOCAL в свойствеDatabaseName поставитьLoc_IBLOCAL.
Определениесобственногодиалога присоединениис базой данных
Поумолчанию присоединениис базой данныхиспользуетсядиалог опросаимени и пароляпользователя,показанныйна рис.2
Рис.B:Диалог авторизациипользователя
При желанииможно изменитьвнешний виддиалога иливообще егоотменить. Дляэтого используютсясвойства исобытия классаTDataBase - LoginPrompt, Paramsи OnLogin.
Чтобыотключить опросимени и пароляустановитесвойство LoginPromptв False. При этом всвойстве Paramsтребуется вявном виде (вовремя дизайналибо во времявыполнения)указать имяи пароль пользователя.Например, впрограмме можнонаписать (домомента соединенияс базой, напримерв событии дляForm1 OnCreate):
DataBase1.LoginPrompt:=False;
DataBase1.Params.Clear;
DataBase1.Params.Add(‘USERNAME=SYSDBA’);
DataBase1.Params.Add(‘PASSWORD=masterkey’);
DataBase1.Connected:=True;
Чтобыиспользоватьсвой собственныйдиалог, в которомможно опрашиватьне только имяи пароль пользователя,но и, например,сетевой протокол- создайте обработчиксобытия OnLogin дляDataBase1:
procedureTForm1.Database1Login(Database: TDatabase;
LoginParams:TStrings);
begin
Form2.ShowModal;
ifForm2.ModalResult = mrOK then
withLoginParams do begin
Values['USERNAME'] := User_Name;
Values['PASSWORD']:= User_Pass;
end;
end;
ЗдесьForm2 -новое окно-диалогдля ввода имении пароля, User_Nameи User_Pass- строки, кудасохраняютсявведенные имяи пароль.
Созданиелокальногопсевдонимабазы данных
Обычно,псевдоним базыданных(Alias) определяетсяв утилитеконфигурацииBDE и информацияо нем сохраняетсяв файле конфигурацииIDAPI.CFG. Однако, впрограмме можноиспользоватьне только ранееопределенныйв утилитеконфигурацииBDE псевдонимбазы данных,но и так называемыйлокальный (т.е.видимый тольковнутри даннойпрограммы)псевдоним. Этоиногда бываетнужно, например,для того, чтобыобезопаситьпрограмму вслучае удаленияиспользуемогопсевдонимаиз файла конфигурацииBDE.
Длятого, чтобысоздать локальныйпсевдоним БД,положите наглавное окнопроекта компонентDataBase1. Дальнейшиедействия можновыполнить спомощью ИнспектораОбъектов, ноудобнее этосделать черезредактор компонент.Щелкните дваждымышкой на DataBase1 -появится диалог,показанныйна рис.3
Рис.C:Редактор компонентыкласса TDataBase
Вэтом диалогетребуетсяуказать имябазы данных- это будет еелокальныйпсевдоним, накоторый ссылаютсятаблицы (свойствоDatabaseName); тип драйвера(в нашем примереэто INTRBASE); а такжепараметры,используемыепри соединениис базой данных.Получить списокпараметровв поле “Parameter Overrides”можно по нажатиюкнопки “Defaults”.Набор параметровзависит от типаБД, с которойвы работаете.Этим параметрамнужно присвоитьтребуемыезначения - указатьпуть к серверу,имя пользователяи т.д. После выходаиз редакторакомпонент имя,указанное вполе “Name” появитсяв списке именбаз данных длякомпонент типаTDataSet (TTable, TQuery etc.).
Изменениепараметровпри соединении
Иногдатребуетсяизменить определенныев утилитеконфигурацииBDE параметры,используемыепри установлениисоединенияс БД. Это можносделать вовремя дизайнас помощью диалога,показанногона рис.3, в поле“Parameter Overrides”. Либо вовремя выполненияпрограммы (допопытки соединения)прямым присвоениемсвойству Paramsобъекта DataBase1:
DataBase1.Params.Add(‘LANGDRIVER=ancyrr’);
Управлениетранзакциями
TDataBase позволяетначать в БДтранзакцию(метод StartTransaction), закончить(Commit) или откатитьее (RollBack). Кроме того, можно изменятьуровень изоляциитранзакций(свойствоTransIsoltion).
TransIsolationOracleSybase andInformixInterBase
MicrosoftSQL
DirtyreadRead committedRead committedDirty ReadRead committed
Readcommitted(Default)Read committedRead committedRead committedReadcommitted
RepeatablereadRepeatable readRead committedRepeatable ReadRepeatable Read
“DirtyRead” - внутри вашейтекущей транзакциивидны все изменения, сделанныедругими транзакциями,даже если ониеще не завершилисьпо Commit. “Read Committed” - виднытолько “закоммитченные”изменения,внесенные вбазу. “Repeatable Read” - внутритранзакциивидны те данные,что были в базена момент началатранзакции,даже если тамна самом делеуже имеютсяизменения.
ОбъектSession, имеющий типTSession создаетсяавтоматическив программе,работающейс базами данных(в этом случаеDelphi подключаетв программумодуль DB). Вамне нужно заботитьсяо создании иуничтоженииданного объекта,но его методыи свойствамогут бытьполезны в некоторыхслучаях. В этомкомпонентесодержитсяинформацияобо всех базахданных, с которымиработает программа.Ее можно найтив свойствеDataBases.Со свойствомKeepConnectionsданного объектамы уже знакомы.Это свойствоопределяет,нужно ли сохранятьсоединениес базой, еслив программенет ни однойоткрытой таблицыиз этой базы.NetDir -директория,в которой лежитобщий сетевойфайл PDOXUSRS.NET, необходимыйBDE. PrivateDir- директориядля хранениявременныхфайлов.
С помощьюметодов объектаSession можно получитьинформациюо настройкахBDE, например, списоквсех псевдонимов,драйверов базданных илисписок всехтаблиц в базе.
Ещеодно важноеназначениеобъекта Session - доступс его помощьюк таблицамParadox, защищеннымпаролем. Прежде,чем открытьтакую таблицу,требуетсявыполнить методAddPassword :
Session.AddPassword(‘my_pass’);
Удалить парольможно с помощьюметода RemovePassword илиRemoveAllPasswords.
В случае сInterBase можно в явномвиде указать,какой сетевойпротокол используетсяпри соединениис базой данных.Эта установкавыполняетсялибо в утилитеконфигурацииBDE, либо в программе- нужно изменитьпараметр “SERVERNAME”, которыйсодержит полныйпуть к файлус базой данных.
Итак:
ПротоколПараметрSERVER NAME
TCP/IPIB_SERVER:PATH\DATABASE.GDB(nt:c:\ib\base.gdb )( unix:/ib/base.gdb )
IPX/SPXIB_SERVER:PATH\DATABASE.GDB(nw@sys:ib\base.gdb )
NetBEUI\IB_SERVER\PATH\DATABASE.GDB(\nt\c:\ib\base.gdb )
Урок8 : Управлениесоединениемс базой данных
Создание базданных в Delphi
Урок9: Управлениетранзакциями
Содержаниеурока 9:
Обзор2
SQL-выражениядля управлениятранзакциями2
Запусктранзакции2
Завершениетранзакции4
Управлениетранзакциямив Delphi4
Всеоперации, выполняемыес данными наSQL сервере, происходятв контекстетранзакций.Транзакция- это групповаяоперация, т.е.набор действийс базой данных;самым существеннымдля этих действийявляется правилолибо все,либо ни чего.Если во времявыполненияданного наборадействий, накаком-то этапеневозможнопроизвестиочередноедействие, тонужно выполнитьвозврат базыданных к начальномусостоянию(произвестиоткат транзакции).Таким образом(при правильномпланированиитранзакций),обеспечиваетсяцелостностьбазы данных.В данном урокеобъясняется,как начинать,управлять изавершатьтранзакциис помощью SQLвыражений. Атак же рассматриваетсявопрос обиспользованиитранзакцийв приложениях,созданных вDelphi. Вся приведеннаяинформациякасается InterBase.
Дляуправлениятранзакциямиимеется тривыражения:
SETTRANSACTION -Начинает транзакциюи определяетее поведение.
COMMIT-Сохраняетизменения,внесенныетранзакцией,в базе данныхи завершаеттранзакцию.
ROLLBACK- Отменяетизменения,внесенныетранзакцией,и завершаеттранзакцию.
Выполнятьтранзакцииможно, например,из Windows Interactive SQL, из программы,из сохраненнойпроцедуры илитриггера. Вобщем виде,синтаксискоманды SQL длязапуска транзакции:
SETTRANSACTION [Access mode] [Lock Resolution]
[IsolationLevel] [Table Reservation]
Значения,принимаемыепо-умолчанию:
выражение
SETTRANSACTION
равносильновыражению
SETTRANSACTION READ WRITE WAIT ISOLATION LEVEL SNAPSHOT
AccessMode - определяеттип доступак данным. Можетпринимать двазначения:
READ ONLY - указывает,что транзакцияможет толькочитать данныеи не можетмодифицироватьих.
READ WRITE - указывает,что транзакцияможет читатьи модифицироватьданные. Этозначение принимаетсяпо умолчанию.
Пример:
SETTRANSACTION READ WRITE
IsolationLevel - определяетпорядок взаимодействияданной транзакциис другими вданной базе.Может приниматьзначения:
SNAPSHOT - значениепо умолчанию.Внутри транзакциибудут доступныданные в томсостоянии, вкотором онинаходилисьна момент началатранзакции.Если по ходудела в базеданных появилисьизменения,внесенныедругими завершеннымитранзакциями,то данная транзакцияих не увидит.При попыткемодифицироватьтакие записивозникнетсообщение оконфликте.
SNAPSHOT TABLE STABILITY - предоставляеттранзакцииисключительныйдоступ к таблицам,которые онаиспользует.Другие транзакциисмогут толькочитать данныеиз них.
READ COMMITTED - позволяеттранзакциивидеть текущеесостояниебазы.
Конфликты,связанные сблокировкойзаписей происходятв двух случаях:
Транзакцияпытаетсямодифицироватьзапись, котораябыла измененаили удаленауже после еестарта. Транзакциятипа READ COMMITTED можетвносить измененияв записи, модифицированныедругими транзакциямипосле их завершения.
Транзакцияпытаетсямодифицироватьтаблицу, котораязаблокированадругой транзакциейтипа SNAPSHOT TABLE STABILITY.
LockResolution - определяетход событийпри обнаруженииконфликтаблокировки.Может приниматьдва значения:
WAIT - значение поумолчанию.Ожидает разблокировкитребуемойзаписи. Послеэтого пытаетсяпродолжитьработу.
NO WAIT - немедленновозвращаетошибку блокировкизаписи.
TableReservation - позволяеттранзакцииполучитьгарантированныйдоступ необходимогоуровня к указаннымтаблицам. Существуетчетыре уровнядоступа:
PROTECTED READ - запрещаетобновлениетаблицы другимитранзакциями,но позволяетим выбиратьданные из таблицы.
PROTECTED WRITE - запрещаетобновлениетаблицы другимитранзакциями,читать данныеиз таблицымогут толькотранзакциитипа SNAPSHOT или READCOMMITTED.
SHARED READ - самый либеральныйуровень. Читатьмогут все,модифицировать- транзакцииREAD WRITE.
SHARED WRITE - транзакцииSNAPSHOT или READ COMMITTED READ WRITE могутмодифицироватьтаблицу, остальные- только выбиратьданные.
Когда все действия,составляющиетранзакциюуспешно выполненыили возниклаошибка, транзакциядолжна бытьзавершена, длятого, чтобыбаза данныхнаходиласьв непротиворечивомсостоянии. Дляэтого есть дваSQL-выражения:
COMMIT- сохраняетвнесенныетранзакциейизменения вбазу данных.Это означает,что транзакциязавершенауспешно.
ROLLBACK - откат транзакции.Транзакциязавершаетсяи никаких измененийв базу данныхне вносится.Данная операциявыполняетсяпри возникновенииошибки привыполненииоперации (например,при невозможностиобновить запись).
Прежде всего,транзакциив Delphi бывают явныеи неявные.
Явнаятранзакция- это транзакция,начатая и завершеннаяс помощью методовобъекта DataBase:StartTransaction, Commit, RollBack.Посленачала явнойтранзакции,все изменения,вносимые вданные относятсяк этой транзакции.
Другого способаначать явнуютранзакцию,нежели с использованиемDataBase, нет. (Точнееговоря, такаявозможностьесть, но этопотребуетобращения кфункциям APIInterBase. Однако, этоуже достаточнонизкоуровневоепрограммирование.) Следовательно,в рамках одногосоединениянельзя начатьдве транзакции.
Неявнаятранзакциястартует примодификацииданных, еслив данный моментнет явной транзакции.Неявная транзакциявозникает,например, привыполненииметода Postдля объектовTable иQuery. Тоесть, если Выотредактировализапись, в DBGrid ипереходитена другую запись,то это влечетза собой выполнениеPost, что,в свою очередь,приводит кначалу неявнойтранзакции,обновлениюданных внутритранзакциии ее завершению.Важно отметить,что неявнаятранзакция,начатая с помощьюметодов Post,Delete, Insert, Append и т.д.заканчиваетсяавтоматически.
Длямодификацииданных можетиспользоватьсяи PassThrough SQL- SQL-выражение,выполняемоес помощью методаExecSQL классаTQuery. Выполнениемодификациичерез PassThrough SQL такжеприводит кстарту неявнойтранзакции.Дальнейшееповедениетранзакции,начатой такимпутем, определяетсязначениемпараметраSQLPASSTHRU MODE для псевдонимабазы данных(или тот-же параметрв св-ве ParamsобъектаDataBase). Этот параметрможет приниматьтри значения:
SHAREDAUTOCOMMIT - словоSHARED указываетна то, что обавида транзакций(черезPassthrough SQL и через методыTTable и TQuery) разделяютодно и то жесоединениек базе данных.Слово AUTOCOMMIT указываетна то, что неявнаятранзакция,начатая черезPassthrough SQL, завершаетсяпосле выполнениядействия помодификацииданных (автоматическивыполняетсяCOMMIT).
SHAREDNOAUTOCOMMIT - отличаетсяот предыдущеготем, что неявнаятранзакция,начатая черезPassthrough SQL, не завершаетсяпосле выполнения,ее нужно явнозавершить,выполнивSQL-выражение“COMMIT”.
NOTSHARED - транзакцииразных типовработают черезразные соединенияс базой. Данноезначение параметраподразумеваеттакже NOAUTOCOMMIT.То есть всенеявныеPassthroughSQL-транзакциинужно завершатьявно - выполняяSQL-выражение“COMMIT”для Passtrough SQL.
Рассмотримвозможныесценарии поведениятранзакцийпри разныхзначенияхпараметра.
Впервом случае,если нет в данныймомент начатойтранзакции,то попыткамодификацияданных методамиTTable или TQuery, как ивыполнениечерез Passtrough SQL какой-либооперации приведетк старту неявнойтранзакции.После выполнения,такая транзакциябудет автоматическизавершена (еслине возниклоошибки по ходутранзакции).Если уже имеетсяначатая явно(метод StartTransactionобъекта DataBase)транзакция,то изменениябудут проходитьв ее контексте.Все транзакциииспользуютодно и то-жесоединение.
Вовтором случаевсе происходит,как в первом. Отличие в том,что неявнаяPassthroughSQL-транзакцияне завершается,пока не будетвыполненакоманда “COMMIT”.
Втретьем случае,при выполнениикоманды Passthrough SQL,будет установленоеще одно соединение,начата неявнаятранзакцияи выполненыдействия помодификацииданных. Транзакцияне будет завершена,пока не будетвыполненакоманда “COMMIT”.Наличие транзакции,начатой явнос помощью DataBaseникак не отразитсяна ходе выполненияPassthroughSQL-транзакции.Пока PassthroughSQL-транзакцияне завершится,изменения,внесенные ей,не будут видныв объектахTable и Query, работающихчерез другоесоединение.PassthroughSQL-транзакцииможно рассматриватьв некоторомсмысле, кактранзакциииз другогоприложения.
Взаимодействиетранзакцийданной программыс транзакциямииз других приложенийопределяетсясвойствомTransIsolationобъекта DataBase. ДляInterBase имеет смыслдва значения:tiReadCommittedи tiRepeatableRead. Выполнениеметода StartTransactionв этих двухслучаях равносильновыполнениюSQL-выражений,соответственно:
SETTRANSACTION READ WRITE WAIT ISOLATION LEVEL READ COMMITTED
и
SETTRANSACTION READ WRITE WAIT ISOLATION LEVEL SNAPSHOT
Урок 9 :Управлениетранзакциями
Создание базданных в Delphi
Урок 10: Основыязыка SQL
Содержаниеурока 10:
Обзор2
Составязыка SQL2
Реляционныеоперации. Командыязыка манипулированияданными4
КомандаSELECT10
Простейшиеконструкциикоманды SELECT10
Списокполей10
Все поля11
Все поляв произвольномпорядке11
Блобы11
Вычисления12
Литералы12
Конкатенация13
ИспользованиеквалификатораAS13
Работас датами14
Агрегатныефункции15
ПредложениеFROM команды SELECT16
Ограниченияна число выводимыхстрок16
Операциисравнения16
BETWEEN18
IN20
LIKE21
CONTAINING22
IS NULL22
Логическиеоператоры23
Преобразованиетипов (CAST)25
Изменениепорядка выводимыхстрок (ORDER BY)25
Упорядочиваниес использованиемимен столбцов26
Упорядочиваниес использованиемномеров столбцов28
Устранениедублирования(модификаторDISTINCT)29
Соединение(JOIN)30
Внутренниесоединения31
Самосоединения34
Внешниесоединения35
SQL(обычно произносимыйкак "СИКВЭЛ"или “ЭСКЮЭЛЬ”)символизируетсобой СтруктурированныйЯзык Запросов.Это - язык, которыйдает Вам возможностьсоздавать иработать вреляционныхбазах данных,являющихсянаборами связаннойинформации,сохраняемойв таблицах.
Информационноепространствостановитсяболее унифицированным.Это привелок необходимостисоздания стандартногоязыка, которыймог бы использоватьсяв большом количестверазличных видовкомпьютерныхсред. Стандартныйязык позволитпользователям,знающим одиннабор команд,использоватьих для создания,нахождения,изменения ипередачи информации- независимоот того, работаютли они на персональномкомпьютере,сетевой рабочейстанции, илина универсальнойЭВМ.
Внашем все болееи более взаимосвязанномкомпьютерноммире, пользовательснабженый такимязыком, имеетогромное преимуществов использованиии обобщенииинформациииз ряда источниковс помощью большогоколичестваспособов.
Элегантностьи независимостьот спецификикомпьютерныхтехнологий,а также егоподдержкалидерамипромышленностив области технологииреляционныхбаз данных,сделало SQL (и,вероятно, втечение обозримогобудущего оставитего) основнымстандартнымязыком. По этойпричине, любой,кто хочет работатьс базами данных90-х годов, должензнать SQL.
СтандартSQL определяетсяANSI (АмериканскимНациональнымИнститутомСтандартов)и в данное времятакже принимаетсяISO (МеждународнойОрганизациейпо Стандартизации).Однако, большинствокоммерческихпрограмм базданных расширяютSQL без уведомленияANSI, добавляяразличныеособенностив этот язык,которые, какони считают,будут весьмаполезны. Иногдаони нескольконарушают стандартязыка, хотяхорошие идеиимеют тенденциюразвиватьсяи вскоре становитьсястандартами"рынка" самипо себе в силуполезностисвоих качеств.
Наданном урокемы будем, в основном,следоватьстандарту ANSI,но одновременноиногда будетпоказыватьи некоторыенаиболее общиеотклоненияот его стандарта.
Точноеописание особенностейязыка приводитсяв документациина СУБД, которуюВы используете.SQL системы InterBase 4.0соответствуетстандартуANSI-92 и частичностандартуANSI-III.
ЯзыкSQL предназначендля манипулированияданными в реляционныхбазах данных,определенияструктуры базданных и дляуправленияправами доступак данным вмногопользовательскойсреде.
Поэтому,в язык SQL в качествесоставныхчастей входят:
язык манипулированияданными (Data ManipulationLanguage, DML)
язык определенияданных (Data DefinitionLanguage, DDL)
язык управленияданными (Data ControlLanguage, DCL).
Подчеркнем,что это не отдельныеязыки, а различныекоманды одногоязыка. Такоеделение проведенотолько лишьс точки зренияразличногофункциональногоназначенияэтих команд.
Языкманипулированияданными используется,как это следуетиз его названия,для манипулированияданными в таблицахбаз данных. Онсостоит из 4основных команд:
SELECT(выбрать)
INSERT(вставить)
UPDATE(обновить)
DELETE(удалить).
Языкопределенияданных используетсядля созданияи измененияструктуры базыданных и еесоставныхчастей - таблиц,индексов,представлений(виртуальныхтаблиц), а такжетриггеров исохраненныхпроцедур. Основнымиего командамиявляются:
CREATE DATABASE(создать базуданных)
CREATE TABLE(создать таблицу)
CREATE VIEW(создать виртуальнуютаблицу)
CREATE INDEX(создать индекс)
CREATETRIGGER(создатьтриггер)
CREATEPROCEDURE(создатьсохраненнуюпроцедуру)
ALTER DATABASE(модифицироватьбазу данных)
ALTER TABLE(модифицироватьтаблицу)
ALTER VIEW(модифицироватьвиртуальнуютаблицу)
ALTER INDEX(модифицироватьиндекс)
ALTERTRIGGER(модифицироватьтриггер)
ALTERPROCEDURE(модифицироватьсохраненнуюпроцедуру)
DROP DATABASE(удалить базуданных)
DROP TABLE(удалить таблицу)
DROP VIEW(удалить виртуальнуютаблицу)
DROP INDEX(удалить индекс)
DROPTRIGGER(удалитьтриггер)
DROPPROCEDURE(удалитьсохраненнуюпроцедуру).
Языкуправленияданными используетсядля управленияправами доступак данным ивыполнениемпроцедур вмногопользовательскойсреде. Болееточно его можноназвать “языкуправлениядоступом”. Онсостоит из двухосновных команд:
GRANT (датьправа)
REVOKE(забрать права).
Сточки зренияприкладногоинтерфейсасуществуютдве разновидностикоманд SQL:
интерактивныйSQL
встроенныйSQL.
ИнтерактивныйSQL используетсяв специальныхутилитах (типаWISQL или DBD), позволяющихв интерактивномрежиме вводитьзапросы сиспользованиемкоманд SQL, посылатьих для выполненияна сервер иполучать результатыв предназначенномдля этого окне.ВстроенныйSQL используетсяв прикладныхпрограммах,позволяя импосылать запросык серверу иобрабатыватьполученныерезультаты,в том числекомбинируяset-ориентированныйи record-ориентированныйподходы.
Мыне будем приводитьточный синтаксискоманд SQL, вместоэтого мы рассмотримих на многочисленныхпримерах, чтонамного болееважно для пониманияSQL, чем точныйсинтаксис,который можнопосмотретьв документациина Вашу СУБД.
Итак,начнем с рассмотрениякоманд языкаманипулированияданными.
Наиболееважной командойязыка манипулированияданными являетсякоманда SELECT. Закажущейсяпростотой еесинтаксисаскрываетсяогромное богатствовозможностей.Нам важно научитьсяиспользоватьэто богатство!
Наданном урокепредполагается,если не оговоренопротивное, чтовсе командыязыка SQL вводятсяинтерактивнымспособом. Вкачествеинформационнойосновы дляпримеров мыбудем использоватьбазу данных“Служащиепредприятия”(employee.gdb), входящуюв поставкуDelphi и находящуюся(по умолчанию)в поддиректории\IBLOCAL\EXAMPLES.
Рис. 1:Структурабазы данныхEMPLOYEE
Нарис.1 приведенасхема базыданных EMPLOYEE дляLocal InterBase, нарисованнаяс помощьюCASE-средстваS Designor (см. доп. урок).На схеме показанытаблицы базыданных и взаимосвязи,а также обозначеныпервичные ключии их связи свнешними ключами.Многие из примеров,особенно вконце урока,являются весьмасложными. Однако,не следует наэтом основанииделать вывод,что так сложенсам язык SQL. Дело,скорее, в том,что обычные(стандартные)операции настолькопросты в SQL, чтопримеры такихопераций оказываютсядовольнонеинтереснымии не иллюстрируютполной мощностиэтого языка.Но в целяхсистемностимы пройдем повсем возможностямSQL: от самых простых- до чрезвычайносложных.
Начнемс базовых операцийреляционныхбаз данных.Таковыми являются:
выборка(Restriction)
проекция(Projection)
соединение(Join)
объединение(Union).
Операциявыборки позволяетполучить всестроки (записи)либо частьстрок однойтаблицы.
SELECT *FROM countryПолучитьвсе строки
таблицы Country
COUNTRY CURRENCY
=========================
USA Dollar
England Pound
Canada CdnDlr
Switzerland SFranc
Japan Yen
Italy Lira
France FFranc
Germany D-Mark
Australia ADollar
HongKong HKDollar
Netherlands Guilder
Belgium BFranc
Austria Schilling
Fiji FDollar
Вэтом примереи далее - длябольшей наглядности- все зарезервированныеслова языкаSQL будем писатьбольшими буквами.Красным цветомбудем записыватьпредложенияSQL, а светло-синим- результатывыполнениязапросов.
SELECT *FROM country
WHEREcurrency = “Dollar”Получитьподмножествострок таблицыCountry, удовлетворяющееусловию Currency =“Dollar”
Результатпоследнейоперации выглядитследующимобразом:
COUNTRY CURRENCY
=========================
USA Dollar
Операцияпроекциипозволяетвыделить подмножествостолбцов таблицы.Например:
SELECTcurrency FROM countryПолучитьсписок
денежныхединиц
CURRENCY
==========
Dollar
Pound
CdnDlr
SFranc
Yen
Lira
FFranc
D-Mark
ADollar
HKDollar
Guilder
BFranc
Schilling
FDollar
Напрактике оченьчасто требуетсяполучить некоеподмножествостолбцов истрок таблицы,т.е. выполнитькомбинациюRestriction и Projection. Дляэтого достаточноперечислитьстолбцы таблицыи наложитьограниченияна строки.
SELECTcurrency FROM country
WHEREcountry = “Japan”Найтиденежную
единицуЯпонии
CURRENCY
==========
Yen
SELECTfirst_name, last_name
FROMemployee
WHEREfirst_name = "Roger"Получитьфамилии
работников,
которых зовут“Roger”
FIRST_NAME LAST_NAME
===================================
Roger De Souza
Roger Reeves
Этипримеры иллюстрируютобщую формукоманды SELECT вязыке SQL (для однойтаблицы):
SELECT(выбрать)специфицированныеполя
FROM (из)специфицированнойтаблицы
WHERE(где)некотороеспецифицированноеусловие являетсяистинным
Операциясоединенияпозволяетсоединятьстроки из болеечем одной таблицы(по некоторомуусловию) дляобразованияновых строкданных.
SELECTfirst_name, last_name, proj_name
FROMemployee, project
WHEREemp_no = team_leaderПолучитьсписок
руководителейпроектов
FIRST_NAME LAST_NAME PROJ_NAME
=============================== ====================
Ashok Ramanathan Video Database
Pete Fisher DigiPizza
Chris Papadopoulos AutoMap
Bruce Young MapBrowser port
MaryS. MacDonald Marketing project 3
Операцияобъединенияпозволяетобъединятьрезультатыотдельныхзапросов понесколькимтаблицам вединую результирующуютаблицу. Такимобразом, предложениеUNION объединяетвывод двух илиболее SQL-запросовв единый наборстрок и столбцов.
SELECTfirst_name, last_name, job_country
FROMemployee
WHEREjob_country = "France"
UNION
SELECTcontact_first, contact_last, country
FROMcustomer
WHEREcountry = "France"Получитьсписок
работникови заказчиков,
проживающихво Франции
FIRST_NAME LAST_NAME JOB_COUNTRY
================================ ===============
Jacques Glon France
Michelle Roche France
Длясправки, приведемобщую формукоманды SELECT, учитывающуювозможностьсоединениянесколькихтаблиц и объединениярезультатов:
SELECT[DISTINCT]список_выбираемых_элементов(полей)
FROMсписок_таблиц(или представлений)
[WHEREпредикат]
[GROUP BYполе(или поля) [HAVINGпредикат]]
[UNIONдругое_выражение_Select]
[ORDER BYполе(или поля) илиномер (номера)];
Рис. 2: Общийформат командыSELECT
Отметим, чтопод предикатомпонимаетсянекотороеспецифицированноеусловие (отбора),значение которогоимеет булевскийтип. Квадратныескобки означаютнеобязательностьиспользованиядополнительныхконструкцийкоманды. Точкас запятой являетсястандартнымтерминаторомкоманды. Отметим,что в WISQL и в компонентеTQueryставить конечныйтерминаторне обязательно.При этом там,где допустимодин пробелмежду элементами,разрешеноставить любоеколичествопробелов ипустых строк- выполняя желаемоеформатированиедля большейнаглядности.
Гибкостьи мощь языкаSQL состоит в том,что он позволяетобъединитьвсе операцииреляционнойалгебры в однойконструкции,“вытаскивая”таким образомлюбую требуемуюинформацию,что очень частои происходитна практике.
Итак,начнем с рассмотренияпростейшихконструкцийязыка SQL. Послетакого рассмотрениямы научимся:
назначатьполя, которыедолжны бытьвыбраны
назначать квыборке “всеполя”
управлять“вертикальным”и “горизонтальным”порядком выбираемыхполей
подставлятьсобственныезаголовкиполей в результирующейтаблице
производитьвычисленияв списке выбираемыхэлементов
использоватьлитералы всписке выбираемыхэлементов
ограничиватьчисло возвращаемыхстрок
формироватьсложные условияпоиска, используяреляционныеи логическиеоператоры
устранятьодинаковыестроки изрезультата.
Списоквыбираемыхэлементов можетсодержатьследующее:
имена полей
*
вычисления
литералы
функции
агрегирующиеконструкции
SELECTfirst_name, last_name, phone_no
FROMphone_listполучитьсписок
имен,фамилий и служебныхтелефонов
всехработниковпредприятия
FIRST_NAME LAST_NAME PHONE_NO
================================= ====================
Terri Lee (408) 555-1234
OliverH. Bender (408) 555-1234
MaryS. MacDonald (415) 555-1234
Michael Yanowski (415) 555-1234
Robert Nelson (408) 555-1234
Kelly Brown (408) 555-1234
Stewart Hall (408) 555-1234
...
Отметим,что PHONE_LIST - это виртуальнаятаблица (представление),созданная вInterBase и основаннаяна информациииз двух таблиц- EMPLOYEE и DEPARTMENT. Она непоказана нарис.1, однако,как мы уже указывалив общей структурекоманды SELECT, к нейможно обращатьсятак же, как и к“настоящей”таблице.
SELECT *
FROMphone_listполучитьсписок служебныхтелефонов
всехработниковпредприятия
совсей необходимойинформацией
EMP_NOFIRST_NAME LAST_NAME PHONE_EXT LOCATION PHONE_NO
================ ========= ========= ============= ==============
12Terri Lee 256 Monterey (408) 555-1234
105Oliver H. Bender 255 Monterey (408) 555-1234
85Mary S. MacDonald 477 San Francisco (415) 555-1234
127Michael Yanowski 492 San Francisco (415) 555-1234
2Robert Nelson 250 Monterey (408) 555-1234
109Kelly Brown 202 Monterey (408) 555-1234
14Stewart Hall 227 Monterey (408) 555-1234
...
SELECTfirst_name, last_name, phone_no,
location, phone_ext,emp_no
FROMphone_listполучитьсписок служебныхтелефонов
всехработниковпредприятия
совсей необходимойинформацией,
расположивих в требуемомпорядке
FIRST_NAMELAST_NAME PHONE_NO LOCATION PHONE_EXT EMP_NO
=================== ============== ============= ========= ======
Terri Lee (408) 555-1234 Monterey 256 12
OliverH. Bender (408) 555-1234 Monterey 255 105
MaryS. MacDonald (415) 555-1234 San Francisco 477 85
Michael Yanowski (415) 555-1234 San Francisco 492 127
Robert Nelson (408) 555-1234 Monterey 250 2
Kelly Brown (408) 555-1234 Monterey 202 109
Stewart Hall (408) 555-1234 Monterey 227 14
...
Получениеинформациио BLOb выглядитсовершенноаналогичнообычным полям.Полученныезначения можноотображатьс использованиемdata-aware компонентDelphi, например, TDBMemoили TDBGrid.Однако, в последнемслучае придетсясамому прорисовыватьсодержимоеблоба (например,через OnDrawDataCell).Подробнее обэтом см. на уроке, посвященномработе с полями.
SELECTjob_requirement
FROM jobполучитьсписок
должностныхтребований
ккандидатамна работу
JOB_REQUIREMENT:
Nospecific requirements.
JOB_REQUIREMENT:
15+years in finance or 5+ years as a CFO
witha proven track record.
MBAor J.D. degree.
...
SELECTemp_no, salary, salary * 1.15
FROMemployeeполучитьсписок номеров
служащихи их зарплату,
в том числеувеличеннуюна 15%
EMP_NO SALARY
============================ ======================
2 105900.00 121785
4 97500.00 112125
5 102750.00 118162.5
8 64635.00 74330.25
9 75060.00 86319
11 86292.94 99236.87812499999
12 53793.00 61861.95
14 69482.62 79905.01874999999
...
Порядоквычислениявыраженийподчиняетсяобщепринятымправилам: сначалавыполняетсяумножение иделение, а затем- сложение ивычитание.Операции одногоуровня выполняютсяслева направо.Разрешеноприменятьскобки дляизмененияпорядка вычислений.
Например,в выраженииcol1 +col2 * col3 сначаланаходитсяпроизведениезначений столбцовcol2и col3,а затем результатэтого умноженияскладываетсясо значениемстолбца col1.А в выражении(col1+ col2) * col3 сначалавыполняетсясложение значенийстолбцов col1и col2,и только послеэтого результатумножаетсяна значениестолбца col3.
Дляпридания большейнаглядностиполучаемомурезультатуможно использоватьлитералы. Литералы- это строковыеконстанты,которые применяютсянаряду с наименованиямистолбцов и,таким образом,выступают вроли “псевдостолбцов”.Строка символов,представляющаясобой литерал,должна бытьзаключена водинарные илидвойные скобки.
SELECTfirst_name, "получает",salary, "долларовв год"
FROMemployeeполучитьсписок сотрудников
иих зарплату
FIRST_NAME SALARY
=================== ========== ==============
Robert получает 105900.00 долларов вгод
Bruce получает 97500.00 долларов вгод
Kim получает 102750.00 долларов вгод
Leslie получает 64635.00 долларов вгод
Phil получает 75060.00 долларов вгод
K.J. получает 86292.94 долларов вгод
Terri получает 53793.00 долларов вгод
...
Имеетсявозможностьсоединять дваили более столбца,имеющие строковыйтип, друг с другом,а также соединятьих с литералами.Для этогоиспользуетсяоперация конкатенации(||).
SELECT"сотрудник" || first_name || " " || last_name
FROMemployeeполучитьсписок всехсотрудников
==============================================
сотрудникRobert Nelson
сотрудникBruce Young
сотрудникKim Lambert
сотрудникLeslie Johnson
сотрудникPhil Forest
сотрудникK. J. Weston
сотрудникTerri Lee
сотрудникStewart Hall
...
Дляпридания наглядностиполучаемымрезультатамнаряду с литераламив списке выбираемыхэлементов можноиспользоватьквалификаторAS. Данный квалификаторзаменяет врезультирующейтаблице существующееназвание столбцана заданное.Это наиболееэффективныйи простой способсоздания заголовков(к сожалению,InterBase, как уже отмечалось,не поддерживаетиспользованиерусских буквв наименованиистолбцов).
SELECTcount(*) AS number
FROMemployeeподсчитатьколичествослужащих
NUMBER
===========
42
SELECT"сотрудник" || first_name || " " || last_name ASemployee_list
FROMemployeeполучитьсписок всехсотрудников
EMPLOYEE_LIST
==============================================
сотрудникRobert Nelson
сотрудникBruce Young
сотрудникKim Lambert
сотрудникLeslie Johnson
сотрудникPhil Forest
сотрудникK. J. Weston
сотрудникTerri Lee
сотрудникStewart Hall
...
Мыуже рассказывалио типах данных,имеющихся вразличных СУБД,в том числе ив InterBase. В разныхсистемах имеетсяразличное числовстроенныхфункций, упрощающихработу с датами,строками идругими типамиданных. InterBase, ксожалению,обладает достаточноограниченнымнабором такихфункций. Однако,поскольку языкSQL, реализованныйв InterBase, соответствуетстандарту, тов нем имеютсявозможностиконвертациидат в строкии гибкой работыс датами. Внутреннедата в InterBase содержитзначения датыи времени. Внешнедата может бытьпредставленастроками различныхформатов, например:
“October 27,1995”
“27-OCT-1994”
“10-27-95”
“10/27/95”
“27.10.95”
Кромеабсолютныхдат, в SQL-выраженияхможно такжепользоватьсяотносительнымзаданием дат:
“yesterday”вчера
“today”сегодня
“now”сейчас(включая время)
“tomorrow”завтра
Датаможет неявноконвертироватьсяв строку (изстроки), если:
строка,представляющаядату, имеетодин из вышеперечисленныхформатов;
выражениене содержитнеоднозначностейв толкованиитипов столбцов.
SELECTfirst_name, last_name, hire_date
FROMemployee
WHEREhire_date > '1-1-94'получитьсписок сотрудников,
принятыхна работу после
1января 1994 года
FIRST_NAME LAST_NAME HIRE_DATE
=================================== ===========
Pierre Osborne 3-JAN-1994
John Montgomery 30-MAR-1994
Mark Guckenheimer 2-MAY-1994
Значениядат можно сравниватьдруг с другом,сравниватьс относительнымидатами, вычитатьодну дату издругой.
SELECTfirst_name, last_name, hire_date
FROMemployee
WHERE'today' - hire_date > 365 * 7 + 1
получитьсписок служащих,
проработавшихна предприятии
кнастоящемувремени
более7 лет
FIRST_NAME LAST_NAME HIRE_DATE
=================================== ===========
Robert Nelson 28-DEC-1988
Bruce Young 28-DEC-1988
Кагрегирующимфункциям относятсяфункции вычислениясуммы (SUM), максимального(SUM) и минимального(MIN) значенийстолбцов,арифметическогосреднего (AVG), атакже количествастрок, удовлетворяющихзаданномуусловию (COUNT).
SELECTcount(*), sum (budget), avg (budget),
min(budget), max (budget)
FROMdepartment
WHEREhead_dept = 100вычислить:количествоотделов,
являющихсяподразделениями
отдела 100 (Маркетинги продажи),
ихсуммарный,средний, мини-мальныйи максимальныйбюджеты
COUNT SUM AVG MIN MAX
================= ========== ========== ===========
5 3800000.00 760000.00 500000.00 1500000.00
ВпредложенииFROM перечисляютсявсе объекты(один или несколько),из которыхпроизводитсявыборка данных(рис.2). Каждаятаблица илипредставление,о которых упоминаетсяв запросе, должныбыть перечисленыв предложенииFROM.
Числовозвращаемыхв результатезапроса строкможет бытьограниченопутем использованияпредложенияWHERE, содержащегоусловия отбора(предикат, рис.2).Условие отборадля отдельныхстрок можетприниматьзначения true,falseили unnown.При этом запросвозвращаетв качестверезультататолько те строки(записи), длякоторых предикатимеет значениеtrue.
Типыпредикатов,используемыхв предложенииWHERE:
сравнениес использованиемреляционныхоператоров
=равно
неравно
!=неравно
>больше
>=большеили равно
BETWEEN
IN
LIKE
CONTAINING
IS NULL
EXIST
ANY
ALL
Рассмотримоперации сравнения.Реляционныеоператоры могутиспользоватьсяс различнымиэлементами.При этом важнособлюдатьследующееправило: элементыдолжны иметьсравнимые типы.Если в базеданных определеныдомены, тосравниваемыеэлементы должныотноситьсяк одному домену.
Чтоже может бытьэлементомсравнения?Элементомсравнения можетвыступать:
значение поля
литерал
арифметическоевыражение
агрегирующаяфункция
другая встроеннаяфункция
значение (значения),возвращаемыеподзапросом.
При сравнениилитераловконечные пробелыигнорируются.Так, предложениеWHEREfirst_name = ‘Петр ‘будет иметьтот же результат,что и предложениеWHEREfirst_name = ‘Петр’.
SELECTfirst_name, last_name, dept_no
FROMemployee
WHEREjob_code = "Admin"получитьсписок сотрудников
(иномера ихотделов),
занимающихдолжность
администраторов
FIRST_NAME LAST_NAME DEPT_NO
=================================== =======
Terri Lee 000
Ann Bennet 120
SueAnne O'Brien 670
Kelly Brown 600
SELECTfirst_name, last_name, dept_no,
job_country
FROMemployee
WHEREjob_country "USA"получитьсписок сотрудников
(атакже номераих отделов
истрану),
работающихвне США
FIRST_NAME LAST_NAME DEPT_NO JOB_COUNTRY
=============================== ======= ==============
Ann Bennet 120 England
Roger Reeves 120 England
Willie Stansbury 120 England
Claudia Sutherland 140 Canada
Yuki Ichida 115 Japan
Takashi Yamamoto 115 Japan
Roberto Ferrari 125 Italy
Jacques Glon 123 France
Pierre Osborne 121 Switzerland
ПредикатBETWEEN задает диапазонзначений, длякоторого выражениепринимаетзначение true.Разрешено такжеиспользоватьконструкцию NOT BETWEEN.
SELECTfirst_name, last_name, salary
FROMemployee
WHEREsalary BETWEEN 20000 AND 30000
получитьсписок сотрудников,
годоваязарплатакоторых
больше20000 и меньше 30000
FIRST_NAME LAST_NAME SALARY
========================= ===============
Ann Bennet 22935.00
Kelly Brown 27000.00
Тотже запрос сиспользованиемоператоровсравнения будетвыглядетьследующимобразом:
SELECTfirst_name, last_name, salary
FROMemployee
WHEREsalary >= 20000
ANDsalary годоваязарплатакоторых
больше20000 и меньше 30000
FIRST_NAME LAST_NAME SALARY
========================= ===============
Ann Bennet 22935.00
Kelly Brown 27000.00
Запросс предикатомBETWEEN может иметьследующий вид:
SELECTfirst_name, last_name, salary
FROMemployee
WHERElast_name BETWEEN "Nelson" AND "Osborne"
получитьсписок сотрудников,
фамилиикоторых начинаются
с“Nelson”
и заканчиваются“Osborne”
FIRST_NAME LAST_NAME SALARY
============================== ================
Robert Nelson 105900.00
Carol Nordstrom 42742.50
SueAnne O'Brien 31275.00
Pierre Osborne 110000.00
Значения,определяющиенижний и верхнийдиапазоны,могут не являтьсяреальнымивеличинамииз базы данных.И это оченьудобно - ведьмы не всегдаможем указатьточные значениядиапазонов!
SELECTfirst_name, last_name, salary
FROMemployee
WHERElast_name BETWEEN "Nel" AND "Osb"
получитьсписок сотрудников,
фамилиикоторых находятся
между “Nel” и “Osb”
FIRST_NAME LAST_NAME SALARY
============================== ================
Robert Nelson 105900.00
Carol Nordstrom 42742.50
SueAnne O'Brien 31275.00
В данном примерезначений “Nel”и “Osb” в базе данныхнет. Однако,все сотрудники,входящие вдиапазон, внижней частикоторого началофамилий совпадаетс “Nel” (т.е. выполняетсяусловие “большеили равно”), ав верхней частифамилия неболее “Osb” (т.е.выполняетсяусловие “меньшеили равно” - аименно “O”, “Os”,“Osb”), попадутв выборку. Отметим,что при выборкес использованиемпредикатаBETWEEN поле, на котороенакладываетсядиапазон, считаетсяупорядоченнымпо возрастанию.
ПредикатBETWEEN с отрицаниемNOT (NOT BETWEEN) позволяетполучить выборкузаписей, указанныеполя которыхимеют значенияменьше нижнейграницы и большеверхней границы.
SELECTfirst_name, last_name, hire_date
FROMemployee
WHEREhire_date NOT BETWEEN "1-JAN-1989" AND"31-DEC-1993"получитьсписок самых“старых”
исамых “молодых”(по времени
поступленияна работу)
сотрудников
FIRST_NAME LAST_NAME HIRE_DATE
=============================== ===========
Robert Nelson 28-DEC-1988
Bruce Young 28-DEC-1988
Pierre Osborne 3-JAN-1994
John Montgomery 30-MAR-1994
Mark Guckenheimer 2-MAY-1994
ПредикатIN проверяет,входит ли заданноезначение,предшествующееключевому слову“IN” (например,значение столбцаили функцияот него) в указанныйв скобках список.Если заданноепроверяемоезначение равнокакому-либоэлементу всписке, то предикатпринимаетзначение true.Разрешено такжеиспользоватьконструкцию NOT IN.
SELECTfirst_name, last_name, job_code
FROMemployee
WHEREjob_code IN ("VP", "Admin", "Finan")
получитьсписок сотрудников,
занимающихдолжности
“вице-президент”,“администратор”,
“финансовыйдиректор”
FIRST_NAME LAST_NAME JOB_CODE
=============================== ========
Robert Nelson VP
Terri Lee Admin
Stewart Hall Finan
Ann Bennet Admin
SueAnne O'Brien Admin
MaryS. MacDonald VP
Kelly Brown Admin
Авот примерзапроса, использующегопредикат NOT IN:
SELECTfirst_name, last_name, job_country
FROMemployee
WHEREjob_country NOT IN
("USA","Japan", "England")
получитьсписок сотрудников,
работающихне в США, не вЯпонии
и нев Великобритании
FIRST_NAME LAST_NAME JOB_COUNTRY
=============================== ===============
Claudia Sutherland Canada
Roberto Ferrari Italy
Jacques Glon France
Pierre Osborne Switzerland
ПредикатLIKE используетсятолько с символьнымиданными. Онпроверяет,соответствуетли данное символьноезначение строкес указанноймаской. В качествемаски используютсявсе разрешенныесимволы (с учетомверхнего инижнего регистров),а также специальныесимволы:
%- замещаетлюбое количествосимволов (в томчисле и 0),
_ - замещаеттолько одинсимвол.
Разрешено такжеиспользоватьконструкцию NOT LIKE.
SELECTfirst_name, last_name
FROMemployee
WHERElast_name LIKE "F%"получитьсписок сотрудников,
фамилиикоторых начинаютсяс буквы “F”
FIRST_NAME LAST_NAME
===================================
Phil Forest
Pete Fisher
Roberto Ferrari
SELECTfirst_name, last_name
FROMemployee
WHEREfirst_name LIKE "%er"получитьсписок сотрудников,
именакоторых заканчиваютсябуквами “er”
FIRST_NAME LAST_NAME
===================================
Roger De Souza
Roger Reeves
Walter Steadman
Атакой запроспозволяетрешить проблемупроизношения(и написания)имени:
SELECTfirst_name, last_name
FROMemployee
WHEREfirst_name LIKE "Jacq_es"
найтисотрудника(ов),
в имени которого
неизвестнопроизношение
буквы передокончанием“es”
FIRST_NAME LAST_NAME
===================================
Jacques Glon
Чтоделать, еслитребуется найтистроку, котораясодержит указанныевыше специальныесимволы (“%”,“_”) в качествеинформационныхсимволов? Естьвыход! Для этогос помощью ключевогослова ESCAPE нужноопределитьтак называемыйescape символ,который, будучипоставленнымперед символом“%” или “_”, укажет,что этот символявляетсяинформационным.Escape символ неможет бытьсимволом “\”(обратная косаячерта) и, вообщеговоря, долженпредставлятьсобой символ,никогда непоявляющийсяв упоминаемомстолбце какинформационныйсимвол. Частодля этих целейиспользуютсясимволы “@”и “~”.
SELECTfirst_name, last_name
FROMemployee
WHEREfirst_name LIKE "%@_%" ESCAPE "@"
получитьсписок сотрудников,
вимени которыхсодержится“_”
(знак подчеркивания)
ПредикатCONTAINING аналогиченпредикату LIKE,за исключениемтого, что он нечувствителенк региструбукв. Разрешенотакже использоватьконструкцию NOT CONTAINING.
SELECTfirst_name, last_name
FROMemployee
WHERElast_name CONTAINING "ne"
получитьсписок сотрудников,
фамилиикоторых содержатбуквы
“ne”, “Ne”,“NE”, “nE”
FIRST_NAME LAST_NAME
===================================
Robert Nelson
Ann Bennet
Pierre Osborne
ВSQL-запросах NULLозначает, чтозначение столбцанеизвестно.Поисковыеусловия, в которыхзначение столбцасравниваетсяс NULL, всегда принимаютзначение unknown(и, соответственно,приводят кошибке), впротивоположностьtrueили false,т.е.
WHERE dept_no = NULL
или даже
WHERE NULL = NULL.
Предикат IS NULL принимаетзначение trueтолько тогда,когда выражениеслева от ключевыхслов “IS NULL” имеетзначение null(пусто, не определено).Разрешено такжеиспользоватьконструкцию IS NOT NULL, котораяозначает “непусто”, “имееткакое-либозначение”.
SELECTdepartment, mngr_no
FROMdepartment
WHEREmngr_no IS NULLполучитьсписок отделов,
в которых ещене назначены
начальники
DEPARTMENT MNGR_NO
================================
Marketing
SoftwareProducts Div.
SoftwareDevelopment
FieldOffice: Singapore
ПредикатыEXIST, ANY, ALL, SOME, SINGULAR мы рассмотримв разделе,рассказывающемо подзапросах.
Клогическимоператорамотносятсяизвестныеоператоры AND,OR, NOT, позволяющиевыполнятьразличныелогическиедействия: логическоеумножение (AND,“пересечениеусловий”), логическоесложение (OR,“объединениеусловий”), логическоеотрицание (NOT,“отрицаниеусловий”). Внаших примерахмы уже применялиоператор AND.Использованиеэтих операторовпозволяет гибко“настроить”условия отборазаписей.
ОператорAND означает,что общий предикатбудет истиннымтолько тогда,когда условия,связанные по“AND”, будут истинны.
ОператорOR означает,что общий предикатбудет истинным,когда хотя быодно из условий,связанных по“OR”, будет истинным.
ОператорNOT означает,что общий предикатбудет истинным,когда условие,перед которымстоит этотоператор, будетложным.
Водном предикателогическиеоператорывыполняютсяв следующемпорядке: сначалавыполняетсяоператор NOT,затем - AND и толькопосле этого- оператор OR.Для измененияпорядка выполненияоператоровразрешаетсяиспользоватьскобки.
SELECTfirst_name, last_name, dept_no,
job_code,salary
FROMemployee
WHEREdept_no = 622
ORjob_code = "Eng"
ANDsalary
ORDER BYlast_nameполучитьсписок служащих,
занятыхв отделе 622
или
на должности“инженер” сзарплатой
невыше 40000
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
========================= ======= ======== ===========
JenniferM. Burbank 622 Eng 53167.50
Phil Forest 622 Mngr 75060.00
T.J. Green 621 Eng 36000.00
Mark Guckenheimer 622 Eng 32000.00
John Montgomery 672 Eng 35000.00
Bill Parker 623 Eng 35000.00
Willie Stansbury 120 Eng 39224.06
SELECTfirst_name, last_name, dept_no,
job_code,salary
FROMemployee
WHERE(dept_no = 622
ORjob_code = "Eng")
ANDsalary
ORDER BYlast_nameполучитьсписок служащих,
занятыхв отделе 622
илина должности“инженер”,
зарплатакоторых не выше40000
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
========================= ======= ======== ===========
T.J. Green 621 Eng 36000.00
Mark Guckenheimer 622 Eng 32000.00
John Montgomery 672 Eng 35000.00
Bill Parker 623 Eng 35000.00
Willie Stansbury 120 Eng 39224.06
ВSQL имеется возможностьпреобразоватьзначение столбцаили функциик другому типудля более гибкогоиспользованияопераций сравнения.Для этогоиспользуетсяфункция CAST.
Типыданных могутбыть конвертированыв соответствиисо следующейтаблицей:
Из типаданныхВ типданных
---------------------------------------
NUMERICCHAR,VARCHAR, DATE
CHAR,VARCHARNUMERIC, DATE
DATECHAR, VARCHAR,DATE
SELECTfirst_name, last_name, dept_no
FROMemployee
WHERECAST(dept_no AS char(20))
CONTAINING"00"получитьсписок сотрудников,
занятыхв отделах,
номеракоторых содержат“00”
FIRST_NAME LAST_NAME DEPT_NO
=================================== =======
Robert Nelson 600
Terri Lee 000
Stewart Hall 900
Walter Steadman 900
MaryS. MacDonald 100
OliverH. Bender 000
Kelly Brown 600
Michael Yanowski 100
Порядоквыводимых строкможет бытьизменен с помощьюопционального(дополнительного)предложенияORDER BY в концеSQL-запроса. Этопредложениеимеет вид:
ORDER BY [ASC | DESC]
Порядокстрок можетзадаватьсяодним из двухспособов:
именамистолбцов
номерамистолбцов.
Способупорядочиванияопределяетсядополнительнымизарезервированнымисловами ASC и DESC.Способом поумолчанию -если ничегоне указано -являетсяупорядочивание“по возрастанию”(ASC). Если же указанослово “DESC”, тоупорядочиваниебудет производиться“по убыванию”.
Подчеркнемеще раз, чтопредложениеORDER BY должно указыватьсяв самом концезапроса.
SELECTfirst_name, last_name, dept_no,
job_code,salary
FROMemployee
ORDER BYlast_nameполучитьсписоксотрудников,
упорядоченныйпо фамилиям
в алфавитномпорядке
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
========================= ======= ======== ===========
Janet Baldwin 110 Sales 61637.81
OliverH. Bender 000 CEO 212850.00
Ann Bennet 120 Admin 22935.00
Dana Bishop 621 Eng 62550.00
Kelly Brown 600 Admin 27000.00
JenniferM. Burbank 622 Eng 53167.50
Kevin Cook 670 Dir 111262.50
Roger De Souza 623 Eng 69482.62
Roberto Ferrari 125 SRep 99000000.00
...
SELECTfirst_name, last_name, dept_no,
job_code,salary
FROMemployee
ORDER BYlast_name DESCполучитьсписоксотрудников,
упорядоченныйпо фамилиям
в порядке,обратном алфавитному
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
========================= ======= ======== ===========
Katherine Young 623 Mngr 67241.25
Bruce Young 621 Eng 97500.00
Michael Yanowski 100 SRep 44000.00
Takashi Yamamoto 115 SRep 7480000.00
Randy Williams 672 Mngr 56295.00
K.J. Weston 130 SRep 86292.94
Claudia Sutherland 140 SRep 100914.00
Walter Steadman 900 CFO 116100.00
Willie Stansbury 120 Eng 39224.06
Roger Reeves 120 Sales 33620.62
...
Столбец,определяющийпорядок выводастрок, не обязательнодожен присутствоватьв списке выбираемыхэлементов(столбцов):
SELECTfirst_name, last_name, dept_no,
job_code
FROMemployee
ORDER BYsalaryполучитьсписоксотрудников,
упорядоченныйпо их зарплате
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE
============================== ======= ========
Ann Bennet 120 Admin
Kelly Brown 600 Admin
SueAnne O'Brien 670 Admin
Mark Guckenheimer 622 Eng
Roger Reeves 120 Sales
Bill Parker 623 Eng
SELECTfirst_name, last_name, dept_no,
job_code,salary * 1.1
FROMemployee
ORDER BY5получитьсписоксотрудников,
упорядоченныйпо их зарплате
с10% надбавкой
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE
========================= ======= ======== ===========
Ann Bennet 120 Admin 25228.5
Kelly Brown 600 Admin 29700
SueAnne O'Brien 670 Admin 34402.5
Mark Guckenheimer 622 Eng 35200
Roger Reeves 120 Sales 36982.6875
Bill Parker 623 Eng 38500
Допускаетсяиспользованиенесколькихуровней вложенностипри упорядочиваниивыводимойинформациипо столбцам;при этом разрешаетсясмешивать обаспособа.
SELECTfirst_name, last_name, dept_no,
job_code,salary * 1.1
FROMemployee
ORDER BYdept_no, 5 DESC, last_name
получитьсписоксотрудников,
упорядоченныйсначала по
номерам отделов,
в отделах - поубыванию их
зарплаты (с10%),
а в пределаходной зарплаты- по фамилиям
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE
===================== ======= ======== ===============
OliverH. Bender 000 CEO 234135
Terri Lee 000 Admin 59172.3
MaryS. MacDonald 100 VP 122388.75
Michael Yanowski 100 SRep 48400.000000001
Luke Leung 110 SRep 75685.5
Janet Baldwin 110 Sales 67801.59375
Takashi Yamamoto 115 SRep 8228000.0000001
Yuki Ichida 115 Eng 6600000.0000001
Дублированнымиявляются такиестроки в результирующейтаблице, в которыхидентиченкаждый столбец.
Иногда(в зависимостиот задачи) бываетнеобходимоустранить всеповторы строкиз результирующегонабора. Этойцели служитмодификаторDISTINCT. Данныймодификаторможет бытьуказан толькоодин раз в спискевыбираемыхэлементов идействует навесь список.
SELECTjob_code
FROMemployeeполучитьсписок должностейсотрудников
JOB_CODE
========
VP
Eng
Eng
Mktg
Mngr
SRep
Admin
Finan
Mngr
Mngr
Eng
...
Данныйпример некорректнорешает задачу“получения”списка должностейсотрудниковпредприятия,так как в немимеются многочисленныеповторы, затрудняющиевосприятиеинформации.Тот же запрос,включающиймодификаторDISTINCT, устраняющийдублирование,дает верныйрезультат.
SELECTDISTINCT job_code
FROMemployeeполучить списокдолжностейсотрудников
JOB_CODE
========
Admin
CEO
CFO
Dir
Doc
Eng
Finan
Mktg
Mngr
PRel
SRep
Sales
VP
Дваследующихпримера показывают,что модификаторDISTINCT действуетна всю строкусразу.
SELECTfirst_name, last_name
FROMemployee
WHEREfirst_name = "Roger"получитьсписок служащих,
именакоторых - Roger
FIRST_NAME LAST_NAME
===================================
Roger De Souza
Roger Reeves
SELECTDISTINCT first_name, last_name
FROMemployee
WHEREfirst_name = "Roger"получитьсписок служащих,
именакоторых - Roger
FIRST_NAME LAST_NAME
===================================
Roger De Souza
Roger Reeves
Операциясоединенияиспользуетсяв языке SQL длявывода связаннойинформации,хранящейсяв несколькихтаблицах, водном запросе.В этом проявляетсяодна из наиболееважных особенностейзапросов SQL -способностьопределятьсвязи междумногочисленнымитаблицами ивыводить информациюиз них в рамкахэтих связей.Именно этаоперация придаетгибкость илегкость языкуSQL.
Послеизучения этогораздела мыбудем способны:
соединятьданные из несколькихтаблиц в единуюрезультирующуютаблицу;
задавать именастолбцов двумяспособами;
записыватьвнешние соединения;
создаватьсоединениятаблицы с собой.
Операциисоединенияподразделяютсяна два вида -внутренниеи внешние. Обавида соединенийзадаются впредложенииWHERE запроса SELECT спомощью специальногоусловия соединения.Внешние соединения(о которых мыпоговоримпозднее) поддерживаютсястандартомANSI-92 и содержатзарезервированноеслово “JOIN”, в товремя как внутренниесоединения(или простосоединения)могут задаватьсякак без использованиятакого слова(в стандартеANSI-89), так и с использованиемслова “JOIN” (встандартеANSI-92).
Связываниепроизводится,как правило,по первичномуключу однойтаблицы и внешнемуключу другойтаблицы - длякаждой парытаблиц. Приэтом оченьважно учитыватьвсе поля внешнегоключа, иначерезультат будетискажен. Соединяемыеполя могут (ноне обязаны!)присутствоватьв списке выбираемыхэлементов.ПредложениеWHERE может содержатьмножественныеусловия соединений.Условие соединенияможет такжекомбинироватьсяс другими предикатамив предложенииWHERE.
Внутреннеесоединениевозвращаеттолько те строки,для которыхусловие соединенияпринимаетзначение true.
SELECTfirst_name, last_name, department
FROMemployee, department
WHEREjob_code = "VP"получитьсписок сотрудников,
состоящихв должности“вице-
президент”,а также названия
ихотделов
FIRST_NAME LAST_NAME DEPARTMENT
=============================== ======================
Robert Nelson Corporate Headquarters
MaryS. MacDonald Corporate Headquarters
Robert Nelson Sales and Marketing
MaryS. MacDonald Sales and Marketing
Robert Nelson Engineering
MaryS. MacDonald Engineering
Robert Nelson Finance
MaryS. MacDonald Finance
...
Этотзапрос (“безсоединения”)возвращаетневерный результат,так как имеющиесямежду таблицамисвязи не задействованы.Отсюда и появляетсядублированиеинформациив результирующейтаблице. Правильныйрезультат даетзапрос с использованиемоперации соединения:
SELECTfirst_name, last_name, department
FROMemployee, department
WHEREjob_code = "VP"
ANDemployee.dept_no = department.dept_no
именатаблиц
получитьсписок сотрудников,
состоящихв должности“вице-
президент”,а также названия
ихотделов
FIRST_NAME LAST_NAME DEPARTMENT
=============================== ======================
Robert Nelson Engineering
MaryS. MacDonald Sales and Marketing
Ввышеприведенномзапросе использовалсяспособ непосредственногоуказания таблицс помощью ихимен. Возможен(а иногда и простонеобходим)также способуказания таблицс помощью алиасов(псевдонимов).При этом алиасыопределяютсяв предложенииFROM запроса SELECT ипредставляютсобой любойдопустимыйидентификатор,написаниекоторого подчиняетсятаким же правилам,что и написаниеимен таблиц.Потребностьв алиасах таблицвозникаеттогда, когданазвания столбцов,используемыхв условияхсоединениядвух (или более)таблиц, совпадают,а названиятаблиц слишкомдлинны...
Замечание1: в одном запросенельзя смешиватьиспользованиенаписания иментаблиц и ихалиасов.
Замечание2: алиасы таблицмогут совпадатьс их именами.
SELECTfirst_name, last_name, department
F
ROMemployee e, department dWHEREjob_code = "VP"
AND e.dept_no = d.dept_no
алиасытаблиц
получитьсписок сотрудников,
состоящихв должности“вице-
президент”,а также названия
ихотделов
FIRST_NAME LAST_NAME DEPARTMENT
=============================== ======================
Robert Nelson Engineering
MaryS. MacDonald Sales and Marketing
Авот примерзапроса, соединяющегосразу три таблицы:
SELECTfirst_name, last_name, job_title,
department
FROMemployee e, department d, job j
WHEREd.mngr_no = e.emp_no
ANDe.job_code = j.job_code
ANDe.job_grade = j.job_grade
ANDe.job_country = j.job_country
получитьсписок сотрудников
сназваниямиих должностей
и названиямиотделов
FIRST_NAMELAST_NAME JOB_TITLE DEPARTMENT
====================== ======================= ======================
Robert Nelson Vice President Engineering
Phil Forest Manager Quality Assurance
K.J. Weston Sales Representative Field Office: East Coast
Katherine Young Manager Customer Support
Chris Papadopoulos Manager Research and Development
Janet Baldwin Sales Co-ordinator Pacific Rim Headquarters
Roger Reeves Sales Co-ordinator European Headquarters
Walter Steadman Chief Financial Officer Finance
Вданном примерепоследние триусловия необходимыв силу того,что первичныйключ в таблицеJOB состоит изтрех полей -см. рис.1.
Мырассмотреливнутренниесоединенияс использованиемстандартаANSI-89. Теперь опишемновый (ANSI-92) стандарт:
условия соединениязаписываютсяв предложенииFROM, в которомслева и справаот зарезервированногослова “JOIN” указываютсясоединяемыетаблицы;
условия поиска,основанныена правойтаблице, помещаютсяв предложениеON;
условия поиска,основанныена левой таблице,помещаютсяв предложениеWHERE.
SELECTfirst_name, last_name, department
FROMemployee e JOIN department d
ONe.dept_no = d.dept_no
ANDdepartment = "Customer Support"
WHERElast_name starting with "P"
получитьсписок служащих
(азаодно и названиеотдела),
являющихсясотрудникамиотдела
“CustomerSupport”, фамилиикото-
рых начинаютсяс буквы “P”
FIRST_NAME LAST_NAME DEPARTMENT
============================ ===================
Leslie Phong Customer Support
Bill Parker Customer Support
Внекоторыхзадачах необходимополучить информацию,выбраннуюособым образомтолько из однойтаблицы. Дляэтого используютсятак называемыесамосоединения,или рефлексивныесоединения.Это не отдельныйвид соединения,а просто соединениетаблицы с собойс помощью алиасов.Самосоединенияполезны в случаях,когда нужнополучить парыаналогичныхэлементов изодной и той жетаблицы.
SELECTone.last_name, two.last_name,
one.hire_date
FROMemployee one, employee two
WHEREone.hire_date = two.hire_date
ANDone.emp_no получитьпары фамилийсотрудников,
которыеприняты наработу в один
и тот же день
LAST_NAME LAST_NAME HIRE_DATE
======================================== ===========
Nelson Young 28-DEC-1988
Reeves Stansbury 25-APR-1991
Bishop MacDonald 1-JUN-1992
Brown Ichida 4-FEB-1993
SELECTd1.department, d2.department, d1.budget
FROMdepartment d1, department d2
WHEREd1.budget = d2.budget
ANDd1.dept_no получитьсписок паротделов с
одинаковымигодовыми бюджетами
DEPARTMENT DEPARTMENT BUDGET
======================== ========================= =========
SoftwareDevelopment Finance 400000.00
FieldOffice: East Coast Field Office: Canada 500000.00
FieldOffice: Japan Field Office: East Coast 500000.00
FieldOffice: Japan Field Office: Canada 500000.00
FieldOffice: Japan Field Office: Switzerland 500000.00
FieldOffice: Singapore Quality Assurance 300000.00
FieldOffice: Switzerland Field Office: East Coast 500000.00
Напомним,что внутреннеесоединениевозвращаеттолько те строки,для которыхусловие соединенияпринимаетзначение true.Иногда требуетсявключить врезультирующийнабор большееколичествострок.
Вспомним,запрос вида
SELECTfirst_name, last_name, department
FROMemployee e, department d
WHEREe.dept_no = d.dept_no
возвращаеттолько те строки,для которыхусловие соединения (e.dept_no= d.dept_no) принимаетзначение true.
Внешнее соединениевозвращаетвсе строкииз одной таблицыи только тестроки из другойтаблицы, длякоторых условиесоединенияпринимаетзначение true.Строки второйтаблицы, неудовлетворяющиеусловию соединения(т.е. имеющиезначение false),получают значениеnullв результирующемнаборе.
Существуетдва вида внешнегосоединения: LEFT JOIN и RIGHT JOIN.
В левом соединении(LEFT JOIN) запросвозвращаетвсе строкииз левой таблицы(т.е. таблицы,стоящей слеваот зарезервированногословосочетания“LEFT JOIN”) и толькоте из правойтаблицы, которыеудовлетворяютусловию соединения.Если же в правойтаблице ненайдется строк,удовлетворяющихзаданномуусловию, то врезультатеони замещаютсязначениямиnull.
Для правогосоединения- все наоборот.
SELECTfirst_name, last_name, department
FROMemployee e LEFT JOIN department d
ONe.dept_no = d.dept_no
получитьсписок сотрудников
иназвание ихотделов,
включаясотрудников,еще
не назначенныхни в какой отдел
FIRST_NAME LAST_NAME DEPARTMENT
============================= =====================
Robert Nelson Engineering
Bruce Young Software Development
Kim Lambert Field Office: East Coast
Leslie Johnson Marketing
Phil Forest Quality Assurance
...
Вданном запросевсе сотрудникиоказалисьраспределеныпо отделам,иначе названияотделов заместилисьбы значениемnull.
Авот примерправого соединения:
SELECTfirst_name, last_name, department
FROMemployee e RIGHT JOIN department d
ONe.dept_no = d.dept_no
получитьсписок сотрудников
иназвание ихотделов,
включаяотделы, в которыееще
не назначенысотрудники
FIRST_NAME LAST_NAME DEPARTMENT
============================ =========================
Terri Lee Corporate Headquarters
OliverH. Bender Corporate Headquarters
MaryS. MacDonald Sales and Marketing
Michael Yanowski Sales and Marketing
Robert Nelson Engineering
Kelly Brown Engineering
Stewart Hall Finance
Walter Steadman Finance
Leslie Johnson Marketing
Carol Nordstrom Marketing
Bruce Young Software Development
...
Врезультирующийнабор входити отдел “Software ProductsDiv.” (а также отдел“Field Office: Singapore”, не представленныйздесь), в которомеще нет ни одногосотрудника.
Урок 10:Основы языкаSQL
Урок 11:Генерацияотчетов1
Содержание1
1. Компонентыдля построенияотчетов2
2. КомпонентTQuickRep3
Свойства4
Методы7
События9
3. КомпонентTQRBand9
4. Созданиепростейшегоотчета11
5. ИспользованиекомпонентаTQREXPR14
6. ИспользованиеTQRBand для представлениязаголовковстолбцов18
7. ИспользованиеTQRBand для показазаголовка иподвала страницы.18
8. ИспользованиекомпонентаTQRSysData19
9. Группировкиданных20
10. Множественнаягруппировкаданных23
11. Построениеотчета главный-детальный24
12. Построениекомпозитногоотчета28
На страницепалитры компонентовQReport расположеноболее двухдесятков компонентов,применяемыхдля построенияотчетов.
Центральнымкомпонентомявляется TQuickRep,определяющийповедениеотчета в целом.С помощью другихкомпонентовсоздаютсясоставные частиотчета.
TQRBand –заготовкадля расположенияданных, заголовков,титула отчетаи др. Отчет, восновном, строитсяиз компонентовTQRBand, которыереализуют:
областьзаголовкаотчета;
областьзаголовкастраницы;
областьзаголовкагруппы;
областьназваний столбцовотчета;
областьдетальныхданных, предназначеннуюдля отображенияданных самогонижнего уровнядетализации;
областьподвала группы;
областьподвала страницы;
областьподвала отчета.
TQRStringsBand –имеет то женазначение,что и TQRBand.Отличаетсявстроеннымсписком строкItems, содержимоекоторого становитсявидным в режимепечати и предварительногопросмотра, еслина компонентTQRStringsBand положенкомпонентTQRExpr. Длякаждой строкив Items выводитсясвоя полосаTQRStringsBand.
TQRSubDetail –дочерняяполоса. Привязываетсяк родительскойполосе и служитдля ее расширения.Любая полосаможет статьродительскойс помощью установкизначения Trueв ее свойствоHasChild.
TQRGroup –применяетсядля группировокданных в отчетах.
TQRLabel –позволяетразместитьв отчете произвольнуютекстовуюстроку.
TQRDBText –служит длявывода в отчетсодержимоготекстовогополя набораданных.
TQRExpr –применяетсядля выводазначений, являющихсярезультатомвычисленийвыражений.Алгоритм вычислениявыраженийстроится припомощи редактораформул данногокомпонента.
TQRSysData –служит длявывода в отчетесистемнойвеличины: даты,времени, номерастраницы и т.п.
TQRMemo –вставляетв отчет многостраничныйтекст.
TQRExprMemo – используетсядля созданиямногострочныхвычисляемыхполей.
TQRRichText –вставляетв отчет многострочныйтекст в форматеRTF.
TQRDBRichText –служит длявывода в отчетеполей НД, содержащихмногострочныйтекст в форматеRTF.
TQRShape –служит длявывода в отчетеграфическихфигур, например,прямоугольников.
TQRImage –служит длявывода в отчетеграфическойинформации,источникомкоторой являетсяполе набораданных.
TQRPreview –базовый компонентдля созданиянестандартныхокон предварительногопросмотра.Стандартноеокно реализуетсяс помощью методаPreview компонентаTQuickRep.
TQRXXXFilter –фильтрующиекомпонентыдля преобразованияотчета в текст,страницу HTMLи т.п. при печатиотчета.
TQRChart –служит длявстраиванияв отчет графиков.
При размещенииэтого компонентана форме в нейпоявляетсясетка отчета(рис.1). В дальнейшемв этой сеткерасполагаютсясоставные частиотчета, например,полосы TQRBand(рис.2).
Рис.1. Пустая сеткаотчета. Образуетсяпосле размещенияна форме компонентаTQuickRep.
Рис.2. Сетка отчетас размещеннымив ней компонентамиотчета.
Перечислимважнейшиесвойства, методыи события компонентаTQuickRep.
Свойство | Назначение |
propertyBands: TQuickRepBands; | Объект Bandsсодержитлогическиесвойства, которыепосле установкив них значенийTrue включаютв отчет: HasColumnHeader– заголовкистолбцов;HasDetail –детальнуюинформацию;HasPageFooter –подвал страницы;HasPageHeader –заголовокстраницы;HasSummary –подвалотчета; HasTitle– заголовокотчета. |
propertyDataset:TDataSet; | Указываетнабор данныхна основе которогосоздаетсяотчет. Еслинужно вывестисвязаннуюинформациюиз несколькихтаблиц БД, ееобъединяютв одном НД припомощи компонентаTQuery.Информациюиз несколькихсвязанных НДможно включатьв отчет, еслиэти НД связаныв приложенииотношениемглавный-подчиненный.В этом случаев качествеНД отчетауказываетсяглавный набор,а ссылка насоответствующиеподчиненныенаборы осуществляетсяв компонентахTQRSubDetail.Если в отчетнужно включитьинформациюиз несвязанныхНД, применяетсякомпозитныйотчет, то естьотчет, составленныйиз группы другихотчетов. |
propertyFrame:TQRFrame; | Определяетпараметрырамки отчета:Color –цвет линий;DrawBottom –наличие линииснизу;DrawLeft –наличиелинии слева;DrawRight –наличиелинии справа;DrawTop –наличиелинии сверху;Style –стильлинии (сплошная,пунктирнаяи т.п.); Width –толщиналинии в пикселях. |
propertyOptions:TQuickReportOptions; | Содержитмножество изследующихлогическихзначений:HasFirstHeader –разрешаетпечатать заголовокпервой страницы;HasLastFooter –разрешаетпечатать подвалпоследнейстраницы;Compression –разрешаетсжимать отчетпри выводеего в метафайл. |
propertyPage:TQRPage; | Определяетпараметрыстраницы отчета.Все подсвойстваэтого сложногосвойства доступныв окне ReportSetting (см. нижегруппы Pagesize и Margin окнаредакторасвойств). |
propertyPrintIfEmpty:boolean; | Разрешает/запрещаетпечатать отчетв том случаеесли он несодержит данных. |
propertyReportTitle:String; | Имя отчета(не его заголовок!). Используетсядля идентификацииотчета в заданиина сетевуюпечать, возвращаетсякомпонентомQRSysData приData =ReportTitle иможет использоватьсядля набораодного изнесколькихдоступныхотчетов. |
propertyShowProgress:boolean; | Разрешает/запрещаетпоказыватьиндикаторпроцесса печатиотчета. |
propertySnapToGrid:boolean; | Еслисодержит True,размещаемыев отчете компонентыпривязываютсяк сетке отчета. |
typeTQRUnits = (Inches,MM,Pixels, Native, Characters); propertyUnits:TQRUnits; | Определяетединицы измерениярасстоянийв отчете: Inches– дюймы;MM –миллиметры;Pixels –пиксели;Native –внутренниеединицы TQuickRep(равны0,1 мм); Characters –символы текста. |
propertyZoom: Integer; | Определяетмасштаб отображенияотчета (в процентахот его размеровна листе бумаги)на этапе разработки.Может иметьзначение вдиапазоне1..300. Значениесвойства неучитываетсяпри печатиотчета или врежиме егопредварительногопоказа. |
Многие свойстваотчета можноустановитьна этапе конструированияс помощью редакторасвойств – вызовителокальное менюкомпонентаTQuickRep и выберитеопцию ReportSettings.
Рис.3. Окно установкипараметровотчета.
Группа Paper size задаетхарактеристикистраницы: ееформат (A4 210 x 270 mm),ширину (Width), длину(Length) и направлениепечати – вдолькороткой сторонылиста (Portait) иливдоль длинной(Landscape).
Группа элементовMargin указываетполя отчета:сверху (Top),снизу (Bottom),слева (Left),справа(Right),а также количествоколонок (Numberof columns) и расстояниемежду ними(Column Space).
С помощью элементовгруппы Otherможно задатьшрифт (Font),его высоту(Size)и используемыеединицы измерениядлины (Units).
Группа Pageframe определяетсвойства рамки:наличие линиисверху (Top),снизу (Bottom),слева (Left),справа(Right),цвет линий(Color)и их толщину(Width).
Группа Bandsопределяетналичие полосзаголовкови подвалов(Page header – заголовокстраницы;Title – заголовокотчета; Columnheader – заголовокколонок; Detailband – полосадля детальнойинформации;Page footer – подвалстраницы; Summary– подвал отчета),а также высотусоответствующейполосы (строкаLength справаот переключателявыбора). Послевыбора типаи высоты полосыона появляетсяв отчете, еслиокно закрытокнопкой OKили была нажатакнопка Applay.ЭлементыPrint first page header иPrint last page footer управляютсоответственнопечатью заголовкана первой страницеи подвала наего последнейстранице.
Метод | Назначение |
procedureNewColumn; | Реализуетвывод информациив следующейколонке отчета,а если определенаединственнаяколонка, – вего следующейстранице. |
procedureNewPage; | Реализуетвывод информациив следующейстранице отчета. |
procedurePrepare; | Готовитотчет для выводав файл (см. нижепримечание1). |
procedurePreview; | Выводитстандартноеокно предварительногопросмотра(см. ниже примечание2). |
procedurePrint; | Печатаетотчет на принтере. |
procedurePrintBackGround; | Инициируетпечать отчетав фоновом режиме(в отдельномпотоке команд).После завершенияпечати вызываетсяобработчиксобытия OnAfterPrint. |
procedurePrinterSetup; | Вызываетстандартноеокно установкипараметровпринтера. |
Примечание1.
Для выводаотчета в файлнужно сначалаподготовитьего с помощьюобращения кметоду Prepare, затемсохранить вфайле методомSave объектаTQuickRep.QRPrinter, после чегоуничтожитьэтот объекти поместитьNIL в свойствеTQuickRep.QRPrinter:
MyReport.Prepare;
MyReport.QRPrinter.Save(‘REport.QRP’);
MyReport.QRPrinter.Free;
MyReport.QRPrinter:= NIL;
Примечание2.
Стандартноеокно предварительногопросмотрапоказано нарис. 4.
Рис.4. Окно предварительногопросмотраотчета.
Чтобы на этапеконструированияпросмотретьв окне предварительногопросмотрасодержимоеотчета в томвиде, как онбудет выводитьсяна печать, нужновыбрать опциюPreview во вспомогательномменю компонентаQuickRep. Следуетзаметить, чтопри этом небудут виднынекоторыеданные, напримерзначения вычисляемыхполей. Они будутвыводитьсятолько во времявыполнения.
Н
азначениеинструментальныхкнопок окна:М
асштабируетотчет так, чтобыего страницаполностьюпоказываласьв окне.О
тображаетотчет в масштабе1:1.М
асштабируетотчет так, чтобыширина страницыотчета соответствовалаширине окна.П
оказываетпервую (последнюю)страницу отчета.П
оказываетпредыдущую(следующую)страницу отчета.Вызываетстандартноеокно настройкипринтера (печатаетотчет).
С
охраняетотчет в файле(загружаетотчет из файла).Событие | Назначение |
propertyAfterPreview : TQRAfterPreviewPrint; | Возникаетв момент закрытияокна предварительногопросмотраотчета. |
propertyAfterPrint:TQRAfterPrintEvent; | Наступаетпосле печатиотчета илиего подготовкик печати. |
propertyBeforePrint:TQRBeforePrintEvent; | Наступаетв момент началагенерацииотчета (до выдачиокна предварительногопросмотраотчета илидо его печати). |
propertyOnEndPage:procedure (Sender:TObject); | Возникаетв момент подготовкик генерациипоследнейстраницы отчета. |
propertyOnNeedData:procedure(Sender:TObject; varMoreData:boolean); | Используетсяпри созданииотчета по данным,которые берутсяне из НД, а изтекстовогофайла, спискастрок, массива и т.п. В параметреMoreDataобработчикдолжен вернутьTrue,если источникданных ещене исчерпан. |
propertyOnPreview:procedure(Sender:TObject); | Используетсядля связыванияс отчетомнестандартногоокна просмотра(см. ниже). |
propertyOnStartPage:procedure (Sender:TObject); | Возникаетв момент подготовкик генерациипервой страницыотчета. |
С помощью компонентаQRPreviewпрограммистможет создатьнестандартноеокно предварительногопросмотра. Длясвязи с отчетомиспользуетсясобытие OnPreviewпо следующейсхеме:
ProcedureRepForm.MyREportOnPreviewEvent(Sender: TObject);
begin
MyPrevForm.QRPreview1.QRPrinter:= TQRPrinter(Sender);
MyPreviewForm.Show;
end;
Чтобы явноеприведениетипа TQRPrinter(Sender) сталовозможным,необходимассылка на модульQRPrntr в предложенииUses соответствующегомодуля (в примере– модуля RepForm).
КомпонентыTQRBand являютсяосновнымичастями отчетаи используютсядля размещенияна них отображающихкомпонентов,таких как TQRLabel,TQRDBText,TQRImage ит.п.
Свойства компонента:
Свойство | Назначение |
propertyAlignToBottom: boolean; | Еслиимеет значениеTrueполосапечатаетсянепосредственнонад подваломстраницы вместообычногорасположениясправа/снизуот предыдущейполосы. |
typeTQRBandType = (rbTitle,rbPageHeader,rbDetail,rbPageFooter, rbSummary, rbGroupHeader, rbGroupFooter,rbSubDetail, rbColumnHeader); propertyBandType:TQRBandType; | Указываетназначениеполосы:rbTitle– содержитзаголовокотчета; rbPageHeader– содержитзаголовокстраницы (напервой страницепечатаетсяпод rbTitle);rbDetaul –содержитинформациюиз НД; выводитсявсякий разпри переходена новую записьНД; эта полосаповторяетсядля всех записейDataSet,начиная с первойзаписи и заканчиваяпоследней;позицированиена первую записьи последовательныйих переборосуществляетсякомпонентомTQuickRepавтоматически;rbPageFooter– содержитподвал страницы;выводится вконце каждойстраницы отчетапосле всехдругих полос;rbSummary –подвал отчета;выводится напоследнейстранице отчетапосле всейиной информации,но перед подваломпоследнейстраницы;rbGroupHeader –содержит заголовокгруппы; применяетсяпри группировкахинформациив отчете ивыводитсявсякий разпри выводеновой группы;rbGroupFooter– содержитподвал группы;выводитсявсякий разпри окончаниивывода группы,после всехданных группы;rbSubDetail– содержитдетальнуюинформациюиз подчиненногоНД при выводев отчете информациииз двух илиболее наборовданных, связанныхв приложениикак главный-подчиненный;этот тип назначаетсяполосе автоматическипри размещениина форме компонентаTQRSubDetail;rbColumnHeader– содержитзаголовкистолбцов;размещаетсяна каждой страницеотчета послезаголовкастраницы. |
propertyEnabled:boolean; | Разрешает/запрещаетпечать полосы. |
propertyForceNewColumn:boolean; | Если содержитTrue, полоса печатаетсяв следующейколонке. |
propertyForceNewPage:boolean; | Еслисодержит True,полосапечатаетсяна новой странице. |
propertyHasChild:boolean; | Еслисодержит True,полоса имеетдочернюю полосуTChildBand.УстановкаTrueв этосвойствоавтоматическисоздает в отчетедочернюю полосу. |
События
propertyAfterPrint: TQRAfterPrintEvent;
и
propertyBeforePrint: TQRBeforePrintEvent;
наступаютсоответственнодо и после печатиполосы. Метод
functionAddPrintable(PrintableClass: TQRNewComponentClass): TQRPrintable;
используетсядля вставкив полосу отображающегокомпонентав процессепрогона программы.Он автоматическиустанавливаетмежду полосамиотношениесобственности.Два следующихфрагментавыполняютодинаковуюработу:
withDetailBand1.AddPrintable(TQRLabel) do
begin
Size.Left := 20;
Size.Top := 5;
Caption := ‘Новаяполоса’;
end;
var
aLabel: TQRLabel;
begin
aLabel:= TQRLabel.Create(ReportForm);
aLabel.Parent :=DetailBand1;
withaLabel do
begin
Size.Left := 20;
Size.Top := 5;
Caption := ‘Новаяполоса’;
end;
end;
КомпонентыTQuickRep иTQRBand являютсяминимальнодостаточнымидля созданияпростейшегоотчета, несодержащеговнутри себягруппировокинформации.
Пусть имеетсятаблица БДRashod.DB, содержащаясведения оботпуске материаловсо склада. Всостав ТБДвходят поля
N_RASH– уникальныйномер событияотпуска товара;
DEN– номер дня;
MES– номер месяца;
GOD– номер года;
TOVAR– наименованиеотпущенноготовара;
POKUP– наименованиепокупателя;
KOLVO– количествоединиц отпущенноготовара.
Заметим,что дата отпускатовара хранитсяв разбивке надень, год и месяц.Сделано такспециально,с целью показать,как в отчетахиспользуютсявыражения ивычисляемыеполя.
Создадим простейшийотчет, состоящийиз заголовкаи сведений оботпуске товара.В отчет включаютсявсе факты отпускатовара. Сортировкапроизводитсяпо номеру событияотпуска товара.Для этого разместимна форме компонентTTable, свяжемего с таблицейRashod.DB и откроем(Active = True).Разместимна форме компонентTQuickRep.Поместим вего свойствоDataSet значениеTable1, назначивтаким образомотчету НД, записикоторого будутвыводитьсяв отчете. Добавимв отчет компонентTQRBand. В егосвойство BandTypeкомпонентаQRBand1 по умолчаниюбудет установленозначение rbTitle,то есть компонентQRBand1 определяетзаголовокотчета Разместимна QRBand1 компонентTQRLabel. Установимв свойствоCaption этогокомпонентазначение Отпусктоваров сосклада и выберемв свойстве Fontжирный наклонныйшрифт высотой16 пунктов. Видформы отчетак этому моментупоказан нарис.5.
Рис.5. В отчете определентолько егозаголовок.
Теперь разместимв отчете данные,соответствующиетекущей записитаблицы Rashod.Для этогопоместим вотчет новыйкомпонентTQRBand (имяQRBand2) иустановим вего свойствоBandType значениеrbDetail. Затемразместим наполосе QRBand2шестькомпонентовTQRDBText.Свяжем этикомпонентыс полями НД –N_RASH, TOVAR,KOLVO,DEN, MES,GOD. Дляэтого в свойствоDataSet каждогокомпонентаQRDBText установимзначение Table1,а в свойствоDataField –имя соответствующегополя. Вид отчетак этому моментупоказан нарис.6.
Рис.6. Отчет с заголовкоми группой детальнойинформации.
Для просмотраполучившегосяотчета щелкнемпо нему правойкнопкой мышии из всплывающегоменю выберемэлемент Preview.Окно предварительногопросмотраотчета показанона рис. 7.
Рис.7. Содержимоеотчета в окнепредварительногопросмотра.
Чтобы окнопредварительногопросмотраоткрывалосьпри активизацииформы, создадимтакой обработчиксобытия OnActivateформы:
procedureTForm1.FormActivate(Sender: TObject);
begin
QuickRep1.Preview;
end;
а чтобыпосле выходаиз окна предварительногопросмотразакрываласьбы форма, накоторой расположентекст, используемтакой обработчиксобытия AfterPreview:
procedureTForm1.QuickRep1AfterPreview(Sender: TObject);
begin
Form1.Close;
end;
Из рис.7 видно,что в простейшемотчете выводитсядата, составленнаяиз трех полей– DEN, MES, GOD. Объединимзначения изэтих полей водно значение,являющеесярезультатомвычислениявыражения.Выражение вотчетах формируетсяпри помощикомпонентаTQRExpr. Удалимиз компонентаQRBand2 компонентыQRDBText4,QRDBText5 и QRDBText6,связанные сполями DEN,MES, GOD.Вместо нихразместим вотчете компонентTQRExpr (имяQRExpr1).
Выражения вTQRExpr формируютсяс помощьюспециальногоредактора,который вызываетсяв окне инспектораобъектов кнопкойв поле данныхсвойства Expressionэтого компонента(рис.8).
Рис.8. Окно редактораформул компонентаTQRExpr.
В поле Enterexpression можноввести илиотредактироватьвыражение,которое обычносостоит из именполей НД, преобразующихфункций и переменных,связанныхоперациямиотношения.Имена полейНД добавляютсяв текущее положениекурсора (полеEnter expression)с помощьювспомогательногоокна, связанногос кнопкой Function,а переменные– с кнопкойVariable.
Нажмите кнопкуFunction, в левомокне выберитекатегорию Other(другие) ифункцию STRв правом окне– эта функцияпреобразуетчисловое значениев строковое.Нажмите Continue,чтобы перейтик вводу параметров(рис.9). Надписьнад строкойввода окнаExpression Wizard напоминаето том, что выбраннаянами функцияимеет одинчисловой параметр.
Рис.9. Формированиечасти выражения.
Для его вводанажмите кнопкусправа от строкиввода – на экраневновь появитсяначальное окноредактораформул. Посколькумы хотим преобразоватьв строку номердня, нажмитекнопку Databasefield и выберитеполе DENв списке полейтаблицы Table1.Нажмите OK,чтобы завершитьввод параметра.В поле Enterexpression будет сформированачасть формулы– STR(Table1.DEN).На панелиInsert at cursor position нажмемкнопку «+» ивручную введемразделитель‘.’ (рис.10).
Рис.10. Создание частиформулы выражения.
Продолжитеформироватьвыражение так,чтобы в концеконцов оноприобрело такойвид:
STR(Table1.DEN)+ ‘.’ + STR(Table1.MES) + ‘.’ +STR(Table1.GOD)
(возможнопроще ввестиего вручную).Затем нажмитекнопку OK,чтобы закрытьокно редактораформул. С помощьюИнспектораобъектов установитев свойствоAutoSize компонентаQRExpr1 значениеFalse, изменитеразмеры компонентатак, чтобы онмог отображатьпримерно 10 символов,и установитевыравниваниевправо (свойствоAlignment = taRightJustify).Запуститережим предварительногопросмотрасодержимогоотчета (рис.11).Как видим, датаотпуска товараприобрела болеепривычный вид.
Рис.11. Результатвычислениявыраженияпоявился вотчете.
Замечание.
Другим способомсоставлениязначения датыиз трех полеймогло бы бытьсоздание вычисляемогополя (например,SumData) иопределениеалгоритмавычисленияего значенияв таком обработчикесобытия OnCalcFields:
procedureTForm1.TableCalcFields(DataSet: TDataSet);
begin
Table1SumData.Value:= Table1DEN.AsString + ‘.’ +
Table1MES.AsString +‘.’ + Table1GOD.AsString;
end;
КомпонентTQRBand, укоторого всвойство BandTypeустановленозначениеrbColumnHeader, используетсядля размещениязаголовковстолбцов. Собственнозаголовкистолбцов формируютсяпри помощикомпонентовTQRLabel.
В рассмотренномв предыдущихразделах отчетеразместимкомпонентTQRBand (имяQRBand3) иустановим всвойства Captionэтих компонентовсоответственнозначения №№,Товар, Количество,Дата. В свойствахFont компонентоввыберем наклонныйи подчеркнутыйшрифт. Вызовемокно предварительногопросмотраотчета – длякаждой страницыотчета теперьбудут выводитьсяназвания столбцов(рис.12).
Рис.12.В отчете появилисьзаголовкистолбцов.
КомпонентTQRBand, у которогов свойствоBandType установленозначениеrbPageHeader, используетсядля показазаголовкастраницы, аесли это свойствоустановленов rbPageFooter, – для показаподвала страницы.Заголовоквыводится вначале каждойстраницы, аподвал – в ееконце. Информацияв заголовкеи подвале страницыможет формироватьсяна основестатическоготекста (компонентыTQRLabel), значенийполей (компонентыTQRDBText) и результатоввычисленийвыражений(компонентыTQRExpr).
Вернувшиськ предыдущемупримеру, разместимв отчете компонентTQRBand (имя QRBand4) иустановим вего свойствоBandType значениеrbPageHeader. Не будемразмещать взаголовкеникакого текста,просто отчеркнемлинию вверхустраницы. Дляэтого установимв свойствокомпонентастраницыFrame.DrawTop значениеTrue, что обеспечиваетвывод линиипо верхнемукраю области,занимаемойкомпонентом.Аналогичнымобразом определимв отчете компонентподвала страницы(имя QRBand5)и установимв его свойствоFrame.DrawBottom значениеTrue, чтообеспечиваетвывод линиипо нижнему краюобласти, занимаемойкомпонентом.
Войдя в режимпредварительногопросмотра,увидим, чтовверху и внизукаждой страницыотчета выводятсялинии.
КомпонентTQRSysDataиспользуетсядля показавспомогательнойи системнойинформации.Вид показываемойинформацииопределяетсясвойством
propertyData: TQRSysDataType;
Нижеуказаны возможныезначения этогосвойства.
Значение | Чтовыводится |
qrsColumnNo | Номертекущей колонкиотчета (дляодноколоночногоотчета всегда1). |
qrsDate | Текущаядата. |
qrsDateTime | Текущиедата и время. |
qrsDetailCount | Количествозаписей в НД,а при использованиинесколькихНД – количествозаписей в главномнаборе. Дляслучая, когдаНД представленкомпонентомTQuery,эта возможностьможет бытьнедоступной,что связанос характеромработы компонентаTQuery,который возвращаетстолько записей,сколько необходимодля использованияв текущий момент,а остальныепредоставляетпо мере надобности. |
qrsDetailNo | Номертекущей записив НД. |
qrsPageNumber | Номертекущей страницыотчета. |
qrsPageCount | Общееколичествостраниц отчета. |
qrsReportTitle | Заголовокотчета. |
qrsTime | Текущеевремя |
Разместим вкомпонентеQRBand5 подвалаотчета двакомпонентаTQRSysData. Всвойство Dataпервого изних установимзначение qrsDate,второго –qrsPageNumber. Врежиме предварительногопросмотраувидим, чтотеперь в подвалестраницы выводятсяномер страницыи текущая дата(рис.13)
Рис.13. Показ номерастраницы итекущей датыв подвале страницы.
Для группировокинформациииспользуетсякомпонентTQRGroup. Егосвойство Expressionуказываетнекотороевыражение,которое используетсядля группировки,иными словами,в группу входятзаписи, удовлетворяющиеусловию этоговыражения. Присмене выраженияпроисходитсмена группы.
Для каждойгруппы выводятсяее заголовоки подвал. В качествезаголовкагруппы используетсякомпонентTQRBand со значениемсвойства BandType,равным rbColumnHeader,а в качествеподвала – созначениемrbGroupFooter. СвойствоFooterBandкомпонентаTQRGroup должносодержатьссылку на компонентподвала группы.В заголовкегруппы, какправило, выводитсягруппирующеевыражение, ав подвале группы– агрегированнаяинформация:суммарные,средние и т.п.значения погруппе в целом.
Пример.
Построим отчето расходе товарасо склада, вкотором информациягруппируетсяпо наименованиютовара. Дляэтого определимнабор данныхотчета (компонентTTable, имяTable1).Установим уНД текущиминдекс по полюTOVAR (всвойствеFieldIndexNamesили IndexName).Разместимв отчете:
заголовокотчета – компонентTQRBand сименем QRBand1,свойствоBandType=rbTitle;
заголовокстолбцов –компонентTQRBand сименем QRBand2,свойствоBandType=rbColumnHeader;
группу– компонентTQRGroup сименем QRGroup1;
областьдетальнойинформации– TQRBandс именем QRBand3,свойствоBandType=rbDetail;
подвалгруппы – TQRBandс именем QRBand4,свойствоBandType=rbGroupFooter;
В компонентеQRGroup1установим:
в свойствоFooterBandзначениеQRBand4;
всвойство ExpressionзначениеTable1.TOVAR,которое являетсяформулой истроится вредактореформул.
Посколькусвойство Expressionне визуализируетзначения выражения,необходиморазместитьв группе компонентTQRExpr (имя QRExpr1) иопределитьзначение егосвойства Expressionтак, чтобы оносодержалоTable1.TOVAR.
В компонентеподвала группыQRBand4 будемподсчитыватьсумму по полюKOLVO (суммуотпущенногоконкретноготовара). Дляэтого разместимв подвале группыкомпонентTQRExpr (имяQRExpr2) и определитьзначение егосвойства Expressionтак, чтобы оносодержалоформулу SUM(Table1.TOVAR).
В группе детальнойинформацииразместимкомпонентыTQRDBText,связанныес полями Pokupи Kolvo.Заполним областиотчета статическимтекстом, какэто показанона рис.14.
Рис.14. Макет отчетас группировкойпо товару.
Рис.15. Отчет с группировкойпо товару вокне предварительногопрсмотра.
Часто внутригруппы должнысодержатьсядругие группы,например, поназванию товараи внутри каждойгруппы – попокупателям.В этом случаевнутри однойгруппы определяютдругую посредствомдополнительныхкомпонентовTQRGroup.
Пусть требуетсяпредставитьв отчете сведенияо расходе товаровсо склада группируяданные по товарам,а внутри группы– по покупателям.Установимтекущий индекспо полям TOVAR,POKUP.Общий вид отчетана этапе разработкиприводитсяна рис.17, а в окнепредварительногопросмотра –на рис.18.
Рис.17.Макет отчетас вложеннымигруппами.
Рис. 18.Отчет с вложеннымигруппами.
Еслинеобходимопостроить отчетна основе болеечем одной ТБД,можно поступитьдвумя способами:
с помощьюкомпонентаTQuery произвестисоединениеданных из несколькихтаблиц БД водин НД, послечего определитьв отчете нужныегруппировки;
создатьв приложениипо одному НДна каждую таблицу,соединить этинаборы междусобой связьюглавный-детальный(используясвойстваMasterSource,MasterFieldsнабора данных)и применитьв отчете компонент(или несколькокомпонентов)TQRSubDetail длявывода информациииз детальногоНД (или группыдетальных НД);для выводаинформациииз главногоНД, как и в обычныхотчетах, применяетсякомпонентTQRBand, укоторого всвойстве BandTypeустановленозначение rbDetail.
Построениеотчета дляпервого случаяосуществляетсяаналогичнотому, как этоописано выше.Построениеотчета длявторого случаяимеет некоторыеотличительныеособенности.Рассмотримвторой способ.
КомпонентTQRSubDetailпредназначендля показа вотчете информациииз детальногоНД. Его свойство
PropertyDataSet: TDataSet;
указываетимя детальногоНД, информацияиз которогобудет выводитьсяв пространствекомпонентаTQRSubDetail. В остальномиспользованиеэтого компонентааналогичноиспользованиюкомпонентаTQRBand, у которогов свойствоBandType установленозначение rbDetail.
Пусть имеетсятаблица БДTOVARY.DB,содержащаяпомимо прочихполе TOVAR(названиетовара). Пустьтакже имеетсятаблица БДRASHOD.DB,содержащаясведения оботпуске материаловсо склада. В еесостав входятполя N_RASH(уникальныйномер событияотпуска товара),DEN (номердня), MES(номер месяца),GOD (номергода), TOVAR(наименованиеотпущенноготовара), POKUP(наименованиепокупателя)и KOLVO(количествоединиц отпущенноготовара).
Таблицы TOVARY.DBи RASHOD.DB находятсяв отношенииодин-ко-многим,то есть одномутовару можетсоответствоватьболее одногофакта отпускатовара со склада.
Разместим наформе компонентTTable (им TovaryTable), ассоциированныйс ТБД TOVARY.DB,и связанныйс ним компонентTDataSource (имя DS_TovaryTable).Разместим такжееще один компонентTTable (им RashodTable), ассоциированныйс ТБД RASHOD.DB,и установиммежду НД связьглавный-детальный.Для этого установимв свойствоRashodTable.MasterSource значениеDS_TovaryTable, а в свойствоRashodTable.MasterFields значениеTOVAR (рис.19).
Рис.19. Установкасвязи главный-детальный.
Заметим, чтопосле установлениясвязей НД и НДRashodTable текущиминдексом долженбыть индекспо полю Tovar(свойствоRashodTable.IndexFieldNames).
Приступим кразработкеотчета. Определимзаголовокотчета – компонентTQRBand с именемQRBand1, в свойствоBandType которогоустановленозначение rbTitle.Установимв качествеосновного НДотчета TovaryTable,указав QuickRep1.DataSet= TovaryTable.Разместимв отчете компонентTQRBand сименем QRBand2и установимв его свойствоBandType значениеrbDetail. Этоткомпонент будетиспользоватьсядля отображениядетальнойинформациииз НД TovaryTable.
Разместим вотчете компонентTQRSubTetail (имя QRSubDetail).Установим вего свойствоDataSet значениеRashodTable, связав такимобразом данныйкомпонент сподчиненнымНД. Разместимв области компонентаQRSubDetail три компонентаTQRDBText и свяжем ихсоответственнос полями Pokup,Kolvo и D НД RashodTable (полеD определенов НД RashodTable каквычисляемоепо значениямполей DEN, MES, GOD).Разместим вобласти компонентаQRBand2 заголовкистолбцов.
Вид формы отчетапоказан нарис.20.
Рис.20. Макет отчета,в которомпоказываютсязаписи из связанныхнаборов данных.
В результирующемотчете (рис.21)для каждойзаписи НД TovaryTableвыводятсяподчиненныеей записи изНД RashodTable.
Рис.21. Отчет, в которомпоказываютсязаписи из связанныхнаборов данных.
Замечание.
Если необходимоопределитьзаголовок иподвал дляинформации,группируемойв компонентеTQRSubDetail, следуетвоспользоватьсясвойством
propertyBands: TQRSubDetailGroupBands;
этогокомпонента,которое имеетдва логическихподсвойства(HasHeader иHasFooter),указывающихна наличие илиотсутствиесоответственнозаголовка иподвала.
Композитный(составной,сложный) отчетобъединяетв себе несколькопростых отчетов.При печатикомпозитногоотчета, входящиев его составпростые отчетыпечатаютсядруг за другом.
Композитныйотчет реализуетсяпри помощикомпонентаTQRCompositeReport. В егообработчикесобытия OnAddReportранее определенныепростые отчетыдобавляютсяв списковоесвойство Report.Например, так:
propertyTCompositnyjOtchet.QRCompositeReport1AddReports(Sender: TObject);
begin
withQRCompositeReport1 do
begin
Reports.Add(ManyGroup.QuickRep1);
Reports.Add(Prostoj.QuickRep1);
end
end;
В этом примерекомпозитныйотчет составляетсяиз двух отчетов:QuickRep1(определенныйв форме ManyGroup)и QuickRep1(определенныйв форме Prostoj).Почать композитногоотчета или егопредварительныйпросмотросуществляетсятак же, как дляпростых отчетов,например
QRCompositeReport1.Preview;
Нарис.22 показанкомпозитныйотчет, построенныйиз двух ранееразработанныхнами отчетов– простейшегоотчета и отчетас группировкамиданных.
Рис.22. Композитныйотчет, составленныйиз двух простыхотчетов.
У
Разработкабаз данных вDelphi
Вводный | Вводныйурок | Вводныйурок.doc |
Урок01 | НастройкаBDE | Урок01.doc |
Урок02 | Созданиетаблиц спомощью | Урок02.doc |
Урок03 | Созданиетаблиц спомощью | Урок03.doc |
Урок04 | Обзорвизуальныхкомпонент. | Урок04.doc |
Урок05 | КомпонентTtable. Созданиетаблиц с помощьюкомпонентаTTable | Урок05.doc |
Урок06 | КомпонентTQuery | Урок06.doc |
Урок07 | РедакторDataSet, вычисляемыеполя | Урок07.doc |
Урок08 | КомпонентTDatabase | Урок08.doc |
Урок09 | Управлениетранзакциями | Урок09.doc |
Урок10 | Основыязыка SQL | Урок10.doc |
Урок11 | Генерацияотчетов | Урок11.doc |