Групповые символы поля varchar
Я создаю инструмент импорта данных от нескольких поставщиков. К сожалению, данные генерируются не мной, поэтому я должен работать с ними. Я столкнулся со следующей ситуацией.
У меня есть таблица, подобная следующей:
ID |SartDate |Availability
========================================
H1 |20130728 |YYYYYYNNNNQQQQQ
H2 |20130728 |NNNNYYYYYYY
A3 |20130728 |NNQQQQNNNNNNNNYYYYYY
A2 |20130728 |NNNNNYYYYYYNNNNNN
Чтобы объяснить, что означают эти данные,: Каждая буква в столбце доступность является флагом доступности для определенной даты, начиная с даты, указанной в столбце дата начала.
- Y: Доступно
- N: Недоступно
- Q : По Запросу
Например, для ID H1 20130728-20130802 доступен, затем с 20130803 - 20130806 недоступен и с 20130807-20130811 доступен по запросу.
Что мне нужно сделать, так это преобразовать эту таблицу в следующую настройку:
ID |Available |SartDate |EndDate
========================================
H1 |Y |20130728 |20130802
H1 |N |20130803 |20130806
H1 |Q |20130806 |20130811
H2 |N |20130728 |20130731
H2 |Y |20130801 |20130807
A3 |N |20130728 |20130729
A3 |Q |20130730 |20130802
A3 |N |20130803 |20130810
A3 |Y |20130811 |20130816
A2 |Y |20130728 |20130801
A2 |Y |20130802 |20130807
A2 |Y |20130808 |20130813
Начальная таблица содержит приблизительно 40 000 строк.
Колонка доступности может иметь несколько дней (я видел до 800).
Я попытался превратить доступность в строки, а затем сгруппировать последовательные дни вместе, а затем получить мин и Макс дату для каждой группы. Для этого я использовал три или четыре CTE
Это прекрасно работает для нескольких идентификаторов, но когда я пытаюсь применить его ко всей таблице, это занимает годы (я остановил начальный тестовый запуск после дурацкого сна, и он не закончился, и да, я имею в виду, что я спал, пока он работал!!!!)
Я подсчитал, что если я переверну каждый символ в одной строке, то у меня получится что-то вроде 14,5 миллионов строк.Итак, я спрашиваю, есть ли более эффективный способ сделать это? (Я знаю, что есть, но мне нужно, чтобы вы мне сказали)
Заранее благодарю.
4 ответа:
Это можно сделать в SQL Server, используя рекурсивные CTEs. Вот пример:
with t as ( select 'H1' as id, cast('20130728' as date) as StartDate, 'YYYYYYNNNNQQQQQ' as Availability union all select 'H2' as id, cast('20130728' as date) as StartDate, 'NNNNYYYYYYY' as Availability union all select 'H3' as id, cast('20130728' as date) as StartDate, 'NQ' as Availability ), cte as ( select id, left(Availability, 1) as Available, StartDate as thedate, substring(Availability, 2, 1000) as RestAvailability, 1 as i, 1 as periodcnt from t union all select t.id, left(RestAvailability, 1), dateadd(dd, 1, thedate), substring(RestAvailability, 2, 1000) as RestAvailability, 1 + cte.i, (case when substring(t.Availability, i, 1) = substring(t.Availability, i+1, 1) then periodcnt else periodcnt + 1 end) from t join cte on t.id = cte.id where len(RestAvailability) > 0 ) select id, min(thedate), max(thedate), Available from cte group by id, periodcnt, Available;
Способ, которым это работает, заключается в том, что он сначала распределяет даты. Это будет "типичное" использование обобщенных табличных выражений. В процессе он также отслеживает, изменилось ли
Available
от предыдущего значения (в переменнойperiodcnt
. Для этого он использует струнные манипуляции.С этой информацией конечный результат является просто агрегацией из этого CTE.
Поскольку SQL Server не является лучшим инструментом, если бы мне пришлось это сделать, я, вероятно, настроил бы пакет служб Integration Services, где я использовал бы компонент сценария для кодирования создания нескольких записей из одной в C#.
Вы пробовали использовать
CROSS APPLY
Может ли это дать лучшую производительность? Это не полный ответ. Просто еще один способ разбора?Edit : теперь я использую
table variable
для индексной таблицы.DECLARE @MaxLen INT SELECT @MaxLen = MAX(LEN(Availability)) FROM InputTable DECLARE @a TABLE (i int) ;WITH x AS ( SELECT 1 AS i UNION ALL SELECT i + 1 FROM x WHERE i <= @MaxLen ) INSERT INTO @a SELECT i FROM x OPTION (MAXRECURSION 0); ;WITH cte AS ( SELECT *, DATEADD(DAY, i-1, StartDate) StatusAtDay FROM InputTable t cross apply ( select SUBSTRING(t.Availability, i, 1) as c, i from @a WHERE LEN(Availability) >= i ) ca ) SELECT * FROM cte order by 1
Я попробовал с некоторыми 5000 строк и где длина
Availability
> 1250 это заняло 19 секунд (бросая вывод в временную таблицу).
Я попробовал другой подход. Вместо того чтобы использовать библиотеку SQLXMLBulkLoad с исходным xml-файлом, я подумал, что могу выполнить преобразование с помощью LINQ, а затем массово загрузить выходные данные в БД.
Итак, мой исходный xml был примерно таким:
<vacancies> <vacancy> <code>AT1010.200.1</code> <startday>2010-07-01</startday> <availability>YYYYYYNNNNQQQQQ</availability> <changeover>CCIIOOX</changeover> <minstay>GGGGGGGG</minstay> <flexbooking>YYYYY</flexbooking> </vacancy> <vacancy> <code>AT1010.200.2</code> <startday>2010-07-01</startday> <availability>NNNNYYYYYYY</availability> <changeover>CCIIOOX</changeover> <minstay>GGGGGGGG</minstay> <flexbooking>YYYYY</flexbooking> </vacancy> <vacancy> <code>AT1010.200.3</code> <startday>2010-07-01</startday> <availability>NNQQQQNNNNNNNNYYYYYY</availability> <changeover>CCIIOOX</changeover> <minstay>GGGGGGGG</minstay> <flexbooking>YYYYY</flexbooking> </vacancy> <vacancy> <code>AT1010.200.4</code> <startday>2010-07-01</startday> <availability>NNNNNYYYYYYNNNNNN</availability> <changeover>CCIIOOX</changeover> <minstay>GGGGGGGG</minstay> <flexbooking>YYYYY</flexbooking> </vacancy> </vacancies>
Задача здесь будет состоять в создании нового xml, который будет иметь даты начала и окончания для каждой группы флагов доступности.
XElement xe = XElement.Load(file); int i = 0; char previousFlag = ' '; int GroupIndex = 0; XElement vacancies = new XElement ( "vacancies", xe.Elements("vacancy") .Select ( x => { i = 0; GroupIndex = 0; return new { availabilities = x.Element("availability") .Value .Select ( v => { if (previousFlag != v) { GroupIndex++; } previousFlag = v; return new { Code = x.Element("code").Value, startday = x.Element("startday").Value, Date = DateTime.Parse(x.Element("startday").Value).AddDays(i++), GIndex = GroupIndex }; } ) }; } ) .SelectMany ( x => x.availabilities ) .GroupBy ( g => new { Code = g.Code, startday = g.startday, GroupIndex = g.GIndex } ) .Select ( x => new XElement ( "vacancy", new XElement("code", x.Key.Code), new XElement("startday", x.Key.startday), new XElement("GroupIndex", x.Key.GroupIndex), new XElement("minDate", x.Min(z => z.Date)), new XElement("maxDate", x.Max(z => z.Date)) ) ) ); vacancies.Save(outputfile);
Открывая выходной файл у меня есть следующий формат xml:
<vacancies> <vacancy> <code>AT1010.200.1</code> <startday>2010-07-01</startday> <GroupIndex>1</GroupIndex> <minDate>2010-07-01T00:00:00</minDate> <maxDate>2010-07-06T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.1</code> <startday>2010-07-01</startday> <GroupIndex>2</GroupIndex> <minDate>2010-07-07T00:00:00</minDate> <maxDate>2010-07-10T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.1</code> <startday>2010-07-01</startday> <GroupIndex>3</GroupIndex> <minDate>2010-07-11T00:00:00</minDate> <maxDate>2010-07-15T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.2</code> <startday>2010-07-01</startday> <GroupIndex>1</GroupIndex> <minDate>2010-07-01T00:00:00</minDate> <maxDate>2010-07-04T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.2</code> <startday>2010-07-01</startday> <GroupIndex>2</GroupIndex> <minDate>2010-07-05T00:00:00</minDate> <maxDate>2010-07-11T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.3</code> <startday>2010-07-01</startday> <GroupIndex>1</GroupIndex> <minDate>2010-07-01T00:00:00</minDate> <maxDate>2010-07-02T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.3</code> <startday>2010-07-01</startday> <GroupIndex>2</GroupIndex> <minDate>2010-07-03T00:00:00</minDate> <maxDate>2010-07-06T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.3</code> <startday>2010-07-01</startday> <GroupIndex>3</GroupIndex> <minDate>2010-07-07T00:00:00</minDate> <maxDate>2010-07-14T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.3</code> <startday>2010-07-01</startday> <GroupIndex>4</GroupIndex> <minDate>2010-07-15T00:00:00</minDate> <maxDate>2010-07-20T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.4</code> <startday>2010-07-01</startday> <GroupIndex>1</GroupIndex> <minDate>2010-07-01T00:00:00</minDate> <maxDate>2010-07-05T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.4</code> <startday>2010-07-01</startday> <GroupIndex>2</GroupIndex> <minDate>2010-07-06T00:00:00</minDate> <maxDate>2010-07-11T00:00:00</maxDate> </vacancy> <vacancy> <code>AT1010.200.4</code> <startday>2010-07-01</startday> <GroupIndex>3</GroupIndex> <minDate>2010-07-12T00:00:00</minDate> <maxDate>2010-07-17T00:00:00</maxDate> </vacancy> </vacancies>
Которая плоская и готов к обработке инструментом SQLXMLBulkLoad без необходимости дальнейшего процесса.
Мой исходный xml был 60 МБ, и он был преобразован в файл 45 МБ менее чем за одну минуту, и хотя я не тестировал SQLXMLBulkLoad в новом файле, это будет молниеносно, так как я знаю его производительность с исходным файлом.
Я все равно буду пробовать все ваши решения, так как вы, безусловно, стоите попробовать, и я приму лучшее из них.Спасибо всем за усилия.