суббота, 27 ноября 2010 г.

sqlite: приятные мелочи в области агрегирующих функций

Продолжаю очень приятно удивляться sqlite. Очередным поводом стали агрегирующие функции:

  • есть две функции для вычисления сумм: традиционная sum() и чуть менее традиционная total() – обе суммируют не-NULL значения, но первая (в соответствии со стандартом SQL) возвращает пустой набор данных в случае, если таких значений не было, а вот вторая всегда возвращает 0.0 (что в некоторых случаях может быть весьма удобно). Кроме того, функция total() всегда возвращает double и не подвержена арифметическому переполнению;
  • есть крайне полезная функция group_concat(field[, separator]), которая объединяет значения поля в одну строку. Разделитель по-умолчанию – запятая. Причем в варианте с разделителем по-умолчанию можно использовать еще и DISTINCT:
    SELECT group_concat(DISTINCT user_name) FROM …. GROUP BY …
    Замечу, что в SQL Server-е (горячо и искренне мною любимом) нет простого способа сделать тоже самое – только через custom aggregate (т.е. специальную сборку, которую писать/разворачивать).

В общем: чем больше смотрю - тем больше нравится.

HTH, AlexS

вторник, 16 ноября 2010 г.

Эволюция MS Full-Text Search: доступ к анализатору полнотекстовых запросов

Так и работает эволюция: незаметно и по чуть-чуть. Недавно на StackOverflow задали вопрос (в вольном переводе):

Можно ли получить доступ к стеммеру, который используется в SQL Server FullText Search?

Моей первой мыслью было “нельзя”. Но на всякий случай решил свериться с MSDN. Так вот оказалось, что “почти можно”: в SQL Server 2008 в FullText Search помимо радикального изменения архитектуры, внесли менее заметные, но полезные изменения. В частности, была добавлена следующая системная табличная функция:

sys.dm_fts_parser('query_string', lcid, stoplist_id, accent_sensitivity)

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

select * from sys.dm_fts_parser('FORMSOF(inflectional, Worked)', 1033, 0, 0)

Т.е. мы запросили все словоформы слова “worked” в английской локали (locId = 1033). И вот какие ключевые слова будут соответствовать этому запросу:

image

Было бы еще хорошо иметь возможность заглянуть внутрь самого индекса (как это делает Luke для Lucene), но даже с этими средствами жить  становится намного веселее.

HTH, AlexS

четверг, 29 июля 2010 г.

Чем точнее указан путь – тем быстрее можно по нему идти ((С) Кэп)

Недавно попросили помочь с оптимизацией обработки XML в SQL Server-е. Запрос выглядит примерно следующим образом:

SELECT
  nref.value('./../../@id', 'int') AS articleid,
  nref.value('type[1]', 'nvarchar(256)') AS type,
  nref.value('theme[1]', 'varchar(256)') AS theme,
  nref.value('score[1]', 'varchar(32)') AS score
INTO #tmpTheme
FROM @bulk_xml.nodes('//theme') AS R(nref)

Очень простое действие – указание точного XPath выражения вместо обобщенного ('//theme') позволяет ускорить работу данного запроса в 1.5 – 2 раза.

HTH,

AlexS

воскресенье, 11 июля 2010 г.

Заблуждения: “restore from database”

Недавно пришлось развенчивать миф: “restore from database” (см. скриншот) позволяет использовать журнал транзакций [базы данных А] для “point in time recovery” [базы банных Б, восстановленной из резервной копии базы данных А].

image

Контекст, в котором происходило обсуждение, тоже представляет определенный интерес Есть база данных [пусть будет “База А”], которая довольно сильно нагружена. Она работает в режиме восстановления от сбоев “Full”. Каждую ночь делается полный бэкап этой базы, никакие другие бэкапы (дифференциальный, журнала) не делаются. Совет перевести базу в режим восстановления “Simple” (нагрузка на подсистему ввода/вывода меньше, размер журнала меньше, по возможностям  восстановления ничего не теряем) наталкивается на следующий аргумент  (вольный перевод)

“если База А будет повреждена, то мы сможем восстановить ее из резервной копии и донакатить транзакции из журнала, поэтому Simple нам не подходит”

Так вот, “эта штука так не работает”. Опция “from database” просто-напросто подхватывает историю резервных копий выбранной базы данных – так что пользователю не нужно лазить по дискам в поисках какого-то конкретного бэкапа (или самого свежего бэкапа). Журнал транзакций “намертво” привязан к базе и только SQL Server может что-то с ним делать (пользователь может только посмотреть что в нем, да и то используя недокументированные функции). Единственный [для пользователя] способ использовать информацию об операциях, хранящуюся в журнале, – сделать резервную копию журнала после создания полного бэкапа базы данных. В этом случае резервная копия журнала (а не сам журнал!) может быть использована для повторного выполнения операций в базе, восстановленной из этого полного бэкапа (т.е. “point in time recovery”).

