Metrika

23 июня 2011 г.

C#: как получить символ по коду UTF-8

Мне нужно было из строки удалить символ, который имел код UTF-8 8232. Думал получить по коду переменную типа string и с помощью метода Replace заменить на пустую строку.
Что бы получить строку по коду, можно воспользоваться методом:
System.Text.Encoding.UTF8.GetString
Метод принимает массив byte[], соответственно код 8232 нельзя туда подсунуть, т.к. он больше максимального байта. Я подумал, может, если я разобью этот код на два байта, то все получиться, т.е. сделал так:
string str = System.Text.Encoding.UTF8.GetString(new byte[] { 0x20, 0x28 }, 0, 2);
Но в итоге, в переменной str оказывалось значение " (". Совсем не то, что нужно.
Погуглив нашел решение, надо взять не код в таблице символов, а строку, в которую кодируется этот символ в URL. В моем случае - это строка %E2%80%A8 и выполнить следующий метод:
string str = Uri.UnescapeDataString("%E2%80%A8");
Тогда в str окажется необходимый символ. И далее можно с ним работать строковыми методами.

8 июня 2011 г.

T-SQL: Функция ROW_NUMBER

Недавно открыл для себя очень удобную в некоторых случаях функцию ROW_NUMBER. Появилась она в SQL Server 2005.
Ее можно использовать в селектах для нумирации строк, можно с помощью нее селектить только строки с такого-то по такой-то номер, а так же можно выдавать номер строки в некоторй группе строк. Примеры можно посмотреть в описании.
Мне больше всего нравиться последний случай ее применения. Т.к. раньше такого рода задачи приходилось решать через курсоры, что вело к большим блокам кода и тормозам при исполнении.
Т.е. допустим есть таблица:
CREATE TABLE user_items
(
    id int not null primary key,
    user_id int not null,
    item_id int not null,
    added_at datetime not null
)
И хочется, что бы для каждого пользователя в таблице осталось не более 5 записей, которые были добавлены последними. С использованием ROW_NUMBER это можно сдедать так:

delete u from user_items u
inner join
(
    select ROW_NUMBER() OVER (partition by user_id order by added_at desc) rn, user_id, item_id, id
    from user_items
) a on u.id = a.id
where a.rn > 5
 Т.е. во вложенном запросе выбираются все записи и проставляются номера записей для каждого пользователя в обратном порядке добавления. Соответственно записи, у которых номер больше 5, надо удалить.

2 июня 2011 г.

Автоматическое партицирование OLAP-кубов в SSAS 2008


В статье Партицирование OLAP-кубов в SSAS 2008 я рассказывал зачем и как партицировать кубы в SSAS. Бывают случаи, когда партицировать надо часто и руками это делать скучно. Поэтому хочется этот процесс автоматизировать.
В моем случае мне нужно партицировать в трех кубах в общей сложности 10 групп мер. Если этого не делать каждую неделю, то время процессинга за две недели возрастает с 3,5 часов до 5 с лишним.
В статье Взаимодействие с OLAP кубами (SSAS 2008) на C# я рассказывал, как работать с кубами из кода. Сначала я начал решать задачу автоматического партицирования через разработку проги, но что то сильно углубился в программирование и мне эта идея перестала нравиться, поэтому я выбрал другой путь - создание пакета SQL Server Integration Services (SSIS). Здесь я и опишу этот способ.
Идея следующая - группы мер партицируются по годам плюс есть текущая партиция, которая процесситься каждый день. Т.е., например, для продаж есть партиции за 2010 год, 2011 и current. Раз в неделю для каждой группы мер необходимо изменять партицию за текущий год (2011) и партицию current, так что бы текущая партиция содержала данные только за несколько дней. Потом процессить партицию за текущий год, что бы не было пробелов в данных. После нового года надо будет руками создавать новую годовую партицию и менять процедуру изменения партиций.
Начальное партицирование куба надо провести руками, автоматически все с нуля не создается.
Для примера возьмем, что партиции группы мер продаж формируются следующими SQL-запросами:
2010: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2010-01-01' AND sale_date < '2011-01-01'
2011: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2011-01-01' AND sale_date < '2011-05-31'
current: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2010-05-31'
Эти запросы и надо будет менять в партициях за 2011 год и в текущей партиции.
Для начала необходимо сгенерить скрипты XMLA для изменения партиций. Я буду делать все на примере только одной группы мер. Пакет SSIS будет сделан так, что количество не будет иметь значения.
Cоздаем папку: C:\XMLAScripts. Далее, для генерации скрипта в Management Studio открываем список партиций нужной группы мер, на текущей партиции тыкаем правой кнопкой, а далее как на картинке:

