none
UPDATE ACCESS WINDOWS 10 RRS feed

  • Вопрос

  • Элементарный запрос:

    "UPDATE Client SET BalancePersonalAccount = 20,3 WHERE ID = 531707020"

    Не обновляется, т.к 20,3 (запятая). Ставлю точку (20.3) - все работает.

    Как от этого избавиться?

    Aссess стоит 2016, но база - Aссess 2000

    Язык в Windows - русский.

    Разделитель целой и дробной части - запятая.

    3 января 2017 г. 14:53

Ответы

  • Никаких грабель, ведь это не строка в каком то локализованном формате, а float/double (или другой нужный тип). Если это переменная то присвойте ее значение без преобразования в строку.

    Использование параметров разом излечивает от проблем глобализации/локализации (это то что у вас), а заодно избавляет от атак класса SQL Injection. Так же повышается производительность, ведь нет необходимости преобразовывать числа в строки и потом обратно в числа (на удивление дорогая операция). Так же при большом числе операций можно использовать один подготовленный запрос меняя каждый раз лишь значения параметров, это может поднять скорость на порядок.

    Передача значений в запросах в общем случае является грубой ошибкой с серьезными проблемами и опасными последствиями. 

    Что до SQL то он возьмет формат чисел который установлен на сервере (а не на клиенте). Если настройки на клиенте и сервере разные (как скорее всего у вас) то начинаются проблемы с форматами если передавать их в строках запросов.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 января 2017 г. 7:30
    Модератор
  • Не хотел в этой простенькой программе замораживаться с параметрами, но видно придется.

    Я, по глупости своей, забыл, что в параметр передается значение (не так часто с БД работаю). В любом случае - спасибо.

    Реализую - отпишусь. Может еще кому поможет.


        public Single Update_Object_BalancePersonalAccount(Int32 objectID) {
          Single s = Calculate_ObjectBalancePersonalAccount(objectID);
          string sql = "UPDATE [Object] SET [Object].BalancePersonalAccount = @bpa WHERE [Object].ID = @id";
          OleDbCommand cmd = new OleDbCommand(sql, _connection);
          cmd.Parameters.AddWithValue("@bpa", SqlDbType.Money);
          cmd.Parameters["@bpa"].Value = s;
          cmd.Parameters.AddWithValue("@id", SqlDbType.Int);
          cmd.Parameters["@id"].Value = objectID;
          try {
            _connection.Open();
            cmd.ExecuteScalar();
          } catch(Exception ex) {
            Console.WriteLine(ex.Message);
          } finally { _connection.Close(); }
          return s;
        }
    4 января 2017 г. 9:59

