Подсчет количества ссылок на запись в таблице через Foreign Keys

    Потребовалось для собственных целей решить следующую задачку — для одной таблички (File) для каждой записи автоматом рассчитывать количество внешних записей, связанных через foreign key. Задача решалась для конкретной структуры таблички File, но при желании решение можно переделать на более универсальное.

    Оговорюсь, что решение писалось для ненагруженной базы, без миллионов строк записей и ежеминутного обновления, поэтому вопрос просаживания производительности не стоял.

    Основной причиной было то, что количество внешних связей до таблицы File может меняться в процессе разработки и постоянно переделывать запрос было бы просто неразумным. Планировалась некая модульность в самой системе, поэтому точно неизвестны все конечные таблицы.

    Скрипт для создания двух табличек:

    CREATE TABLE [dbo].[File](
    	[IdFile] [int] IDENTITY(1, 1) NOT NULL,
    	[NameFile] [nvarchar](max) NOT NULL,
    	[CountUsage] [int] NOT NULL,
    	PRIMARY KEY (IdFile)
    )
    
    SET identity_insert [dbo].[File] ON;
    
    INSERT INTO [dbo].[File] ([IdFile], [NameFile],[CountUsage])
    VALUES (1, 'test1', 0), (2, 'test2', 1000)
    
    SET identity_insert [dbo].[File] OFF;
    
    CREATE TABLE [dbo].[TestForFiles](
    	[IdTest] [int] IDENTITY(1, 1) NOT NULL,
    	[IdFileForTest] [int] NOT NULL,
    	PRIMARY KEY (IdTest)
    )
    
    ALTER TABLE [dbo].[TestForFiles]  WITH CHECK ADD  CONSTRAINT [FK_TestForFiles_File] FOREIGN KEY([IdFileForTest])
    REFERENCES [dbo].[File] ([IdFile])
    
    ALTER TABLE [dbo].[TestForFiles] CHECK CONSTRAINT [FK_TestForFiles_File]
    
    INSERT INTO [dbo].[TestForFiles] ([IdFileForTest])
    VALUES (1), (1), (1), (2)
    

    Получаем таблицы File и TestForFiles. Таблица TestForFiles ссылается на File по полю IdFileForTest.

    Получается следующий набор данных:



    Следующий скрипт генерирует запрос для подсчета количества записей в таблице:

    DECLARE @sql_tables nvarchar(max) =	null;
    
    SELECT @sql_tables = CASE WHEN @sql_tables IS NULL THEN '' ELSE @sql_tables + CHAR(13) + CHAR(10) + '		UNION ALL' + CHAR(13) + CHAR(10) END + '		SELECT ' + c.name + ' AS IdFile, count(*) AS FileCount FROM ' + t.name + ' GROUP BY ' + c.name
    FROM sys.foreign_key_columns AS fk
    INNER JOIN sys.tables AS t ON fk.parent_object_id = t.object_id
    INNER JOIN sys.columns AS c ON fk.parent_object_id = c.object_id AND fk.parent_column_id = c.column_id
    INNER JOIN sys.columns AS c2 ON fk.referenced_object_id = c2.object_id AND fk.referenced_column_id = c2.column_id
    WHERE fk.referenced_object_id = (SELECT object_id FROM sys.tables WHERE name = 'File') AND c2.name = 'IdFile';
    
    IF @sql_tables IS NOT NULL
    BEGIN
    	DECLARE @sql nvarchar(max) =	'UPDATE dbo.[File]' + CHAR(13) + CHAR(10) + 
    		'SET CountUsage = t2.FileCount' + CHAR(13) + CHAR(10) + 
    		'FROM dbo.[File]' + CHAR(13) + CHAR(10) + 
    		'INNER JOIN (' + CHAR(13) + CHAR(10) +
    		'	SELECT IdFile, SUM(FileCount) AS FileCount ' + CHAR(13) + CHAR(10) + 
    		'	FROM (' + CHAR(13) + CHAR(10) + 
    			@sql_tables + CHAR(13) + CHAR(10) + 
    		'	) t' + CHAR(13) + CHAR(10) +
    		'	GROUP BY IdFile' + CHAR(13) + CHAR(10) +
    		') t2 ON t2.IdFile = dbo.[File].IdFile';
    
    	print @sql;
    	EXEC sp_executesql @sql;
    END;
    

    Генерируется вот такой запрос:

    UPDATE dbo.[File]
    SET CountUsage = t2.FileCount
    FROM dbo.[File]
    INNER JOIN (
    	SELECT IdFile, SUM(FileCount) AS FileCount 
    	FROM (
    		SELECT IdFileForTest AS IdFile, count(*) AS FileCount FROM TestForFiles GROUP BY IdFileForTest
    	) t
    	GROUP BY IdFile
    ) t2 ON t2.IdFile = dbo.[File].IdFile
    

    В результате выполнения имеем следующее содержимое таблиц:



    Еще раз повторюсь — задача решалась под конкретную таблицу File, подсчет работает только для случаев, когда есть foreign keys по полю IdFile.

    Заранее благодарю за критику.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 3
    • 0
      Оконные функции?
    • +1
      при условии, что сложение с NULL даёт NULL, можно исправить
      CASE WHEN @sql_tables IS NULL THEN '' ELSE @sql_tables + CHAR(13) + CHAR(10) + '		UNION ALL' + CHAR(13) + CHAR(10) END

      на
      COALESCE(@sql_tables+CHAR(13)+CHAR(10)+'UNION ALL'+CHAR(13)+CHAR(10),'')

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.