2016-08-23 5 views
4

多くのテーブル30+ 1つのカラムのデータタイプをIntからBigIntに変更する必要があります。 私の問題は、特定の列が1つのテーブルPKと多くの他のテーブルFKにもあるということです。したがって、エラーメッセージが表示されるので、私は変更できません。スクリプトを使用して1つのデータ型から別のデータ型にカラムを変更する方法

私はアルゴリズムやスクリプトが必要です。しかし、私は手順や方法を理解することができません:レコードを削除し、常に最初のものを取得するためにcurrsor、または単なる一時テーブルを使用する必要があります...

そして私は何をすべきですか?スタックから最初のテーブルを取得し、そのカラムがPKかFKのどちらかで参照されているかどうかを確認し、そうであれば参照(PK、FKなど)を落としてからカラムのタイプを変更し、PkとFKを再作成しますか?私はドープしたものを保存する必要がありますか?私は非常に混乱しています...

すべてのヒント?

+0

に戻ってそれらのフルテキストインデックスを追加してみましょう。 SQL Server Data Toolがインストールされている必要があります。 – qxg

+0

@qxg:ありがとう、私は既にSSDTでVSを使用しています。私はちょうどそれを試してみましょう – BogdanM

答えて

2

これはスクリプトの獣ですが、私は現時点で同じ問題を解決しています。このスクリプトはまだ進行中の作業ですが、何をする必要があるのか​​をお手伝いしてください。

私の現在のコードは、与えられたテーブルとフィールドを使用しています。自由に編集できます。

DECLARE @TableToEdit varchar(255); SET @TableToEdit = 'Entity' 
DECLARE @MasterField sysname; SET @MasterField = 'Entity_Identifier' 

まず、すべての外部キーを削除する必要があります。

IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL 
    DROP TABLE #ForeignKeys 

CREATE TABLE #ForeignKeys 
(
    PKTABLE_QUALIFIER nvarchar(255) NULL 
    ,PKTABLE_OWNER nvarchar(255) NULL 
    ,PKTABLE_NAME nvarchar(255) NULL 
    ,PKCOLUMN_NAME varchar(255) NULL 
    ,FKTABLE_QUALIFIER nvarchar(255) NULL 
    ,FKTABLE_OWNER nvarchar(255) NULL 
    ,FKTABLE_NAME nvarchar(255) NULL 
    ,FKCOLUMN_NAME nvarchar(255) NULL 
    ,KEY_SQL int NULL 
    ,UPDATE_RULE int NULL 
    ,DELETE_RULE int NULL 
    ,FK_NAME nvarchar(255) NULL 
    ,PK_NAME nvarchar(255) NULL 
    ,DEFERRABILITY int NULL 
) 


/* find all tables that have an Entity_Identifier field that's not already an int */ 
IF OBJECT_ID('tempdb..#MasterTables') IS NOT NULL DROP TABLE #MasterTables 
CREATE TABLE #MasterTables (MasterTableName sysname, MasterTableSQL nvarchar(max)) 
INSERT INTO #MasterTables (MasterTableName, MasterTableSQL) 
SELECT DISTINCT 
o.name TableName 
,'INSERT INTO #ForeignKeys EXEC sp_fkeys ' + o.name MasterTableSQL 
FROM sys.objects o 
JOIN sys.columns c 
    ON o.object_id = c.object_id 
JOIN sys.types t 
    ON c.user_type_id = t.user_type_id 
WHERE o.type = 'u' 
--AND t.name NOT IN ('int','bigint') 
--AND c.name LIKE '%Entity_Identifier%' 
AND c.name LIKE '%' + @MasterField + '%' 



/* Let's find all Foreign Keys based upon a table that has an Entity_Identifier field that needs to be converted */ 

DECLARE @execspfkeys nvarchar(max) 

DECLARE spfkeyscursor CURSOR LOCAL FOR 
    SELECT MasterTableSQL FROM #MasterTables 

OPEN spfkeyscursor 

FETCH NEXT FROM spfkeyscursor INTO @execspfkeys 

WHILE @@FETCH_STATUS = 0 BEGIN 

    --execute your sproc on each row 
    EXEC sp_executesql @execspfkeys 

    FETCH NEXT FROM spfkeyscursor INTO @execspfkeys 
