Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Базы данных и репортинг > Медленно работает


Автор: MihailCarib 15.11.2010, 21:48
Здравствуйте! Работаю с БД Access через ADO. Данные получаю в таблицу с внешнего носителя. Вид данных - сильно замусоренные. Для фильтрации ввожу линейный фильтр с окном по 10 значениям, паолученные данные кидаю в другую табличку в той же базе. Обе таблички работают на разных ADOConnection DBGrid не подключен. Все работает, но получается, что 2-2,5 тысячи записей он обсчитывает крайне медленно. Работать с массивом гораздо быстрее, но опять-таки, в массив данные из БД еще надо записать, а это секунд 5, не меньше. В то же время, простой Excel делает все так, что заметить не успеваешь. Как мой код можно оптимизировать, что я делаю не так? Привожу пример функции
Код

procedure Filter(Sender: TObject);
var i,j,records:integer;
sum_X,sum_Y,sum_Z,sum_A:real;
begin
Screen.Cursor:=crHourGlass;
records:= Form8.ADOQuery1.RecordCount; //первоначально записей в базе

//все хранится в базе filter
// трем базу
    try
      Zapros:='DELETE * FROM Filter';
      form8.ADOQuery2.SQL.Clear;
      form8.ADOQuery2.SQL.Add(Zapros);
      form8.ADOQuery2.ExecSQL;

      Zapros:= 'SELECT * FROM Filter';
      form8.ADOQuery2.SQL.Clear;
      form8.ADOQuery2.SQL.Add(Zapros);
      form8.ADOQuery2.Active:=True;
    except
    on e:Exception do
    end;
 //обратно выводим табличку

Zapros:= 'SELECT * FROM '+ IntToStr(Card_num)+' Order by №';
  try
     form8.ADOQuery2.SQL.Clear;
     form8.ADOQuery2.SQL.Add(Zapros);
     form8.ADOQuery2.Active:=True;
  except
    on e:Exception do
    MessageDlg('Не могу подключиться к базе данных!', mtError, [mbOk], 0);
  end;
//теперь задаем смещение и окно
for i:=0 to  records-10 do
begin //1
     sum_X:=0;
     sum_Y:=0;
     sum_Z:=0;
     sum_A:=0;
     for j:=i to (i+9) do  //9 - окно, ровно 10 значений
      begin
        form8.ADOQuery2.Locate('№',j,[loCaseInsensitive]);  //нашел запись по номеру
        sum_X:=sum_X+Form8.ADOQuery2.FieldByName('X').AsFloat; // сложил со старой суммой
        sum_Y:=sum_Y+Form8.ADOQuery2.FieldByName('Y').AsFloat; // сложил со старой суммой
        sum_Z:=sum_Z+Form8.ADOQuery2.FieldByName('Z').AsFloat; // сложил со старой суммой
        sum_A:=sum_A+Form8.ADOQuery2.FieldByName('Общая').AsFloat; // сложил со старой суммой
      end;
       sum_X:=sum_X/10;
       sum_Y:=sum_Y/10;
       sum_Z:=sum_Z/10;
       sum_A:=sum_A/10;
      // вставляем в базу Filter полученные значения
      try
        Zapros:='INSERT INTO Filter (№,X,Y,Z,Общая)VALUES('+IntToStr(i)+','+FloatToStr(sum_X)+','+FloatToStr(sum_Y)+','+FloatToStr(sum_Z)+','+FloatToStr(sum_A)+')';
        form8.ADOQuery3.SQL.Clear;
        form8.ADOQuery3.SQL.Add(Zapros);
        form8.ADOQuery3.ExecSQL;
      except
      end;

end; //1

    try
    Zapros:= 'SELECT * FROM Filter';
      form8.ADOQuery3.SQL.Clear;
      form8.ADOQuery3.SQL.Add(Zapros);
      form8.ADOQuery3.Active:=True;
    except
    on e:Exception do
    end;


Screen.Cursor:=crDefault;
Refresh_table_3(nil);
end;


Спасибо!

Автор: CROTishka 16.11.2010, 09:40
Мало того, что ваши данные можно получить без всяких временных таблиц одним скулёвым запросом, так ещё и непонятно зачем вы через запросы данные в ADOQuery3 пихаете. Советую переделать добавление так так: http://msdn.microsoft.com/en-us/library/ms677536(VS.85).aspx . Ещё очень много съедает локейт. Используйте TDataSet.Next/Prior + ORDER BY или TCustomADODataSet.Sort. В общем учитесь.

Автор: MihailCarib 16.11.2010, 18:15
О, то, что нужно. Спасибо. Буду познавать. Честно говоря, считал, что локейт быстрее... Только вот не понятно, каким таким хитрым  SQL запросом можно решить сразу всю задачку?

Автор: CROTishka 16.11.2010, 18:41
самое банальное, но не очень оптимальное:
Код

