转置表格并另存为视图
Posted
技术标签:
【中文标题】转置表格并另存为视图【英文标题】:Transpose table and save as View 【发布时间】:2013-07-17 10:54:08 【问题描述】:我有一个动态的pivot
/unpivot
脚本可以转置表格。这足够动态,可以返回我想要的某些列并使用动态列。
我正在寻找的是将其转换为 UDF
或 VIEW
以便我可以将其加入其他表。
请帮忙。
ALTER PROC [dbo].[uspGetUserByValues]
(
@Select NVARCHAR(4000) = '*',
@Where NVARCHAR(4000) = NULL,
@OrderBy NVARCHAR(4000) = NULL
)
AS
BEGIN
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT distinct ',P.' + QUOTENAME(PropertyDescription)
from System_Properties
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'SELECT ' + @cols + ', M.Email, C.Company_Name, C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles,0) SMSProfiles, U.UserID
from
(
select PropertyDescription, UP.UserID, PropertyValue
from User_Properties UP
JOIN System_Properties SP ON UP.PropertyID = SP.PropertyID
JOIN aspnet_Membership M ON UP.UserID = M.UserID
) X
pivot
(
min(PropertyValue)
for PropertyDescription in (' + REPLACE(@cols,'P.','') + ')
) P
JOIN aspnet_Membership M ON P.UserID = M.UserID
JOIN aspnet_Users U on P.UserID = U.UserID
JOIN Companies C ON C.Company_ID = P.Company_ID
LEFT JOIN (SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile GROUP BY UserID ) SMS ON SMS.UserID = P.UserID
'
SET @query = 'SELECT ' + @Select + ' FROM ('+ @query +') A'
IF ISNULL(@Where,'NULL') != 'NULL'
BEGIN
SET @query = @query + ' WHERE ' + @Where
END
IF ISNULL(@OrderBy,'NULL') != 'NULL'
BEGIN
SET @query = @query + ' ORDER BY ' + @OrderBy
END
execute(@query)
--PRINT(@query)
END
【问题讨论】:
您不能在视图或 UDF 中使用动态 SQL。您必须使用存储过程。 【参考方案1】:哇,我成功了。
我知道这是使用“已知”列名,但实际上我不必知道它们。
首先,这是我用来创建视图的查询。至少每次添加新属性时,我都需要删除视图,或者我实际上可以编写一个作业来检查 System_Properties 中的所有属性是否都显示在视图中,如果没有,则删除视图并运行此代码。
CREATE PROC [dbo].[uspCreateViewUsers]
AS
BEGIN
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT distinct ',P.' + QUOTENAME(PropertyDescription)
from System_Properties
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'CREATE VIEW vwUsers AS SELECT ' + @cols + ', M.Email, C.Company_Name, C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles,0) SMSProfiles, U.UserID
from
(
select PropertyDescription, UP.UserID, PropertyValue
from User_Properties UP
JOIN System_Properties SP ON UP.PropertyID = SP.PropertyID
JOIN aspnet_Membership M ON UP.UserID = M.UserID
) X
pivot
(
min(PropertyValue)
for PropertyDescription in (' + REPLACE(@cols,'P.','') + ')
) P
JOIN aspnet_Membership M ON P.UserID = M.UserID
JOIN aspnet_Users U on P.UserID = U.UserID
JOIN Companies C ON C.Company_ID = P.Company_ID
LEFT JOIN (SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile GROUP BY UserID ) SMS ON SMS.UserID = P.UserID
'
execute(@query)
END
他们的视图,它不能用表格连接的图形表示,看起来像这样:
SELECT P.[Company_ID], P.[Created_Date], P.[Created_User], P.[Cust_ID], P.[FirstName], P.[IPCheck], P.[JobTitle], P.[LastLogin], P.[LastModified_Date], P.[LastModified_User],
P.[LastName], P.[Newsletter_OptIn], P.[Password_Change], P.[SupAdmin], P.[SysAccess], P.[SysAdmin], P.[User_Cat_1], P.[User_Cat_10], P.[User_Cat_2],
P.[User_Cat_3], P.[User_Cat_4], P.[User_Cat_5], P.[User_Cat_6], P.[User_Cat_7], P.[User_Cat_8], P.[User_Cat_9], P.[UserClient_ID], M.Email, C.Company_Name,
C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles, 0) SMSProfiles, U.UserID
FROM (SELECT PropertyDescription, UP.UserID, PropertyValue
FROM User_Properties UP JOIN
System_Properties SP ON UP.PropertyID = SP.PropertyID JOIN
aspnet_Membership M ON UP.UserID = M.UserID) X PIVOT (min(PropertyValue) FOR PropertyDescription IN ([Company_ID], [Created_Date], [Created_User],
[Cust_ID], [FirstName], [IPCheck], [JobTitle], [LastLogin], [LastModified_Date], [LastModified_User], [LastName], [Newsletter_OptIn], [Password_Change], [SupAdmin],
[SysAccess], [SysAdmin], [User_Cat_1], [User_Cat_10], [User_Cat_2], [User_Cat_3], [User_Cat_4], [User_Cat_5], [User_Cat_6], [User_Cat_7], [User_Cat_8],
[User_Cat_9], [UserClient_ID])) P JOIN
aspnet_Membership M ON P.UserID = M.UserID JOIN
aspnet_Users U ON P.UserID = U.UserID JOIN
Companies C ON C.Company_ID = P.Company_ID LEFT JOIN
(SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile
GROUP BY UserID) SMS ON SMS.UserID = P.UserID
现在我可以像查询表格一样查询视图。
我希望这对将来的其他人有所帮助。
【讨论】:
这是一个完全不同的问题。它不需要输入参数,这里的逻辑解决方案是一个视图。如果您认为对某人有帮助,则必须更新您的问题,否则粗心的读者可能会不必要地浪费时间。 我不明白你的意思,因为我展示了我拥有的存储过程,我需要将其转换为视图,以便我可以JOIN
查询中的结果表。是的,您不能使用参数来创建视图,但现在我实际上可以说SELECT x,y,z FROM vwUsers WHERE a=b ORDER BY x
。该视图显然会存储所有转置的行,这就是我的问题。
在您的问题中,您明确指出存在动态 WHERE 子句,并且您希望将查询转换为 UDF 或视图。 WHERE 子句中使用了输入参数,这不适用于视图。您的答案没有这样的 where 子句,因此避免了定义您的问题的关键部分。如果其他人提供了这样的答案,他们可能会被否决。
哦,对不起,我没有意识到。我想说动态列。我知道 UDF/VIEW 不需要 WHERE 子句。我想我的问题应该被否决,因为它表明对 UDF/Views 缺乏理解。【参考方案2】:
简单地说:你不能
至少你不能使用传统的 TSQL 编程来做到这一点。这意味着您将不得不使用一些技巧。让我解释一下。
最接近您的 SP 的是 UDF。但是,UDF 相当受限制。 UDF 期望的一件事是数据在执行时和执行后保持不变。当然,这意味着 EXEC() 在该范围内是被禁止的。
另一种可能性是视图。但是,您有许多参数,并且视图的架构取决于这些参数。 SQL server 中不存在根据输入参数更改视图架构的功能。
现在是黑客攻击。
我能想到的一个技巧是:
但它可能有效,也可能无效(毕竟这是一个 hack)。
如果 hack 不起作用,您可以尝试照本宣科。这意味着创建一个 CLR UDF,在其中组装 select 语句并执行它,这意味着您将不得不丢弃原来的 SP。但是,它不是黑客攻击,因为 SQL CLR UDF 是针对此类(和其他)情况而设计的。您唯一需要注意的是使用SqlMetaData
,因为UDF 没有预定义的结果集。见this。
在我之前的回答中,我说过可以使用 CLR UDF 来完成,但这是错误的。我忘记的一件事是微软坚持为 UDF 提供有限数量的列。这在 .NET 中开发时可能并不明显——毕竟,您可以将任意数量的列返回给 SqlPipe。请参阅此(未经测试的)代码...
[SqlFunction(DataAccess = DataAccessKind.Read)]
public static void DynOutFunc(SqlString select, SqlString where, SqlString orderBy)
// 1: Create SQL query
string query = "select db_id(), db_name()";
// 2: Find out which colums and their types are part of the output
SqlMetaData[] metaData =
new SqlMetaData("ID", System.Data.SqlDbType.Int),
new SqlMetaData("Database", System.Data.SqlDbType.NVarChar, 256)
;
using (SqlConnection connection = new SqlConnection("context connection=true"))
connection.Open();
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
using (reader)
while (reader.Read())
SqlDataRecord record = new SqlDataRecord(metaData);
SqlContext.Pipe.SendResultsStart(record);
for(int i = 0; i < metaData.Length; i++)
if(metaData[i].DbType == DbType.String)
record.SetString(i, reader.GetString(i));
else if(metaData[i].DbType == DbType.Int32)
record.SetInt32(i, reader.GetInt32(i));
// else if's should cover all supported data taypes
SqlContext.Pipe.SendResultsRow(record);
SqlContext.Pipe.SendResultsEnd();
注意 SqlMetaData 集合包含有关列的信息。是什么阻止您在其中添加另一列?
但是(!)在 SQL Server 本身中注册该函数时,您必须提供参数,例如:
CREATE FUNCTION DynOutFunc
@select [nvarchar](4000),
@where [nvarchar](4000),
@orderBy [nvarchar](4000)
RETURNS TABLE (p1 type1, p2 type2, ... pN typeN)
AS EXTERNAL NAME SqlClrTest.UserDefinedFunctions.DynOutFunc;
事实证明,我想不出任何技巧。或者这里根本没有黑客。
【讨论】:
你能给我举个例子吗?某种指南? @SollyM:具体是什么例子?通用CLR UDF?还是黑客? 我需要在 SQL 上完成此操作,以便它独立于应用程序技术。基本上,我们有 ColdFusion 和 .NET 应用程序读取相同的数据库并一起进行更改。 .NET 代码在这方面无济于事。 SQL Server CLR 都是关于 .NET 代码的。但是,这与您的应用程序层无关。 CLR 驻留在与外界隔离的 SQL Server 中,除了在执行时引入的薄层,例如创建功能...作为外部。然后在 CLR 和您的应用程序之间创建一个接口,您的应用程序只需调用该函数即可使用该代码。但是,要编写函数,必须有一些 .NET 背景。 哦好的,现在我明白了。我的要求只是简单地获取一个表 User_Properties 并将其转换为并通过 UDF 或视图访问它,以便我可以将它与 SQL 中的其他表连接起来。我事先不知道这些列,因此我需要一种方法来读取这些列,然后进行转置,这是我从我的代码中存档的。以上是关于转置表格并另存为视图的主要内容,如果未能解决你的问题,请参考以下文章