質問:以下のクエリ(外部キーの依存関係によるテーブルのリスト)をPostGreSqlに移植しています。
WITH Fkeys AS (
SELECT DISTINCT
OnTable = OnTable.name
,AgainstTable = AgainstTable.name
FROM sysforeignkeys fk
INNER JOIN sysobjects onTable
ON fk.fkeyid = onTable.id
INNER JOIN sysobjects againstTable
ON fk.rkeyid = againstTable.id
WHERE 1=1
AND AgainstTable.TYPE = 'U'
AND OnTable.TYPE = 'U'
-- ignore self joins; they cause an infinite recursion
AND OnTable.Name <> AgainstTable.Name
)
,MyData AS (
SELECT
OnTable = o.name
,AgainstTable = FKeys.againstTable
FROM sys.objects o
LEFT JOIN FKeys
ON o.name = FKeys.onTable
WHERE (1=1)
AND o.type = 'U'
AND o.name NOT LIKE 'sys%'
)
,MyRecursion AS (
-- base case
SELECT
TableName = OnTable
,Lvl = 1
FROM MyData
WHERE 1=1
AND AgainstTable IS NULL
-- recursive case
UNION ALL
SELECT
TableName = OnTable
,Lvl = r.Lvl + 1
FROM MyData d
INNER JOIN MyRecursion r
ON d.AgainstTable = r.TableName
)
SELECT
Lvl = MAX(Lvl)
,TableName
--,strSql = 'delete from [' + tablename + ']'
FROM
MyRecursion
GROUP BY
TableName
ORDER BY lvl
/*
ORDER BY
2 ASC
,1 ASC
*/
Information_schemaを使用すると、クエリは次のようになります。
WITH Fkeys AS
(
SELECT DISTINCT
KCU1.TABLE_NAME AS OnTable
,KCU2.TABLE_NAME AS AgainstTable
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2
ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION
WHERE (1=1)
AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME
)
,MyData AS
(
SELECT
TABLE_NAME AS OnTable
,FKeys.againstTable AS AgainstTable
FROM INFORMATION_SCHEMA.TABLES
LEFT JOIN FKeys
ON TABLE_NAME = FKeys.onTable
WHERE (1=1)
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties')
)
,MyRecursion AS
(
-- base case
SELECT
OnTable AS TableName
,1 AS Lvl
FROM MyData
WHERE 1=1
AND AgainstTable IS NULL
-- recursive case
UNION ALL
SELECT
OnTable AS TableName
,r.Lvl + 1 AS Lvl
FROM MyData d
INNER JOIN MyRecursion r
ON d.AgainstTable = r.TableName
)
SELECT
MAX(Lvl) AS Lvl
,TableName
--,strSql = 'delete from [' + tablename + ']'
FROM
MyRecursion
GROUP BY
TableName
ORDER BY lvl
/*
ORDER BY
2 ASC
,1 ASC
*/
私の質問は今です:
SQL Server(2008 R2でテスト済み):置き換えるとクエリが1秒から11分にジャンプするのはなぜですか
SELECT DISTINCT
OnTable = OnTable.name
,AgainstTable = AgainstTable.name
FROM sysforeignkeys fk
INNER JOIN sysobjects onTable
ON fk.fkeyid = onTable.id
INNER JOIN sysobjects againstTable
ON fk.rkeyid = againstTable.id
WHERE 1=1
AND AgainstTable.TYPE = 'U'
AND OnTable.TYPE = 'U'
-- ignore self joins; they cause an infinite recursion
AND OnTable.Name <> AgainstTable.Name
と
SELECT DISTINCT
KCU1.TABLE_NAME AS OnTable
,KCU2.TABLE_NAME AS AgainstTable
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2
ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION
WHERE (1=1)
AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME
???
私の知る限り、部分クエリのみを個別に実行しても、速度に大きな違いはありません。また、順序は異なりますが、結果セットはまったく同じです(Excelですべての行を確認しました)。
作業中のPostGreSQLバージョンより下(まったく同じdbコンテンツ[35テーブル]で35ミリ秒で終了...)
-いかなる保証もありません-
WITH RECURSIVE Fkeys AS
(
SELECT DISTINCT
KCU1.TABLE_NAME AS OnTable
,KCU2.TABLE_NAME AS AgainstTable
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2
ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION
)
,MyData AS
(
SELECT
TABLE_NAME AS OnTable
,FKeys.againstTable AS AgainstTable
FROM INFORMATION_SCHEMA.TABLES
LEFT JOIN FKeys
ON TABLE_NAME = FKeys.onTable
WHERE (1=1)
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_SCHEMA = 'public'
--AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties')
)
,MyRecursion AS
(
-- base case
SELECT
OnTable AS TableName
,1 AS Lvl
FROM MyData
WHERE 1=1
AND AgainstTable IS NULL
-- recursive case
UNION ALL
SELECT
OnTable AS TableName
,r.Lvl + 1 AS Lvl
FROM MyData d
INNER JOIN MyRecursion r
ON d.AgainstTable = r.TableName
)
SELECT
MAX(Lvl) AS Lvl
,TableName
--,strSql = 'delete from [' + tablename + ']'
FROM
MyRecursion
GROUP BY
TableName
ORDER BY lvl
/*
ORDER BY
2 ASC
,1 ASC
*/
それもそうです
AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME
information_schemaを使用する場合は不要であるため、実際には高速です。
私はおそらくここで_INFORMATION_SCHEMA
_ビューを放棄し、代わりに新しい_sys.
_ビューを使用します(下位互換性ビューとは対照的に)、または少なくともJOIN
の結果をインデックスに具体化します最初のテーブル。
再帰CTEは常にSQL Serverで 同じ基本計画 を取得します。SQLServerでは、各行がスタックスプールに追加され、1つずつ処理されます。つまり、_REFERENTIAL_CONSTRAINTS RC, KEY_COLUMN_USAGE KCU1, KEY_COLUMN_USAGE KCU2
_間の結合は、次のクエリSELECT COUNT(*) FROM MyRecursion
の結果と同じ回数だけ発生します。
あなたの場合(11分の実行時間から)はおそらく何千回もあると思いますので、再帰部分をできるだけ効率的にする必要があります。クエリは、次のようなことを何千回も実行します。
_ SELECT
KCU1.TABLE_CATALOG,
KCU1.TABLE_SCHEMA,
KCU1.TABLE_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2
ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION
WHERE KCU2.TABLE_NAME = 'FOO'
_
(注意:異なるスキーマの同じテーブル名の場合、クエリの両方のバージョンで誤った結果が返されます)
ご覧のとおり、この計画はかなり恐ろしいものです。
これをsys
クエリのプランと比較すると、やや簡単です。
_SELECT OnTable = OnTable.name,
AgainstTable = AgainstTable.name
FROM sysforeignkeys fk
INNER JOIN sysobjects OnTable
ON fk.fkeyid = OnTable.id
INNER JOIN sysobjects AgainstTable
ON fk.rkeyid = AgainstTable.id
WHERE AgainstTable.name = 'FOO'
_
MyData
の定義を次のように変更することにより、_#temp
_テーブルを明示的に作成せずに中間マテリアライズを促進できる場合があります
_MyData AS
(
SELECT TOP 99.999999 PERCENT
TABLE_NAME AS OnTable
,Fkeys.AgainstTable AS AgainstTable
FROM INFORMATION_SCHEMA.TABLES
LEFT JOIN Fkeys
ON TABLE_NAME = Fkeys.OnTable
WHERE (1=1)
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties')
ORDER BY TABLE_NAME
)
_
私のマシンで_Adventureworks2008
_をテストすると、ランタイムが約10秒から250ミリ秒に短縮されました(最初の実行がコンパイルに2秒かかったため、最初の実行が邪魔にならなかった後)。これは、最初の再帰呼び出しでの結合の結果を具体化する熱心なスプールを計画に追加し、その後の呼び出しでそれを再生します。この動作は保証されていませんが、Connectアイテムリクエストに賛成投票することをお勧めします CTEまたは派生テーブルの中間マテリアライズを強制するヒントを提供します
この振る舞いに頼るよりも、以下のように_#temp
_テーブルを明示的に作成する方が安全だと思います。
_CREATE TABLE #MyData
(
OnTable SYSNAME,
AgainstTable NVARCHAR(128) NULL,
UNIQUE CLUSTERED (AgainstTable, OnTable)
);
WITH Fkeys AS
(
SELECT DISTINCT
KCU1.TABLE_NAME AS OnTable
,KCU2.TABLE_NAME AS AgainstTable
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2
ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION
WHERE (1=1)
AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME
)
,MyData AS
(
SELECT
TABLE_NAME AS OnTable
,Fkeys.AgainstTable AS AgainstTable
FROM INFORMATION_SCHEMA.TABLES
LEFT JOIN Fkeys
ON TABLE_NAME = Fkeys.OnTable
WHERE (1=1)
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties')
)
INSERT INTO #MyData
SELECT *
FROM MyData;
WITH MyRecursion AS
(
-- base case
SELECT
OnTable AS TableName
,1 AS Lvl
FROM #MyData
WHERE 1=1
AND AgainstTable IS NULL
-- recursive case
UNION ALL
SELECT
OnTable AS TableName
,r.Lvl + 1 AS Lvl
FROM #MyData d
INNER JOIN MyRecursion r
ON d.AgainstTable = r.TableName
)
SELECT
MAX(Lvl) AS Lvl
,TableName
--,strSql = 'delete from [' + tablename + ']'
FROM
MyRecursion
GROUP BY
TableName
ORDER BY Lvl
DROP TABLE #MyData
_
または代わりに
どちらの場合も、ビューにクエリを実行していますが、互換性のために残されています: 互換性ビュー および 情報スキーマビュー 。
最高のパフォーマンスを得るには、代わりに catalog views を使用してください(msdn: "カタログビューは、カタログメタデータへの最も一般的なインターフェイスであり、取得、変換、および表示する最も効率的な方法であるため、使用することをお勧めします。この情報のカスタマイズされた形式」)。