Как передать имя таблицы в сохраненный процесс?
Я просто столкнулся с чем-то странным... на нашем сайте есть код, который принимает гигантский оператор SQL, изменяя его в коде, выполняя поиск и замену на основе некоторых пользовательских значений, а затем передавая его на SQL Server в качестве запроса.
Я думал, что это будет чище, как параметризованный запрос к сохраненному процессу, с пользовательскими значениями в качестве параметров, но когда я посмотрю более внимательно, я понимаю, почему они могут это делать... таблица, в которой они находятся выбор из переменной зависит от этих пользовательских значений.
Например, в одном случае, если значения были ( "FOO", "BAR" ), запрос оказался бы чем-то вроде "SELECT * FROM FOO_BAR"
Есть ли простой и понятный способ сделать это? Все, что я пытаюсь, кажется неэлегантным.
EDIT: Я мог бы, конечно, динамически генерировать sql в сохраненном proc и exec, который (bleh), но в этот момент мне интересно, получил ли я что-нибудь.
EDIT2: Рефакторинг имен таблиц каким-то разумным способом, скажем, что их всех в одной таблице с разными именами в качестве нового столбца было бы хорошим способом решить все это, что несколько человек указывали прямо или упоминались. К сожалению, это не вариант в этом случае.
Ответы
Ответ 1
Прежде всего, НИКОГДА НЕ выполняйте составы команд SQL в клиентском приложении, как это, вот что такое SQL Injection. (Это нормально для администратора, у которого нет собственных привилегий, но не для совместного использования).
Во-вторых, да, параметризованный вызов хранимой процедуры является более чистым и безопасным.
Однако, поскольку вам нужно будет использовать Dynamic 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
Некоторые из них справедливо спросили, почему это безопаснее. Будем надеяться, что маленькие Bobby Tables могут сделать это более ясным:
![alt text]()
Ответы на дополнительные вопросы:
-
Только QUOTENAME не гарантируется. MS поощряет нас использовать его, но они не дали гарантии, что они не могут быть исключены хакерами. FYI, настоящая безопасность - все о гарантиях. Поиск таблицы с помощью QUOTENAME - это еще одна история, она нерушима.
-
QUOTENAME не является строго необходимым для этого примера, обычно выполняется проверка Lookup на INFORMATION_SCHEMA. QUOTENAME здесь, потому что это хорошая форма безопасности, чтобы включить полное и правильное решение. QUOTENAME здесь фактически защищает от четкой, но аналогичная потенциальная проблема известна как скрытая инъекция.
Ответ 2
(Un), к счастью, нет способа сделать это - вы не можете использовать имя таблицы, переданное как параметр для сохраненного кода, кроме динамического поколения sql. Когда дело доходит до решения, где генерировать код sql, я предпочитаю код приложения, а не тот сохраненный код. Код приложения обычно быстрее и проще в обслуживании.
Если вам не нравится решение, с которым вы работаете, я бы предложил более глубокий редизайн (т.е. изменил логику схемы/приложения, чтобы вам больше не пришлось передавать имя таблицы в качестве параметра в любом месте).
Ответ 3
Я бы возражал против динамического генерирования SQL в хранимом процессе; это вызовет у вас проблемы и может вызвать уязвимость в отношении инъекций.
Вместо этого я бы проанализировал все таблицы, на которые мог повлиять запрос, и создать какое-то перечисление, которое определит, какую таблицу использовать для запроса.
Ответ 4
Похоже, вам будет лучше с решением ORM.
Я сжимаю, когда вижу динамическую sql в хранимой процедуре.
Ответ 5
Одна вещь, которую вы можете рассмотреть, - сделать оператор case, который содержит ту же самую команду SQL, которую вы хотите, один раз для каждой допустимой таблицы, затем передать в качестве строки имя таблицы в эту процедуру и выбрать случай, какую команду выполнить.
Кстати, как лицо безопасности, предложение, предлагающее вам выбрать из системных таблиц, чтобы убедиться, что у вас есть действительная таблица, кажется мне лишней операцией. Если кто-то может вставить переданный QUOTENAME(), тогда инъекция будет работать как на системной таблице так и на базовой таблице. Единственное, что это помогает, чтобы убедиться, что это допустимое имя таблицы, и я думаю, что вышеприведенное предложение является лучшим подходом к этому, поскольку вы вообще не используете QUOTENAME().
Ответ 6
В зависимости от того, является ли набор столбцов в этих таблицах одинаковым или различным, я бы подошел к нему двумя способами в долгосрочной перспективе:
1), если они одинаковы, почему бы не создать новый столбец, который будет использоваться в качестве селектора, значение которого получено из параметров, предоставленных пользователем? (это оптимизация производительности?)
2), если они разные, есть вероятность, что обработка их также различна. Таким образом, похоже, что разделение кода select/handle на отдельные блоки и последующее обращение к ним по отдельности было бы самым модульным подходом ко мне. Вы будете повторять часть "select * from",
но в этом случае набор таблиц, надеюсь, конечен.
Предоставление вызывающему коду для подачи двух произвольных частей имени таблицы для выбора из очень опасного.
Ответ 7
Я не знаю, почему у вас есть данные, распространяемые по нескольким таблицам, но похоже, что вы нарушаете одну из основ. Данные должны быть в таблицах, а не в виде имен таблиц.
Если таблицы имеют более или менее одинаковый макет, подумайте, было бы лучше помещать данные в одну таблицу. Это решит вашу проблему с динамическим запросом, и это сделает формат базы данных более гибким.
Ответ 8
Вместо запроса таблиц на основе пользовательских значений ввода вы можете выбрать эту процедуру.
то есть 1. Создайте процедуру FOO_BAR_prc и внутри нее вы поместите запрос "select * from foo_bar", таким образом запрос будет предварительно скомпилирован в базе данных.
2. Затем, основываясь на пользовательском вводе, выполните правильную процедуру из вашего кода приложения.
Поскольку у вас есть около 50 таблиц, это может оказаться нецелесообразным решением, хотя для этого потребуется много работы с вашей стороны.
Ответ 9
Фактически, я хотел знать, как передать имя таблицы для создания таблицы в хранимой процедуре. Прочитав некоторые ответы и попытавшись внести некоторые изменения в конце, я наконец смог создать таблицу с именем, переданным как параметр. Ниже приведена хранимая процедура для других, чтобы проверить наличие ошибок.
ИСПОЛЬЗОВАТЬ [Имя базы данных]
ИДТИ
/****** Объект: StoredProcedure [dbo]. [Sp_CreateDynamicTable] Script Дата: 06/20/2015 16:56:25 ******/
SET ANSI_NULLS ON
ИДТИ
SET QUOTED_IDENTIFIER ON
ИДТИ
ПРОЦЕДУРА СОЗДАНИЯ [dbo]. [Sp_CreateDynamicTable] @tName varchar (255)
В ВИДЕ
НАЧАТЬ SET NOCOUNT ON; DECLARE @SQL nvarchar (max)
SET @SQL = N'CREATE TABLE [DBO].['+ @tName + '] (DocID nvarchar(10) null);'
EXECUTE sp_executesql @SQL
КОНЕЦ
Ответ 10
@RBarry Young
Вам не нужно добавлять скобки к @ActualTableName в строке запроса, потому что она уже включена в результат запроса в INFORMATION_SCHEMA.TABLES. В противном случае при выполнении будут ошибки.
CREATE PROC spCountAnyTableRows (@PassedTableName как NVarchar (255)) AS
- подсчитывает количество строк из любой несистемной таблицы, БЕЗОПАСНО
НАЧАТЬ 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 + '];'
-- changed to this
SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'
EXEC(@SQL)
КОНЕЦ