Как мне передать имя таблицы в хранимой процедуре?

Я только что столкнулся со странной вещью...на нашем сайте есть код, который принимает гигантскую инструкцию SQL, изменяет ее в коде, выполняя поиск и замену на основе некоторых пользовательских значений, а затем передает ее SQL Server в качестве запроса.

Я думал, что это будет чище как параметризованный запрос к сохраненному proc, с пользовательскими значениями в качестве параметров, но когда я посмотрел более внимательно, я понял, почему они могут это делать...таблица, из которой они выбирают зависит от этих пользовательских значений.

например, в одном случае, если значения были ("FOO", "BAR"), запрос в конечном итоге будет чем-то вроде "SELECT * FROM FOO_BAR"

есть ли простой и понятный способ сделать это? Все, что я пытаюсь сделать, кажется неэлегантным.

EDIT: я мог бы, конечно, динамически генерировать sql в сохраненном proc и выполнять это (bleh), но в этот момент мне интересно, получил ли я что угодно.

EDIT2: рефакторинг имен таблиц каким-то разумным способом, скажем, имея их все в одной таблице с разными именами в качестве нового столбца, было бы хорошим способом решить все это, на что несколько человек указали напрямую или ссылались. К сожалению, в данном случае это не вариант.

10 ответов


прежде всего, вы должны никогда do SQL командные композиции на клиентском приложении, как это,это что такое SQL-инъекция. (Это нормально для инструмента администратора, который не имеет собственных привов, но не для приложения общего использования).

во-вторых, да, параметризованный вызов хранимой процедуры является более чистым и безопасным.

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

вот простой наивный пример:

CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS
-- Counts the number of rows from any non-system Table, *SAFELY*
BEGIN
    DECLARE @ActualTableName AS NVarchar(255)

    SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
    FROM INFORMATION_SCHEMA.TABLES
    WHERE TABLE_NAME = @PassedTableName

    DECLARE @sql AS NVARCHAR(MAX)
    SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

    EXEC(@SQL)
END

некоторые справедливо спросили, почему это безопаснее. Надеюсь, маленькие таблицы Бобби могут сделать это более ясным:

alt text


ответы на вопросы:

  1. только QUOTENAME не гарантирует безопасность. MS призывает нас использовать его, но они не дали гарантии, что он не может быть обманут хакерами. К твоему сведению, настоящая безопасность - это гарантии. Поиск таблицы с QUOTENAME-это другая история, она нерушима.

  2. QUOTENAME не является строго необходимым для этого примера, перевод поиска только на INFORMATION_SCHEMA обычно достаточно. QUOTENAME находится здесь потому что это хорошая форма в безопасности, чтобы включить полное и правильное решение. QUOTENAME здесь фактически защищает от отдельной, но подобной потенциальной проблемы, известной как латентный инъекций.


(Un)к счастью, нет способа сделать это - вы не можете использовать имя таблицы, переданное в качестве параметра для сохраненного кода, кроме динамического создания sql. Когда дело доходит до решения, где генерировать код sql, я предпочитаю код приложения, а не сохраненный код. Код приложения, как правило, быстрее и проще в обслуживании.

Если вам не нравится решение, с которым вы работаете, я бы предложил более глубокий редизайн (т. е. изменить логику схемы / приложения, чтобы вам больше не нужно было проходить имя таблицы в качестве параметра в любом месте).


Я бы возражал против динамического создания SQL в сохраненном proc; это приведет вас к проблемам и может вызвать уязвимость инъекций.

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


звучит так, как будто вам было бы лучше с решением ORM.

Я съеживаюсь, когда вижу динамический sql в хранимой процедуре.


одна вещь, которую вы можете рассмотреть, - это сделать оператор case, который содержит ту же команду SQL, которую вы хотите, один раз для каждой допустимой таблицы, а затем передать в качестве строки имя таблицы в эту процедуру и выбрать команду case для запуска.

кстати, как человек безопасности предложение выше, говорящее вам выбрать из системных таблиц, чтобы убедиться, что у вас есть действительная таблица, кажется мне пустой операцией. Если кто-то может ввести переданное QUOTENAME (), то тогда инъекция будет работать как на системной таблице, так и на базовой таблице. Единственное, что это помогает с ним, чтобы убедиться, что это действительное имя таблицы, и я думаю, что предложение выше-лучший подход к этому, так как вы вообще не используете QUOTENAME ().


в зависимости от того, является ли набор столбцов в этих таблицах одинаковым или разным, я бы подошел к нему двумя способами в долгосрочной перспективе:

1) если они одинаковы, почему бы не создать новый столбец, который будет использоваться в качестве селектора, значение которого получено из пользовательских параметров ? (это оптимизация производительности?)

2) если они различны, то шансы что регулировать их также различны. Таким образом, это похоже на разделение кода select/handle На отдельные блоки, а затем называть их отдельно было бы самым модульным подходом ко мне. Вы повторите" select * from " часть, но в этом сценарии набор таблиц, надеюсь, конечен.

позволяя вызывающему коду предоставить две произвольные части имени таблицы, чтобы сделать выбор, чувствует себя очень опасным.


Я не знаю, почему у вас есть данные, разбросанные по нескольким таблицам, но похоже, что вы нарушаете один из основных принципов. Данные должны быть в таблицах, а не в именах таблиц.

Если таблицы имеют более или менее одинаковый макет, подумайте, лучше ли поместить данные в одну таблицу. Это решит вашу проблему с динамическим запросом, и это сделает макет базы данных более гибким.


вместо запроса таблиц на основе значений пользовательского ввода можно выбрать процедуру. то есть
1. Создайте процедуру FOO_BAR_prc и внутри нее вы поместите запрос "select * from foo_bar", таким образом, запрос будет предварительно скомпилирован базой данных.
2. Затем на основе пользовательского ввода теперь выполните правильную процедуру из кода приложения.

поскольку у вас есть около 50 таблиц, это не может быть допустимым решением, хотя, как это было требует много работы с вашей стороны.


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

использовать [имя базы данных ] ИДТИ / ****** Объект: StoredProcedure [dbo].[sp_CreateDynamicTable] дата сценария: 20.06.2015 16:56:25 ******/ НАБОР ПАРАМЕТРОВ ANSI_NULLS НА ИДТИ УСТАНОВИТЬ ЗНАЧЕНИЕ ПАРАМЕТРА НА ИДТИ Создать процедуру [dbo].[sp_CreateDynamicTable] @tName varchar (255) КАК НАЧИНАТЬ УСТАНОВИТЬ NOCOUNT НА; Объявить @в SQL тип nvarchar(Макс)

SET @SQL = N'CREATE TABLE [DBO].['+ @tName + '] (DocID nvarchar(10) null);'

    EXECUTE sp_executesql @SQL

конец


@RBarry Young Вам не нужно добавлять скобки в @ActualTableName в строке запроса, потому что он уже включен в результат запроса в INFORMATION_SCHEMA.ТАБЛИЦЫ. В противном случае при выполнении возникнут ошибки.

создать PROC spCountAnyTableRows (@PassedTableName как NVarchar (255)) как -- Подсчитывает количество строк из любой несистемной таблицы,безопасное НАЧИНАТЬ Объявить @ActualTableName как Тип nvarchar(255)

SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = @PassedTableName

DECLARE @sql AS NVARCHAR(MAX)
--SELECT @sql = 'SELECT COUNT(*) FROM [' + @ActualTableName + '];'

-- changed to this
SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

EXEC(@SQL)

конец