ストアドプロシージャの結果セットから列を選択する


448

80列と300行を返すストアドプロシージャがあります。これらの列のうち2つを取得する選択を記述したいと思います。何かのようなもの

SELECT col1, col2 FROM EXEC MyStoredProc 'param1', 'param2'

上記の構文を使用すると、エラーが発生します。

「無効な列名」。

ストアドプロシージャを変更することが最も簡単な解決策であることはわかっていますが、私はそれを記述しておらず、変更することはできません。

やりたいことはありますか?

  • 結果を入れる一時テーブルを作成することもできますが、80列なので、2列を取得するためだけに80列の一時テーブルを作成する必要があります。返されたすべての列を追跡しないようにしたいと思いました。

  • WITH SprocResults AS ....マークの提案に従って使用してみましたが、2つのエラーが発生しました

    キーワード「EXEC」付近の構文が正しくありません。
    「)」付近の構文が正しくありません。

  • テーブル変数を宣言しようとすると、次のエラーが発生しました

    挿入エラー:列名または指定された値の数がテーブル定義と一致しません

  • 私がしよう
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    とすると、エラーが発生します:

    キーワード「exec」付近の構文が正しくありません。


好奇心の外で、このクエリは機能しますか?
Dave Costa、

5
私はこれに対する答えを見つけたことはありません。
ロッシーニ

32
さてあなたは非常に重要な質問に答えたことはありません!どのSQLプラットフォームについて質問していますか?MySQL、Microsoft SQL Server、Oracleなど。SQLServerのように見えますが、人に伝える必要があります。そうしないと、質問に確実に答えることができません。
JMTyler 2010

6
まあ、それはMS-SQLでなければなりません。 EXECはMySQLキーワードではありません(MySQLの同等物は準備済みステートメントです)。MySQLの答えを知りたいのですが、以下の答えはT-SQLを対象としています。再タグ付け。
bobobobo 2013年

1
私はこれに対する答えを見つけたことはありませんでした
ロッシーニ

回答:


186

クエリを分割できますか?ストアドプロシージャの結果をテーブル変数または一時テーブルに挿入します。次に、テーブル変数から2つの列を選択します。