В последнем меню выбираем File... и сохраняем в созданной папке под названием sales_current.xmla. Далее тоже самое повторяем для партиции текущего года и сохраняем в другой файл sales_2011.xmla.
Далее открываем файлы по-очереди и видим что-то похожее на это:
<Alter ObjectExpansion="ExpandFull" xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
    <Object>
        <DatabaseID>cubeDbSales</DatabaseID>
        <CubeID>cubeSales</CubeID>
        <MeasureGroupID>Vw Sales 1</MeasureGroupID>
        <PartitionID>Vw Sales 1</PartitionID>
    </Object>
    <ObjectDefinition>
        <Partition ...>
            <ID>Vw Sales 1</ID>
            <Name>Vw Sales</Name>
            <Source xsi:type="QueryBinding">
                <DataSourceID>dbOLAP</DataSourceID>
                <QueryDefinition>SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date &gt;= '2010-05-31'
</QueryDefinition>
            </Source>
            <StorageMode>Molap</StorageMode>
            <ProcessingMode>Regular</ProcessingMode>
            <ProactiveCaching>
                <SilenceInterval>-PT1S</SilenceInterval>
                <Latency>-PT1S</Latency>
                <SilenceOverrideInterval>-PT1S</SilenceOverrideInterval>
                <ForceRebuildInterval>-PT1S</ForceRebuildInterval>
                <Source xsi:type="ProactiveCachingInheritedBinding" />
            </ProactiveCaching>
        </Partition>
    </ObjectDefinition>
</Alter>

В каждом файле заменяем дату, которая разделяет партицию текущего года и текущую партицию, в данном примере это 2011-05-31, на какую-нибудь последовательность символов. Я поменял на "###dt###", т.е. запрос в скрипте будет выглядеть вот так:
 <QueryDefinition>SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date &gt;= '###dt###'</QueryDefinition>
Далее эта последовательность символов будет меняться на нужную дату.

В BIDS создаем Integration Services Project. Из Toolbox'а перетаскиваем Foreach Loop Container, а в него Script Task и Analysis Services Execute DDL Task. В итоге должно получиться вот так:
Далее создаем переменные:
Розовые уголки в иконках - это у меня плагин стоит, который я описываю в статье Как поменять область видимости переменной в BIDS (SSIS).
Для переменных dt и dt_str устанавливаем свойство EvaluateAsExpression = True. Для dt свойство Expression: GETDATE(). Для dt_str свойство Expression:
(DT_WSTR, 4)(YEAR(@[User::dt]))+"-"+
(LEN((DT_WSTR, 2)(MONTH(@[User::dt])))==1 ? "0"+(DT_WSTR, 2)(MONTH(@[User::dt])) : (DT_WSTR, 2)(MONTH(@[User::dt])))+"-"+
(LEN((DT_WSTR, 2)(DAY(@[User::dt])))==1 ? "0"+(DT_WSTR, 2)(DAY(@[User::dt])) : (DT_WSTR, 2)(DAY(@[User::dt])))
Такое выражение формирует из даты строку формата yyyy-MM-dd, другого пути я не нашел.
В совойствах Foreach контенера устанавливаем:
  1. В разделе Collection:
    1. Enumerator = Foreach File Enumerator
    2. Folder = C:\XMLAScripts
    3. Files = *.xmla 
  2.  В разделе Variable Mappings: Variable=User:file_name, Index = 0
 В свойствах блока Script в разделе Script:
  1. ReadOnlyVariables = User::dt_str,User::file_name
  2. ReadWriteVariables = User::alter_code