SELECT F.№,
       (SELECT SUM (X)
          FROM Filter tmpF
         WHERE tmpF.№ BETWEEN F.№ AND F.№ + 9)
          AS X,
       (SELECT SUM (Y)
          FROM Filter tmpF
         WHERE tmpF.№ BETWEEN F.№ AND F.№ + 9)
          AS Y,
       (SELECT SUM (Z)
          FROM Filter tmpF
         WHERE tmpF.№ BETWEEN F.№ AND F.№ + 9)
          AS Z,
       (SELECT SUM (Общая)
          FROM Filter tmpF
         WHERE tmpF.№ BETWEEN F.№ AND F.№ + 9)
          AS Общая
  FROM Filter F

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

Автор: MihailCarib 16.11.2010, 19:43
А, я понял. Сделать запрос на вычисления одного окна -  не проблема. Проблема сдвинуть это окно, ну уж это ладно. Проверил - first/next действительно быстрее Locate, правда не на много, но по копеечке набегает. А запись мне все равно в другую табличку делать, потому как в конце все данные (с обработкой и без) подключаются к DBChart по желанию пользователя, значит их по-любому где-то хранить. Вы мне вот еще что подскажите- буду очень признателен, а нет ли возможности кинуть в DBChart сразу массив данных, не забирая их поэлементно?
Признаюсь, ранее я работал с БД очень мало,  в основном с BDE и задачи такие не ставились, объем информации перелопатить сразу очень большой тяжко, Вы мне хоть направления обозначьте. Спасибо!

Автор: superVad 16.11.2010, 20:22
MihailCarib, ты количество записей вычисляешь до открытия таблицы.
Код

records:= Form8.ADOQuery1.RecordCount;

Не знаю есть ли в этом ошибка (таблица могла быть и до этого открыта), но как то не правильно.

Автор: MihailCarib 16.11.2010, 21:10
Спасибо. Таблица открывается ранее, но ошибка быть могла. Исправлю.

Автор: CROTishka 17.11.2010, 09:33
Цитата

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

что мешает вам хранить их в рекордсете/датасете?
Цитата

Вы мне вот еще что подскажите- буду очень признателен, а нет ли возможности кинуть в DBChart сразу массив данных, не забирая их поэлементно?

TChartSeries.AddArray()

Автор: MihailCarib 30.11.2010, 21:16
Все попробовал. Ысе заработало, спасибо! 
Выяснилось, что запрос на добавление записи 
'INSERT INTO '+IntToStr(Card_num) и т.д.

конструкция 

form8.ADOQuery1.Insert;
form8.ADOQuery1.FieldByName('№').AsInteger:=

а также 

form8.ADOQuery1.recordset.AddNew(field,value);

работают примерно одинаково по времени, и для меня неприемлемо медленно. 

Как делаю: есть цикл на чтение данных, данных много, 10000 строк - это норма, инфа читается с флешки. 
В этом цикле после чтения блока данных построчно добавляю информацию в базу, и это не катит по скорости записи. 
Вопрос, как подобные задачи решают нормальные программисты?
Скорость чтения с флешки бешеная, все тормозит именно запись в базу. Куда мне лучше (быстрее) заносить данные во время чтения флешки и как их потом быстро скопом перекидывать в базу?

Автор: CROTishka 1.12.2010, 15:21
ну добавляйте не построчно, а скопом/блоками, если оперативки хватит в чём проблема?
триггеры на инсерт у вас не стоят?
существенно ускорить процесс поможет только смена БД на нереляционную.

Автор: MihailCarib 1.12.2010, 22:39
Стоп стоп стоп, не так быстро! Я же все-таки чайник, причем кипящий.  Добавлять скопом как? Писать все в массив/файл, а дальше
 ADOQuery1.recordset.AddNew(field,value); 
в value вносить этот массив? Можно примерчик, маленький?
Триггеры на инсерт не стоят, если я правильно понимаю, это счетчики количества записей в самой базе, я их отключил. Но в цикле есть необходимость крутить переменную, я ее использую в качестве поля номера строки. Не очень оптимально,  но я отключал это дело,  совсем копеечки по производительности.
 Вот теперь самое интересное. Программа работала раньше через BDE на таблицах Paradox и все было не сказать что шустро очень, но меня устраивало. Потом оказалось нужно, чтобы все работало без BDE и единственный удачный пример на тот момент я нашел под  Access. 
Access мне использовать удобно, но я был не в курсе таких проблем, как скорость записи/чтения и прочие "мелочи". А теперь эти "мелочи" меня дрючат, но опыта чуток прибавилось, возможно осилю чего другое.
Посоветуйте, с чем сейчас стОит поработать, чтобы пошустрее было? 

Автор: CROTishka 2.12.2010, 09:55
Для начала научитесь использовать  http://lmgtfy.com/?q=%D0%A2%D1%80%D0%B8%D0%B3%D0%B3%D0%B5%D1%80+(%D0%B1%D0%B0%D0%B7%D1%8B+%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85), прежде чем писать всякую фигню.
Во вторых конечно стоит научиться использовать документацию: http://msdn.microsoft.com/en-us/library/aa933206(SQL.80).aspx
И в третьих, хотя бы отдалённо разобраться в том, как работает то, что вы делаете. (Почитайте хотя бы встроенный хелп - встать на метод, Ctrl+F1)
В четвертых, если хотите помощи, то стоит писать нормально по схеме: "что делаете (ПОДРОБНО), что хотите, что на самом деле получается." 
Иначе помогать никто вам не будет. (У меня сегодня просто ангельское настроение и терпение)

