Как передать имя таблицы в хранимый процесс? - PullRequest
14 голосов
/ 08 августа 2009

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

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

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

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

РЕДАКТИРОВАТЬ: Я мог бы, конечно, динамически генерировать sql в хранимом процессе и выполнить его (блеф), но в этот момент мне интересно, получил ли я что-нибудь.

РЕДАКТИРОВАТЬ 2: Рефакторизовать имена таблиц каким-либо разумным способом, скажем, иметь их все в одной таблице с разными именами в качестве нового столбца было бы хорошим способом решить все это, что есть у нескольких человек указал прямо или ссылался на. К сожалению, в данном случае это не вариант.

Ответы [ 10 ]

40 голосов
/ 08 августа 2009

Прежде всего, вы должны НИКОГДА не делать композиции команд 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 здесь фактически защищает от отдельной, но похожей потенциальной проблемы, известной как скрытая инъекция .

5 голосов
/ 08 августа 2009

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

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

2 голосов
/ 08 августа 2009

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

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

2 голосов
/ 08 августа 2009

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

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

1 голос
/ 01 марта 2011

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

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

0 голосов
/ 12 августа 2016

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

CREATE PROC spCountAnyTableRows (@PassedTableName as NVarchar (255)) AS - Подсчитывает количество строк в любой не системной таблице, БЕЗОПАСНО НАЧАТЬ ОБЪЯВИТЬ @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 + '];'

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

EXEC(@SQL)

END

0 голосов
/ 20 июня 2015

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

USE [Имя базы данных] ИДТИ / ****** Объект: StoredProcedure [dbo]. [Sp_CreateDynamicTable] Дата скрипта: 20.06.2015 16:56:25 ****** / SET ANSI_NULLS ON ИДТИ SET QUOTED_IDENTIFIER ON ИДТИ CREATE PROCEDURE [dbo]. [Sp_CreateDynamicTable] @tName varchar (255) КАК НАЧАТЬ УСТАНАВЛИВАЙТЕ NOCOUNT ON; ОБЪЯВИТЬ @SQL nvarchar (max)

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

    EXECUTE sp_executesql @SQL

END

0 голосов
/ 08 августа 2009

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

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

0 голосов
/ 08 августа 2009

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

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

0 голосов
/ 08 августа 2009

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

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

2) если они разные, скорее всего, обработка с ними также различна. Таким образом, кажется, что разделение кода выбора / обработки на отдельные блоки и последующий вызов их по отдельности было бы для меня наиболее модульным подходом. Вы будете повторять часть «выберите * из», но в этом сценарии, мы надеемся, что набор таблиц конечен.

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

...