9

INパラメータで使用するデータベースを指定できるストアドプロシージャがあります。次に、そのデータベースで事前に決定されたテーブルをクエリに使用します。私が抱えている問題は、クエリ内でテーブル名をそのデータベース名に連結することです。T-SQLに評価関数があれば、次のようなことができます。

eval(@dbname + 'MyTable')

exec()現在、文字列を作成してから、その文字列をクエリとして実行するために使用するのに行き詰まっています。これは面倒なので、文字列を作成する必要はありません。変数または文字列を評価して、次のようなことができるようにする方法はありますか?

SELECT *
FROM eval(@dbname + 'MyTable')

評価して、次のように表示されるようにします。

SELECT *
FROM myserver.mydatabase.dbo.MyTable
4

11 に答える 11

16

これを読んでください... The Curse and Blessings of Dynamic SQLは、この種の問題を解決する方法を理解するのに大いに役立ちます。

于 2009-04-01T20:59:38.480 に答える
9

これを行うための「きちんとした」方法はありません。あなたがそれを受け入れて何か他のものを見ればあなたは時間を節約するでしょう。

編集: ああ!「毎月新しいデータベースにデータをロードする必要があります。そうしないと、データが大きくなりすぎます」というOPのコメントについて。振り返ってみると、この問題のかすかな匂いについて誰も気づかなかったことに驚いています。

SQL Serverは、「大きすぎる」テーブル(特にパーティション分割)を処理するためのネイティブメカニズムを提供します。これにより、テーブルをバックグラウンドで個別のファイルに分割しながら、テーブルを単一のエンティティとしてアドレス指定できるため、現在の完全に問題。

言い換えると、これはDBコンシューマーではなく、DB管理者にとっての問題です。それがあなたにも当てはまる場合は、このテーブルのパーティション化を検討することをお勧めします。

于 2009-03-27T03:40:26.507 に答える
6

sp_executesql 組み込み関数を試してください。基本的に、procでSQL文字列を作成してから呼び出すことができます

exec sp_executesql @SQLString.

DECLARE @SQLString nvarchar(max)
SELECT @SQLString = '
SELECT *
FROM  ' +  @TableName 

EXEC sp_executesql @SQLString
于 2009-03-27T03:29:59.683 に答える
2

SQL Server では動的テーブル名を指定できません。

いくつかのオプションがあります:

  1. 動的 SQL を使用する
  2. シノニムをいじってみましょう (これは、動的 SQL が少ないことを意味しますが、それでもいくつかあります)。

1が気に入らないと言ったので、2にしましょう。

最初のオプションは、乱雑さを 1 行に制限することです。

begin transaction t1;
declare @statement nvarchar(100);

set @statement = 'create synonym temptablesyn for db1.dbo.test;'
exec sp_executesql @statement

select * from db_syn

drop synonym db_syn;

rollback transaction t1;

これが好きかどうかはわかりませんが、これが最良の選択肢かもしれません。このようにして、すべての SELECT が同じになります。

これを心ゆくまでリファクタリングできますが、シノニムがトランザクションで作成されるなど、これには多くの欠点があるため、2 つのクエリを同時に実行することはできません (両方が実行しようとするため)。 temptablesyn を作成します)。ロック戦略に応じて、一方が他方をブロックします。

シノニムは永続的であるため、トランザクションでこれを行う必要があります。

于 2009-04-01T11:16:40.393 に答える
1

考えただけですが、これらのデータベースの事前定義されたリストがある場合は、データベースに接続してそれらに参加する単一のビューを作成できます-次のようなものです:

CREATE VIEW dbo.all_tables
AS

SELECT  your_columns,
        'db_name1' AS database_name
FROM    db_name1.dbo.your_table

UNION ALL

SELECT  your_columns,
        'db_name2'
FROM    db_name2.dbo.your_table

etc...

次に、データベース名をストアド プロシージャに渡し、それを WHERE 句のパラメータとして使用するだけです。テーブルが大きい場合は、新しい database_name 列(またはそれを何と呼ぶか​​)とテーブルの主キー(テーブルのスキーマが同じであるという質問から推測していますか? )。

明らかに、データベースのリストが頻繁に変更される場合、これはさらに問題になります。しかし、いずれにしてもこれらのデータベースを作成する必要がある場合は、同時にこのビューを維持することは、あまりオーバーヘッドになりません!

于 2009-04-04T07:20:18.667 に答える
1

テーブルにアクセスするために、SQL CLR テーブル値 UDF を作成できます。TV-UDF は動的スキーマをサポートしていないため、スキーマに関連付ける必要があります。(私のサンプルには ID と Title 列が含まれています - 必要に応じて変更してください)

これが完了したら、次のクエリを実行できるはずです。

SELECT * FROM dbo.FromMyTable('table1')

その文字列にもマルチパート名を含めることができます。

SELECT * FROM dbo.FromMyTable('otherdb..table1')

そのテーブルから ID,Title 列を返します。

SQL CLR を有効にし、TRUSTWORTHY オプションをオンにする必要があります。

sp_configure 'clr enabled',1
go
reconfigure
go
alter database mydatabase set trustworthy on

