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

Я просто столкнулся с чем-то странным... на нашем сайте есть код, который принимает гигантский оператор 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)

КОНЕЦ