Автор: MihailCarib 2.12.2010, 21:16
Уважаемый CROTishka! Огромное Вам спасибо, за ангельское настроение и терпение! Смею Вас заверить, что в тех областях, в которых я являюсь специалистом, я также, как Вы, на профильных форумах делаю такие приписки, за что меня очень ценят и хотят спрашивать еще и еще. 
Поймите, познать все и сразу невозможно, у меня две вышки, диссер, куча дипломов и пр., но при этом я НЕ УМЕЮ программировать. Пока. Но хочу научиться, не потому, что денег нет,  а потому что интересно. Времени на изучение мне никто не выделяет, все факультативно. Поэтому, не ругайтесь на новичка, пожалуйста. 

За ссылку на MSDN спасибо, про F1 тоже вовремя, но все это я читал, и мои вопросы остались нерешенными. 

Код привести полностью не могу (мне не жалко, просто он большой), процедура такая:
Код

repeat
  SetFilePointer(hdevice,0,nil,FILE_CURRENT);
  ReadFile(hdevice, field, 512, cb, nil); //читает и голову тоже

  for j:=0 to 31 do
                 begin
                 //  присвоение  данных и запись в БД
                  H_r:=(field.srt_w[((j*16)+0)]);       //часы
                  M_t:=(field.srt_w[((j*16)+1)]);        //минуты
                  S_d:=(field.srt_w[((j*16)+2)]);       //секунды
                  D_:=(field.srt_w[((j*16)+3)]);      //день
                  M_:=(field.srt_w[((j*16)+4)]);      // месяц
                  Y_:=(field.srt_w[((j*16)+5)])+2000;     //год
                  X:=coef*ShortInt(field.srt_w[((j*16)+6)]);
                  Y:=coef*ShortInt(field.srt_w[((j*16)+7)]);
                  Z:=coef*ShortInt(field.srt_w[((j*16)+8)]);
               //  T:=(field.srt_w[((a*16)+9)]);  //темп не исп
               //  V:=(field.srt_w[((a*16)+10)]); //скорость не исп
                  a_sum:=SQRT(x*x+y*y+z*z);//суммарная перегрузка

                   begin
                      Date_time_now:= DateTimeToStr(EncodeDateTime(Y_,M_,D_,H_r,M_t,S_d,0));
                        try
                          form8.ADOQuery1.Insert;
                          form8.ADOQuery1.FieldByName('№').AsInteger:=recN_R;
                          form8.ADOQuery1.FieldByName('Дата_Время').AsDateTime:=EncodeDateTime(Y_,M_,D_,H_r,M_t,S_d,0);
                          form8.ADOQuery1.FieldByName('X').AsFloat:=X;
                          form8.ADOQuery1.FieldByName('Y').AsFloat:=Y;
                          form8.ADOQuery1.FieldByName('Z').AsFloat:=Z;
                          form8.ADOQuery1.FieldByName('Общая').AsFloat:=a_sum;
                          form8.ADOQuery1.FieldByName('Оценка').AsInteger:=mark;
                          form8.ADOQuery1.FieldByName('Описание').AsString:=priznak;
                           {    это дб быстрее, но я не заметил
                          value_base:=VarArrayOf([recN_R, EncodeDateTime(Y_,M_,D_,H_r,M_t,S_d,0),X,Y,Z,a_sum,mark,priznak]);
                          form8.ADOQuery1.recordset.AddNew(field_base,value_base); }
                      except
                        on e:Exception do
                      end;
                      inc(recN_R);
                    end;
                 end;

  Progress_pos:=i;
  View_progress(nil);
  Application.ProcessMessages();
  inc(i);
until(i=Sector_count);// крайний сектор  sector_end


В чем проблема: я кручу в цикле обращение к карте ReadFile(hdevice, field, 512, cb, nil); и тут же в цикле открываю таблицу и делаю запись
ADOQuery1.FieldByName('Z').AsFloat:=Z; 
Это не правильно и долго! 
Мне бы хотелось, чтобы в цикле было только обращение к карте и запись , скажем в файл - это будет быстрее, а после цикла в отдельной процедуре вносить данные из файла в базу.  Все примеры, которые мне удалось найти, вносят в базу данные построчно, т.е. так, как это делал я в приведенном коде, либо при помощи ADOQuery1.recordset.AddNew, как описано тут http://msdn.microsoft.com/en-us/library/ms677536(v=VS.85).aspx

Спасибо.


Автор: CROTishka 3.12.2010, 10:04
Конечно неправильно. Пользоваться активным подключением к бд вообще не правильно. Используйте TADOCommand и изучайте SQL. Ссылку на инсерт я вам дал.
Еще обычно интерфейсную часть тоже показывают.

Автор: MihailCarib 3.12.2010, 21:36
Ок, спасибо,буду постигать.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)