1

テーブルを転置する動的pivot/スクリプトがあります。unpivotこれは、必要な特定の列を返すのに十分な動的であり、動的列を使用しています。

私が探しているのは、これを aUDFまたは aに変換し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
4

2 に答える 2

0

おお、出来ました。

これは「既知の」列名であることは知っていますが、実際にはそれらを知る必要はありませんでした。

まず、これはビューを作成するために使用したクエリです。少なくとも新しいプロパティを追加するたびにビューを削除する必要があります。そうでない場合は、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

これにより、ビューをテーブルであるかのようにクエリできるようになりました。

これが将来誰かに役立つことを願っています。

于 2013-07-18T11:08:41.253 に答える
0

簡単に言えば、あなたはできません

少なくとも、従来の TSQL プログラミングを使用してそれを行うことはできません。つまり、ハックを使用する必要があります。説明させてください。

SP に最も近いのは UDF です。ただし、UDF はかなり制限されています。UDF が期待することの 1 つは、実行中および実行後にデータが同じままであることです。もちろん、これはそのスコープでは EXEC() が禁止されていることを意味します。

別の可能性はビューです。ただし、いくつかのパラメーターがあり、ビューのスキーマはこれらのパラメーターに依存します。入力パラメーターに基づいてビューのスキーマを変更する機能は、SQL サーバーには存在しません。

そして今、ハックのために。

私が考えることができる1つのハックは次のとおりです。

  • CLR UDF を作成する
  • コンテキスト接続に基づいて新しい接続を作成します(同じサーバー、同じデータベース)
  • そこでSPを実行します
  • 結果を元のパイプに返す

しかし、うまくいくかもしれないし、うまくいかないかもしれません (結局のところ、これはハックです)。

ハックが機能しない場合は、本で試してみることができます。これは、CLR UDF を作成し、そこに select ステートメントをアセンブルして実行することを意味します。つまり、元の SP を破棄する必要があります。ただし、SQL CLR UDF はそのような (およびその他の) 状況向けに作成されているため、ハックではありません。SqlMetaDataUDF には定義済みの結果セットがないため、注意が必要なのは使用だけです。これを参照してください。

以前の回答では、CLR UDF を使用して実行できると述べましたが、それは間違いでした。私が忘れていたことの 1 つは、Microsoft が 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;

私が考えることができるこれにはハックがないことがわかりました。または、ここにはハックがまったくありません。

于 2013-07-17T13:06:37.437 に答える