END 

CLOSE spfkeyscursor 
DEALLOCATE spfkeyscursor 



/* Ok, let's get the foreign key definitions from all relevant tables */ 

IF OBJECT_ID('tempdb..#FKScripts') IS NOT NULL 
    DROP TABLE #FKScripts 
CREATE TABLE #FKScripts 
(
    FKName nvarchar(255) 
    ,FKTableName nvarchar(255) 
    ,FKSchema nvarchar(10) 
    ,FKDatabase nvarchar(255) 
    ,PKName nvarchar(255) 
    ,PKTableName nvarchar(255) 
    ,PKSchema nvarchar(10) 
    ,PKDatabase nvarchar(255) 
    ,FKDisableScript nvarchar(max) 
    ,FKRebuildScript nvarchar(max) 
    ,FKCheckScript nvarchar(max) 
) 

INSERT INTO #FKScripts (FKName, FKTableName, FKSchema, FKDatabase, PKName, PKTableName, PKSchema, PKDatabase, FKDisableScript, FKRebuildScript, FKCheckScript) 
SELECT DISTINCT 
    fk.FK_NAME 
    ,fk.FKTABLE_NAME 
    ,fk.FKTABLE_OWNER 
    ,fk.FKTABLE_QUALIFIER 
    ,fk.PK_NAME 
    ,fk.PKTABLE_NAME 
    ,fk.PKTABLE_OWNER 
    ,fk.PKTABLE_QUALIFIER 
    ,'ALTER TABLE [' + fk.FKTABLE_OWNER + '].[' + fk.FKTABLE_NAME + '] DROP CONSTRAINT [' + fk.FK_NAME + ']' FKDisableScript 
    ,'ALTER TABLE [' + fk.FKTABLE_OWNER + '].[' + fk.FKTABLE_NAME + '] WITH CHECK ADD CONSTRAINT [' + fk.FK_NAME + '] FOREIGN KEY (' + PKList.FieldList +') REFERENCES [dbo].[' + fk.PKTABLE_NAME + '] (' + FKlist.FieldList + ')' FKRebuildScript 
    ,'ALTER TABLE [' + fk.FKTABLE_OWNER + '].[' + fk.FKTABLE_NAME + '] CHECK CONSTRAINT [' + fk.FK_NAME + ']' FKCheckScript 