HTH, AlexS

воскресенье, 16 мая 2010 г.

Право на жизнь для табличных переменных

С момента появления в SQL Server табличных переменных я все никак не мог понять (для себя): зачем же они прямо так нужны (по большому счету). Аргумент “передавать много параметров в хранимую процедуру в табличной форме” звучал (и до сих пор звучит) не слишком убедительно: никто не отменял упаковки/распаковки (в случае, когда вызов идет из кода) и … собственно таблиц (в случае, когда мы вызываем хранимку из другой хранимки/скрипта).

Недавно на StackOverflow был задан вопрос, который в вольном переводе звучит следующим образом: как мне сохранить какие-то данные из транзакции, которая затем будет отменена? Сценарий, который стоит за этом вопросом, более чем реален: протоколирование. Ответом на этот вопрос и являются табличные переменные: изменения значений переменных не является частью транзакции (и, соответственно, не подлежат отмене).

Проверил: да, таки работает. Но тут как в том старом анекдоте: “ложечки нашлись, но осадок остался”. Табличная переменная на самом деле – это временная таблица (что легко проверить). При этом изменения, вносимые в “обычные” временные таблицы протоколируются и являются частью транзакции, в которой эти изменения происходят (что тоже легко проверяется). Получается некоторое противоречие: табличная переменная – это временная таблица, но изменения, которые в нее вносятся не протоколируются. Покопавшись в гуглоридере, нашел (отмеченный мною же!) пост в блоге SQL Server Storage Engine на эту тему – “перечитал и переосмыслил” :-)

HTH

AlexS

вторник, 6 апреля 2010 г.

Бытовые особенности SQLite

В последние пару недель работаю с SQLite (не то чтобы очень плотно, но плотнее, чем “просто поковырять”). Штуковина безусловно полезная и нужная, но не без особенностей. Ниже приведу список того, обо что споткнулся сам:

  • Нет типа данных DateTime или чего-то похожего – все даты нужно хранить в виде строки; проблема мелкая, но стоит о ней помнить. Более подробное описание типов данных SQLite лежит здесь.
  • Нет функции поиска подстроки/символа в строке (т.е. ничего, аналогичного CHARINDEX в SQL Server-е). Можно, конечно, самому написать такую функцию (на C) и подключить ее на лету, но … в общем лучше бы она была сразу.
  • Не поддерживается [ставшая уже привычной] конструкция UPDATE|DELETE|INSERT …. FROM ….
  • Крайне любопытно организовано хранение данных, в частности – в каждой таблице неявно присутствует “автоматический первичный ключ” (поле ROWID), который к тому же является “кластерным” (в привычных терминах). Причем явное объявление “целочисленного, автоинкрементного первичного ключа” в большинстве случаев – просто псевдоним для ROWID.
  • Есть доступ к метаданным (их, правда, немного) – таблица sqlite_master.
  • Сравнение строк может производиться как с учетом регистра, так и без оного – зависит от того, как была собрана библиотека и от того, какие строки (ASCII/Unicode) сравниваются.
  • Поддерживаются триггеры, причем можно повесить триггер на обновление какой-то конкретной колонки.
  • Параметры могут определяться так же, как и в SQL Server-е - @ParamName (но поддерживаются и другие варианты: ?Name, :Name, $Name).

HTH,

AlexS

четверг, 11 февраля 2010 г.

Синхронизация доступа: on Type or not on Type

Сегодня на собеседовании задал кандидату вопрос о том, как реализовать Singleton в C#. Сначала он предложил самый простой вариант (не thread safe) и в ходе дальнейшего обсуждения (“а почему так? а что если у нас многопоточное приложение? и что делать в этом случае?” и т.п.) предлагается вариант с блокировкой (внимание):

lock(typeof(MySingleton)) { …

В этот момент я подумал: “однако…”. На вопрос: “А почему так?” кандидат ответить затруднился, сославшись на некую статью, читаную им когда-то на просторах интернета.

На первый взгляд идея выглядит подкупающе свеж: не нужно держать отдельный объект, по которому идет синхронизация, все просто и элегантно. Даже MSDN подтверждает, что объекты класса Type уникальны (т.е. в своем роде синглтоны):

A Type object that represents a type is unique; that is, two Type object references refer to the same object if and only if they represent the same type.

Там же сказано, что объекты этого класса являются безопасными с точки зрения многопоточного доступа – ну прямо идеальный кандидат для наших нужд. Но мысль о том, почему же я никогда раньше не слышал о таком варианте, так и не давала покоя. Ответ содержится в той же самой статье:

Note:

In multithreading scenarios, do not lock Type objects in order to synchronize access to static data. Other code, over which you have no control, might also lock your class type. This might result in a deadlock. Instead, synchronize access to static data by locking a private static object.

Так что “увы и ах” - не судьба (даром что выглядит элегантно). И не забываем о пользе чтения документации.

HTH,

AlexS