Transforming XML data into a rowset using the nodes() method and Performance

Преобразуем XML данные из столбца таблицы в табличные используя метод xml nodes() и cross apply.
Пример, три варианта, в первых двух сгенерирована XML с 1000 узлов, в последнем 10000, первый и второй варианты отличаются представлением XML, значения в атрибутах или в тэгах. В третьем, главное отличие, XML сохраняется в переменную, и результат, производительность на несколько порядков выше.
Итог: 8 сек, 12 сек, и 125 мс, если хмл-ка маленькая 10-50 узлов можно и не использовать переменную, иначе придется, или использовать OpenXML.
Если запустить первый вариант с 10К, то будем ждать долго, минут 10 (не дождался прервал на 5), деградация в прогрессии.
with 
t as (
 select * from (values(1),(2),(3),(4),(5),(6),(7),(8),(9))t(gid)
), t2 as (
 select row_number() over (order by t.gid) r,t.* from t, t tt1, t tt2, t tt3, t tt4--, t tt5
), t_xml as (
 select cast((select top(1000) * from t2 for xml raw) as xml) xmlData -- всего 1000 нод
), t_from_xml as (
 select
  x.xCol.value('@r','int') id
   ,x.xCol.value('@gid','int') gid 
from  t_xml b
 cross apply b.xmlData.nodes('/row') as x(xCol)  
)
--select top(1000) * from t2
--select * from t_xml
select * from t_from_xml
with 
t as (
 select * from (values(1),(2),(3),(4),(5),(6),(7),(8),(9))t(gid)
), t2 as (
 select row_number() over (order by t.gid) r,t.* from t, t tt1, t tt2, t tt3, t tt4--, t tt5
), t_xml as (
 select cast((select top(1000) * from t2 for xml path('row')) as xml) xmlData -- всего 1000 нод
), t_from_xml as (
 select
  x.xCol.value('(r/text())[1]','int') id
   ,x.xCol.value('(gid/text())[1]','int') gid 
from  t_xml b
 cross apply b.xmlData.nodes('/row') as x(xCol)  
)
--select top(1000) * from t2
--select * from t_xml
select * from t_from_xml
DECLARE @data xml
with 
t as (
 select * from (values(1),(2),(3),(4),(5),(6),(7),(8),(9))t(gid)
), t2 as (
 select row_number() over (order by t.gid) r,t.* from t, t tt1, t tt2, t tt3, t tt4--, t tt5
), t_xml as (
 select cast((select top(10000) * from t2 for xml path('row')) as xml) xmlData -- всего 1000 нод
), t_from_xml as (
 select
  x.xCol.value('(r/text())[1]','int') id
   ,x.xCol.value('(gid/text())[1]','int') gid 
from  t_xml b
 cross apply b.xmlData.nodes('/row') as x(xCol)  
)
--select top(1000) * from t2
select @data = (select xmlData from t_xml)

select
 x.xCol.value('(r/text())[1]','int') id
  ,x.xCol.value('(gid/text())[1]','int') gid 
from  @data.nodes('/row') as x(xCol)  
sql xml

Из таблицы в XML и обратно, for xml, from openxml

