Создание и подготовка базы данных
Структура нашего Интернет-магазина
Связь с базами данных в C++Builder
В первых статьях серии <% ASP на блюдечке %> (<% ASP на блюдечке %> — части 1, 2, 3 и 4, КомпьютерПресс № 9, 10, 11 и 12 за 2000 год соответственно) мы ознакомились с ASP, с принципами построения простейшего интерфейса к базе данных с его помощью (газетный сайт со встроенными возможностями его пополнения новыми статьями, снабжаемыми фотографиями непосредственно с самого сайта и без программирования), с основами использования ActiveX компонентов в ASP, а также с азами разработки собственных компонентов с помощью известных всем Microsoft Visual Basic и Microsoft Visual C++ и с возможностями, предоставляемыми ASP в поддержку программирования WAP-сайтов (создание WAP-системы бронирования авиабилетов).
Далее была представлена система Интернет-торговли (базa данных перечня товаров гипотетического магазина компьютеров и комплектующих, средства предъявления товаров, выбoрки и добавления в «корзину» и посылки заказа по электронной почте), или, говоря попросту, Интернет-магазин.
Настоящая статья предназначена для тех, кто собирается автоматизировать процесс подготовки шаблонов конфигураций продаваемого оборудования. Как отмечалось, не все покупатели точно знают, что именно хотят приобрести, да и не во всех может параметрах ряда товаров разобраться обыватель (например, бытовая техника или компьютеры), зачастую требуется консультация эксперта.
В предыдущей статье (КомпьютерПресс № 12 за 2000 год, <%ASP на блюдечке%>. Часть 4) такой эксперт-консультант был реализован программно. Было создано несколько шаблонов типовых конфигураций персональных компьютеров, к примеру «Графическая станция», «Мультимедийный компьютер для дома с Интернет-возможностями», «Сервер баз данных» и т.д. Шаблоны создавались вручную. Далее некомпетентным покупателям предлагалось выбрать наиболее подходящий шаблон и таким образом избавиться от необходимости подбора комплектующих, в ходе которого могут быть допущены ошибки, например выбраны два несовместимых устройства.
Однако, как отмечалось, если шаблоны будут меняться достаточно часто и если их много, то процесс их подготовки необходимо автоматизировать (для этого проще всего написать программу — построитель шаблонов при помощи Borland C++Builder-а). Кроме того, прайс-листы различных компаний, как правило, поставляются в электронном виде, например в виде *.xls- или *.doc-файлов, и необходимо средство систематического преобразования таких прайс-листов в базу данных.
![]() |
Для создания базы данных (назовем ее Ishop) нам понадобится Microsoft Access 2000. От процесса проектирования базы данных будет зависеть дальнейший объем программной логики нашего приложения, поэтому этот этап крайне ответственен. И еще один совет: при проектировании Интернет-магазина стремитесь к тому, чтобы ваши странички содержали как можно меньше строк и констант, выведите все что можно за их пределы, вплоть до наименований позиций, так как последние могут меняться довольно часто. Говоря другими словами, добейтесь от своего кода универсальности и независимости от данных. Однако, поскольку принципы проектирования баз данных выходят за рамки настоящей статьи, не будем останавливаться на них и представим структуру нашей базы данных так, как показано на рис. 1.
Создадим три управляющие таблицы для хранения информации о наименованиях всех категорий и их соответствия таблицам с позициями, ценами и условиями гарантий (таблицу _Components (рис. 2)), таблицы с перечнем дополнительных категорий (таблицу _Equipment (рис. 3) для компетентных покупателей и таблицу _PCType (рис. 4) для некомпетентных покупателей, желающих приобрести компьютер с определенной целью (к примеру, для дома или для офиса).
Следует отметить, что поле «IsPCComponent» в таблице _Components, имеющее булево значение, позволяет определить, является ли данный компонент обязательным составляющим персонального компьютера или нет (это понадобиться нам в дальнейшем).
Все остальные таблицы создадим по одному и тому же шаблону — с наименованием позиции (поле Title), розничной ценой (поле Price 1), мелкооптовой ценой (поле Price 2) и крупнооптовой ценой (поле Price 3), а также с условиями гарантии на товар (поле Description) (рис. 5).
Как видите, таблица _PCType осуществляет взаимосвязь всех остальных таблиц и позволяет организовать системы предложения оптимальной конфигурации компьютера. Пример такой взаимосвязи представлен на рис. 6.
То есть каждая строка таблицы _PCType содержит номера ключевых полей всех таблиц с позициями комплектующих с указанием количества каждой комплектующей той или иной типовой конфигурации.
![]() |
Как помните, структура нашего Интернет-магазина представляется так, как показано на рис. 7.
Формы и действия при выборе конфигурации компьютера автоматически представлены синим цветом, при выборе конфигурации самостоятельно — зеленым, при выборе дополнительного оборудования — сиреневым, а начало и конец алгоритма-схемы — красным.
В настоящей статье мы рассмотрим лишь одну ветвь этой схемы — синюю. А если быть точнее, попытаемся написать программу-конфигуратор, с помощью которой будут создаваться и редактироваться типовые конфигурации продаваемого оборудования (суть задачи не меняется от вида товара — будь то компьютеры или автомобили).
Уяснив структуру проектируемого приложения, мы можем приступать к написанию кода.
![]() |
Конечно, можно обойтись и Access’ом 97 или 2000, однако советую перевести Access- базу нашего магазина в Microsoft SQL Server. Причина здесь очевидна — колоссальная прибавка производительности Microsoft SQL Server’а по сравнению с Microsoft Access'ом. Но как быть? Ведь база уже подготовлена с помощью Access. Рассмотрим подробно процесс создания SQL-версии базы данных.
Итак, для того чтобы скопировать таблицы из базы данных формата Microsoft Access 2000 в Microsoft SQL Server 2000, необходимо запустить средство транспортировки данных DTS (Data Transformation Service), а для этого нужно запустить: Programs->Microsoft SQL Server->Import and Export Data.
Перед вами появится окно мастера импорта и экспорта данных, в котором необходимо последовательно задать источник и приемник данных. В качестве источника данных выберем исходный файл с Access’овской базой данных (рис. 8).
А в качестве приемника данных выберем Microsoft OLE DB Provider for SQL Server, введем пароль системного администратора, а также имя SQL-базы данных нашего магазина (можно либо создать базу данных заблаговременно, либо выбрать в списке элемент «<new>» и в появившемся окне впечатать имя создаваемой базы данных — в нашем случае IShop и продолжим процесс импорта нажатием на кнопку Next (рис. 9)
После этого следует еще раз нажать кнопку Next, выбирая из предлагаемых операций простое копирование таблиц, в появившемся списке таблиц проставить галочки слева от тех таблиц, которые должны быть скопированы (в нашем случае можно воспользоваться кнопкой Select All, выделив все таблицы Access’овской базы) и запустить процесс импорта данных (Run Immediately), запуская таким образом процесс копирования данных немедленно. После этого начнется процесс копирования данных (рис. 10).
По окончании процесса копирования можно запустить Enterprise Manager (Programs->Microsoft SQL Server-> Enterprise Manager) и убедиться в том, что таблицы попали в нужную базу данных (рис. 11)
Вроде бы все готово и можно приступать к процессу создания приложения — лиента к SQL-базе данных. Однако перед этим следует выполнить еще несколько действий. Во-первых, после перегона данных из Access’а в SQL Server следует внимательно просмотреть все типы полученных там полей данных и проверить их правильность. Если какие-либо типы данных не соответствуют требуемым, можно выполнить преобразование вручную, если же данные в ходе этого могут быть искажены (о чем вас предупредит Enterprise Manager), можно повторить процесс перегона данных, в ходе которого воспользоваться кнопкой Advanced в программе — трансформаторе данных, где задать правила перетипизации данных. Далее необходимо указать ключевое поле Set Primary Key и включить свойство счетчика Identity, выставив в нем значение Yes.
В результате каждая таблица данных в режиме редактирования типов полей Design должна выглядеть так, как показано на рис. 12.
И наконец, рекомендуется создать еще один псевдоним к нашей базе данных — для упрощения процесса создания приложения с помощью Borland C++Builder. Он называется алиасом (от англ. Alias) и служит для связи реальной базы данных с так называемым BDE (Borland Database Engine).
![]() |
Основой работы C++Builder с базами данных является Borland Database Engine (BDE) — процессор баз данных фирмы Borland. BDE служит посредником между приложением и базами данных. Он предоставляет пользователю единый интерфейс для работы, освобождающий пользователя от конкретной реализации базы данных. Благодаря этому отпадает необходимость менять приложение при смене реализации базы данных. Приложение C++ Builder никогда не обращается к базе данных непосредственно, а только к BDE.
Приложение C++Builder, когда ему нужно связаться с базой данных, обращается к BDE и обычно сообщает псевдоним базы данных и необходимую таблицу в ней. BDE реализован в виде динамически подключаемых библиотек DLL, которые, как и любые другие библиотеки, снабжены API (Application Program Interface — интерфейс прикладных программ), названным IDAPI (Integrated Database Application Program Interface). Это список процедур и функций для работы с базами данных, которым и пользуются приложения, создаваемые с помощью Borland C++Builder.
BDE по псевдониму находит подходящий для указанной базы данных драйвер. Драйвер — это вспомогательная программа, «понимающая», как общаться с базами данных определенного типа. Если в BDE имеется собственный драйвер соответствующей СУБД, то BDE связывается через него с базой данных и с нужной таблицей в ней, отрабатывает запрос пользователя и возвращает в приложение результаты обработки. BDE поддерживает естественный доступ к таким базам данных, как Microsoft Access, FoxPro, dBase и Paradox.
Если же собственного драйвера нужной СУБД в BDE нет, то можно воспользоваться ODBC (в предыдущей статье описано, как это делается для базы данных MS Access). ODBC (Open Database Connectivity) — DLL, аналогичная по функциям BDE, но разработанная компанией Microsoft. Поскольку Microsoft включила поддержку ODBC в свои офисные продукты и для ODBC созданы драйверы практически к любым СУБД, компания Borland включила в BDE драйверы, позволяющие использовать ODBC-псевдонимы.
В отличие от BDE работа через ODBC осуществляется гораздо медленнее, поэтому рекомендуется по возможности пользоваться именно BDE-алиасами и BDE-драйверами к базе данных. Однако, если это невозможно (по причине отсутствия таковых), можно воспользоваться и ODBC-алиасами и драйверами соответственно.
Теперь рассмотрим процесс создания BDE-алиаса к нашей базе данных во всех подробностях.
Для начала следует запустить программу-конфигуратор Database Desktop, входящую в состав пакета Borland C++Builder. Для этого выполним команду: Programs->Borland C++Builder->Database Desktop. Далее уже в Database Desktop выполним команду меню Tools->Alias Manager, в появившемся окне конструктора псевдонимов нажмите на кнопку «New» для создания нового псевдонима и заполните поля соответствующими значениями (см. рис. 13).
Теперь можно проверить правильность только что созданного псевдонима (кнопка Connect Now). При выходе из программы Database Desktop попросит сохранить созданный алиас и при этом обновит файл конфигурации всех алиасов системы.
Как видите, создавать ODBC-алиас при разработке приложений клиентов к базам данных с помощью Borland C++Builder’а в общем случае вовсе не обязательно. Однако не следует забывать, что в нашем случае клиентом к базе данных служит не только Windows-приложение, которое мы собираемся разрабатывать, но и Web-приложение (рассмотренное в деталях в предыдущей статье). Поэтому нам без создания ODBC-псевдонима не обойтись.
Для этого:
Вы увидите появившуюся строку в списке источников данных в вашей системе (рис. 14).
Разберемся, каким образом следует сконфигурировать разрабатываемое приложение в зависимости от того, какой псевдоним к базе данных будет использоваться. Речь идет о свойствах объекта Tdatabase. В нашем случае следует заполнить его свойства следующими значениями:
Вся остальная информация о нашей базе данных уже содержится в псевдониме и поэтому может не указываться. Если же по каким-то причинам использование BDE-псевдонима невозможно, то свойства объекта TDatabase в нашем случае будут выглядеть следующим образом:
DatabaseName = 'ISHop' LoginPrompt = False Params.Strings = ( [ODBC] DRIVER=MSSQL — драйвер базы данных UID=sa — учетная запись DATABASE=ISHop — название базы данных APP=ISHManager — название приложения SERVER= Aquarius — название сервера USER NAME=sa — учетная запись пользователя PASSWORD=123 — пароль пользователя )
Что же необходимо изменить в ASP-коде магазина для того, чтобы все работало под управлением SQL-сервера. По сути, все изменения относятся к способу подключения к базе данных, а именно:
вместо строки: db.Open "DSN=IShop;UID=sa;PWD=;"
будем использовать строку: db.Open "DSN=ISHop; UID=sa;PWD=;database=ISHop"
![]() |
…Хочется дать один совет относительно способов проектирования приложений-клиентов к базам данных с помощью Borland C++ Builder. Речь идет о двух компонентах, без существования которых не обошлось бы ни одно СУБД-приложение, а именно о TTable и TQuery.
Понимание разницы между ними очень важно для дальнейшей работы. Оба эти компонента предоставляют доступ к базе данных. Компонент TTable предоставляет более удобный интерфейс к таблицам базы данных, однако в отличие от компонента TQuery усложняет разработку сетевых многопользовательских приложений. Дело в том, что компонент TTable в подключенном к таблице базы данных состоянии (Active = true) не дает двум или более пользователям одновременно редактировать таблицу в базе данных. Эту проблему можно решить, открывая и закрывая элемент типа TTable всякий раз, когда происходит обращение к таблице. Однако при этом, естественно, указатель активной записи в таблице теряет свое положение. Поэтому, открывая таблицу в последующем, придется снова находить нужную (последнюю) запись. Конечно, это не составляет труда, но тем не менее может серьезно замедлить работу многопользовательского приложения. В нашем случае многопользовательность — не самое главное, однако мы будем использовать как компонент TTable, так и TQuery.
![]() |
Теперь рассмотрим компоненты, используемые для визуализации данных и одновременной связи с ними. Такие компоненты расположены в палитре Data Controls Borland C++Builder, из которой нам понадобится лишь один — ТDBLookupComboBox — выпадающий список выбора, связанный с определенным полем заданной таблицы заданной базы данных. Это означает, что изменение текущего значения в таком поле приводит к изменению позиции указателя в связанной с ним таблице данных и наоборот. Согласитесь, что такой компонент избавляет нас от необходимости писать громоздкие функции-обработчики; надо всего лишь заполнить его свойства, а именно:
![]() |
Прежде всего необходимо подготовить форму приложения, причем отметим сразу, что оно будет состоять из двух частей:
Для этого удобнее всего воспользоваться компонентом TPageControl со вкладки Win32 палитры компонентов Borland C++Builder’а версии 5.0.
Создадим главное окно нашего приложения, поле типа TlistBox, в котором будут отображаться ключевые поля анализируемой таблицы (исходные данные) и поле с названиями соответствующих таблиц (также типа TListBox). Эти поля будут служить для установления соответствия названий ключевых полей исходного прайса таблицам базы данных, в которые программа будет вставлять значения.
Далее создадим компоненты типа TQuery для построения запросов к базам данных и определим процедуру инициализации формы:
//--------------------------------------------------------------------------- void __fastcall TMainForm::FormCreate(TObject *Sender) { AnsiString Str; Height = 386; ErrStr1 = ""; ErrStr2 = ""; ErrStr3 = ""; // Подключение базы данных Database1->Connected = true; //Подключение таблиц данных ConfTB->Active = true; MBTB->Active = true; CPTB->Active = true; SVTB->Active = true; IDEHTB->Active = true; SCSITB->Active = true; SndTB->Active = true; KBDTB->Active = true; MouseTB->Active = true; CaseTB->Active = true; MonTB->Active = true; SpeakerTB->Active = true; FaxTB->Active = true; RAMTB->Active = true; FDDTB->Active = true; CDTB->Active = true; PadTB->Active = true; OpenConfiguration(); // Функция загрузки текущего шаблона ReadKF(); // Функция чтения ключевых полей исходного прайс-листа Str = AnsiString(KeyFields->Items->Count); Label1->Caption = Label1->Caption + Str; Str = AnsiString(Map->Items->Count); Label5->Caption = Label5->Caption + Str; KeyFields->ItemIndex = 0; Map->ItemIndex = 0; KeyFieldsClick(Sender); MapClick(Sender); } //---------------------------------------------------------------------------
Кроме того, процедуру, вызываемую при закрытии формы:
//--------------------------------------------------------------------------- void __fastcall TMainForm::FormDestroy(TObject *Sender) { SaveConfiguration(); // Сохранение текущей конфигурации ConfTB->Active = false; MBTB->Active = false; CPTB->Active = false; SVTB->Active = false; IDEHTB->Active = false; SCSITB->Active = false; SndTB->Active = false; KBDTB->Active = false; MouseTB->Active = false; CaseTB->Active = false; MonTB->Active = false; SpeakerTB->Active = false; FaxTB->Active = false; RAMTB->Active = false; FDDTB->Active = false; CDTB->Active = false; PadTB->Active = false; Query1->Close(); Query2->Close(); Query3->Close(); Query4->Close(); Database1->Connected = false; } //---------------------------------------------------------------------------
Создадим две вкладки и назовем их «Прайс-процессор» и «Конфигуратор ПК» соответственно. Определим процедуру переключения вкладок следующим образом:
//--------------------------------------------------------------------------- void __fastcall TMainForm::PageControlChange(TObject *Sender) { if (PageControl->ActivePage == TabSheet1) Height = 386; else Height = 518; } //---------------------------------------------------------------------------
Таким образом, высота окна нашей формы будет изменяться в зависимости от того, какая из двух вкладок активна. Далее определим связанные с источниками данных списки выбора комплектующих (для этого воспользуемся компонентами DBLookupComboBox из палитры DataAccess) и функцию инициализации состояния списков выбора комплектующих:
//--------------------------------------------------------------------------- void TMainForm::OpenConfiguration() { Configuration->DataField = "PCType"; Configuration->KeyValue = ConfTB->FieldByName("ID")->AsInteger; MB->DataField = "Title"; MB->KeyValue = ConfTB->FieldByName("MainBoardID")->AsInteger; // Текущее значение выпадающего списка позиций совпадает с // заданной позицией указателя в соответствующей таблице MBPrc->Text = AnsiString(ConfTB->FieldByName("MainBoardNum")->AsInteger); // Занесем в поле типа TEdit значение количества экземпляров текущей позиции // … // И далее для всех таблиц данных // … CalcTotalPrice(); // Функция подсчета интегральной стоимости набора } //---------------------------------------------------------------------------
Прежде чем приступить к написанию функции подсчета интегральной стоимости всего набора, подготовим поля для указания стоимости (TDBEdit) и количества экземпляров (TEdit) каждого типа комплектующих. Теперь напишем функцию подсчета стоимости всего набора:
//--------------------------------------------------------------------------- void TMainForm::CalcTotalPrice() { AnsiString Str, dig = ""; float CurVal, TotalVal = 0.0; int CurNum; Str = MBPrc->Text; try { CurNum = Str.ToInt(); } // Проверка правильности ввода catch (Exception &E) { Str = "Допустимы только целые численные значения!\n" Str = Str + Str + "—- не верный формат целого числа."; Application->MessageBox(Str.c_str(), "Ошибка!", MB_ICONERROR|MB_OK); return; } Str = MBNum->Field->AsString; CurVal = Str.ToDouble(); TotalVal = TotalVal + CurVal * CurNum; // Подсчет общей стоимости // … // И далее для всех таблиц данных // … // Отсечем ненужные знаки после запятой (после второй цифры) Str = AnsiString (TotalVal); for (int i = 1; i <= Str.Length(); i++) { if (Str[i] == ',') { dig = dig + Str[i]; if (Str.Length() >= i+1) dig = dig + Str[i+1]; if (Str.Length() >= i+2) dig = dig + Str[i+2]; break; } dig = dig + Str[i]; } Total->Text = dig; // И выведем полученное значение суммарной стоимости в соответствующее поле } //---------------------------------------------------------------------------
Теперь настало время разобраться с ключевыми полями нашего прайса. Допустим, прайс-лист ежедневно поставляется в формате Excel-файла, причем все позиции в нем представлены в одной таблице в форме списка (это наиболее типичный случай). Для автоматизации его разбивки на соответствующие разделы необходимо выявить признаки, свойственные наименованиям этих разделов. Назовем эти разделы ключевыми полями и сымпортируем Excel-файл в SQL Server (это делается точно так же, как и в случае с Access’ом) в таблицу с именем Src. Далее от нас потребуется, «проходя» последовательно по всем записям этой таблицы и выявляя ключевые поля, вставлять записи в соответствующие названиям ключевых полей таблицы. Остается одно: определить то свойство, которое позволит нам отличить наименование раздела (например, «Процессоры» или «Материнские платы» от наименования позиций). Предположим, что в нашем случае такая разница заключается в отсутствии значения в столбцах «Цена» у наименования позиции.
//--------------------------------------------------------------------------- void TMainForm::ReadKF() { AnsiString sSQL, CurField, p1, p2, p3, p4, p5, descr; int i; sSQL = "SELECT * FROM Src"; // Выборка всех записей таблицы-исходного прайс-листа Query1->Close(); Query1->SQL->Clear(); Query1->SQL->Add(sSQL); Query1->Open(); // Выполнение запроса KeyFields->Items->Clear(); while (!Query1->Eof) { CurField = Query1->FieldByName("Title")->AsString; // Поле «Title» текущей записи p1 = Query1->FieldByName("Price1")->AsString; p2 = Query1->FieldByName("Price2")->AsString; p3 = Query1->FieldByName("Price3")->AsString; descr = Query1->FieldByName("Description")->AsString; // Если значение цен в столбцах отсутствует, значит это ключевое поле // В противном случае продолжаем… if (p1 != "" || p2 != "" || p3 != "" || descr != "") { Query1->Next(); continue; } int k; k = CurField.Length(); // Отсечем возможные порядковые номера раздела if (CurField[1] == '1' || CurField[1] == '2' || CurField[1] == '3' || CurField[1] == '4' || CurField[1] == '5' || CurField[1] == '6' || CurField[1] == '7' || CurField[1] == '8' || CurField[1] == '9' || CurField[1] == '0' || CurField[1] == '.') { for (i = 1; i <= k; i++) if (CurField[i] == '1' || CurField[i] == '2' || CurField[i] == '3' || CurField[i] == '4' || CurField[i] == '5' || CurField[i] == '6' || CurField[i] == '7' || CurField[i] == '8' || CurField[i] == '9' || CurField[i] == '0' || CurField[i] == '.') { CurField = CurField.SubString(i+1, CurField.Length()); i = 0; k = CurField.Length(); } else break; } if (CurField[1] == ' ') CurField = CurField.SubString(2, CurField.Length()); // Все изменения будем делать непосредственно в таблице Src Query1->Edit(); Query1->FieldByName("Title")->AsString = CurField; Query1->Post(); // И добавим полученное значение текущего распознанного ключевого поля к списку ключевых полей KeyFields->Items->Add(CurField); Query1->Next(); } Query1->First(); sSQL = "SELECT * FROM _Components ORDER BY CategoryTableName ASC"; // Выберем из управляющей таблицы с наименованиями таблиц данных все записи, // расположенные в алфавитном порядке по наименованиям таблиц данных Query4->Close(); Query4->SQL->Clear(); Query4->SQL->Add(sSQL); Query4->Open(); // Выполним запрос // И загрузим результат запроса в список выбора Map->Items->Clear(); while (!Query4->Eof) { Map->Items->Add(Query4->FieldByName("CategoryTableName")->AsString); Query4->Next(); } Query4->First(); } //---------------------------------------------------------------------------
Поскольку функция обновления всех таблиц данных производится оператором регулярно, то перед каждым добавлением новых значений все таблицы данных необходимо очистить от старых значений. Для этого:
//--------------------------------------------------------------------------- void __fastcall TMainForm::DropAllClick(TObject *Sender) { AnsiString sSQL; if (Application->MessageBox("Данная операция очистит содержимое ВСЕХ таблиц базы. Выполнить?", "Внимание!", MB_ICONQUESTION|MB_YESNO) == IDYES) { sSQL = "SELECT * FROM _Components"; // Из всех таблиц, чье название содержится в столбце // "CategoryTableName" управляющей таблицы _Components, // удалим все данные Query3->Close(); Query3->SQL->Clear(); Query3->SQL->Add(sSQL); Query3->Open(); while (!Query3->Eof) { sSQL = "DELETE FROM " + Query3->FieldByName("CategoryTableName")->AsString; Query2->Close(); Query2->SQL->Clear(); Query2->SQL->Add(sSQL); Query2->ExecSQL(); Query3->Next(); } Application->MessageBox("Содержимое таблиц данных уничтожено!", "Готово!", 0); } } //---------------------------------------------------------------------------
После того как содержимое всех таблиц данных уничтожено, можно вставить в них новые значения:
//--------------------------------------------------------------------------- void __fastcall TMainForm::UpdateAllClick(TObject *Sender) { AnsiString gSQL, CItem, NItem, CTitle; int i; gSQL = "SELECT * FROM Src"; // Выборка всех записей таблицы — исходного прайс-листа Query2->Close(); Query2->SQL->Clear(); Query2->SQL->Add(gSQL); Query2->Open(); // Выполнение запроса i = 0; while (i < KeyFields->Items->Count) { // Для каждого ключевого поля while (!Query2->Eof) { CItem = KeyFields->Items->Strings[i]; //Начиная со следующей за ключвым полем записи (до следующего ключевого поля) if (i < KeyFields->Items->Count — 1) NItem = KeyFields->Items->Strings[i+1]; else NItem = "NItem"; CTitle = Query2->FieldByName("Title")->AsString; // Если текущее поле совпадает с текущим ключевым полем if (CTitle == CItem) { // То вставим в таблицу с названием, совпадающим с названием текущего ключевого поля, // Строку"Выберите позицию" InsRecord(CItem, "Выберите позицию", NULL, NULL, NULL, NULL); // И далее в цикле все значения до следующего ключевого поля while (CTitle != NItem && !Query2->Eof) { if (CTitle == CItem) { Query2->Next(); CTitle = Query2->FieldByName("Title")->AsString; continue; } CTitle = Replace(CTitle); InsRecord(CItem, CTitle, Query2->FieldByName("Price1")->AsFloat, Query2->FieldByName("Price2")->AsFloat, Query2->FieldByName("Price3")->AsFloat, Query2->FieldByName("Description")->AsString); Query2->Next(); CTitle = Query2->FieldByName("Title")->AsString; } i++; Query2->First(); break; } else Query2->Next(); } } Application->MessageBox("Содержимое таблиц данных обновлено!", "Готово!", 0); } //---------------------------------------------------------------------------
Теперь разберемся с функциями InsRecord и Replace. Функция InsRecord, по сути, генерирует SQL-строку для ввода значений в заданную таблицу базы данных:
//--------------------------------------------------------------------------- void TMainForm::InsRecord(AnsiString TName, // Название таблицы AnsiString Title, // Наименование позиции float Price1, float Price2, float Price3, // Значения цен AnsiString Description) // Строка с дополнительным описанием { AnsiString Str, tmp, dig; Str = "INSERT INTO " + TName; Str = Str + "(Title, Price1, Price2, Price3, Description)"; Str = Str + " VALUES"; Текущее значение не должно содержать символов кавычек for (int j = 1; j<= Title.Length(); j++) if (Title[j] == '\'') Title[j] = ' '; Str = Str + "('" + Title; Str = Str + "', "; // Прежде чем вставить численные значения, заменим в строках символы «,» на символы «.» dig = ""; tmp = AnsiString (Price1); for (int j = 1; j <= tmp.Length(); j++) { if (tmp[j] == ',') { tmp[j] = '.'; dig = dig + tmp[j]; if (tmp.Length() >= j+1) dig = dig + tmp[j+1]; if (tmp.Length() >= j+2) dig = dig + tmp[j+2]; break; } dig = dig + tmp[j]; } Str = Str + dig; Str = Str + ", "; dig = ""; tmp = AnsiString (Price2); for (int j = 1; j <= tmp.Length(); j++) { if (tmp[j] == ',') { tmp[j] = '.'; dig = dig + tmp[j]; if (tmp.Length() >= j+1) dig = dig + tmp[j+1]; if (tmp.Length() >= j+2) dig = dig + tmp[j+2]; break; } dig = dig + tmp[j]; } Str = Str + dig; Str = Str + ", "; dig = ""; tmp = AnsiString (Price3); for (int j = 1; j <= tmp.Length(); j++) { if (tmp[j] == ',') { tmp[j] = '.'; dig = dig + tmp[j]; if (tmp.Length() >= j+1) dig = dig + tmp[j+1]; if (tmp.Length() >= j+2) dig = dig + tmp[j+2]; break; } dig = dig + tmp[j]; } Str = Str + dig; Str = Str + ", '"; Str = Str + Description + "')"; Query3->Close(); Query3->SQL->Clear(); Query3->SQL->Add(Str); Query3->ExecSQL(); // И выполним SQL-запрос } //---------------------------------------------------------------------------
Заметьте, что в случаях, когда результат выполнения запроса не формирует данные, вместо Query3->Open() применяется Query3->ExecSQL().
Функция же Replace попросту подменяет в строке один символ другим:
//--------------------------------------------------------------------------- AnsiString TMainForm::Replace(AnsiString S) { for (int i = 1; i <= S.Length(); i++) if (S[i] == '#') S[i] = ' '; return S; } //---------------------------------------------------------------------------
Еще одна полезная функция (без которой не представляет себе жизни бухгалтерия) предоставляет возможность умножения всех цен на задаваемый коэффициент (очень часто используется для «накрутки» цен). Давайте реализуем эту возможность:
//--------------------------------------------------------------------------- void __fastcall TMainForm::MulByFactorBtnClick(TObject *Sender) { AnsiString Str, sSQL; double factor; // Для начала проверим, ввел ли пользователь корректное значение try { factor = Factor->Text.ToDouble(); } catch(Exception &E) { // Если нет, то выдадим предупреждение и прекратим дальнейшие вычисления Str = "Допустимы только численные значения множителя!\n" + Factor->Text + " — неверный формат числа с плавающей точкой."; Application->MessageBox(Str.c_str(), "Ошибка!", MB_ICONERROR|MB_OK); return; } // Уверен ли пользователь? Str = "Данная операция умножит значения ВСЕХ столбцов \n с ценами таблиц данных базы на "; Str = Str + Factor->Text + ". Выполнить?"; if (Application->MessageBox(Str.c_str(), "Внимание!", MB_ICONQUESTION|MB_YESNO) == IDYES) { // Если да, то прочитаем значение фактора умножения в строке текста // И заменим в ней символ ',' на '.' Str = Factor->Text; for (int i = 1; i <= Str.Length(); i++) if (Str[i] == ',') Str[i] = '.'; sSQL = "SELECT * FROM _Components"; // Далее во всех таблицах с данными (чьи имена хранятся в таблице "_Components") Query3->Close(); Query3->SQL->Clear(); Query3->SQL->Add(sSQL); Query3->Open(); // Обновим значения цен while (!Query3->Eof) { sSQL = "UPDATE " + Query3->FieldByName("CategoryTableName")->AsString + " SET "; sSQL = sSQL + "Price1 = Price1 * " + Str + ", "; sSQL = sSQL + "Price2 = Price2 * " + Str + ", "; sSQL = sSQL + "Price3 = Price3 * " + Str; Query2->Close(); Query2->SQL->Clear(); Query2->SQL->Add(sSQL); Query2->ExecSQL(); Query3->Next(); } Application->MessageBox("Содержимое таблиц данных пересчитано!", "Готово!", 0); } } //---------------------------------------------------------------------------
И наконец, функция сохранения текущей конфигурации, по сути, будет записывать состояние указателей во всех таблицах данных в управляющую таблицу _PCType:
//--------------------------------------------------------------------------- void TMainForm::SaveConfiguration() { ConfTB->Edit(); ConfTB->FieldByName("MainBoardID")->AsInteger = MB->KeyValue; ConfTB->FieldByName("MainBoardNum")->AsInteger = MBPrc->Text.ToInt(); ConfTB->FieldByName("CPUID")->AsInteger = CPU->KeyValue; ConfTB->FieldByName("CPUNum")->AsInteger = CPPrc->Text.ToInt(); ConfTB->FieldByName("RAMID")->AsInteger = RAM->KeyValue; ConfTB->FieldByName("RAMNum")->AsInteger = RAMPrc->Text.ToInt(); ConfTB->FieldByName("SVGAID")->AsInteger = SVGA->KeyValue; ConfTB->FieldByName("SVGANum")->AsInteger = SVPrc->Text.ToInt(); ConfTB->FieldByName("IDEHDDID")->AsInteger = IDEH->KeyValue; ConfTB->FieldByName("IDEHDDNum")->AsInteger = IDPrc->Text.ToInt(); ConfTB->FieldByName("SCSIHDDID")->AsInteger = SCSIH->KeyValue; ConfTB->FieldByName("SCSIHDDNum")->AsInteger = SCPrc->Text.ToInt(); ConfTB->FieldByName("SBID")->AsInteger = Sound->KeyValue; ConfTB->FieldByName("SBNum")->AsInteger = SBPrc->Text.ToInt(); ConfTB->FieldByName("KBID")->AsInteger = KBD->KeyValue; ConfTB->FieldByName("KBNum")->AsInteger = KBPrc->Text.ToInt(); ConfTB->FieldByName("MOUSEID")->AsInteger = Mouse->KeyValue; ConfTB->FieldByName("MouseNum")->AsInteger = MouPrc->Text.ToInt(); ConfTB->FieldByName("CASEID")->AsInteger = Case->KeyValue; ConfTB->FieldByName("CaseNum")->AsInteger = CSPrc->Text.ToInt(); ConfTB->FieldByName("MONITORID")->AsInteger = Monitor->KeyValue; ConfTB->FieldByName("MONITORNum")->AsInteger = MonPrc->Text.ToInt(); ConfTB->FieldByName("SpeakerID")->AsInteger = Speakers->KeyValue; ConfTB->FieldByName("SpeakerNum")->AsInteger = SPKPrc->Text.ToInt(); ConfTB->FieldByName("FaxID")->AsInteger = Fax->KeyValue; ConfTB->FieldByName("FaxNum")->AsInteger = FaxPrc->Text.ToInt(); ConfTB->FieldByName("FDDID")->AsInteger = FDD->KeyValue; ConfTB->FieldByName("FDDNum")->AsInteger = FDDPrc->Text.ToInt(); ConfTB->FieldByName("CdromID")->AsInteger = CDROM->KeyValue; ConfTB->FieldByName("CDRomNum")->AsInteger = CDPrc->Text.ToInt(); ConfTB->FieldByName("PadID")->AsInteger = Pad->KeyValue; ConfTB->FieldByName("PadNum")->AsInteger = PadPrc->Text.ToInt(); ConfTB->Post(); } //---------------------------------------------------------------------------
Теперь поговорим о событиях. Фактически интегральная стоимость всего набора должна пересчитываться всякий раз, когда выбирается позиция из любого списка выбора или вводится значение в поле — указатель количества единиц. В первом случае для каждого из списков выбора необходимо написать функцию, которая будет отрабатываться всякий раз, как только из соответствующего списка выбора позиции будет выбираться значение:
//--------------------------------------------------------------------------- void __fastcall TMainForm::FaxCloseUp(TObject *Sender) { CalcTotalPrice(); } //---------------------------------------------------------------------------
А во втором случае пересчет интегральной стоимости целесообразно выполнить непосредственно после того, как оператор переместит фокус ввода в другой компонент интерфейса программы (используя для этого сообщение OnExit):
//--------------------------------------------------------------------------- void __fastcall TMainForm::CPPrcExit(TObject *Sender) { CalcTotalPrice(); } //---------------------------------------------------------------------------
Приложение-клиент к базам данных в нашем случае является инструментом подготовки базы данных. Им будут пользоваться операторы. А поскольку операторы могут ошибаться, рекомендуется свести возможность допущения таких ошибок к минимуму. Для этого необходимо спроектировать интерфейс программы весьма тщательным образом, по возможности минимизируя действия, выполняемые оператором при редактировании и пополнении базы данных.
Вот собственно и все. Откомпилируем и запустим полученное приложение, которое будет выглядеть следующим образом: первая вкладка («Прайс процессор») — рис. 15 и. вторая вкладка («ПК конфигуратор») — рис. 16.
![]() |
Существует множество Интернет-магазинов. От большинства из них требуется мгновенная реакция на изменение ценовой политики, перечня товаров или услуг. А все это невозможно без надежных средств автоматизации обработки огромного массива данных. Ведь перечень позиций в некоторых крупных Интернет-супермаркетах переваливает за 2-3 тысячи, а количество ежечасных транзакций (обращений) зачастую намного больше. Здесь и от сервера, и от серверной СУБД требуется недюжинное быстродействие (обычным настольным ПК и Access’ом здесь не обойтись). Да и сам ASP-код должен быть написан таким образом, чтобы исключить неоправданные циклы, задержки или лишние обращения к базе данных. ASP-код, по сути, является лишь средством визуализации содержимого базы данных, позволяющим клиентам приобретать или заказывать товары или услуги. Вся «подводная часть» айсберга манипуляций с базой данных (автоматизация обновления, редактирования, реструктуризации и т.д.) ложится именно на вспомогательные средства. Разработка таких средств требует, как правило, гораздо больше времени и сил, чем разработка самих магазинов, хотя в последнее время все чаще и чаще под последними профессионалы подразумевают именно симбиоз собственно магазинов, и средств их поддержки, профилактики, наполнения, систематизации и т.д.
Полный архив исходных текстов и проекта программы к настоящей статье лежит здесь.
Автор выражает благодарность Архангельскому А.Я. за материалы, предоставленные для подготовки настоящей статьи.
КомпьютерПресс 1'2001