Все ответы

  • От этого не надо избавляться - поведение штатное. Делайте явное преобразование в нужный формат
    3 января 2017 г. 16:49
  • Преобразование форматов не помогает. Мне же нужна строка, а она с ','.

    Да, и это только на 10ке. На windows 7 все нормально. Пришлось написать функцию поверки, что нужно точка или запятая и при создании запроса программно менять запятую на точку, если это нужно.

    Геморройно, но работает.

    3 января 2017 г. 17:13
  • Запустите powershell, выполните команды ниже и запостите вывод:

    $c = Get-Culture
    $c.NumberFormat



    This posting is provided "AS IS" with no warranties, and confers no rights.

    3 января 2017 г. 17:51
    Модератор
  • CurrencyDecimalDigits    : 2
    CurrencyDecimalSeparator : ,
    IsReadOnly               : False
    CurrencyGroupSizes       : {3}
    NumberGroupSizes         : {3}
    PercentGroupSizes        : {3}
    CurrencyGroupSeparator   :  
    CurrencySymbol           : ₽
    NaNSymbol                : не число
    CurrencyNegativePattern  : 8
    NumberNegativePattern    : 1
    PercentPositivePattern   : 1
    PercentNegativePattern   : 1
    NegativeInfinitySymbol   : -∞
    NegativeSign             : -
    NumberDecimalDigits      : 2
    NumberDecimalSeparator   : ,
    NumberGroupSeparator     :  
    CurrencyPositivePattern  : 3
    PositiveInfinitySymbol   : ∞
    PositiveSign             : +
    PercentDecimalDigits     : 2
    PercentDecimalSeparator  : ,
    PercentGroupSeparator    :  
    PercentSymbol            : %
    PerMilleSymbol           : ‰
    NativeDigits             : {0, 1, 2, 3...}
    DigitSubstitution        : None
    3 января 2017 г. 19:39
  • Я не корректно описал проблему, извините.

    Программа на C#. В ней код:

          _SQL = string.Format("UPDATE SETUP SET CheckDot = 0.01");
          _connection.Open();
          OleDbCommand command = new OleDbCommand(_SQL, _connection);
          try { command.ExecuteNonQuery(); }
          catch { isDotSQL = false; DotCommaIndex = 1; }
          _connection.Close();

    Этот работает, если 0,01  - то ошибка  "Ошибка синтаксиса в инструкции UPDATE." 

    • Изменено dav_tag 3 января 2017 г. 19:55
    3 января 2017 г. 19:40
  • Для начала перепишите это с использованием параметров, а там посмотрим что к чему.

    This posting is provided "AS IS" with no warranties, and confers no rights.

    3 января 2017 г. 20:55
    Модератор
  • Было так:

        /// <summary>
        /// Обновление баланса объекта
        /// </summary>
        /// <param name="ObjectID"></param>
        public void Update_ObjectBalancePersonalAccount(Int32 objectID) {
          ExecuteSQL("UPDATE [Object] SET [Object].BalancePersonalAccount = " + Calculate_ObjectBalancePersonalAccount(objectID) + " WHERE [Object].ID = " + objectID);
        }
        /// <summary>
        /// Пересчет баланс объекта (сумма балансов клиентов)
        /// </summary>
        /// <param name="objectID"></param>
        /// <returns>баланс объекта</returns>
        private decimal Calculate_ObjectBalancePersonalAccount(Int32 objectID) {
          return ExecuteScalar_decimal("SELECT SUM(BalancePersonalAccount) FROM Client WHERE ObjectID = " + objectID);
        }
        /// <summary>
        /// ExecuteScalar инструкцией SQL, которая возвращает итоговый результат
        /// </summary>
        /// <param name="queryString"> инструкция SQL</param>
        /// <returns>decimal</returns>
        private decimal ExecuteScalar_decimal(string queryString) {
          decimal result;
          _connection.Open();
          OleDbCommand command = new OleDbCommand(queryString, _connection);
          try { result = (decimal)command.ExecuteScalar(); }
          catch { result = (decimal)0; }
          _connection.Close();
          return result;
        }
        /// <summary>
        /// ExecuteNonQuery для инструкций UPDATE, INSERT и DELETE
        /// </summary>
        /// <param name="queryString"> инструкция SQL</param>
        private void ExecuteSQL(string queryString) {
          _connection.Open();
          OleDbCommand command = new OleDbCommand(queryString, _connection);
          try {
            command.ExecuteNonQuery();
          } catch { }
          _connection.Close();
        }


    Имя таблицы [Object] не влияет. Есть точно такая же с именем Client и проблема та-же.
    • Изменено dav_tag 3 января 2017 г. 22:32
    3 января 2017 г. 21:08

  •        _SQL = "UPDATE SETUP SET CheckDot = ?";  // Или по имени если поддерживается
          _connection.Open();
          OleDbCommand command = new OleDbCommand(_SQL, _connection);

          // Тут добавить параметр 

         command.Parameters.Add(...).Value = 0.01;

    Детали/примеры тут:

    https://msdn.microsoft.com/en-us/library/system.data.oledb.oledbparameter(v=vs.110).aspx



    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 января 2017 г. 3:46
    Модератор
  • НЕ. Тут другое. Зачем м не в SQL обязательно точка: 0(точка)01.

    Если я число преобразую в строку стандартно,  то получаю 0(запятая)01. Я думал, что и SQL тоже возьмет с запятой. Ан-нет. SQL говорит - давай только точку.

    И в Вашем  решении те-же грабли.      command.Parameters.Add(...).Value = 0.01;

    Ручками я 0.01. пропишу, а если это переменная?

    4 января 2017 г. 6:53
  • Никаких грабель, ведь это не строка в каком то локализованном формате, а float/double (или другой нужный тип). Если это переменная то присвойте ее значение без преобразования в строку.

    Использование параметров разом излечивает от проблем глобализации/локализации (это то что у вас), а заодно избавляет от атак класса SQL Injection. Так же повышается производительность, ведь нет необходимости преобразовывать числа в строки и потом обратно в числа (на удивление дорогая операция). Так же при большом числе операций можно использовать один подготовленный запрос меняя каждый раз лишь значения параметров, это может поднять скорость на порядок.

    Передача значений в запросах в общем случае является грубой ошибкой с серьезными проблемами и опасными последствиями. 

    Что до SQL то он возьмет формат чисел который установлен на сервере (а не на клиенте). Если настройки на клиенте и сервере разные (как скорее всего у вас) то начинаются проблемы с форматами если передавать их в строках запросов.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 января 2017 г. 7:30
    Модератор
  • Не хотел в этой простенькой программе замораживаться с параметрами, но видно придется.

    Я, по глупости своей, забыл, что в параметр передается значение (не так часто с БД работаю). В любом случае - спасибо.

    Реализую - отпишусь. Может еще кому поможет.


        public Single Update_Object_BalancePersonalAccount(Int32 objectID) {
          Single s = Calculate_ObjectBalancePersonalAccount(objectID);
          string sql = "UPDATE [Object] SET [Object].BalancePersonalAccount = @bpa WHERE [Object].ID = @id";
          OleDbCommand cmd = new OleDbCommand(sql, _connection);
          cmd.Parameters.AddWithValue("@bpa", SqlDbType.Money);
          cmd.Parameters["@bpa"].Value = s;
          cmd.Parameters.AddWithValue("@id", SqlDbType.Int);
          cmd.Parameters["@id"].Value = objectID;
          try {
            _connection.Open();
            cmd.ExecuteScalar();
          } catch(Exception ex) {
            Console.WriteLine(ex.Message);
          } finally { _connection.Close(); }
          return s;
        }
    4 января 2017 г. 9:59
  • Зачем вы в AddWithValue вторым параметром тип передаёте?
    4 января 2017 г. 13:54
  • Еще несколько моментов:

    1. Открывать и закрывать соединение при каждом запросе нет необходимости и скорее всего проблематично с точки зрения производительности. Откройте при запуске программы, закройте при выходе.

    2. Правильный метод распечатки исключения не ex.Message, a ex.ToString(). При этом в дополнение к сообщению (которого может и не быть) печатается тип исключения и стек вызовов. Так же, исключения для работы с базами (в том числе и OleDbException) имеют дополнительную информацию которую надо распечатывать отдельно. Детали тут: https://msdn.microsoft.com/en-us/library/system.data.oledb.oledbexception(v=vs.110).aspx 

    3. Вы "проглатывайте" исключения (причем все) и если произойдет любая проблема то программа (и ее пользователь) об этом не узнает. После записи лога (для чего консоль плохо подходит) надо кидать исключение, оно должно обрабатываться кодом выше и сообщать пользователю о проблеме и/или предпринимать что то для исправления ситуации (например делать еще попытку, откатывать всю транзакцию и тому подобное.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 января 2017 г. 16:46
    Модератор
  •  "Открывать и закрывать соединение при каждом запросе нет необходимости и скорее всего проблематично с точки зрения производительности."

    Насколько я знаю, механика Ole DB автоматически использует пул соединений. Операция закрытия соединения физически не освобождает ресурсы, связанные с ним, поэтому можно открывать и закрывать до посинения без потерь производительности. Хотя, надо конечно не закрывать в явном виде а просто использовать using.

    4 января 2017 г. 17:38
  • AddWithValue - посоветовала Студия. Я особо не вникал.

    Сейчас задача накидать код, а потом буду "вылизывать. Но спасибо - обращу внимание.

    19 января 2017 г. 18:36
  • ex.Message - стоял в примере, который Вы мне посоветовали посмотреть :-).

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

    19 января 2017 г. 18:37
  • Код в большинстве примеров весьма плох, его надо не копировать, а анализировать и делать свой.

    Не вижу как число пользователей может влиять на обработку ошибок.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    19 января 2017 г. 19:00
    Модератор
  • Задача тривиальная. Логикой все варианты легко проверить и отследить. Вот и не парился. Да, это неправильно, но, честно, лень.

    Думаю, когда первый вариант закончу, то возьмусь за "вылизывайте" и тогда ошибки обработаю. В моем случае пользователь такой, что писать об ошибке записи в БД бесполезно (он не знает что такое БД :-) ). Нужно отловить ошибку и устранить ее не "напрягая" такой мелочью ум пользователя.

    "Ты напиши, что бы я кнопку нажал и все было" - это ТЗ с коим я бодаюсь.

    19 января 2017 г. 22:32
  • Записи об ошибках не для пользователя, а для программиста. Не на экран, а в файл с логом. Чтоб "вылизывать" было легче.

    Пользователю показываем сообщение из серии "Что то сильно поломалось. Звони в поддержку по телефону 555-1234. Скажи код 123"

    Код - это чтоб в логах можно было найти.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    20 января 2017 г. 1:56
    Модератор
  • Выводить на экран осмысленное сообщение об ошибке можно, и нужно. При использовании программы пользователь далеко не всегда будет обращаться напрямую в техподдержку разработчика, в первую очередь - к системному администратору. И для него будет полезно будет видеть технические данные ошибки. (Сообщение типа "OleDbException: Не удалось получить доступ к файлу" несет больше информации чем "Что-то поломалось"). Просто надо сбалансировать компактность сообщения и полноту информации. Для меня, правильный шаблон обработки исключения должен выглядеть как-то так

    try
    {
    	/* ... */
    }
    catch(Exception ex)
    {
    	MessageBox.Show(ex.GetType().ToString()+": "+ex.Message+" в "+ex.Source, "Ошибка");
    	ErrorLog.WriteLine(DateTime.Now.ToString()+". При выполнении операции (...) произошла ошибка:");
    	ErrorLog.WriteLine(ex.ToString());	
    }

    Если же принцип разработки "лишь бы работало", используется технология WinForms или ASP.NET, и приложение однопоточное, исключения лучше вообще не обрабатывать. WinForms при необрабатываемом исключении сама выводит сообщение с подробной информацией на экран. В случае ASP.NET пользователю выводится стандартное бессмысленное сообщение, а подробную информацию об ошибке IIS складывает в системный журнал. К сожалению, это не работает в других типах приложений:

    WPF - при необрабатываемом исключении программа падает с бессмысленным системным сообщением об ошибке.

    Служба - ошибка будет проигнорирована без какой-либо возможности узнать что произошло

    Многопоточное приложение WinForms - ошибка в фоновом потоке будет также молча проигнорирована

    В общем, обработка ошибок всегда - по ситуации.

    20 января 2017 г. 14:42
  • Это меня заставило задуматься... Я до такого не додумался, но интуитивно поступил примерно так же.

    Поясните (что бы не искать) "ErrorLog.WriteLine(DateTime.Now.ToString()+...". При вы" - куда пишет?

    И еще SQLException - аналогично? Мне кажется, что там есть отличия: сначала SQL-сервер выдает ошибки, а топом только в программу попадает.

    20 января 2017 г. 16:07
  • В моем случае - юзер сидит рядом :-) , но идею понял. Думаю, что буду активно ее использовать.
    20 января 2017 г. 16:09
  • Так делали много лет назад когда пользователи компьютеров еще были более-менее профессиональными и многие могли "расшифровать" сообщение и понять что надо делать чтоб решить проблему. 

    Сегодня выдача подобных сообщений является плохой идеей. Мало того что они ничего не объясняют среднему пользователю, так еще и запутывают их. Скажем, в примере выше как Message так и Souce могут быть пустыми строками что выведет какую то абракадабру с точки зрения пользователя. Да и сам по себе вывод MessageBox неудачен, с ним ведь ничего не сделать путного, в результате пользователь скажет что ему "показали какую то ошибку".

    Я бы сказал что код выше - пример как не надо делать. Если уж очень хочется что то вывести, то сделать это надо в в отдельном блоке который открывается при нажатии специальной кнопки. И выводить туда следует всю информацию - exception.ToString() как минимум. При этом надо использовать UI который позволяет легко скопировать информацию чтоб послать ее в поддержку. Изначально видимая часть сообщения должна описывать что пользователю нужно сделать чтоб решить проблему. Например, звонить в поддержку.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    20 января 2017 г. 17:05
    Модератор
  • "Поясните (что бы не искать) "ErrorLog.WriteLine(DateTime.Now.ToString()+...".  При вы" - куда пишет?"

    В моем примере ErrorLog это гипотетический класс, реализуемый программистом, который просто пишет лог в любое удобное место (например, в файл в папку с программой, в базу данных или в системный журнал). Стандартного класса для ведения лога нет, но в любом крупном проекте обычно такой класс создается.

    "И еще SQLException - аналогично? Мне кажется, что там есть отличия: сначала SQL-сервер выдает ошибки, а топом только в программу попадает."

    При возникновении ошибки в SQL Servere действительно сналчала генерируется внутреннее исключение, которое можно обработать из кода Transact SQL. Но обычно это не нужно, если только не используется сложная логика транзакций. Так что обработка исключения SQL Server не отличается от обработки любых других исключений (кроме того факта, что параметр Message этих исключений более информативен чем других, а в дополнительных данных исключения даже храниться идентификатор соединения и ссылка на страницу справки на сайте microsoft).

    20 января 2017 г. 17:10
  • "Скажем, в примере выше как Message так и Souce могут быть пустыми строками что выведет какую то абракадабру с точки зрения пользователя. Да и сам по себе вывод MessageBox неудачен"

    Я привел только как пример, передать суть алгоритма. В реальном проекте надо конечно проверить на пустые строки и реализовать свое удобное окно сообщений.  

    "в результате пользователь скажет что ему "показали какую то ошибку""

    От пользователя больше и не надо. Зная, когда и при каком действии возникла ошибка, специалист сможет найти в логе необходимую информацию.

    20 января 2017 г. 17:53
  • Раз разговор затягивается :-) позвольте предложить реальную проблему:

    Есть набор справочников (DBF-файлы) изменяемых на стороне и предоставляемых мне 1 раз в сутки.

    Есть набор Access'овских баз заполняемых в реальном времени средствами Access. Т.е. есть MDB-база и на рабочих местах стоят MDB'шки с формами и запросами для ввода данных. Таких связок (БАЗА-клиенты) несколько.

    (Изменить это уже не реально. Проще переписать заново)

    Задача: нужно периодически получать отчеты использующие все MDB-базы и DBF-справочники.

    Как это реализовать?

    Я решил использовать SQL-сервер для объединения ВСЕГО в единую базу. При создании отчета обновляю SQL и делаю отчеты.

    Ваше мнение...


    • Изменено dav_tag 20 января 2017 г. 19:09
    20 января 2017 г. 19:08
  • Для начала, можно просто создать центральную Access базу, в которую таблицы локальных баз будут добавлены как внешние ссылки. SQL-сервер тоже хорошо, если он уже развернут. (Если нет, только ради отчетов его развертывать наверно не стоит.)
    20 января 2017 г. 19:40
  • Т.е. одна центральная Access'овская база и из нее делать отчеты... Тут проблема в том, что в некоторых базах таблицы велись по годам (например ТЕКУЩАЯ_БАЗА, 2016_БАЗА, 2015_БАЗА). И каждый год они таким образом накапливаются. Можно конечно создать таблицу наименований таблиц :-)...

    А по поводу - развернуть SQL-сервер. Можно использовать Express или MDF.

    Не знаю почему, но Access у меня тормозит сильно...
    • Изменено dav_tag 20 января 2017 г. 19:54
    20 января 2017 г. 19:53