我想为表中的每一行添加一行,但要尽可能高效地将列转换为xml集合。在下面的示例中,它是一个展平的表 - 但在现实世界中,列需要许多连接才能获得 - 导致许多读取。
例如:
declare @tbl table (
Id int identity (1, 1) primary key
,PolicyNumber varchar(100) not null
,InsuredName varchar(100) not null
,EffectiveDate datetime2 not null
,Premium numeric(22, 7)
)
insert into @tbl (PolicyNumber, InsuredName, EffectiveDate, Premium)
values ('2017A-ALKJ02', 'Insured Number 1', '2017-01-01', 1000)
,('2017A-BSDSDFWEF2', 'Insured Number 2', '2017-06-01', 2000)
select Id
,(select [@name] = 'PolicyNumber', [@type] = 'string', [text()] = PolicyNumber from @tbl [inner] where [inner].Id = [outer].Id for xml path ('dt'))
,(select [@name] = 'InsuredName', [@type] = 'string', [text()] = [inner].InsuredName from @tbl [inner] where [inner].Id = [outer].Id for xml path ('dt'))
,(select [@name] = 'EffectiveDate', [@type] = 'datetime', [text()] = [inner].EffectiveDate from @tbl [inner] where [inner].Id = [outer].Id for xml path ('dt'))
,(select [@name] = 'Premium', [@type] = 'numeric', [text()] = [inner].Premium from @tbl [inner] where [inner].Id = [outer].Id for xml path ('dt'))
from @tbl [outer]
在各自的xml元素中产生各列,但我在每一行后都有它的主键和结构:
<dts>
<dt name="PolicyNumber" type="string">2017A-ALKJ02</dt>
<dt name="InsuredName" type="string">Insured Number 1</dt>
<dt name="EffectiveDate" type="datetime">2017-01-01T00:00:00</dt>
<dt name="Premium" type="numeric">1000.0000000</dt>
</dts>
我知道这可以通过许多子查询来实现,但有没有人知道一个简单的方法来获得一个足够智能的单个查询,以便将PK和所有单独的列转换为dts集合中的元素?
答案 0 :(得分:1)
这可以使用不同的技术来解决。以下是使用UNPIVOT
生成类型列的其中一个:
WITH DataSource AS
(
SELECT [id]
,[column]
,[value]
,CASE [column]
WHEN 'PolicyNumber' THEN 'string'
WHEN 'InsuredName' THEN 'string'
WHEN 'EffectiveDate' THEN 'datetime'
WHEN 'Premium' THEN 'numeric'
END AS [type]
FROM
(
SELECT Id
,PolicyNumber
,InsuredName
,CAST(EffectiveDate AS VARCHAR(100)) AS EffectiveDate
,CAST(Premium AS VARCHAR(100)) AS Premium
FROM @tbl
) DS
UNPIVOT
(
[value] FOR [column] IN ([PolicyNumber], [InsuredName], [EffectiveDate], [Premium])
) UNPVT
)
SELECT DISTINCT [id]
,[Info]
FROM @tbl DS
CROSS APPLY
(
SELECT [column] "@name"
,[type] "@type"
,CASE WHEN [column] = 'EffectiveDate' THEN CONVERT(VARCHAR(32), CAST([value] AS DATETIME2), 126) ELSE [value] END "text()"
FROM DataSource Info
WHERE DS.[Id] = Info.[Id]
FOR XML PATH('dt'), ROOT('dts')
) DSInfo (Info);
它会为每行提供这样的XML:
<dts>
<dt name="PolicyNumber" type="string">2017A-ALKJ02</dt>
<dt name="InsuredName" type="string">Insured Number 1</dt>
<dt name="EffectiveDate" type="datetime">2017-01-01T00:00:00</dt>
<dt name="Premium" type="numeric">1000.0000000</dt>
</dts>
答案 1 :(得分:1)
如果您事先了解所有元数据(列名称和类型),则可以非常简单地完成此操作:
declare @tbl table (
Id int identity (1, 1) primary key
,PolicyNumber varchar(100) not null
,InsuredName varchar(100) not null
,EffectiveDate datetime2 not null
,Premium numeric(22, 7)
);
insert into @tbl (PolicyNumber, InsuredName, EffectiveDate, Premium)
values ('2017A-ALKJ02', 'Insured Number 1', '2017-01-01', 1000)
,('2017A-BSDSDFWEF2', 'Insured Number 2', '2017-06-01', 2000);
SELECT 'PolicyNumber' AS [dt/@name]
,'string' AS [dt/@type]
,PolicyNumber AS [dt]
,''
,'InsuredName' AS [dt/@name]
,'string' AS [dt/@type]
,InsuredName AS [dt]
,''
,'EffectiveDate' AS [dt/@name]
,'datetime' AS [dt/@type]
,EffectiveDate AS [dt]
,''
,'Premium' AS [dt/@name]
,'numeric' AS [dt/@type]
,Premium AS [dt]
FROM @tbl
FOR XML PATH('dts'),ROOT('root')
结果
<root>
<dts>
<dt name="PolicyNumber" type="string">2017A-ALKJ02</dt>
<dt name="InsuredName" type="string">Insured Number 1</dt>
<dt name="EffectiveDate" type="datetime">2017-01-01T00:00:00</dt>
<dt name="Premium" type="numeric">1000.0000000</dt>
</dts>
<dts>
<dt name="PolicyNumber" type="string">2017A-BSDSDFWEF2</dt>
<dt name="InsuredName" type="string">Insured Number 2</dt>
<dt name="EffectiveDate" type="datetime">2017-06-01T00:00:00</dt>
<dt name="Premium" type="numeric">2000.0000000</dt>
</dts>
</root>
诀窍是无名的空&#34;列&#34;在<dt>
个元素之间。引擎被告知:看,有一个新元素,先关闭一个元素并开始一个新元素!
否则你会收到错误......
这将提取所有元数据并构造与上面相同的语句,该语句使用EXEC
执行:
CREATE TABLE tmpTbl (
Id int identity (1, 1) primary key
,PolicyNumber varchar(100) not null
,InsuredName varchar(100) not null
,EffectiveDate datetime2 not null
,Premium numeric(22, 7)
);
insert into tmpTbl (PolicyNumber, InsuredName, EffectiveDate, Premium)
values ('2017A-ALKJ02', 'Insured Number 1', '2017-01-01', 1000)
,('2017A-BSDSDFWEF2', 'Insured Number 2', '2017-06-01', 2000);
DECLARE @cmd NVARCHAR(MAX)='SELECT ' +
STUFF(
(
SELECT ',''' + c.COLUMN_NAME + ''' AS [dt/@name]' +
',''' + c.DATA_TYPE + ''' AS [dt/@type]' +
',' + QUOTENAME(c.COLUMN_NAME) + ' AS [dt]' +
','''''
FROM INFORMATION_SCHEMA.COLUMNS AS c WHERE TABLE_NAME='tmpTbl'
FOR XML PATH('')
),1,1,'') +
'FROM tmpTbl FOR XML PATH(''dts''),ROOT(''root'')';
EXEC( @cmd);
GO
--cleanup (careful with real data)
--DROP TABLE tmpTbl;
如果你需要例如&#34; string&#34;而不是&#34; varchar&#34;您需要映射表或CASE
表达式。
答案 2 :(得分:0)
在SQL Server中没有方便的方法来生成这种输出。一种可能的解决方案可能是FLWOR转换,但我怀疑它确实会非常复杂。
另一种方法是使用UNPIVOT
,如下例所示,尽管它远非易于扩展:
select (
select upt.ColumnName as [@name],
isnull(dt.ColumnType, 'string') as [@type],
upt.ColumnValue as [text()]
from (
select t.Id, t.PolicyNumber, t.InsuredName,
convert(varchar(100), t.EffectiveDate, 126) as [EffectiveDate],
cast(t.Premium as varchar(100)) as [Premium]
from @tbl t
) sq
unpivot (
ColumnValue for ColumnName in (
sq.PolicyNumber, sq.InsuredName, sq.EffectiveDate, sq.Premium
)
) upt
left join (values
('EffectiveDate', 'datetime'),
('Premium', 'numeric')
) dt (ColumnName, ColumnType) on upt.ColumnName = dt.ColumnName
where upt.Id = t.Id
for xml path('dt'), type
)
from @tbl t
for xml path('dts'), type;
首先,您需要将列值转换为行,以便您的关系输出将开始类似于您所需的XML。为了使所有列适合相同的ColumnValue
,您必须将它们转换为相同的数据类型。
其次,您必须提供type
属性的数据。在上面的示例中,我使用了内联表构造函数,因为您无法动态地从TV列获取数据类型。如果实际数据驻留在静态表中,则可以尝试将其与系统元数据对象(例如INFORMATION_SCHEMA.COLUMNS
)连接。虽然对于您所需的值,您可能还需要一个额外的映射表(例如,为varchar
替换string
)。
最后,为了为每个原始表行获取单个/dts
元素,我再次将表格中的未标记数据加入。这允许生成所需的XML元素嵌套,因为root()
子句不适用于此。
答案 3 :(得分:0)
感谢您的建议!我决定将查询扁平化为临时表是最好的方法,然后使用上面的通用方法加上空白列技巧满足需要。
if object_id('tempdb..#tmp') is not null
drop table #tmp
create table #tmp (
Id int identity (1, 1) primary key
,PolicyNumber varchar(100) not null
,InsuredName varchar(100) not null
,EffectiveDate datetime2 not null
,Premium numeric(22, 7)
);
insert into #tmp (PolicyNumber, InsuredName, EffectiveDate, Premium)
values ('2017A-ALKJ02', 'Insured Number 1', '2017-01-01', 1000)
,('2017A-BSDSDFWEF2', 'Insured Number 2', '2017-06-01', 2000);
DECLARE @cmd NVARCHAR(MAX)='
select [outer].Id
,convert(xml, (SELECT ' +
STUFF(
(
SELECT ',[dt/@n] = ''' + c.name + '''' +
',[dt/@t] = ''' + case when t.name = 'bit' then 'b'
when t.name in ('date', 'smalldatetime', 'datetime2', 'datetime', 'datetimeoffset') then 'd'
when t.name = 'bigint' then 'g'
when t.name in ('tinyint', 'smallint', 'int', 'time', 'timestamp') then 'i'
when t.name in ('real', 'smallmoney', 'money', 'float', 'decimal', 'numeric') then 'n'
else 's'
end + '''' +
',[dt] = ' + QUOTENAME(c.name) +
','''''
FROM tempdb.sys.columns c
inner join sys.types t on c.system_type_id = t.system_type_id
where object_id = object_id('tempdb..#tmp')
FOR XML PATH('')
),1,1,'') + '
FROM #tmp [inner]
where [inner].Id = [outer].id
for xml path (''dts'')))
from #tmp [outer]'
EXEC( @cmd);