Конференция "Базы" » [D2010] Магия мыши в TSQLQuery + DateTimePicker [MySQL]
 
  • megavoid (14.12.10 20:13) [0]
    Здравствуйте, Мастера!

    Столкнулся с такой проблемой:
    Есть форма, на ней stringgrid, sqlquery, datetimepicker. Юзеры выбирают дату в datetimepicker, в onChange конструируется select с нужной датой, выполняется через query, в цикле (query.next) читаю ответ от query и заполняю stringgrid нужными значениями. Всё вроде бы шоколадно, но есть одно но - если выбирать значение в picker'e мышью (sic!), то query при попытке интерпретировать ответ (.asString, .asInteger) выдаёт ошибку: Query: Field 'N1' not found. Не всегда выдаёт, произвольно, один раз из десяти-пятнадцати. Если же выбирать дату в пикере стрелочками, всё прекрасно, никакой ошибки. Более того, по событию FormCreate отрабатывает datetimepickerchange(self), и при запуске проги в гриде тоже всё прекрасно отображено.

    Усугубляет разброд в моём мозге то, что поидее сообщения вообще выдаваться не должно бы (try..except), а оно выдаётся :)

    Выглядит же этот безобразный цикл примерно так:

    try
     Query.Open;
     for j := 0 to query.RecordCount-1 do begin
       // здесь идут блоки s := fieldbyname .asstring; .asinteger; итд
       try
         S := query.FieldByName('N1').AsString;
       except
       end;
       // здесь мы эти s пихаем в stringgrid
       Query.Next;
       Application.ProcessMessages;
     end;
    finally
     Query.Close;
    end;



    При возникновении этого глюка отладчик вылетает в окно CPU, а не на строку между try..except. Стэк вызовов процедур получается такой:
    KERNELBASE.RaiseException
    DatabaseError
    DatabaseErrorFmt
    Dataset.FieldByName
    стоп указывает на инструкцию leave
    перед ней call dword ptr [$7555116c] - что там у меня по этому адресу - Готт его знает, перешёл туда - там jnbe, перед ней ещё пара инструкций и нули, много-много нулей :) Увы, мой ассемблер закончился в досе на уровне mov ah, 9 int 21h, так что мне это ровным счётом ничем не помогло :)

    Вопрос: что же всё-таки её вызывает, ошибку эту, если мышкой выбирать дату? Пока вопрос решён чисто по-русски, то есть юзеры (~80 человек) не выбирают мышкой, но мучит меня совесть и интерес профессиональный, что ж за фигня такая :)
    Плиз хелп, всем заранее спасибо!
  • 12 © (14.12.10 21:07) [1]
    чего показывает


    > try
    showmessage(Query.sql.text);
    >  Query.Open;
    >  for j := 0 to query.ParamCount-1 do showmessage(query.params[i].Name + query.params[i].value);

    if Query.IsEmpty then showmessage('Пусто');
    >  for j := 0 to query.RecordCount-1 do begin
    showmessage(query.fields[i].FieldName + query.fields[i].asstring);
    >    // здесь идут блоки s := fieldbyname .asstring; .asinteger;
    >  итд
    >    try
    >      S := query.FieldByName('N1').AsString;
    >    except
    >    end;
    >    // здесь мы эти s пихаем в stringgrid
    >    Query.Next;
    >    Application.ProcessMessages;
    >  end;
    > finally
    >  Query.Close;
    > end;
  • 12 © (14.12.10 21:08) [2]

    > >    try
    > >      S := query.FieldByName('N1').AsString;
    > >    except
    > >    end;

    а за это тут расстреливают :)
  • megavoid (14.12.10 21:46) [3]
    showmessage(Query.sql.text)

    как и положено, показывает нужный SELECT

    for j := 0 to query.ParamCount-1 do showmessage(query.params[i].Name + query.params[i].value);


    только всё-таки так:
    for j := 0 to qry.Params.Count-1 do showmessage(qry.params[i].Name + qry.params[i].value);


    гм, ничего не показывается

    showmessage(query.fields[i].FieldName + query.fields[i].asstring);


    тоже немножечко не то :) у меня получилось:
    for i := 0 to 9 do showmessage(qry.fields[i].FieldName + ':' + qry.fields[i].asstring);


    выводит что и положено, имя поля бд со значением из ответа

    try .. except end; я прикрутил специально из-за этой вот ситуации, уж не по уши деревянный же, понимаю, что расстреливают )) если есть ошибка, её надо обработать :)

    повторюсь, алгоритм нормальный, работает нормально. даже когда вылазит эта ошибка, все данные отображены. если ответ empty, то тоже всё нормально!
    беда в том, что если выбирать дату мышкой (или слишком быстро клавой), то вылазит вот этот вот мерзкий query: field not found, хотя все fields на самом деле есть и всё отображается нормально.
  • Ega23 © (14.12.10 21:59) [4]

    > showmessage(Query.sql.text) как и положено, показывает нужный
    > SELECT


    1. А ты его в тот самый except поставь. Пустое гашение исключения надо ещё хорошенько обосновать. У меня на практике такое всего пару раз было.
    2. Никогда не любил DateTimePicker, всегда Rx-овским пользовался.
    3. На OnChange закладываться точно не стал бы. Представь, что ты хочешь на пару месяцев вперёд пролистать, а у тебя на каждом месяце выборка пойдёт. Нехорошо. Положи кнопочку "Обновить", пусть пользователь спокойно выберет дату, а потом уже получает данные.
  • megavoid (14.12.10 22:17) [5]
    @Extended Graphics Array
    1. Хоспади, да что вы к этому исключению привязались, не глядите на него,  нету его там, нету, я пытаюсь лишь подавить сообщение об ошибке, это удовлетворило бы меня на 101% в этом случае. Нету там такого, там
    s1 = query.FieldByName('N1').AsString;
    s2 = query.FieldByName('N2').AsString;
    s3 = query.FieldByName('N3').AsString;


    Всё и так внутри try..finally, без всяких try..except, это никоим образом не влияет ни на что там. Этот except я ввёл, разбираясь в этой ситуации, пытаясь подавить злосообщение.

    2. Нельзя использовать сторонние компоненты, не входящие в стандартную поставку дельфи. Даже бесплатные. Даже JEDI. Это придумал не я.

    3. Не поверите. Рядом с пикером лежит кнопка обновить. По ней (хе-хе) вызывается OnChange(Self) пикера. Я представлял - юзеры в 99.9% случаев листают на день-два вперёд назад, обычно даже не листают вообще (в грид выводится график на сегодня, им больше и не надо, изредка на завтра). Да, выборка идёт. Занимает 2-3 секунды. Юзеры не расстраиваются. Гораздо больше они расстраиваются из-за field not found :'(
  • Ega23 © (14.12.10 22:24) [6]

    >  Хоспади, да что вы к этому исключению привязались, не глядите
    > на него,  нету его там, нету,


    Как это нету, когда "Field not found"?
    А исключение - это для тебя. Между прочим - бесценная информация. Чтобы было понятно, где что происходит.
    Ещё раз, возьми и сделай следующее:

      try
        S := query.FieldByName('N1').AsString;
      except on E: Exception do
          ShowMessage(E.Message + #13#10 + query.SQL.Text);
      end;


    И после этого станет ясно, где там и чего not found.
  • Ega23 © (14.12.10 22:28) [7]

    >  По ней (хе-хе) вызывается OnChange(Self) пикера.

    Это неправильно. Хотя бы потому, что Self (с вероятностью 99%) - это объект класса твоей формы. И с какого перепуга ты его в качестве Sender передаёшь пикеру - непонятно. Это не значит, что так делать запрещено, но ты делаешь неправильно (скорее всего потому, что не разобрался, для чего Sender используется).
    Возьми заведи себе метод
    TForm1 = class (TForm)
    private
     .....
     procedure UpdateDBData;

    И дёргай его, что по нажатию на кнопку, что в OnChange пикера.
  • megavoid (14.12.10 22:35) [8]
    да ё же моё!!!
         try
           S := TimeToStr(Qry.FieldByName('dtime').AsDateTime);
         except
           on E: Exception do
             ShowMessage(E.Message + #13#10 + qry.SQL.Text);
         end;


    получаем:

    Qry: Field 'dtime' not found
    и мой SELECT
    [OK]


    а потом
    Qry: Cannot perform this operation on a closed dataset.


    я же не просто так тут распинаюсь :)

    Self - объект класса формы, верно. С таким же успехом можно передавать пикеру nil или что угодно, всё равно sender у меня внутри onchange пикера не используется.

    Метод такой у меня уже есть. Гридов много, пикеров много. В метод я передаю нужный грид и нужный пикер. Дело не в этом.
  • Ega23 © (14.12.10 22:40) [9]

    > Cannot perform this operation on a closed dataset.

    И чего непонятного? У тебя набор данных закрыт.
    Почему - это другой вопрос.
  • Ega23 © (14.12.10 22:46) [10]
    Да, и ещё один момент.
    Вот это:
    try
    Query.Open;
    for j := 0 to query.RecordCount-1 do begin



    перепиши и больше так не делай.

    1. Открытие вынеси за try
    2. Если не хочешь граблей получить, то обходи набор данных через while. Совершенно необязательно, что ты данные получил до конца, это зависит от драйвера доступа и настроек сервера.
    Шаблон примерно такой:

    with Query do
    begin
     Open;
     try
       First;
       while not Eof do
       begin
         try
           .....  Работаем с данными
         finally
           Next;
         end;
       end;
     finally
       Close;
     end;
    end;

  • megavoid (14.12.10 22:57) [11]
    @Ega23

    >> И чего непонятного? У тебя набор данных закрыт.
    >> Почему - это другой вопрос.
    мне непонятно, почему грид правильно берёт все данные из якобы "закрытого набора". Повторяю, всё работает нормально!!! Если выбирать дату в пикере мышкой - то всё тоже работает нормально!!! Но изредка!!! вылазит сообщение об ошибке!

    1. Оно так и есть. Набирал тут, поэтому и всобачил Open после try. Извиняюсь.

    2. Софт уже три месяца в эксплуатации. Все данные всегда получаются нормально, до конца (см. пп. всё работает!!!). Поэтому я не вижу здесь принципиальной разницы между for и while - я ж не убиваю эти данные в qry, они никуда не денутся, что в for 0..count-1, что в while, а памяти на переменную i мне не жалко, да и небось компилятор соптимизирует сам до while, уберёт её.
  • Ega23 © (14.12.10 23:10) [12]

    > Поэтому я не вижу здесь принципиальной разницы между for
    > и while


    Ещё раз: это тебе дружеский совет. Следовать ему или нет - это твоё дело.
    Но RecordCount будет точно определён, когда данные полностью зафетчатся. До конца. А гарантии, что это произойдёт строго после DataSet.Open - нет. Это зависит от настроек конкретной реализации DataSet и от драйвера доступа.
  • Ega23 © (14.12.10 23:14) [13]

    > мне непонятно, почему грид правильно берёт все данные из
    > якобы "закрытого набора".


    Во, кстати. если ты внимательно просмотришь код TDBGrid, то ты увидишь, что он берёт строго те данные, которые в данный момент отображаются на экране.
    Это возвращаясь к моему предыдущему посту.
    Представь, что выборка у тебя на 100 записей. На экране в данный момент ты видишь первые 20. Остальные могут уже находиться в DataSet, могут ещё продолжать "подсасываться". А могут подсосаться тогда, когда ты грид проскроллишь.
  • megavoid (14.12.10 23:24) [14]
    @Ega23
    Хочется выть и биться об стол головой :)
    Где вы у меня TDBGrid и DataSet увидели??? у меня TStringGrid и TSQLQuery!!!
    Поэтому все данные будут у меня сразу после TSQLQuery.Open, а потом вручную данные из Query я вношу в StringGrid с нужным заказчику форматированием.
  • Ega23 © (14.12.10 23:59) [15]

    > Поэтому все данные будут у меня сразу после TSQLQuery.Open


    Не факт. Впрочем, это твоё дело.
  • megavoid (15.12.10 00:26) [16]
    > Не факт. Впрочем, это твоё дело.
    ну тогда мне остаётся лишь признать, что создатели дельфи - полнейшие лохи, ведь они и сами об этом не знают!

    http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SqlExpr_TSQLQuery.html

    http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devwin32/5datasetusingunidirectionalresultsets_xml.html

    впрочем, делу это не помогает нисколечко :(
    ау, эксперты, откликнитесь пожалуйста!!!
  • sniknik © (15.12.10 02:09) [17]
    > впрочем, делу это не помогает нисколечко :(
    а чем это должно помочь? могло бы, если бы прислушивался к советам, а так.. нет.

    по второй ссылке про однонаправленные датасеты, косвенное подтверждение словам Ega23 про неопределенность RecordCount и все проблемы с этим связанные. т.к. однонаправленные могут быть только с серверным курсором, а для серверных это актуально... и то, что "через раз" тоже, т.к. успевает - работает, а чуть "повезло" быстрее сработать - глюк.

    > ау, эксперты, откликнитесь пожалуйста!!!
    тебе уже сказали, или следуй советам, и переделывай, даже не понимая причин, или... "это твоё дело", как бы переубеждать "насильно" это тебе нужны другие эксперты.
  • megavoid (15.12.10 04:37) [18]
    @sniknik

    > > впрочем, делу это не помогает нисколечко :(а чем это должно
    > помочь? могло бы, если бы прислушивался к советам, а так.
    > . нет.

    Единственный совет, к которому я не прислушался - это переделать цикл с предусловия for на постусловие while. Всё остальное из ответов я проделал с прилежностью ученика церковно-приходской школы, и привёл все полученные результаты в топике.

    Query.Clear;
    Query.Add('SELECT * from table WHERE date=' + format2sql(datetimepicker.date) + ';');
    Query.Open;
    try
     /* приведите мне, пожалуйста, пример, когда TSqlQuery в этом случае вернёт мне НЕ ВСЕ нужные записи. Может быть, я с этим просто не столкнулся. Обычно возвращается в среднем порядка 100 записей. */
     for i := 0 to Query.RecordCount-1 do begin
         // here are we're getting info from answer
       Query.Next;
     end;
    finally
     Query.Close;
    end;

    Во всех примерах использования dbExpress без отображения в dbgrid, для простого формирования запроса к БД указан именно этот путь - делаем запрос, open, читаем записи next, закрываем sqlquery. Если база не возвращает результат - используем execsql. Можно, конечно, и переделать цикл на while not eof, если recordcount врёт, но с чего ему врать? В той же документации указано, что RecordCount содержит количество записей в наборе данных однонаправленного датасета.
  • megavoid (15.12.10 04:50) [19]
    В этом проекте 20 форм и более 100 запросов. SQLQuery один. Все они (запросы) for i < recordcount всегда отрабатывают нормально (open/execsql). Кроме этого случая с выбором даты в пикере мышью.
 
Конференция "Базы" » [D2010] Магия мыши в TSQLQuery + DateTimePicker [MySQL]
Есть новые Нет новых   [134431   +15][b:0.001][p:0.002]