ListView и разбиение данных на порции (ч.1) |
20.07.2009 Leonardo M. Rame |
Иногда возникает ситуация, когда необходимо просмотреть довольно большой объём данных собственными глазами. Например в 1С при несвоевременном обнаружении бухгалтерами косяков в начислении заработной платы. :-) Глаз замылиливается при просмотре и ошибку удаётся найти не всегда.
Леонардо предложил очень хороший способ - разбиение всего большого объёма данных на порции, который не зависит от среды разработки, а использует особенности построения запроса к БД.
Это одна из тех вещей, в которых я нуждался довольно давно, но не решался сделать, т.к. думал, что потребуется излишне большой объем работы и поэтому выбирал менее взыскательные методы.
Речь пойдёт о методе, который возвращает данные по порциям, чтобы затем представить их в ListView незаметно для пользователя.
Я знаю, что TDbGrid в Delphi позволяет делать нечто подобное, когда подключена к базе данных через ADO, но как найти общий метод постраничного просмотра данных, независимо от механизма подключения к СУБД?
Но не всё так плохо... :-)
Давайте создадим новое приложение, которое содержит соединение с базой данных, набор данных и транзакцию. Я предполагаю, что Вы знаете, как создавать подключения к базам данных, наборы данных и как делать запросы.
Примечание переводчика: | Здесь в качестве TDataSet используется компонент SQLQuery, который входит в стандартный комплект Lazarus и позволяет делать запрос к базам данных различных типов. В качестве базы данных используется БД типа SQLite3, дабы не привязывать пример к серверу и потому что SQLite3 (в отличие от Firebird последних версий) не требует большого количества файлов для обслуживания БД. :-) |
В БД у нас есть таблицы с названием Sol и Rus, с более чем полутора тысячами записей. У таблиц есть следующие поля:
Таблица Sol | Таблица Rus | Тип поля |
---|---|---|
Id | Id | Integer |
Sol_id | Integer | |
Name | Name | Varchar |
Текст основного запроса:
SELECT rus.id, sol.name, rus.name FROM rus INNER JOIN sol ON rus.sol_id=sol.id ORDER BY sol.name, rus.name
Положите TListView на форму, затем установите следующие свойства:
ViewStyle := vsReport Columns := (3 колонки: №№, Исполнитель, Песня)
В событии создания формы напишем следующий код:
procedure TForm1.FormCreate(Sender: TObject); begin //Определяем общее количество записей SQLQuery1.SQL.Clear; SQLQuery1.SQL.Text:='SELECT COUNT(*) FROM rus'; SQLQuery1.Open; FAllRecords:=SQLQuery1.Fields[0].AsInteger; SQLQuery1.Close; //Основной запрос SQLQuery1.SQL.Clear; SQLQuery1.SQL.Add('SELECT rus.id, sol.name, rus.name'); SQLQuery1.SQL.Add('FROM rus INNER JOIN sol'); SQLQuery1.SQL.Add('ON rus.sol_id=sol.id'); SQLQuery1.SQL.Add('LIMIT '+cPageSize+' OFFSET :n'); SQLQuery1.Params[0].AsInteger:=0; SQLQuery1.Open; GetCurrentPage(dqCurr); end;
То, что я хочу сделать, делит данные по 100 записей на порцию, затем показывает по одной порции за раз. Запрос только по 100 записей позволяет практически мгновенно показывать первую порцию данных, далее пользователь просматривает следующие 100 записей, повторно сделав запрос к базе данных для записей 101 - 200 и так далее.
Почти у каждого механизма СУБД есть метод, который выполняет запрос данных частями - у MySql есть "LIMIT nn TO mm", у Firebird есть "ROWS nn TO mm", у MsSql есть "TOP", у SQLite3 есть "LIMIT nn OFFSET mm" и т.д.
Итак, я добавлю новый метод в свою программу, чтобы запрос получал только заданную страницу:
procedure TForm1.GetCurrentPage(Dir: TDirQuery); Var i: Integer; Begin //В зависимости от параметра процедуры определяем с какого номера записи проводить выборку Case Dir of dqPrev: If SQLQuery1.Params[0].AsInteger>=cPageSize Then SQLQuery1.Params[0].AsInteger:=SQLQuery1.Params[0].AsInteger-cPageSize; dqNext: If SQLQuery1.Params[0].AsInteger<=FAllRecords-cPageSize Then SQLQuery1.Params[0].AsInteger:=SQLQuery1.Params[0].AsInteger+cPageSize; End; SQLQuery1.Close; SQLQuery1.Open; ListView1.Items.Clear; //Заполняем полученой порцией данных компонент отображения For i:=0 To SQLQuery1.RecordCount-1 Do Begin ListView1.Items.Add; ListView1.Items[i].Caption:=SQLQuery1.Fields[0].AsString; ListView1.Items[i].SubItems.Add(SQLQuery1.Fields[1].AsString); ListView1.Items[i].SubItems.Add(SQLQuery1.Fields[2].AsString); SQLQuery1.Next; End; End;
Добавьте константу "const cPageSize = 100;" в секцию модуля "implementation". Также добавьте скрытый атрибут "FAllRecords: integer;" в private секции формы.
В текст запроса добавим параметр n определяющий, с какого номера записи получать данные.
Я надеюсь, что вы обрадованы этим так же как и я, когда закончил писать. :-) В следующей части статьи я переделаю эту версию, используя TCollections вместо TDataSets так же, как обычно делаю в своих проектах.
Пример 1: listview1.s3.7z