Declare @tablevar table(col1 col1Type,..
insert into @tablevar(col1,..) exec MyStoredProc 'param1', 'param2'

SELECT col1, col2 FROM @tablevar

27
また、テーブルの定義がわからない場合も機能しません
Ian Boyd

そのタイプについて知りませんでした。それらは一時テーブルと同じように実装されていますか?それとも厳密にメモリにありますか?
d -_- b 2012

これは面白かった:sqlnerd.blogspot.com/2005/09/...
D -_- B

これは、一時テーブルに指定された列の数が、ストアドプロシージャの出力の列の数と同じである場合に機能します。Chagbert。
Chagbert 2016年

83

これは、問題を解決するためのさまざまな方法をすべて説明している非常に優れたドキュメントへのリンクです(ただし、既存のストアドプロシージャを変更できないため、それらの多くは使用できません)。

ストアドプロシージャ間でデータを共有する方法

Gulzarの答えは機能します(上記のリンクに記載されています)が、書くのは面倒です(@tablevar(col1、...)ステートメントで80個の列名をすべて指定する必要があります。そして将来的には列がスキーマに追加された場合、または出力が変更された場合、コードで更新する必要があるか、エラーになります。


1
そのリンクのOPENQUERYの提案は、OPが探しているものに非常に近いと思います。
コリン


39

これは私にとってはうまくいきます:(つまり、から返される30以上の列のうち2列だけが必要ですsp_help_job

SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, 
  'EXEC msdb.dbo.sp_help_job @job_name = ''My Job'', @job_aspect = ''JOB''');  

これが機能する前に、私はこれを実行する必要がありました:

sp_serveroption 'MYSERVER', 'DATA ACCESS', TRUE;

.... sys.serversテーブルを更新します。(つまり、OPENQUERY内で自己参照を使用することは、デフォルトでは無効になっているようです。)

私の単純な要件では、Lanceの優れたリンクのOPENQUERYセクションで説明されている問題に遭遇しませんでした。

Rossini、これらの入力パラメーターを動的に設定する必要がある場合、OPENQUERYの使用は少し面倒になります。

DECLARE @innerSql varchar(1000);
DECLARE @outerSql varchar(1000);

-- Set up the original stored proc definition.
SET @innerSql = 
'EXEC msdb.dbo.sp_help_job @job_name = '''+@param1+''', @job_aspect = N'''+@param2+'''' ;

-- Handle quotes.
SET @innerSql = REPLACE(@innerSql, '''', '''''');

-- Set up the OPENQUERY definition.
SET @outerSql = 
'SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, ''' + @innerSql + ''');';

-- Execute.
EXEC (@outerSql);

sp_serveroption既存のsys.servers自己参照を直接更新する場合と使用する場合の違い(ある場合)がわかりませんsp_addlinkedserver(ランスのリンクで説明されているように)複製/エイリアスを作成するために使用する。

注1:OPENQUERYはプロシージャ内で接続文字列の定義を必要としないため、OPENROWSETよりもOPENQUERYを優先します。

注2:これらすべてを言った:通常、私はINSERT ... EXECを使用するだけです:)はい、10分の余分な入力ですが、私がそれを助けることができるなら、私は次のことでぐずぐずしないことを好み
ます:(a)引用符内の引用符引用符、および
(b)sysテーブル、および/または卑劣な自己参照リンクサーバーの設定(つまり、これらの場合、私は全力のDBAに私の主張を訴える必要があります:)

ただし、この例でsp_help_jobは、既に使用しているINSERT ... EXEC構文を使用できませんでした。(「INSERT EXECステートメントはネストできません。」)


3
dynamic-sql-that-generated-dynamic-sql-that-generated-dynamic-sql ...の前に、13個の単一引用符を続けて使用しました...
ErikE

ジョブが終了したかどうかを確認する必要があります。「INSERT EXECステートメントはネストできません」。マイクロソフト嫌い。
alexkovelsky 2018

11

これを達成するには、まず#test_table以下のようなものを作成します:

create table #test_table(
    col1 int,
    col2 int,
   .
   .
   .
    col80 int
)

次に、プロシージャを実行して値を入力し#test_tableます。

insert into #test_table
EXEC MyStoredProc 'param1', 'param2'

次に、値をフェッチします#test_table

select col1,col2....,col80 from #test_table

2
テーブル変数の代わりに一時テーブルを作成する利点はありますか?
Dave Kelly

1
私がstackoverflowで見つけた最高の解決策!:)
Umar

4
他のストアドプロシージャの列が1つだけ必要な場合はどうなりますか?
Keval Patel 2017

11

ストアドプロシージャを変更できる場合は、必要な列定義をパラメーターとして簡単に配置し、自動作成された一時テーブルを使用できます。

CREATE PROCEDURE sp_GetDiffDataExample
      @columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
    DECLARE @query NVARCHAR(MAX)
    SET @query = N'SELECT ' + @columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
    EXEC sp_executeSql @query
    SELECT * FROM ##TempTable
    DROP TABLE ##TempTable
END

この場合、一時テーブルを手動で作成する必要はありません-自動的に作成されます。お役に立てれば。


セッション間で共有される## tablesの使用には注意してください
eKelvin '25

1
#テーブルと##テーブルの違いについての簡単な説明は、stackoverflow.com
a / 34081438/352820

これはSQLインジェクションになりやすいですか?
ブッシュロッド

11

なぜこれがそんなに難しいのかを知っておくと役立つでしょう。ストアドプロシージャは、テキスト(print 'text')のみを返すか、複数のテーブルを返すか、まったくテーブルを返さない場合があります。

のようなものSELECT * FROM (exec sp_tables) Table1 は機能しません


12
SQL Serverは、エラーが発生した場合に自由にエラーを発生させます。たとえば、複数の値を返すサブクエリを書いた場合。はい、それはでき起こるが、現実にはそれはしていません。そして、たとえそうであっても、エラーを発生させることは難しくありません。
Ian Boyd

8

(SQL Serverを想定)

T-SQLでストアドプロシージャの結果を操作する唯一の方法は、INSERT INTO ... EXEC構文を使用することです。これにより、一時テーブルまたはテーブル変数に挿入し、そこから必要なデータを選択することができます。


1
そのためには、テーブル定義を知っている必要があります。役に立たない。
Triynko 2016年

8

簡単なハックは、新しいパラメーターを追加し'@Column_Name'、呼び出す関数に取得する列名を定義させることです。sprocのreturn部分には、if / elseステートメントがあり、指定された列のみを返すか、空の場合はすべてを返します。

CREATE PROCEDURE [dbo].[MySproc]
        @Column_Name AS VARCHAR(50)
AS
BEGIN
    IF (@Column_Name = 'ColumnName1')
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1'
        END
    ELSE
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1', @ColumnItem2 as 'ColumnName2', @ColumnItem3 as 'ColumnName3'
        END
END

7

データの手動検証のためにこれを行っている場合は、LINQPadを使用してこれを行うことができます。

LinqPadでデータベースへの接続を作成してから、次のようなC#ステートメントを作成します。

DataTable table = MyStoredProc (param1, param2).Tables[0];
(from row in table.AsEnumerable()
 select new
 {
  Col1 = row.Field<string>("col1"),
  Col2 = row.Field<string>("col2"),
 }).Dump();

リファレンスhttp://www.global-webnet.net/blogengine/post/2008/09/10/LINQPAD-Using-Stored-Procedures-Accessing-a-DataSet.aspx


7

SQL Serverの場合、これは正常に機能することがわかりました。

一時テーブル(または実際には関係ない永続テーブル)を作成し、ストアドプロシージャに対してInsert intoステートメントを実行します。SPの結果セットはテーブルの列と一致する必要があります。一致しない場合、エラーが発生します。

次に例を示します。

DECLARE @temp TABLE (firstname NVARCHAR(30), lastname nvarchar(50));

INSERT INTO @temp EXEC dbo.GetPersonName @param1,@param2;
-- assumption is that dbo.GetPersonName returns a table with firstname / lastname columns

SELECT * FROM @temp;

それでおしまい!


このためには、テーブル定義のコピーを作成する必要があります。それを避ける方法はありますか?
Hardik

7

質問で述べたように、ストアドプロシージャを実行する前に80列の一時テーブルを定義することは困難です。

したがって、これを回避するもう1つの方法は、ストアドプロシージャの結果セットに基づいてテーブルにデータを設定することです。

SELECT * INTO #temp FROM OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;'
                                   ,'EXEC MyStoredProc')

エラーが発生した場合は、次のクエリを実行してアドホック分散クエリを有効にする必要があります。

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

sp_configure両方のパラメーターを指定して実行して構成オプションを変更するか、RECONFIGUREステートメントを実行するには、ALTER SETTINGSサーバーレベルの権限が付与されている必要があります。

これで、生成されたテーブルから特定の列を選択できます

SELECT col1, col2
FROM #temp

4

これを試して

use mydatabase
create procedure sp_onetwothree as
select 1 as '1', 2 as '2', 3 as '3'
go
SELECT a.[1], a.[2]
FROM OPENROWSET('SQLOLEDB','myserver';'sa';'mysapass',
    'exec mydatabase.dbo.sp_onetwothree') AS a
GO

1
笑-彼はしませんでした。代わりに、ネットワークスニッフィングによってデータベースにアクセスしなくてもはるかに簡単に取得できるストアドプロシージャの呼び出しにコードをコーディングしました。
マーティンミラノ

Githubからも簡単に取得できます。
nomen

3

私はspから実行して一時テーブルまたはテーブル変数に挿入することもオプションであることを知っていますが、それはあなたの要件ではないと思います。あなたの要件に従って、この以下のクエリステートメントは機能するはずです:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);uid=test;pwd=test'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

信頼できる接続がある場合は、次のクエリステートメントを使用します。

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);Trusted_Connection=yes;'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

上記のステートメントを実行するとエラーが発生する場合は、このステートメントを以下で実行してください。

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

これがこの種の同様の問題に直面したことのある人に役立つことを願っています。誰かが以下のような一時テーブルまたはテーブル変数を試す場合、このシナリオでは、spが返す列の数を知っておく必要があります。その場合、一時テーブルまたはテーブル変数にその数の列を作成する必要があります。

--for table variable 
Declare @t table(col1 col1Type, col2 col2Type)
insert into @t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM @t

--for temp table
create table #t(col1 col1Type, col2 col2Type)
insert into #t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM #t

1

SQL 2012以降を使用しているすべての人にとって、動的ではなく、毎回同じ列が出力されるストアドプロシージャを使用してこれを実現できました。

一般的なアイデアは、一時テーブルを作成、挿入、選択、およびドロップする動的クエリを構築し、すべてが生成された後でこれを実行することです。最初にストアドプロシージャから列の名前と型を取得して、一時テーブルを動的に生成します

注:SPを更新したり、構成を変更して使用したりできる場合は、より少ないコード行で機能する、より優れた、より普遍的なソリューションがありますOPENROWSET。他に方法がない場合は、以下を使用してください。

DECLARE @spName VARCHAR(MAX) = 'MyStoredProc'
DECLARE @tempTableName VARCHAR(MAX) = '#tempTable'

-- might need to update this if your param value is a string and you need to escape quotes
DECLARE @insertCommand VARCHAR(MAX) = 'INSERT INTO ' + @tempTableName + ' EXEC MyStoredProc @param=value'

DECLARE @createTableCommand VARCHAR(MAX)

-- update this to select the columns you want
DECLARE @selectCommand VARCHAR(MAX) = 'SELECT col1, col2 FROM ' + @tempTableName

DECLARE @dropCommand VARCHAR(MAX) = 'DROP TABLE ' + @tempTableName

-- Generate command to create temp table
SELECT @createTableCommand = 'CREATE TABLE ' + @tempTableName + ' (' +
    STUFF
    (
        (
            SELECT ', ' + CONCAT('[', name, ']', ' ', system_type_name)
            FROM sys.dm_exec_describe_first_result_set_for_object
            (
              OBJECT_ID(@spName), 
              NULL
            )
            FOR XML PATH('')
        )
        ,1
        ,1
        ,''
    ) + ')'

EXEC( @createTableCommand + ' '+ @insertCommand + ' ' + @selectCommand + ' ' + @dropCommand)

0

これが一度だけ必要な場合の最も簡単な方法:

インポートおよびエクスポートウィザードでExcelにエクスポートしてから、このExcelをテーブルにインポートします。


4
ストアドプロシージャを作成する全体のポイントは、再利用性です。あなたの答えは完全にそれと矛盾します。
deutschZuid 2013年

6
deutschZuidに対抗するために、元の投稿では、これを再利用するかどうか、またはストアドプロシージャの結果を調べているだけかどうかについては触れていません。マーティンは正しいです、彼が一度だけそれをする必要があるなら、これはおそらく最も簡単な方法です。
ビショップ

0

動的ビューを作成し、それから結果を取得します。

CREATE PROCEDURE dbo.usp_userwise_columns_value
(
    @userid BIGINT
)
AS 
BEGIN
        DECLARE @maincmd NVARCHAR(max);
        DECLARE @columnlist NVARCHAR(max);
        DECLARE @columnname VARCHAR(150);
        DECLARE @nickname VARCHAR(50);

        SET @maincmd = '';
        SET @columnname = '';
        SET @columnlist = '';
        SET @nickname = '';

        DECLARE CUR_COLUMNLIST CURSOR FAST_FORWARD
        FOR
            SELECT columnname , nickname
            FROM dbo.v_userwise_columns 
            WHERE userid = @userid

        OPEN CUR_COLUMNLIST
        IF @@ERROR <> 0
            BEGIN
                ROLLBACK
                RETURN
            END   

        FETCH NEXT FROM CUR_COLUMNLIST
        INTO @columnname, @nickname

        WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @columnlist = @columnlist + @columnname + ','

                FETCH NEXT FROM CUR_COLUMNLIST
                INTO @columnname, @nickname
            END
        CLOSE CUR_COLUMNLIST
        DEALLOCATE CUR_COLUMNLIST  

        IF NOT EXISTS (SELECT * FROM sys.views WHERE name = 'v_userwise_columns_value')
            BEGIN
                SET @maincmd = 'CREATE VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END
        ELSE
            BEGIN
                SET @maincmd = 'ALTER VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END

        --PRINT @maincmd
        EXECUTE sp_executesql @maincmd
END

-----------------------------------------------
SELECT * FROM dbo.v_userwise_columns_value

-1

元のSPをカットアンドペーストして、必要な2つを除くすべての列を削除します。または。結果セットを元に戻し、適切なビジネスオブジェクトにマップしてから、2つの列をLINQします。


1
人々はこれをしないでください。これはDRY原則に違反します。物事が変わったときではなく、変わったときに、すべての場所で変更を追跡して入力する必要があります。
jriver27
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.