500 列と 1 億行の大きなテーブルがあります。小規模なサンプルに基づくと、値が含まれているのは約 50 列のみであり、残りの 450 列には NULL 値のみが含まれていると考えられます。データが含まれていない列を一覧表示したい。
現在のハードウェアでは、すべての列をクエリするのに約 24 時間かかります ( select count(1) from tab where col_n is not null
)
列が完全に空/NULL であることを判断する安価な方法はありますか?
500 列と 1 億行の大きなテーブルがあります。小規模なサンプルに基づくと、値が含まれているのは約 50 列のみであり、残りの 450 列には NULL 値のみが含まれていると考えられます。データが含まれていない列を一覧表示したい。
現在のハードウェアでは、すべての列をクエリするのに約 24 時間かかります ( select count(1) from tab where col_n is not null
)
列が完全に空/NULL であることを判断する安価な方法はありますか?
これはどうですか:
SELECT
SUM(CASE WHEN column_1 IS NOT NULL THEN 1 ELSE 0) column_1_count,
SUM(CASE WHEN column_2 IS NOT NULL THEN 1 ELSE 0) column_2_count,
...
FROM table_name
?
INFORMATION_SCHEMA.COLUMNS テーブルを使用すると、このクエリを簡単に作成できます。
編集:
別のアイデア:
SELECT MAX(column_1), MAX(column_2),..... FROM テーブル名
結果に値が含まれている場合、列に値が入力されます。1 つのテーブル スキャンが必要です。
これを試してみてください -
DDL:
IF OBJECT_ID ('dbo.test2') IS NOT NULL
DROP TABLE dbo.test2
CREATE TABLE dbo.test2
(
ID BIGINT IDENTITY(1,1) PRIMARY KEY
, Name VARCHAR(10) NOT NULL
, IsCitizen BIT NULL
, Age INT NULL
)
INSERT INTO dbo.test2 (Name, IsCitizen, Age)
VALUES
('1', 1, NULL),
('2', 0, NULL),
('3', NULL, NULL)
クエリ 1:
DECLARE
@TableName SYSNAME
, @ObjectID INT
, @SQL NVARCHAR(MAX)
SELECT
@TableName = 'dbo.test2'
, @ObjectID = OBJECT_ID(@TableName)
SELECT @SQL = 'SELECT' + CHAR(13) + STUFF((
SELECT CHAR(13) + ', [' + c.name + '] = ' +
CASE WHEN c.is_nullable = 0
THEN '0'
ELSE 'CASE WHEN ' + totalrows +
' = SUM(CASE WHEN [' + c.name + '] IS NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END'
END
FROM sys.columns c WITH (NOWAIT)
CROSS JOIN (
SELECT totalrows = CAST(MIN(p.[rows]) AS VARCHAR(50))
FROM sys.partitions p
WHERE p.[object_id] = @ObjectID
AND p.index_id IN (0, 1)
) r
WHERE c.[object_id] = @ObjectID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ') + CHAR(13) + 'FROM ' + @TableName
PRINT @SQL
EXEC sys.sp_executesql @SQL
出力 1:
SELECT
[ID] = 0
, [Name] = 0
, [IsCitizen] = CASE WHEN 3 = SUM(CASE WHEN [IsCitizen] IS NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END
, [Age] = CASE WHEN 3 = SUM(CASE WHEN [Age] IS NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END
FROM dbo.test2
クエリ 2:
DECLARE
@TableName SYSNAME
, @SQL NVARCHAR(MAX)
SELECT @TableName = 'dbo.test2'
SELECT @SQL = 'SELECT' + CHAR(13) + STUFF((
SELECT CHAR(13) + ', [' + c.name + '] = ' +
CASE WHEN c.is_nullable = 0
THEN '0'
ELSE 'CASE WHEN '+
'MAX(CAST([' + c.name + '] AS CHAR(1))) IS NULL THEN 1 ELSE 0 END'
END
FROM sys.columns c WITH (NOWAIT)
WHERE c.[object_id] = OBJECT_ID(@TableName)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ') + CHAR(13) + 'FROM ' + @TableName
PRINT @SQL
EXEC sys.sp_executesql @SQL
出力 2:
SELECT
[ID] = 0
, [Name] = 0
, [IsCitizen] = CASE WHEN MAX(CAST([IsCitizen] AS CHAR(1))) IS NULL THEN 1 ELSE 0 END
, [Age] = CASE WHEN MAX(CAST([Age] AS CHAR(1))) IS NULL THEN 1 ELSE 0 END
FROM dbo.test2
結果:
ID Name IsCitizen Age
----------- ----------- ----------- -----------
0 0 0 1
ほとんどのレコードが null でない場合は、提案されたアプローチのいくつか (たとえば、null 可能なフィールドのみをチェックする) をこれと組み合わせることができます。
if exists (select * from table where field is not null)
条件が満たされるとすぐに exists が検索を停止するため、これにより検索が高速化されます。この例では、フィールドのステータスを決定するには、null でないレコードが 1 つあれば十分です。フィールドにインデックスがある場合、これはほぼ瞬時に行われます。
通常、このクエリに上位 1 を追加する必要はありません。クエリ オプティマイザーは、一致するすべてのレコードを取得する必要がないことを認識しているためです。
このストアド プロシージャをトリックに使用できます。クエリを実行するテーブル名を指定する必要があります。プロシージャに @exec パラメータ = 1 を渡すと、select クエリが実行されることに注意してください。
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[SP_SELECT_NON_NULL_COLUMNS] ( @tablename varchar (100)=null, @exec int =0)
AS BEGIN
SET NOCOUNT ON
IF @tablename IS NULL
RAISERROR('CANT EXECUTE THE PROC, TABLE NAME IS MISSING',16 ,1)
ELSE
BEGIN
IF OBJECT_ID('tempdb..#table') IS NOT NULL DROP TABLE #table
DECLARE @i VARCHAR (max)=''
DECLARE @sentence VARCHAR (max)=''
DECLARE @SELECT VARCHAR (max)
DECLARE @LocalTableName VARCHAR(50) = '['+@tablename+']'
CREATE TABLE #table (ColumnName VARCHAR (max))
SELECT @i+=
' IF EXISTS ( SELECT TOP 1 '+column_name+' FROM ' +@LocalTableName+' WHERE ' +column_name+
' '+'IS NOT NULL) INSERT INTO #table VALUES ('''+column_name+''');'
FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=@tablename
INSERT INTO #table
EXEC (@i)
SELECT @sentence = @sentence+' '+columnname+' ,' FROM #table
DROP TABLE #table
IF @exec=0
BEGIN
SELECT 'SELECT '+ LTRIM (left (@sentence,NULLIF(LEN (@sentence)-1,-1)))+
+' FROM ' +@LocalTableName
END
ELSE
BEGIN
SELECT @SELECT= 'SELECT '+ LTRIM (left (@sentence,NULLIF(LEN (@sentence)-1,-1)))+
+' FROM '+@LocalTableName
EXEC (@SELECT)
END
END
END
次のように使用します。
EXEC [dbo].[SP_SELECT_NON_NULL_COLUMNS] 'YourTableName' , 1
100M レコードすべてを「カウント」する必要はありません。null 以外の値を持つ列にヒットするとすぐに TOP 1 を使用してクエリを単純に取り消すと、同じ情報を提供しながら多くの時間を節約できます。
データ型、NOT NULL、および PRIMARY KEY 制約と共にテーブル内の列のリストを取得するための SQL サーバー クエリ
上記の質問の最良の回答で SQL を実行し、以下のような新しいクエリを生成します。
Select ISNULL(column1,1), ISNULL(column2,1), ISNULL(column3,1) from table
500列?!
わかりました、あなたの質問に対する正しい答えは、テーブルを正規化することです。
当面の間、次のことが起こっています。
その列にはインデックスがないため、SQL Server は巨大なテーブルのフル スキャンを実行する必要があります。
SQL Server は確実にすべての行を完全に読み取ります (1 つの列だけに関心がある場合でも、すべての列を意味します)。
そして、あなたの行はおそらく8kbを超えているので... http://msdn.microsoft.com/en-us/library/ms186981%28v=sql.105%29.aspx
真剣に、テーブルを正規化し、必要に応じて水平に分割します(「テーマグループ化」された列を別のテーブルに入れ、必要なときにのみ読み取るようにします)。
編集:このようにクエリを書き直すことができます
select count(col_n) from tab
一度にすべての列を取得したい場合 (より良い):
SELECT
COUNT(column_1) column_1_count,
COUNT(column_2) column_2_count,
...
FROM table_name