C# SQL プロジェクトを作成し、新しい UDF ファイルを追加して、これをそこに貼り付けます。プロジェクト プロパティ、データベース、アクセス許可レベルを外部に設定します。ビルド、デプロイ。VisualStudio なしで実行できます。必要な場合はお知らせください。

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Data.SqlClient;

[assembly: CLSCompliant(true)]
namespace FromMyTable
{
    public static partial class UserDefinedFunctions
    {
        [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true, SystemDataAccess = SystemDataAccessKind.Read, IsPrecise = true, FillRowMethodName = "FillRow", 
            TableDefinition = "id int, title nvarchar(1024)")]
        public static IEnumerable FromMyTable(SqlString tableName)
        {
            return new FromMyTable(tableName.Value);
        }

        public static void FillRow(object row, out SqlInt32 id, out SqlString title)
        {
            MyTableSchema v = (MyTableSchema)row;
            id = new SqlInt32(v.id);
            title = new SqlString(v.title);
        }
    }

    public class MyTableSchema
    {
        public int id;
        public string title;
        public MyTableSchema(int id, string title) { this.id = id; this.title = title; }
    }

    internal class FromMyTable : IEnumerable
    {
        string tableName;

        public FromMyTable(string tableName)
        {
            this.tableName = tableName;
        }

        public IEnumerator GetEnumerator()
        {
            return new FromMyTableEnum(tableName);
        }
    }

    internal class FromMyTableEnum : IEnumerator
    {
        SqlConnection cn;
        SqlCommand cmd;
        SqlDataReader rdr;
        string tableName;

        public FromMyTableEnum(string tableName)
        {
            this.tableName = tableName;
            Reset();
        }

        public MyTableSchema Current
        {
            get { return new MyTableSchema((int)rdr["id"], (string)rdr["title"]); }
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            bool b = rdr.Read();
            if (!b) { rdr.Dispose(); cmd.Dispose(); cn.Dispose(); rdr = null; cmd = null; cn = null; }
            return b;
        }

        public void Reset()
        {
            // note: cannot use a context connection here because it will be closed
            // in between calls to the enumerator.
            if (cn == null) { cn = new SqlConnection("server=localhost;database=mydatabase;Integrated Security=true;"); cn.Open(); }
            if (cmd == null) cmd = new SqlCommand("select id, title FROM " + tableName, cn);
            if (rdr != null) rdr.Dispose();
            rdr = cmd.ExecuteReader();
        }
    }
}
于 2009-04-06T18:48:07.920 に答える
1

いくつかのオプションがありますが、それらはあなたがすでに行っている方法よりも厄介です. 次のいずれかをお勧めします:
(1)現在のアプローチに固執する
(2)とにかく実行しているので、先に進んでSQLをコードに埋め込みます。
(3) SQL インジェクションを避けるために、入力の検証には特に注意してください。

また、動的 SQL の問題は乱雑さだけではありません。次の点に注意してください。
(1) 動的 SQL は、再利用可能な実行計画を作成するサーバーの機能を妨げます。
(2) ExecuteSQL コマンドは、所有権の連鎖を壊します。つまり、コードは、プロシージャの所有者ではなく、ストアド プロシージャを呼び出すユーザーのコンテキストで実行されます。これにより、ステートメントが実行されているテーブルでセキュリティを開く必要が生じ、他のセキュリティの問題が発生する可能性があります。

于 2009-04-01T21:17:07.580 に答える
0

合理的に管理可能な数のデータベースがある場合は、次のような事前定義された条件ステートメントを使用するのが最適な場合があります。

if (@dbname = 'db1')
  select * from db1..MyTable
if (@dbname = 'db2')
  select * from db2..MyTable
if (@dbname = 'db3')
  select * from db3..MyTable

...

クエリに使用できるデータベースのリストを変更する場合は、データベース作成スクリプトの一部としてこのプロシージャを生成できます。

これにより、動的 sql に関するセキュリティ上の問題が回避されます。また、'select' ステートメントを各データベースをターゲットとするストアド プロシージャ (クエリごとに 1 つのキャッシュされた実行プラン) に置き換えることで、パフォーマンスを向上させることもできます。

于 2009-04-05T05:30:06.117 に答える
0
declare @sql varchar(256);
set @sql = 'select * into ##myGlobalTemporaryTable from '+@dbname
exec sp_executesql @sql

select * from ##myGlobalTemporaryTable

グローバル一時テーブルにコピーし、通常のテーブルのように使用できます

于 2009-04-05T02:12:30.757 に答える
0
if exists (select * from master..sysservers where srvname = 'fromdb')
    exec sp_dropserver 'fromdb'
go

declare @mydb nvarchar(99);
set @mydb='mydatabase'; -- variable to select database

exec sp_addlinkedserver @server = N'fromdb',
    @srvproduct = N'',
    @provider = N'SQLOLEDB', 
    @datasrc = @@servername,
    @catalog = @mydb
go

select * from OPENQUERY(fromdb, 'select * from table1') 
go 
于 2009-04-08T21:42:41.230 に答える