Если нужно сохранить результат выполнения запроса, оборачиваем его, и пишем в некий столбец типа xml
". ($saveXml ? "update {$this->db_our}[m_PWStatus] set xmlData = (" : '') . "
select 
tt8.priceN * cnt price,
sum(tt5.selected) over (partition by null) selectedCnt,
sum(coalesce(tt8.priceN * cnt,0)*tt5.selected) over (partition by null) selectedSum,
sum(tt5.moot1) over (partition by tt5.h2_id) smoot1,
sum(tt5.moot2) over (partition by tt5.h2_id) smoot2,
(select count(id) from {$this->db_our}[m_PWSubStatus] pw where pw.deleted is null and pw.statusId=1 and pw.pmesId = h2_id) approvedInPmes,
 * from tt5
 outer apply (
	select et.name etName, ek.name ekName, ek.id ekId, eqs.serialNumber from {$this->db_our}[m_listMeasEqui] eqs
	 inner join {$this->db_our}m_typeMeasEqui et on (et.deleted is null and et.id = eqs.typeMeasEquiID)
	 inner join {$this->db_our}m_equiKinds ek on (ek.deleted is null and ek.id = et.equiKindsID)
	where 1=1 
		and eqs.deleted is null 	
		and eqs.id = tt5.measEquiId 
 ) tt7
 outer apply (
	select tt6.workNameF, tt6.price priceN, tt6.kindId kindId2, tt6.id isPriced from tt6
	where 1=1
	and tt6.documentId = tt5.documentID
	and tt6.entityTypeId = tt5.intEntityType
	and coalesce(tt6.kindId,0) = coalesce(tt7.ekId,0)
	and (coalesce(tt7.ekId,0) not in (30,40) or (tt7.ekId in (30,40) and coalesce(tt6.vl,0) = coalesce(tt5.vr,0)))
 ) tt8

 order by h1_id, h2_id, h3_id, TIName, entity, documentID, vr desc, createdD" .
($saveXml ? "
 for xml raw, BINARY BASE64, root('rows'), XMLSCHEMA('urn:api.mneti.ru') ) 
 where deleted is null and pfId={$p->id} and statusId=2" : '');
raw — построчно в тэгах row,
BINARY BASE64 — бинарные данные преобразовать, если таковые есть
root(‘rows’) — корневой тэг
XMLSCHEMA(‘urn:api.mneti.ru’) — сохранить в том числе и схему (может и пригодится, особенно для внешних систем, данные о типах и размерностях)

Забираем данные, в моем случае, схему для возвращаемых данных описывал подправив сохраненный в результате шаблон схемы
		$sqlReport = [['select * from (',''], ["
select *, sum(priceSum) over (partition by null) priceTotal from (
select h1_id, h2_id, h3_id, h1_name, h2_name, h3_name, entity, vr2 vr, workNameF, sum(cnt) cnt, sum(price) priceSum from (
",
"
group by h1_id, h2_id, h3_id, h1_name, h2_name, h3_name, entity, vr2, workNameF
 ) tt
 order by h1_id, h2_id, h3_id, h1_name, h2_name, h3_name, vr desc, entity, workNameF"]];

		$sql = "
DECLARE @xmlData XML

select @xmlData = xmlData from {$this->db_our}[m_PWStatus] where deleted is null and pfId={$p->id} and statusId=2

DECLARE @handle INT  
DECLARE @PrepareXmlStatus INT  

EXEC @PrepareXmlStatus= sp_xml_preparedocument @handle OUTPUT, @xmlData, ''

".$sqlReport[$report][0]."
SELECT *, (case when ekId in (10) then null else vr end) vr2 FROM OPENXML(@handle, '/rows/ttt:row')
	WITH (
	price decimal(25,3)
	,selectedCnt int
	,selectedSum decimal(38,3)
	,smoot1 int
	,smoot2 int
	,approvedInPmes int
	,selected int
	,inOther int
	,id int
	,h1_id int
	,h2_id int
	,h3_id int
	,h1_name varchar(128)
	,h2_name varchar(128)
	,h3_name varchar(128)
	,h3_extId int
	,documentID int
	,dName varchar(128)
	,entity varchar(255)
	,createdD datetime
	,createdDH varchar(30)
	,docDateH varchar(30)
	,TIName varchar(4000)
	,vr float
	,cnt int
	,measEquiId int
	,pfId int
	,unitPriceId int
	,intEntityType int
	,hashedName varchar(128)
	,originalName varchar(256)
	,moot int
	,moot1 int
	,moot2 int
	,etName varchar(128)
	,ekName varchar(255)
	,ekId int
	,serialNumber varchar(64)
	,workNameF varchar(258)
	,priceN decimal(14,3)
	,kindId2 int
	,isPriced int	
	)
) tt ".$sqlReport[$report][1]."
    
EXEC sp_xml_removedocument @handle
";
Тут есть нюансы, если решили выгружать данные с описанием схемы, то и не забываем указать ее при загрузке, при выгрузке можно не указывать urn схемы, тогда сервер сам подставит что-то вроде этого «urn:schemas-microsoft-com:sql:SqlRowSet22» причем последнее число на свое усмотрение, не наш вариант. И корень не забываем указать.

LAST_VALUE() returns wrong result

LAST_VALUE() возвращает неверный результат, и вот почему
select *,
 first_value(id) over (partition by gr order by id) fv,
 last_value(id) over (partition by gr order by id) lv0, -- неверный результат, совсем не последнее значение в группе
 last_value(id) over (partition by gr order by id RANGE BETWEEN UNBOUNDED PRECED, ING AND CURRENT ROW) lv1, -- неверный результат, вот оно значение для рамки окна по умолчанию
 last_value(id) over (partition by gr order by id RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) lv2,
 first_value(id) over (partition by gr order by id desc) lv3,
 max(id) over () mx,
 max(id) over (partition by gr) mxg,
 max(id) over (partition by gr order by id) mxg1 -- вот и max сломался, добавили применилась рамка окна по умолчанию
 from (values
 (1,1),(2,1),(3,1),
 (4,2),(5,2),
 (6,3),(7,3),(8,3)
) t(id, gr)
order by id, gr
id	gr	fv	lv0	lv1	lv2	lv3	mx	mxg	mxg1
1	1	1	1	1	3	3	8	3	1
2	1	1	2	2	3	3	8	3	2
3	1	1	3	3	3	3	8	3	3
4	2	4	4	4	5	5	8	5	4
5	2	4	5	5	5	5	8	5	5
6	3	6	6	6	8	8	8	8	6
7	3	6	7	7	8	8	8	8	7
8	3	6	8	8	8	8	8	8	8
Читаем документацию :-)
Если предложение ORDER BY не указано, то для рамки окна используется весь раздел. Это относится только к тем функциям, которым не требуется предложение ORDER BY. Если предложение ROWS или RANGE не указаны, а указано предложение ORDER BY, то в качестве значения по умолчанию для рамки окна используется RANGE UNBOUNDED PRECEDING AND CURRENT ROW. Это относится только к тем функциям, которые могут принимать дополнительную спецификацию ROWS или RANGE. Например, ранжирующая функция не может принимать предложение ROWS или RANGE, поэтому данная рамка окна не может использоваться, даже несмотря на наличие предложения ORDER BY, а предложение ROWS или RANGE отсутствует.
И это касается не только SQL Server.

SQL Server 2005 imitate Oracle keep dense_rank

Замена Ораклевого keep dense_rank на SQL Server 2005
select max(m.id),
       m.someId keep (DENSE_RANK FIRST ORDER BY m.UpdateDate desc) 
from MyTable m 
group by m.someId
Исходные данные
id UpdateDate someId
1  20-01-2012 10
2  20-01-2012 10
3  01-01-2012 10
4  10-02-2012 20
5  01-02-2012 20
6  01-04-2012 30
Результат
2 10
4 20
6 30
И конечно это будет работать и на Оракле.
select m.*
from (select *, row_number() over (partition by m.someid ORDER BY m.UpdateDate desc, m.id desc) as seqnum
      from MyTable m
     ) m
where seqnum = 1
Результат,
группировки снаружи не нужно, группировка идет по окну over (partition by m.someid
max(m.id) заменен сортировкой в окне ORDER BY .... m.id DESC
id	UpdateDate	someId	seqnum
2	2012-01-20 00:00:00.000	10	1
4	2012-02-10 00:00:00.000	20	1
6	2012-04-01 00:00:00.000	30	1

Добавить столбец с not null в таблицу с даными

При попытке:
alter table m_listDocs add contractId int not null

Инструкция ALTER TABLE допускает добавление только тех столбцов, которые могут содержать значения NULL, имеют указанное определение DEFAULT, являются столбцами идентификаторов или временной метки; если ни одно из вышеперечисленных условий не выполнено, для добавления такого столбца таблица должна быть пустой. Не удалось добавить столбец «contractId» в непустую таблицу «m_listDocs», так как она не соответствует этим условиям.

Обходим:
alter table m_listDocs add contractId int
update m_listDocs set contractId = 0
alter table m_listDocs alter column contractId int not null

TRY_CONVERT, TRY_CAST в MSSQL 2005

Сообразил такой вариант, прям в запрос, удобно
select c1.value('xs:integer((/v)[1])','int') ci1, c1.value('xs:float((/v)[1])','float') ci1 from (
	select cast('<v> 1234 </v>' as xml) c1 union all
	select cast('<v>1234 Z</v>' as xml) c1 union all
	select cast('<v>12,3</v>' as xml) c1 union all
	select cast('<v>12.3</v>' as xml) c1 union all
	select cast('<v>23e2</v>' as xml) c1
 ) tt

ci1	ci1
1234	1234
NULL	NULL
NULL	NULL
NULL	12,3
NULL	2300

select cast('<v>1234</v>' as xml).value('xs:integer((/v)[1])','int') ci1

Это не проверял
CREATE FUNCTION TryCastInt(@inputStr nvarchar(max))
RETURNS int 
AS 
BEGIN
	DECLARE @ret int;
	set @ret = -999999
	IF  (~(cast(patindex('%[^0-9]%', @inputStr) as bit))) >0
		select @ret = cast (@inputStr as int) 
	ELSE
	 set @ret  = -999999
 RETURN @ret;
END;

Немного другой вариант, для поразмыслить
<code>DECLARE @VARCHARString VARCHAR(8000)
SET @VARCHARString = '000012W`''ASDASDS3A@!#@!#_)+)_4)'
SELECT @VARCHARString
--Remove non numeric charecters from the varchar variable
WHILE PATINDEX('%[^0-9]%', @VARCHARString) > 0
SET @VARCHARString = STUFF(@VARCHARString, PATINDEX('%[^0-9]%', @VARCHARString), 1, '')
SELECT @VARCHARString + c1 from (
 select 1 c1 union all
 select 2 c2
) tt

System.Data.SqlClient.SqlError: Резервный набор данных содержит копию базы данных, отличной от существующей базы данных

Пытаемся восстановить БД, получаем ошибку Безымянный1
  1. Ставим крыжку перезаписать БД Безымянный2
  2.  И обязательно указываем файлы данных и журнала базы которую перезаписываемБезымянный3

Возможен и вариант без пункта 1, БД в которую будем восстанавливать дамп не создаем заранее, при выполнении восстановления в окне выбора источника и назначения, в назначении не выбираем уже существующую БД из выпадающего списка, а вводим новое наименование. И обязательно выполняем 2, переименовываем файлы назначения данных и журнала.