FROM #ForeignKeys fk 
INNER JOIN 
    (
    SELECT DISTINCT 
      fk.FK_NAME, 
      STUFF((SELECT ','+ fk2.PKCOLUMN_NAME 
        FROM (SELECT FK_NAME, '[' + PKCOLUMN_NAME +']' PKCOLUMN_NAME FROM #ForeignKeys) fk2 
       WHERE FK.FK_NAME = fk2.FK_NAME 
       GROUP BY fk2.PKCOLUMN_NAME 
       FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, '') FieldList 
    FROM #ForeignKeys fk 
    ) PKlist 
ON fk.FK_NAME = PKlist.FK_NAME 
INNER JOIN 
    (
    SELECT DISTINCT 
      fk.FK_NAME, 
      STUFF((SELECT ','+ fk2.FKCOLUMN_NAME 
        FROM (SELECT FK_NAME, '[' + FKCOLUMN_NAME + ']' FKCOLUMN_NAME FROM #ForeignKeys) fk2 
       WHERE FK.FK_NAME = fk2.FK_NAME 
       GROUP BY fk2.FKCOLUMN_NAME 
       FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, '') FieldList 
    FROM #ForeignKeys fk 
    ) FKlist 
ON fk.FK_NAME = FKlist.FK_NAME 

DROP TABLE #ForeignKeys 



SELECT * FROM #FKScripts 


/* OK, let's disable these foreign keys, going to have to use a cursor for this (ouch) */ 

DECLARE @disablesql nvarchar(max) 

DECLARE discur CURSOR LOCAL FOR 
    SELECT FKDisableScript FROM #FKScripts 

OPEN discur 

FETCH NEXT FROM discur INTO @disablesql 

WHILE @@FETCH_STATUS = 0 BEGIN 

    --execute your sproc on each row 
    EXEC sp_executesql @disablesql 

    FETCH NEXT FROM discur INTO @disablesql 
END 

CLOSE discur 
DEALLOCATE discur 

/* right, we're finished with the cursor that disables the foreign keys, phew! */ 


/* check that the constraints are now disabled (for testing) */ 
SELECT DISTINCT 
fkt.* 
,CASE WHEN ((C.Status & 0x4000)) = 0 THEN 1 ELSE 0 END [Enabled2] 
FROM #FKScripts fkt 
INNER JOIN sys.sysobjects o2 
    ON o2.name = fkt.FKName 
INNER JOIN sys.sysobjects o 
    ON o2.id = o.parent_obj 
    AND o.xtype='F' 
INNER JOIN sys.sysconstraints c 
    ON o.id = c.constid 

    select * from #FKScripts 

次に、すべてのインデックスを削除する必要があります。

/* Drop index scripts into a temp table */ 

IF OBJECT_ID('tempdb..#CreateIndexes') IS NOT NULL DROP TABLE #CreateIndexes 
GO 

DECLARE @SchemaName varchar(100) 
DECLARE @TableName varchar(256) 
DECLARE @IndexName varchar(256) 
DECLARE @ColumnName varchar(100) 
DECLARE @is_unique varchar(100) 
DECLARE @IndexTypeDesc varchar(100) 
DECLARE @FileGroupName varchar(100) 
DECLARE @is_disabled bit 
DECLARE @is_primary_key bit 
DECLARE @IndexOptions varchar(max) 
DECLARE @IndexColumnId int 
DECLARE @IsDescendingKey int 
DECLARE @IsIncludedColumn int 
DECLARE @TSQLScripCreationIndex varchar(max) 
DECLARE @TSQLScripDisableIndex varchar(max) 

CREATE TABLE #CreateIndexes (
          SchemaName varchar(max) 
          ,TableName varchar(max) 
          ,IndexName varchar(max) 
          ,is_unique varchar(max) 
          ,IndexTypeDesc varchar(max) 
          ,is_disabled bit 
          ,is_primary_key bit 
          ,FileGroupName varchar(max) 
          ,DropScript varchar(max) 
          ,TSQLScripCreationIndex varchar(max) 
          ,TSQLScripDisableIndex varchar(max) 
          ) 

DECLARE CursorIndex CURSOR FOR 
SELECT schema_name(t.schema_id) [schema_name] 
, t.name 
, ix.name 
, CASE WHEN ix.is_unique = 1 THEN 'UNIQUE ' ELSE '' END 
, ix.type_desc 
, '' IndexOptions -- case when ix.is_padded=1 then 'PAD_INDEX = ON, ' else 'PAD_INDEX = OFF, ' end 
    --+ case when ix.allow_page_locks=1 then 'ALLOW_PAGE_LOCKS = ON, ' else 'ALLOW_PAGE_LOCKS = OFF, ' end 
    --+ case when ix.allow_row_locks=1 then 'ALLOW_ROW_LOCKS = ON, ' else 'ALLOW_ROW_LOCKS = OFF, ' end 
    --+ case when INDEXPROPERTY(t.object_id, ix.name, 'IsStatistics') = 1 then 'STATISTICS_NORECOMPUTE = ON, ' else 'STATISTICS_NORECOMPUTE = OFF, ' end 
    --+ case when ix.ignore_dup_key=1 then 'IGNORE_DUP_KEY = ON, ' else 'IGNORE_DUP_KEY = OFF, ' end 
    --+ 'SORT_IN_TEMPDB = OFF, FILLFACTOR = ' + CASE WHEN ix.fill_factor = 0 THEN '90' ELSE CAST(ix.fill_factor AS VARCHAR(3)) END AS IndexOptions 

, ix.is_disabled 
, ix.is_primary_key 
, FILEGROUP_NAME(ix.data_space_id) FileGroupName 
FROM sys.tables t 
INNER JOIN sys.indexes ix on t.object_id=ix.object_id 
INNER JOIN #FKScripts fks ON t.name = fks.FKTableName 
WHERE ix.type>0 
    AND t.is_ms_shipped=0 
    AND t.name<>'sysdiagrams' 
    --and t.name = 'Entity' 
ORDER BY schema_name(t.schema_id), t.name, ix.name 

OPEN CursorIndex 
FETCH NEXT FROM CursorIndex INTO @SchemaName, @TableName, @IndexName, @is_unique, @IndexTypeDesc, @IndexOptions,@is_disabled, @is_primary_key, @FileGroupName 

WHILE (@@fetch_status=0) 
BEGIN 
DECLARE @IndexColumns varchar(max) 
DECLARE @IncludedColumns varchar(max) 

SET @IndexColumns='' 
SET @IncludedColumns='' 

DECLARE CursorIndexColumn CURSOR FOR 
SELECT 
col.name 
,ixc.is_descending_key 
,ixc.is_included_column 
FROM sys.tables tb 
INNER JOIN sys.indexes ix 
    ON tb.object_id=ix.object_id 
INNER JOIN sys.index_columns ixc 
    ON ix.object_id=ixc.object_id 
    AND ix.index_id= ixc.index_id 
INNER JOIN sys.columns col 
    ON ixc.object_id = col.object_id 
    AND ixc.column_id = col.column_id 
WHERE ix.type>0 
    AND (ix.is_primary_key=0 or ix.is_unique_constraint=0) 
    AND schema_name(tb.schema_id) = @SchemaName 
    AND tb.name = @TableName 
    AND ix.name = @IndexName 
ORDER BY ixc.index_column_id 

OPEN CursorIndexColumn 
FETCH NEXT FROM CursorIndexColumn INTO @ColumnName, @IsDescendingKey, @IsIncludedColumn 

WHILE (@@fetch_status=0) 
BEGIN 
    IF @IsIncludedColumn=0 
    SET @[email protected] + @ColumnName + CASE WHEN @IsDescendingKey=1 THEN ' DESC, ' ELSE ' ASC, ' END 
    ELSE 
    SET @[email protected] + @ColumnName +', ' 

    FETCH NEXT FROM CursorIndexColumn INTO @ColumnName, @IsDescendingKey, @IsIncludedColumn 
END 

CLOSE CursorIndexColumn 
DEALLOCATE CursorIndexColumn 

SET @IndexColumns = substring(@IndexColumns, 1, len(@IndexColumns)-1) 
SET @IncludedColumns = CASE WHEN len(@IncludedColumns) >0 THEN substring(@IncludedColumns, 1, len(@IncludedColumns)-1) ELSE '' END 
-- print @IndexColumns 
-- print @IncludedColumns 

SET @TSQLScripCreationIndex ='' 
SET @TSQLScripDisableIndex ='' 
SET @TSQLScripCreationIndex= 
CASE WHEN @is_primary_key = 1 THEN 'ALTER TABLE ' + QUOTENAME(@SchemaName) +'.'+ QUOTENAME(@TableName) + ' ADD CONSTRAINT ' + QUOTENAME(@IndexName) + ' PRIMARY KEY CLUSTERED (' + @IndexColumns + ')' + @IndexOptions + ' ON ' + QUOTENAME(@FileGroupName) + ';' 
     ELSE 'CREATE '+ @is_unique [email protected] + ' INDEX ' +QUOTENAME(@IndexName)+' ON ' + QUOTENAME(@SchemaName) +'.'+ QUOTENAME(@TableName)+ ' ('[email protected]+') ' 
+ CASE WHEN len(@IncludedColumns)>0 THEN 'INCLUDE (' + @IncludedColumns+ ')' ELSE '' END + CHAR(13) --+'WITH (' + @IndexOptions+ ') ON ' + QUOTENAME(@FileGroupName) + ';' 
    END 

--if @is_disabled=1 
    SET @TSQLScripDisableIndex= CASE WHEN @IndexTypeDesc = 'CLUSTERED' THEN 'ALTER TABLE ' + QUOTENAME(@SchemaName) +'.'+ QUOTENAME(@TableName) + ' DROP CONSTRAINT ' + QUOTENAME(@IndexName) 
             ELSE 'DROP INDEX ' + QUOTENAME(@IndexName) + ' ON ' + QUOTENAME(@SchemaName) +'.'+ QUOTENAME(@TableName) + CHAR(13) END 

PRINT @TSQLScripCreationIndex 
PRINT @TSQLScripDisableIndex 

INSERT INTO #CreateIndexes (SchemaName, TableName, IndexName, is_unique, IndexTypeDesc, is_disabled, is_primary_key ,FileGroupName, TSQLScripCreationIndex, TSQLScripDisableIndex) 
SELECT @SchemaName, @TableName, @IndexName, @is_unique, @IndexTypeDesc, @is_disabled, @is_primary_key, @FileGroupName, @TSQLScripCreationIndex, @TSQLScripDisableIndex 

FETCH NEXT FROM CursorIndex INTO @SchemaName, @TableName, @IndexName, @is_unique, @IndexTypeDesc, @IndexOptions,@is_disabled, @is_primary_key ,@FileGroupName 

END 
CLOSE CursorIndex 
DEALLOCATE CursorIndex 

SELECT * FROM #CreateIndexes ORDER BY TableName, IndexName 

フルテキストインデックスがある場合は、これが必要です。

/* NEED TO DISABLE FULL TEXT INDEXES ON ANY OF THESE TABLES - AAARRRGGGGGHHHHHH!!!! */ 

IF OBJECT_ID('tempdb..#FullTextCreateScripts') IS NOT NULL DROP TABLE #FullTextCreateScripts 
CREATE TABLE #FullTextCreateScripts (TableName sysname, ColumnName sysname, FullTextCreateScript nvarchar(max)) 
INSERT INTO #FullTextCreateScripts (TableName, ColumnName, FullTextCreateScript) 
SELECT 
o.name TableName 
,c.name ColumnName 
,'ADD FULLTEXT INDEX ON ' + o.name + 'ADD (' + c.name + ')' FullTextCreateScript 
FROM sys.fulltext_index_columns fic 
LEFT JOIN sys.objects o 
    ON o.object_id = fic.object_id 
LEFT JOIN sys.columns c 
    ON c.object_id = fic.object_id 
    AND c.column_id = fic.column_id 
INNER JOIN #FKScripts fks 
    ON o.name = fks.FKTableName 


SELECT * FROM #FullTextCreateScripts 


IF OBJECT_ID('tempdb..#FullTextDropScripts') IS NOT NULL DROP TABLE #FullTextDropScripts 
CREATE TABLE #FullTextDropScripts (TableName sysname, FullTextDropScript nvarchar(max)) 
INSERT INTO #FullTextDropScripts (TableName, FullTextDropScript) 
SELECT DISTINCT 
o.name TableName 
,'DROP FULLTEXT INDEX ON ' + o.name FullTextDropScript 
FROM sys.fulltext_index_columns fic 
LEFT JOIN sys.objects o 
    ON o.object_id = fic.object_id 
INNER JOIN #FKScripts fks 
    ON o.name = fks.FKTableName 


SELECT * FROM #FullTextDropScripts 

/* Another cursor, this one drops our relevant full text indexes */ 

DECLARE @dropfulltextSQL nvarchar(max) 

DECLARE dropfulltextcursor CURSOR LOCAL FOR 
    SELECT FullTextDropScript FROM #FullTextDropScripts 

OPEN dropfulltextcursor 

FETCH NEXT FROM dropfulltextcursor INTO @dropfulltextSQL 

WHILE @@FETCH_STATUS = 0 BEGIN 

    --execute your sproc on each row 
    EXEC sp_executesql @dropfulltextSQL 

    FETCH NEXT FROM dropfulltextcursor INTO @dropfulltextSQL 
END 

CLOSE dropfulltextcursor 
DEALLOCATE dropfulltextcursor 

/*のカーソルを使用して、それらのインデックスを削除してみましょう*/

​​

これはあなたが祭壇を完了したらあなたは、あなたのALTER SCRIPT

を行うことをお勧めしますWHERE ISあなたが落としたものすべてを再適用し始めます。 、VSでのデータベースプロジェクトにインポートデータベース列のデータ型を変更し、VSが生成されますどのようなスクリプトを参照してください。

/*ちょうど試してみる*/

DECLARE @addfulltextSQL nvarchar(max) 

DECLARE createfulltextcursor CURSOR LOCAL FOR 
    SELECT FullTextCreateScript FROM #FullTextCreateScripts 

OPEN createfulltextcursor 

FETCH NEXT FROM createfulltextcursor INTO @addfulltextSQL 

WHILE @@FETCH_STATUS = 0 BEGIN 

    --execute your sproc on each row 
    EXEC sp_executesql @addfulltextSQL 

    FETCH NEXT FROM createfulltextcursor INTO @addfulltextSQL 
END 

CLOSE createfulltextcursor 
DEALLOCATE createfulltextcursor 


/* Rebuild those indexes */ 


DECLARE @createindexes nvarchar(max) 

DECLARE createindexes CURSOR LOCAL FOR 
    SELECT TSQLScripCreationIndex FROM #CreateIndexes 

OPEN createindexes 

FETCH NEXT FROM createindexes INTO @createindexes 

WHILE @@FETCH_STATUS = 0 BEGIN 

    --execute your sproc on each row 
    EXEC sp_executesql @createindexes 

    FETCH NEXT FROM createindexes INTO @createindexes 
END 

CLOSE createindexes 
DEALLOCATE createindexes 




/* Now let's re-enable those FK constraints */ 

DECLARE @enablesql nvarchar(max) 
DECLARE @checksql nvarchar(max) 

DECLARE enacur CURSOR LOCAL FOR 
    SELECT FKRebuildScript, FKCheckScript FROM #FKScripts 

OPEN enacur 

FETCH NEXT FROM enacur INTO @enablesql, @checksql 

WHILE @@FETCH_STATUS = 0 BEGIN 

    --execute your sproc on each row 
    EXEC sp_executesql @enablesql 
    EXEC sp_executesql @checksql 

    FETCH NEXT FROM enacur INTO @enablesql, @checkSQL 
END 

CLOSE enacur 
DEALLOCATE enacur 





/* Let's check that the constraints are now enabled again (for testing) */ 
SELECT DISTINCT 
fkt.* 
,CASE WHEN ((C.Status & 0x4000)) = 0 THEN 1 ELSE 0 END [Enabled3] 
FROM #FKScripts fkt 
INNER JOIN sys.sysobjects o2 
    ON o2.name = fkt.PKName 
INNER JOIN sys.sysobjects o 
    ON o2.id = o.parent_obj 
    AND o.xtype='F' 
INNER JOIN sys.sysconstraints c 
    ON o.id = c.constid 
+0

うわー、ありがとう。それはまさに私が期待していたものでしたが、まだそれほど得られていませんでした。ありがとう。 – BogdanM

+0

心配することはありませんが、それは間違いなく進行中の作業であり、他のものが含まれる必要があるかもしれません(私はフルテキストインデックスを最初に回したとは考えていませんでした)。 –

3

多くの場合レコードを削除しないでください。代わりに、BigIntデータ型の各テーブルに別の列を追加して、主キー列からこの新しい列にデータを移動することを検討してください。

ALTER TABLE Table1 ADD COLUMN NewPKColumn BigInt; 
UPDATE Table1 SET NewPkColumn = CurrentPKColumn; 

あなたはこの新しいBIGINTカラムを参照する他のテーブルと同じことを行うには、データ型のBigIntの新しい列を追加し、データを移動します。

ALTER TABLE Table2 ADD COLUMN NewPKColumn_FK BigInt; 
UPDATE Table2 SET NewPKColumn_FK = Current_FK_Column; 
(repeat this for all tables...) 

あなたはすべてのテーブルのためにこれをやったときは、元のPK列からPKを削除し、その列を削除することができます(が、この前に、必ず見て、作成されたインデックスの種類を参照しますPK列(クラスタ化されているかクラスタ化されていないか)で、FKを再度作成することを忘れないでください。

ALTER TABLE Table2 DROP CONSTRAINT FK_Tbl1; 
ALTER TABLE Table1 DROP COLUMN CurrentPKColumn; 
ALTER TABLE Table2 ADD CONSTRAINT FK_Tbl1 REFERENCES Table1(NewPKColumn); 

また、現在のPK列が含まれているかどうかを調べ、それらを再作成する必要があります。

そして、常にこれを開発環境でテストして、何もスキップしていないことを確認してください。

+0

洞察のためにRaduに感謝しますが、私は運がいいです。そして、すべてのテーブルは最初に切り捨てられます。したがって、この追加の問題なしで削除して再作成することができます – BogdanM

+0

@BogdanMテーブルが空の場合、 。テーブルにインデックスが作成されていますか? –

+0

はい、少なくともいくつかはクラスタ化されたインデックスがあります – BogdanM

関連する問題