В коде скрипта пишем (у меня SQL Server 2008, поэтому пишу на C#):
        public void Main()
        {
            string file_name = Dts.Variables["file_name"].Value.ToString();
            StreamReader sr = new StreamReader(file_name);

            string content = sr.ReadToEnd();

            sr.Close();
            sr.Dispose();

            Dts.Variables["alter_code"].Value = content.Replace("###dt###",  Dts.Variables["dt_str"].Value.ToString());

            Dts.TaskResult = (int)ScriptResults.Success;
        }
 В свойствах блока Alter Cube в разделе DDL:
  1. Connection -> создаем новое соединение с кубом
  2. SourceType =Variable
  3. Source = User::alter_code
На этом создание SSIS пакета закончено. Foreach контейнер выбирает из папки C:\XMLAScripts все файлы с расширением xmla, в блоке Script текущий файл читается, в нем производиться замена строки "###dt###" на сегодняшнюю дату в формате yyyy-MM-dd, а блок Alter Cube передает полученную таким образом команду OLAP-серверу. Для того, что бы добавить другие группы мер для партицирования, надо в папку C:\XMLAScripts добавить XMLA-файлы с альтерами для этих групп, в которых дата в SQL-запросе будет заменена на "###dt###".
Далее нужно создать джоб, в котором первым шагом сделать бэкап куба на всякий случай, вторым шагом запустить этот SSIS пакет, а далее отпроцессить все партиции за текущий год.


1 июня 2011 г.

Партицирование OLAP-кубов в SSAS 2008

В целях повышения производительности работы кубов в SSAS предусмотрена возможность разбиения групп мер на партиции. Вот так это выглядит в Management Studio:
При процессинге куба процессится только партиция Vw Sales By Products, две другие уже не процессятся, т.к. данные за эти периоды не меняются. При больших объемах данных партицирование может значительно уменьшить время процессинга, а так же уменьшит время обращения к кубу, особенно если партиции разнести по разным физическим носителям.
Создать партиции можно как в Management Studio, так и в BIDS. Рекомендую делать партиции в BIDS, т.к. если их сделать в Management Studio, т.е. на рабочем кубе, а не в проекте, то при следующем деплое куба все партиции удаляться.
Для создания партиций в BIDS необходимо в дизайнере куба открыть вкладку Partitions:












Здесь видно две группы мер Sales и Sales 1 и список их партиций. Для того, что бы разбить на партиции группу мер Sales, необходимо сначала изменить текущую партицию. Разбивать на партиции будем по дате продажи. Для этого в колонке Source для партиции жмем кнопку, которая появляется при получении этим полем фокуса ввода. Появиться окно:

 По умолчанию Binding type = Table Binding. Его надо изменить на Query Binding,  в окне появиться поле с SQL-запросом, который используется для обращения к базе при процессинге куба, с пустым условием WHERE. В условие надо добавить выражение для партицирования. Я хочу разбивать на партиции по дате продажи, текущая партиция будет процесситься каждый день, в условие я добавляю:
WHERE sale_date >= '2011-05-31'
Жмем OK. Текущая партиция создана. Далее необходимо создать партицию (или несколько партиций) для исторических данных. Предположим продажи есть только за 2011 год. Для создания партиции жмем ссылку New Partition. Появиться окно:


 Выбираем таблицу в поле Available tables. Появиться окно, где вводиться запрос, в нем чекаем галку Specify a query to restrict rows и в запросе пишем условие ограничения:

WHERE sale_date >= '2011-01-01' AND sale_date < '2011-05-31'
 Далее нажимаем Next, появиться окно где можно выбрать опцию Processing loaction (не знаю, что это, не разбирался) и Storage location (т.е. где будут физически лежать файлы для этой партиции). Далее нажимеам Next, появиться окно, где в поле Name вбиваем название партиции, например Vw Sales 2011, выбираем Design aggregations later и жмем Finish.
Все, после этого окно партиций примет такой вид:
 Далее деплоим и процессим куб. Далее, в повседневной работе куба,    процесим только партицию Vw Sales, т.к. данные будут обновляться только в ней.