2014-01-17

MSSQL UPSERT, или ON DUPLICATE KEY UPDATE по мелкомягкому

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

В MySQL это выглядит так:

INSERT INTO(id, text) VALUES(12, 'Current text')
ON DUPLICATE KEY UPDATE text='New text';

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

Первый вариант - это просто проверять существование записи перед добавлением. Гром и молния, кэп, как я сам не догадался! Да и не нужен бы мне тогда был upsert, правда? Это совсем не то, что я ищу, отпадает.

Второй вариант, который настойчиво предлагают советчики, это использовать MERGE. Его смысл - перенос данных из одной таблицы (запроса) в другую с проверкой совпадений значений некоторых полей. Что-ж, вроде годная штука, давайте попробуем. Нижеописанную задачу во многих случаях можно решить группировкой исходных данных, но не во всех. Нам нужно при совпадении значений полей увеличивать значение поля в таблице, куда переносятся данные, вместо копирования новой строки.

-- Создадим переменную типа таблицы с составным уникальным индексом
DECLARE @temp TABLE(word NVARCHAR(512), term_id INT, word_count SMALLINT, UNIQUE(word, term_id))

MERGE @temp as TMP
-- Источник данных, им может быть и таблица, и выборка
USING (SELECT word, term_id FROM table1) as TWT
-- Условия совпадения, при которых будем делать Update
ON TMP.word = TWT.word AND TMP.term_id = TWT.term_id
WHEN MATCHED THEN
    -- Нашли совпадение, изменим значение поля счетчика
    UPDATE SET TMP.word_count += 1
WHEN NOT MATCHED THEN
    -- Совпадения не найдено, добавляем
    INSERT (word, term_id word_count) VALUES (TWT.word, TWT.term_id, 1)
;

Сюрприз! При добавлении строк проверка осуществляется только в начале, а строки, которые будут добавлены потом на наличие совпадений не проверяются. Это легко можно проверить, добавив вручную в таблицу @temp пару строк, которые вскоре будут туда добавляться запросом. Так что для нашего случая это не годится.

Наконец третий, рабочий, но медленный вариант. Мы будем использовать конструкцию BEGIN TRY. Работаем с той-же таблицей @temp, что в предыдущем примере. Мы будем проходит курсором по выборке из исходной таблице и пробовать добавлять в @temp, и обновлять данные при ошибке уникального ключа.

-- Переменные, куда будем складывать данные из курсора
DECLARE @word NVARCHAR(512)
DECLARE @term_id INT
-- Объявляем и открываем курсор с данными
DECLARE words SCROLL CURSOR FOR SELECT word, term_id FROM table1
OPEN words
-- Проходим по курсору
FETCH NEXT FROM words INTO @word, @term_id WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        -- Пробуем вставить строку
        INSERT INTO @temp VALUES(@word, @term_id, 1)
    END TRY
    BEGIN CATCH
        -- При ошибке делаем update
        UPDATE @temp SET word_count = word_count + 1
        WHERE word = @word AND term_id =  @term_id
    END CATCH
    -- Берем данные из следующей строки
    FETCH NEXT FROM words INTO @word, @term_id
END
-- Закрываем и освобождаем курсор
CLOSE words
DEALLOCATE words

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

2014-01-09

Python и чтение списков

Не так давно меня спросили - как бы ты прочитал список в Python в обратном порядке? Я предположил, что самым естественным образом будет просто выборка из списка по индексу от максимального (равного len(x) - 1) в порядке уменьшения. Сегодня я решил проверить варианты с подсчетом производительности. Python 2.7.4 x64, 8 Гб RAM, AMD Phenom II X4 960T, 3.00 GHz.

Итак, создаем список из ста миллионов записей и проходим по нему разными способами.


big_list = range(100000000)
big_list_len = len(big_list)

# Прямой перебор, самый быстрый и естественный: 10.3 sec
for item in big_list:
    a = item

# Перебор с выборкой по индексу в прямом порядке: 12.8 sec
for num in xrange(big_list_len):
    a = big_list[num]

# А теперь в обратном порядке: 12.7 sec
for num in xrange(big_list_len-1, 0, -1):
    a = big_list[num]

# И наконец, используем встроенную функцию reversed: 11.9 sec
for item in reversed(big_list):
    a = item

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

2013-12-05

Android, что я натворил

Запоздалый пост о том, что я напрограммировал под Android и выложил в Google Play, пока увлекался этим. В данный момент эти приложения заброшены и я объясню почему.
Первое - это приложение TaskPoint, предназначенное для создания напоминалок с привязкой к географическому расположению. Актуально для меня, постоянно забывающего зайти в магазин или аптеку по дороге домой. Замысел такой: иду мимо аптеки и получаю напоминание - "Купить витамины".
Сначала все было хорошо, потом я понял, что использовать GPS постоянно не получится, ибо батарея садится очень быстро. WiFi рядом не всегда, поэтому тоже отпадает. Тогда я сделал возможность использовать координаты от сотовых вышек, но в какой-то момент данные от них стали настолько неточными, что пропал весь смысл. В итоге - приложение вроде работает, но сильно зависит или от батареи, или от данных сотовых операторов. Несколько раз не получив напоминания вовремя, понял, что именно поэтому таких приложений до сих пор не существовало.
Второе приложение - Столичная ярмарка, создавалось для доступа со смартфона к одноименному сервису бесплатных объявлений. До ума довести не удалось, слишком много задач приходилось делать одновременно, но представление получить можно, и даже попользоваться. Развитие остановлено также по причине смены места работы.

Сортировка по имени в Finder

То, с чем сталкиваются все перешедшие на Mac OS - это непривычная сортировка файлов в Finder. Во-первых - это то, что папки сортируются наравне с файлами, а не идут первыми. Это поведение можно изменить, используя расширение для Finder - XtraFinder, он бесплатный. К тому-же, в нем много других приятных фич, сделавших использование файлового менеджера более привычным.
Второе будет заметно пользователям с русской локалью: русские имена идут в начале списка, в том числе в iTunes. Это изменяется штатными средствами. Идем в "Системные настройки", там выбираем "Язык и регион" и изменяем параметр "Порядок сортировки списков" с "Русский" на "Универсальный". Вот и всё. При желании там-же можно изменить язык системы на английский, если кому привычнее.

2013-11-26

Переход с Ubuntu на Mac

Так уж получилось. Увидел macbook с retina display и не устоял. Не буду тут воспевать яблоки, потому что, как я давно понял, совершенства на земле нет.
Несколько лет Ubuntu служила мне верой и правдой, а теперь придётся ей развиваться без меня. Честно, жаль, но устанавливать на макбук убунту всё-же считаю как минимум странным делом.
Конечно, как разработчику, мне было бы некомфортно без консоли. Пришлось забыть об apt, но, к счастью, есть замена, и даже две: MacPorts и  HomeBrew. Я остановил выбор на втором, показалось попроще. Через него установил vim, mc, s3cmd, enca, apache, mysql и иже с ними. Вобщем, жить и работать можно.
Из приложений с графическим интерфейсом - Sequel Pro, Sublime Text, IntelliJ Idea. Старые добрые Gimp и Inkscape есть и под мак.
В связи со всем этим, разработку приложений под Ubuntu, или интеграцию в неё придётся заморозить. Постараюсь делать что-то более платформонезависимое.