Здраствуйте. Есть проблема , может кто занимался или есть мысли в каком напрвлении двигаться — подскажите.
Пишется 3-звенная прилага (Delphi6-Delphi6-MSSQL2000). Связь с СП через TSocketConnection, с MSSQL — через BDE.
Необходимо организовать загрузку большого объема данных (много записей). Так как скорость критична, желательно скачивать данные порциями, но плавно (не заметно для клиента) — то ли как-то реагировать на прокрутку, то ли еще что...
Заранее спасибо за подсказки
Здравствуйте, bmv, Вы писали:
bmv>Здраствуйте. Есть проблема , может кто занимался или есть мысли в каком напрвлении двигаться — подскажите. bmv>Пишется 3-звенная прилага (Delphi6-Delphi6-MSSQL2000). Связь с СП через TSocketConnection, с MSSQL — через BDE. bmv>Необходимо организовать загрузку большого объема данных (много записей). Так как скорость критична, желательно скачивать данные порциями, но плавно (не заметно для клиента) — то ли как-то реагировать на прокрутку, то ли еще что... bmv>Заранее спасибо за подсказки
1. Зачем клиенту большой объем данных сразу?
2. "Данные закачиваются порциями" = TClientDataSet.PacketRecords
a. PacketRecords is automatically set to -1, meaning that a single packet should contain all records in the dataset.
b. If PacketRecords is greater than zero, then it specifies the number of records to return in a packet.
c. To retrieve the metadata for a dataset, set PacketRecords to 0. When PacketRecords is zero, the provider returns only information from its dataset that defines the database’s structure, such as table, column, constraint, and domain definitions.
3. "как-то реагировать на прокрутку" (не знаю, где прокрутка, на вскидку) TClientDataSet.DisableControls/TClientDataSet.EnableControls
4. Мастер-детаил при больших объемах данных лучше строить на клиенте т.к. для каждой записи мастера выполняется запрос, упаковываются данные в пакет и пересылаются клиенту. Лучше 2 запросами все вытянуть и связать на клинте.
5. "с MSSQL — через BDE" используй ADO.
P.S. Хорошенько подумай над пунктом 1.
Здравствуйте, bmv, Вы писали:
bmv>Необходимо организовать загрузку большого объема данных (много записей). Так как скорость критична, желательно скачивать данные порциями, но плавно (не заметно для клиента) — то ли как-то реагировать на прокрутку, то ли еще что...
А зачем? И исчо, имей в виду, при такой архитектуре масштабируемость приложения падает, ведь по сути на клиент держит подключение к БД. Оптимально подключился к БД, отсосал данные-отключился. И большой набор данных тоже надо давить, пусть ользователь более конкретные критерии поиска вводит.
LG>5. "с MSSQL — через BDE" используй ADO.
Поддерживаю на все 100
Здравствуйте, LG, Вы писали:
LG>1. Зачем клиенту большой объем данных сразу?
Скажу по другому Есть большой перечень к примеру деталей. Надо чтобы пользователь мог свободно передвигаться по нему вверх и вниз. Задание фильтров и поиск — это уже другое дело, но надо обеспечить пользователю свободу перемещения по списку. Замечу — быстрое перемещение.
LG>2. "Данные закачиваются порциями" = TClientDataSet.PacketRecords
Про это знаю, но к сожалению у меня провайдер не статичный. В любом случае это не до конца решает проблему VVVVV
LG>3. "как-то реагировать на прокрутку" (не знаю, где прокрутка, на вскидку) TClientDataSet.DisableControls/TClientDataSet.EnableControls
Имею ввиду что если искусственно закачивать данные порциями (экранами), то возникает проблема с полосой прокрутки...
Вот если бы что-то по типу как это делает сам MSSQL Enterprise Manager, когда открывает большие таблицы...
LG>5. "с MSSQL — через BDE" используй ADO.
Хорошо было бы ... да нельзя
В любом случае, спасибо за ответ. Может кто еще что посоветует...
Здравствуйте, bmv, Вы писали:
bmv>Здравствуйте, LG, Вы писали:
LG>1. Зачем клиенту большой объем данных сразу? bmv>Скажу по другому Есть большой перечень к примеру деталей. Надо чтобы пользователь мог свободно передвигаться по нему вверх и вниз. Задание фильтров и поиск — это уже другое дело, но надо обеспечить пользователю свободу перемещения по списку. Замечу — быстрое перемещение.
Это маразм. Действительно, какой толк ходить по списку из 10000 записей? намного правильнее при проектировании системы редусмотреть толковую фильтрацию.
LG>5. "с MSSQL — через BDE" используй ADO. bmv> Хорошо было бы ... да нельзя
Здравствуйте, bmv, Вы писали:
LG>>3. "как-то реагировать на прокрутку" (не знаю, где прокрутка, на вскидку) TClientDataSet.DisableControls/TClientDataSet.EnableControls bmv>Имею ввиду что если искусственно закачивать данные порциями (экранами), то возникает проблема с полосой прокрутки... bmv>Вот если бы что-то по типу как это делает сам MSSQL Enterprise Manager, когда открывает большие таблицы...
Вопрос порционной(постраничной) выборки тесно связан с вопросом нумерации строк в запросе (см. FAQ.Нумерация записей в запросе). Другими словами, для того, чтобы выбрать N-ую порцию из результатов запроса, нужно сначала пронумеровать результаты этого запроса.
Отсюда и похожие методы решения
Вариант 1 «Классический».
SELECT TOP 100 * FROM MyTable
WHERE id NOT IN (SELECT TOP 100 id FROM MyTable ORDER BY id) ORDER BY id
Главный недостаток этого метода в в том, что т.к. TOP n записей выбираются уже из конечного результата запроса, то проверка условия WHERE будет выполняться для каждой строки главного запроса. При этом, время выполнения этой проверки будет расти вместе с номером выбираемой порции(страницы). Если, например, таблица содержит 100 записей и необходимо выбирать данные порциями по 10 записей, то
для 2-ой порции нужно будет будет проверять подзапрос из 10 записей
для 3-ей порции нужно будет будет проверять подзапрос из 20 записей
для 4-ой порции нужно будет будет проверять подзапрос из 30 записей и т.д.
Достоинство метода в его универсальности, академичности. Он не требует специфики T-SQL, этот метод можно применить практически на любом SQL-сервере.
Вариант 2 «Эффективный, специфический для T-SQL».
Как и в случае нумерации строк данный метод основан на использовании временной таблицы. Для удобства оформим наш запрос как хранимую процедуру, возвращающую n-ую порцию(страницу), содержащую m записей
CREATE PROCEDURE dbo.get_this_page (@rec_per_page int, @page_num int) AS
SELECT identity(int, 1,1) AS RowNum, MyId AS OrigId INTO #tmp FROM mytable
SELECT b.* FROM #tmp AS a
INNER JOIN mytable AS b on a.OrigId = b.MyId
WHERE a.RowNum BETWEEN (@rec_per_page * @page_num + 1)
AND (@rec_per_page * (@page_num + 1))
DROP TABLE #tmp
Прмечания.
— предложенный вариант процедуры будет блокировать базу tempdb на все время выполнения 1-го запроса. Если время блокировки становиться неприемлимым, то необходимо разбить этот запрос таким образом
CREATE TABLE #temp(RowNum int identity, OrigId int)
INSERT INTO #temp(OrigId) SELECT MyId FROM mytable
— Если, поле MyId было создано признаком «IDENTITY», то это поле в запросе необходимо «завернуть» в функцию «CONVERT», иначе будет сообщение об ошибке.
Glory
Здравствуйте, dimzon, Вы писали:
D>Это маразм. Действительно, какой толк ходить по списку из 10000 записей? намного правильнее при проектировании системы редусмотреть толковую фильтрацию.
Предложи, как это будет выглядеть визуально. Если выдавать окошко "Задайте фильтр", то фильтр-то могут задать и такой, что в него все равно попадет очень много записей — В таблице всего 240000...
LG>>5. "с MSSQL — через BDE" используй ADO. bmv>> Хорошо было бы ... да нельзя D>Почему?
Требование заказчика
Здравствуйте, LG, Вы писали:
LG>Здравствуйте, bmv, Вы писали:
LG>>>3. "как-то реагировать на прокрутку" (не знаю, где прокрутка, на вскидку) TClientDataSet.DisableControls/TClientDataSet.EnableControls bmv>>Имею ввиду что если искусственно закачивать данные порциями (экранами), то возникает проблема с полосой прокрутки... bmv>>Вот если бы что-то по типу как это делает сам MSSQL Enterprise Manager, когда открывает большие таблицы... LG>
LG>Вопрос порционной(постраничной) выборки тесно связан с вопросом нумерации строк в запросе (см. FAQ.Нумерация записей в запросе). Другими словами, для того, чтобы выбрать N-ую порцию из результатов запроса, нужно сначала пронумеровать результаты этого запроса.
LG>Отсюда и похожие методы решения
LG>Вариант 1 «Классический».
LG>SELECT TOP 100 * FROM MyTable
LG>WHERE id NOT IN (SELECT TOP 100 id FROM MyTable ORDER BY id) ORDER BY id
LG>Главный недостаток этого метода в в том, что т.к. TOP n записей выбираются уже из конечного результата запроса, то проверка условия WHERE будет выполняться для каждой строки главного запроса. При этом, время выполнения этой проверки будет расти вместе с номером выбираемой порции(страницы). Если, например, таблица содержит 100 записей и необходимо выбирать данные порциями по 10 записей, то
LG>для 2-ой порции нужно будет будет проверять подзапрос из 10 записей
LG>для 3-ей порции нужно будет будет проверять подзапрос из 20 записей
LG>для 4-ой порции нужно будет будет проверять подзапрос из 30 записей и т.д.
LG>Достоинство метода в его универсальности, академичности. Он не требует специфики T-SQL, этот метод можно применить практически на любом SQL-сервере.
LG>Вариант 2 «Эффективный, специфический для T-SQL».
LG>Как и в случае нумерации строк данный метод основан на использовании временной таблицы. Для удобства оформим наш запрос как хранимую процедуру, возвращающую n-ую порцию(страницу), содержащую m записей
LG>CREATE PROCEDURE dbo.get_this_page (@rec_per_page int, @page_num int) AS
LG>SELECT identity(int, 1,1) AS RowNum, MyId AS OrigId INTO #tmp FROM mytable
LG>SELECT b.* FROM #tmp AS a
LG>INNER JOIN mytable AS b on a.OrigId = b.MyId
LG>WHERE a.RowNum BETWEEN (@rec_per_page * @page_num + 1)
LG>AND (@rec_per_page * (@page_num + 1))
LG>DROP TABLE #tmp
LG>Прмечания.
LG>- предложенный вариант процедуры будет блокировать базу tempdb на все время выполнения 1-го запроса. Если время блокировки становиться неприемлимым, то необходимо разбить этот запрос таким образом
LG>CREATE TABLE #temp(RowNum int identity, OrigId int)
LG>INSERT INTO #temp(OrigId) SELECT MyId FROM mytable
LG>- Если, поле MyId было создано признаком «IDENTITY», то это поле в запросе необходимо «завернуть» в функцию «CONVERT», иначе будет сообщение об ошибке.
LG>Glory
Спасибо за развернутый ответ, все это, конечно, здорово, только все-таки как решить при этом вопрос с прокруткой данных пользователем
Здравствуйте, dimzon, Вы писали:
D>Здравствуйте, LG, Вы писали:
LG>>Здравствуйте, bmv, Вы писали:
D>
LG>>Вариант 1 «Классический».
D><опущено>
D>Можно еще использовать: D>
D>Сначала выбираем на клиента все первичные ключи — объем будет невелик ибо в выборке будут только ключи.
D>потом за каждой страницей(блоком) делаем select * from xxx where id in (<список идентификаторов>)
В таблице 240000 записей, причем каждая является уникальной (как я уже говорил, перечень деталей). Мне не очень охота хранить в оперативной памяти 240000 ключей, даже на сервере приложений.
bmv>В таблице 240000 записей, причем каждая является уникальной (как я уже говорил, перечень деталей).
И что, они все будут удовлетворять условию поиска?
И второй вопрос, что пользователь будет делать с такой необъятной таблицей? Глазами нужную искать? Повторюсь — ЗАДУМАЙТЕСЬ О ХОРОШЕЙ ФИЛЬТРАЦИИ, ДАВАТЬ ПОЛЬЗОВАТЕЛЮ ТАКУЮ ПРОСТЫНЮ ЭТО МАРАЗМ
bmv>Мне не очень охота хранить в оперативной памяти 240000 ключей, даже на сервере приложений.
Гы. Простая математика. Пусть у нас есть искуственный первичный ключ IDENTITY. В памяти один первичный ключ занимает 4 байта. (240000 * 4)/1024=937,5 килобайт < 1Мб.
Почему нельзя это счастье забрать на клиетна, разве один мегабайт это так много?
... << RSDN@Home 1.0 beta 7a >>
Re[7]: Загрузка большого объема данных
От:
Аноним
Дата:
25.06.03 10:53
Оценка:
Здравствуйте, dimzon, Вы писали:
D>Здравствуйте, bmv, Вы писали:
bmv>>В таблице 240000 записей, причем каждая является уникальной (как я уже говорил, перечень деталей). D>И что, они все будут удовлетворять условию поиска? D>И второй вопрос, что пользователь будет делать с такой необъятной таблицей? Глазами нужную искать? Повторюсь — ЗАДУМАЙТЕСЬ О ХОРОШЕЙ ФИЛЬТРАЦИИ, ДАВАТЬ ПОЛЬЗОВАТЕЛЮ ТАКУЮ ПРОСТЫНЮ ЭТО МАРАЗМ
Я с этим согласен, пользователю все это не нужно... Только я не могу понять, как будет в Вашем случае интерфейс пользователя выглядеть. Вот пользователь открывает окно и...
Я вижу два варианта: 1 — уже что-то ему показать (что я и пытаюсь сделать), однако этот вариант предполагает, что он может двигаться по списку, не задавая фильтров.
2 — выскакивает окно с просьбой задать фильтр. Однако здесь он может задать такой фильтр, который нас и не спасет вовсе от большого списка.
Может я что-то не вижу? Какие есть еще варианты?
D>Гы. Простая математика. Пусть у нас есть искуственный первичный ключ IDENTITY. В памяти один первичный ключ занимает 4 байта. (240000 * 4)/1024=937,5 килобайт < 1Мб. D>Почему нельзя это счастье забрать на клиетна, разве один мегабайт это так много?
Подсчет конечно верный, только что нам дает хранение этого счастья? Кроме занимаемого 1 Мб ОП (который отнюдь не лишний), по-моему, ничего. Как правило данные отображаемые пользователю отсортированы вовсе не по колонке IDENTITY. И вообще, зачем хранить полный список, если у отображаемой нами порции данных всегда можно узнать начальный ключ и конечный ключ и, соответственно, загрузить следующую необходимую порцию.
Здравствуйте, <Аноним>, Вы писали:
А>Здравствуйте, dimzon, Вы писали:
D>Здравствуйте, bmv, Вы писали:
bmv>>В таблице 240000 записей, причем каждая является уникальной (как я уже говорил, перечень деталей). D>И что, они все будут удовлетворять условию поиска? D>И второй вопрос, что пользователь будет делать с такой необъятной таблицей? Глазами нужную искать? Повторюсь — ЗАДУМАЙТЕСЬ О ХОРОШЕЙ ФИЛЬТРАЦИИ, ДАВАТЬ ПОЛЬЗОВАТЕЛЮ ТАКУЮ ПРОСТЫНЮ ЭТО МАРАЗМ А>Я с этим согласен, пользователю все это не нужно... Только я не могу понять, как будет в Вашем случае интерфейс пользователя выглядеть. Вот пользователь открывает окно и...
---------------------------------------------------------------
| Здесь поля фильтра и кнопка "Обновить" |
---------------------------------------------------------------
| |
| Здесь злосчастная таблица. При открытии вместо нее надпись: |
| Заполните фильтр и нажмите обновить |
| |
| При отсутствии данных, удовлетворяющих фильтру надпись: |
| "Не найденно ни одной записи, удовлетворяющей условию" |
| |
---------------------------------------------------------------
А>Однако здесь он может задать такой фильтр, который нас и не спасет вовсе от большого списка.
Надо искуственно ограничить размер выборки скажем 500-и строк. Тогда пишем такой запрос:
SET ROWCOUNT 501
SELECT ... И понеслась...
SET ROWCOUNT 0
И смотрим сикоко строк вернулось, если 501 то все равно показываем 500 и где-нить на форме выводим предупреждение: "Показаны первые 500 записей. если надо что-то конкретное ужесточите условия поиска"
D>Гы. Простая математика. Пусть у нас есть искуственный первичный ключ IDENTITY. В памяти один первичный ключ занимает 4 байта. (240000 * 4)/1024=937,5 килобайт < 1Мб. D>Почему нельзя это счастье забрать на клиетна, разве один мегабайт это так много? А>Подсчет конечно верный, только что нам дает хранение этого счастья? Кроме занимаемого 1 Мб ОП (который отнюдь не лишний), по-моему, ничего. Как правило данные отображаемые пользователю отсортированы вовсе не по колонке IDENTITY. И вообще, зачем хранить полный список, если у отображаемой нами порции данных всегда можно узнать начальный ключ и конечный ключ и, соответственно, загрузить следующую необходимую порцию.
Ну во первых можно получить список идентификатров уже отсортированный как надо (select ProductID from dbo.Product order by ProductName), Во вторых это "облегчает" выполнение повторных запросов, ведь фильрация может быть далеко не тривиальной, а так один раз уже отфильтровано и при повторных запросов поиск происходит просто по значению первичного ключа что для SQL расплюнуть.
Здравствуйте, bmv, Вы писали:
bmv>Спасибо за развернутый ответ, все это, конечно, здорово, только все-таки как решить при этом вопрос с прокруткой данных пользователем
Так это, вроде как надо свой класс — Грида переопределять, в нем с полосой прокрутки работать.
Не знаю может для дельфы есть готовые решения?
Мы на С++ свой грид делали.
Суть алгоритма — сразу закачивать строк, превышающих, отобрражаемое в гриде, и сверху и снизу, коэффициент зависит от Вас, какой выберете. Далее по мере приближения к краю списка строк пользователем при прокрутки, покачивать строки со стороны, в которую пользователь продвинулся, а сдругой стороны отгружать. Опять таки следует выбрать коэффициент — количество буферных строк, при котором не осуществлять подкачку, чтобы она не была слишком частой, а то пользователь сместиться на одну строку вперед, а потом назад, а программа каждый раз будет подкачку осуществлять
И не слушайте dimson-а. Заладил фильтраци, фильтрация.
То, что Вы делаете есть более красивое решение, а фильрация это отдельная тема.
Здравствуйте, Vitaton, Вы писали:
V>Здравствуйте, bmv, Вы писали:
bmv>>Спасибо за развернутый ответ, все это, конечно, здорово, только все-таки как решить при этом вопрос с прокруткой данных пользователем
V>Так это, вроде как надо свой класс — Грида переопределять, в нем с полосой прокрутки работать.
А почему бы вместо этого не использовать paging-представление? Для показа большого гомогенных данных это явно напрашивается. Да и в реализации вероятно будет проще, чем мудрить с полосами прокрутки.
Здравствуйте, Vitaton, Вы писали:
V>Здравствуйте, bmv, Вы писали:
V>Так это, вроде как надо свой класс — Грида переопределять, в нем с полосой прокрутки работать. V>Не знаю может для дельфы есть готовые решения?
TClientDataSet вообще-то слинкованный с обычным DataGrid работает
V>И не слушайте dimson-а. Заладил фильтраци, фильтрация. V>То, что Вы делаете есть более красивое решение, а фильрация это отдельная тема.
Это НЕ более красивое решение! Это НЕРАЦИОНАЛЬНОЕ решение, к тому-же слабо масштабируемое! Нафиг пользователю в данном как минимум эту дурацкую простыню из 240000 записей видеть не надо! Проектировать надо систему грамотно вместо того чтобы извращаться при реализации бредовой идеи. А то получается (в данном случае)
"мы сделаем чтобы пользователь мог быстро листать охрененную простыню и визуально искать что ему нужно(при этом сами встанем раком реализуя скорость и сервер посадим тоже)"
вместо того чтобы:
"мы сделаем чтобы пользователь мог легко и просто найти нужную запись введя критерии фильтрации"
Здравствуйте, dimzon, Вы писали:
D>Надо искуственно ограничить размер выборки скажем 500-и строк. Тогда пишем такой запрос:
Так ведь об этом то и речь! Только зачем просить его ужесточить условия поиска если можно в фоновом режиме подгрузить следующие записи если ему так приспичило ходить по большой выборке. И здесь мы как раз получаем проблемы со ScrollBar.
D>Ну во первых можно получить список идентификатров уже отсортированный как надо (select ProductID from dbo.Product order by ProductName), Во вторых это "облегчает" выполнение повторных запросов, ведь фильрация может быть далеко не тривиальной, а так один раз уже отфильтровано и при повторных запросов поиск происходит просто по значению первичного ключа что для SQL расплюнуть.
И чем мне поможет, если у меня будет перечень ИД, отсортированных в каком-то там порядке? В очередном запросе для выборки 500 записей я должен буду перечислить 500 ИД? В любом случае придется повторять условие сортировки по-моему.
MX>А почему бы вместо этого не использовать paging-представление? Для показа большого гомогенных данных это явно напрашивается. Да и в реализации вероятно будет проще, чем мудрить с полосами прокрутки.
А что это такое и с чем его едят?
Здравствуйте, dimzon, Вы писали:
V>>Не знаю может для дельфы есть готовые решения? D>TClientDataSet вообще-то слинкованный с обычным DataGrid работает
И как в делфийских гридах можно устанавливать, какую часть DataSet'а следует просматривать? Речь идет о точной установке с какой по какую запись. Это-то по большому счету меня и интересует.
Здравствуйте, Vitaton, Вы писали:
V>Так это, вроде как надо свой класс — Грида переопределять, в нем с полосой прокрутки работать. V>Не знаю может для дельфы есть готовые решения? V>Мы на С++ свой грид делали. V>Суть алгоритма — сразу закачивать строк, превышающих, отобрражаемое в гриде, и сверху и снизу, коэффициент зависит от Вас, какой выберете. Далее по мере приближения к краю списка строк пользователем при прокрутки, покачивать строки со стороны, в которую пользователь продвинулся, а сдругой стороны отгружать. Опять таки следует выбрать коэффициент — количество буферных строк, при котором не осуществлять подкачку, чтобы она не была слишком частой, а то пользователь сместиться на одну строку вперед, а потом назад, а программа каждый раз будет подкачку осуществлять
Во-во, вот эти решения меня больше всего и интересуют. К сожалению свой грид писать — очень много времени потерять (если, конечно, хороший). Я этого в данный момент себе позволить не могу.
В любом случае придется или сделать как советует dimzon, или кто-то еще что скажет.