Универсальная строка даты в MS SQL или ошибка The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value.

При формировании sql запросов очень часто используются строчные значения даты. Все бы хорошо, но формат этих строк зависит от региона, точнее говоря от региональных настроек сервера и клиента и скрытых преобразований формата datetime внутри разных программ и библиотек. Иначе говоря, от множества переменных параметров, не зависящих от программиста. Все это приводит к классическому кошмару программиста - когда один и тот же код выполняется по-разному в зависимости от непредсказуемых внешних условий (например выполняется из SQL Managment Studio и вылетает в C#, или вылетает на отсылке на сервер прочитанных с него же данных). Характерным признаком подобных проблем является ошибка "The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value."

Но не стоит отчаиваться, спасение есть, в MS SQL Server существует универсальный формат строки даты, не зависящий от всего этого ужаса - ГГГГММДД или 20120201 для 1 февраля 2012 . Получить такую строку из DateTime или произвольной строки даты можно следующим образом:

string sqlDateStr = Convert.ToDateTime(dateStr).ToString("yyyyMMdd");

Вообще-то этот формат пришел из стандарта 8601 и для даты и времени можно использовать такую строку в соответствии со стандартом

select cast('2014-01-24T11:19:00.000' as datetime)

TSQL DATETIME ISO 8601
Universal Date format to insert date in to SQL Server? Shock

Обьединение двух выборок или сложное ограничение в Transact sql

Многие программисты новички, особенно начавшие с классических языков программирования, считаю sql весьма примитивным средством для выборок данных, которые затем обрабатываются внутри программы. На самом деле sql запросы позволяют очень быстро и легко решать весьма сложные задачи, просто их структура резко отличается от привычных циклов, ветвлений и классов. Этой заметкой я надеюсь начать серию примеров, которые вызовут успешку у опытных мастеров, но могут оказаться крайне полезными для новичков.

Задача: выбрать все строки из таблицы, определенное поле которых встречается в данной таблице в двух и более строках. Новичок сразу подумает о циклах, но на самом деле с задачей справится очень простой и короткий запрос.

В качестве ограничиваюшего условия для первой выборки будем использовать другую выборку из той же таблицы, содержащую только повторяющиеся строки и присоединенную к первой с помощью INNER JOIN, который и будет являться ограничивающим условием.

SELECT SOME_FIELD1, SOME_FIELD2, DUPLICATING_FIELD   
FROM SOME_TABLE AS result
--- соединение-ограничивающее условие
JOIN 
(
SELECT DUPLICATING_FIELD, COUNT(*) AS DUPLICATE_COUNT 
FROM SOME_TABLE
WHERE SOME_FIELD3 IS NOT NULL
-- группировка по дублирующемуся полю
GROUP BY DUPLICATING_FIELD
-- количество строк с одинаковым дублирующимся полем больше 1
HAVING COUNT(*) > 1
) AS doubles
ON result.DUPLICATING_FIELD  = doubles.DUPLICATING_FIELD
ORDER BY result.DUPLICATING_FIELD

Достаточно один раз понять принцип объединения запроса со вложенным запросом, и многие задачи будут решаться гораздо быстрее.

Основы вложенных запросов

Как сбросить значение столбца identity на 0 в Transact Sql

При удалении строк из таблицы счетчик столбца с identy остается на последнем значении. Удалили тысячу строк, следующая вставленная будет иметь код не 1, а 1001. Чтобы при удалении данных сбросить значение identity используем следующий код:

DELETE FROM SOME_TABLE
DBCC CHECKIDENT('SOME_TABLE', RESEED, 0)

DBCC CHECKIDENT (Transact-SQL)

Выполняем код для каждой строки в выборке Transact SQL с использованием курсора

В SQL мы обычно оперируем выборками, но иногда надо выполнить некие действия для каждой строки в выборке. В обычных языках такая задача решается обходом последовательности в цикле, но в Transact SQL клссические циклы плохо работают, вместо них лучше использовать специальный обьект - курсор. Его синтаксис весьма непривычен для новичков, так что в данной статье я приведу простой и работающий пример, который можно взять за основу. Общая схема действий такова:

  1. Создаем курсор, описывая выборку, со строками которой мы будем работать. Запрос может быть сколь угодно сложным, с кучей JOIN
  2. Открываем курсор
  3. Объявляем переменные, в которые будем выбирать поля каждой строки, количество переменных должно совпадать с количеством столбцов в запросе
  4. Делаем выборку первой строки
  5. Прокручиваем в цикле наш код, завершая его выборкой следующей строки
  6. Закрываем курсор

-- обьявляем курсор 
declare some_cursor cursor
--- sql запрос любой сложности, формирующий набор данных для курсора
for 
  select SOME_INT_FIELD, SOME_VARCHAR_FIELD from SOME_TABLE
-- открываем курсор
open some_cursor
-- курсор создан, обьявляем переменные и обходим набор строк в цикле
declare  @counter int
declare  @int_var int, @string_var varchar(100) 
set @counter = 0
-- выборка первой  строки
fetch next from some_cursor INTO  @int_var, @string_var
-- цикл с логикой и выборкой всех последующих строк после первой
while @@FETCH_STATUS = 0
begin
--- логика внутри цикла
set @counter = @counter + 1
 if @counter >= 5 break  -- возможный код для проверки работы, прерываем после пятой итерации

-- отладочный select, на большом количестве строк выборка данных  в  sql server management studio может привести к ошибке переполнения памяти
SELECT @int_var, @string_var
INSERT INTO OTHER_TABLE (SOME_FIELD1, SOME_FIELD2) VALUES (@string_var, 'Мегастрока')
DELETE FROM OTHER_TABLE2 WHERE ID_FIELD =  @int_var
exec some_stored_procedure
-- выборка следующей строки
fetch next from some_cursor INTO  @int_var, @string_var
-- завершение логики внутри цикла
end
select @counter as final_count
-- закрываем курсор
close some_cursor
deallocate some_cursor

Курсоры (Transact-SQL)
FETCH (Transact-SQL)
@@FETCH_STATUS (Transact-SQL)

Выделяем весь текст в большом количестве NumericUpDown по клику

В руководствах предлагают подсчитывать длину превращенного в строку значения NumericUpDown, но на практике это приводит к мелким глюкам, так что я просто делаю выделение на длину, заведомо большую всех возможных значений:

someNumUpDown.Select(0, 20);

Если NumericUpDown'ов много, то ручное проставление создание функций для каждого события становится весьма муторным занятием, лучше написать универсальную функцию для произвольного NumericUpDown назначить события в обход дизайнера:

someNumUpDown.Enter += new System.EventHandler(selecNumUpDownText);
someNumUpDown.Click += new System.EventHandler(selecNumUpDownText);
anotherNumUpDown.Enter += new System.EventHandler(selecNumUpDownText);
anotherNumUpDown.Click += new System.EventHandler(selecNumUpDownText);

private void selecNumUpDownText(object numUpDownObj, EventArgs e)
{
    (numUpDownObj as NumericUpDown).Select(0, 20);
}

Для новичка последняя функция может показаться несколько запутанной. На самом деле она очень проста. Абсолютно все классы в .Net являются наследниками класса Object. Соответственно любой объект любого класса мы можем превратить в Object, после чего произвести обратную операцию. Поэтому при любом событии мы получаем ссылку на объект, на котором произошло событие, завернутую в абстрактный тип базового для всех Object. В данном случае наша функция предполагает, что получаемый ей Object в прошлой жизни был объектом класса NumericUpDown и преобразует его обратно, после чего вызывает один из методов этого класса. Опытный программист заметит, что подобный код никак не защищен от передачи в него некорректного значения, внутри object может быть все что угодно, но в простых случаях сойдет и так.

How to set all numericupdown auto-select text when got focus?

Control.Select - метод

Как выделить/встать/поставить курсор в TextBox

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

someTextBox.Select();
someTextBox.ScrollToCaret();

TextBox.Select Method

Работает и для MaskedTextBox

Выбрать весь текст можно методом TextBox.SelectAll

Для ComboBox метод Focus работает удовлетворительно:

 someComboBox.Focus(); 

Cursor positioning in a TextBox

Мерцающие элементы управления WinForms/C#

Достаточно редкий и сюрреалистично выглядящий глюк WinForms - пропадающие и снова появляющиеся элементы управления, например связанные комбо-боксы. Это сбои в отрисовке, один из возможных источников - длительное время выполнения кода, подписанного часто возникающие внутри этих управляторов события. Само простое - убрать подобный код, если это невозможно, нас спасет свойство DoubleBuffered, которое можно поставить в true для всей формы.

Во всех остальных случаях надо закапываться в тонкости отрисовки компонентов, что выходит за рамки кругозора новичка, пара статей на эту тему (на английском):

Связанные ComboBox в приложении WinForms/C#

Задача: сделать несколько связанных между собой комбо-боксов, например для выбора адреса - выбираешь район, фильтруются улицы, выбираешь улицу, фильтруются дома - и так далее. Источник данных - связанная DataTable. Данные могут как просто фильтроваться, так и загружать из базы. Должен быть поиск по содержимом комбо-боксов, свои значения вводить нельзя, возможны пустые значения. Задача очень простая, так что ниже просто мини-памятка свойств WinForms, за которые надо дергать в данном случае.

Во-первых если запросы из базы занимают относительно много времени, секунду и больше, то привязка на стандартные события SelectedIndexChanged, SelectedValueChanged чревата очень странными глюками, так как реально код на этих событиях будет вызываться несколько раз и сильно тормозить. Лечить эти глюки нет смысла, так как тормоза останутся, стоит подписаться на событие SelectionChangeCommitted. При этом некоторые функции возможно придется продублировать для случай цепного обновления (изменился район-изменилась первая улица в списке - изменился первый дом в списке-изменилась первая квартира в списке)

Чтобы корректно работала подсказка с поиском по всему списку элементов ComboBox, надо выставить следующее сочетание свойств:

  1. AutoCompleteMode - SuggestAppend
  2. AutoCompleteSource - ListItems
  3. DropDownStyle - DropDownList

Как добавить строку/текст в начало файла в C# и .Net

Задача: добавить текст в начало текстового файла из C#. Стандартные функции добавляют текст в конец файла. Судя по всему законного способа добавления строки в начало не существует. Так что придется загрузить весь файл в память, добавить в начало искомую строку и перезаписать исходный файл. Максимальный размер строки в C# - 2 147 483647 символов, в случае больших файлов главной проблемой будет скорее требуемое количество памяти, в таком случае можно использовать временные файлы. Для решения задачи используем классы StreamReader и StreamWriter. В данном примере файл сохраняется в кодировке Win-1251 для совместимости с корпоративными программами.
Читать далее