T-SQL Dynamic SQL и временные таблицы

Похоже, что #temptables, созданные с использованием динамического SQL с помощью строкового метода EXECUTE, имеют другую область видимости, и на них нельзя ссылаться с помощью «фиксированных» SQL в той же хранимой процедуре. Однако я могу ссылаться на временную таблицу, созданную динамическим оператором SQL в подпоследовательности динамического SQL, но кажется, что хранимая процедура не возвращает результат запроса вызывающему клиенту, если SQL не исправлен.

Простой сценарий с двумя таблицами: у меня есть 2 стола. Назовем их "Заказы" и "Предметы". У Order первичный ключ OrderId, а у Items первичный ключ ItemId. Items.OrderId - это внешний ключ для идентификации родительского Order. В заказе может быть от 1 до n элементов.

Я хочу иметь возможность предоставить пользователю очень гибкий интерфейс типа «построитель запросов», чтобы пользователь мог выбирать, какие элементы он хочет видеть. Критерии фильтрации могут быть основаны на полях из таблицы Items и / или из родительской таблицы Order. Если элемент соответствует условию фильтрации, включая и условие для родительского заказа, если он существует, элемент должен быть возвращен в запросе, а также родительский заказ.

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

Первая причина заключается в том, что мне нужно запросить все столбцы в родительской таблице Order, и если бы я сделал один запрос для присоединения таблицы Orders к таблице Items, я бы повторно отправил информацию о заказе несколько раз. Поскольку обычно в одном Заказе много товаров, я бы хотел избежать этого, потому что это привело бы к передаче гораздо большего количества данных толстому клиенту. Вместо этого, как уже упоминалось, я хотел бы вернуть две таблицы по отдельности в наборе данных и использовать две таблицы внутри для заполнения пользовательских клиентских объектов Order и дочерних элементов. (Я еще недостаточно знаю LINQ или Entity Framework. Я создаю свои объекты вручную). Вторая причина, по которой я хотел бы вернуть две таблицы вместо одной, заключается в том, что у меня уже есть другая процедура, которая возвращает все элементы для данного OrderId вместе с родительским Order, и я хотел бы использовать тот же подход с двумя таблицами, чтобы я мог повторно использовать клиентский код для заполнения моих пользовательских объектов Order и Client из 2 возвращенных таблиц данных.

Я надеялся сделать вот что:

Создайте динамическую строку SQL на клиенте, которая объединяет таблицу заказов с таблицей Items и фильтрует подходящие для каждой таблицы, как указано настраиваемым фильтром, созданным в приложении Winform fat-client. Сборка SQL на клиенте выглядела бы примерно так:

TempSQL = "

    INSERT INTO #ItemsToQuery
       OrderId, ItemsId
    FROM
       Orders, Items 
    WHERE
       Orders.OrderID = Items.OrderId AND
       /* Some unpredictable Order filters go here */
      AND
       /* Some unpredictable Items filters go here */
    "

Затем я бы вызвал хранимую процедуру,

CREATE PROCEDURE GetItemsAndOrders(@tempSql as text)
   Execute (@tempSQL) --to create the #ItemsToQuery table

SELECT * FROM Items WHERE Items.ItemId IN (SELECT ItemId FROM #ItemsToQuery)

SELECT * FROM Orders WHERE Orders.OrderId IN (SELECT DISTINCT OrderId FROM #ItemsToQuery)

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

На ум приходят 3 варианта, но я ищу лучший:

1) Первый SQL может быть выполнен путем выполнения динамически созданного SQL от клиента. Затем результаты могут быть переданы в виде таблицы в модифицированную версию вышеупомянутой хранимой процедуры. Я знаком с передачей табличных данных в формате XML. Если бы я сделал это, сохраненный процесс мог бы затем вставить данные во временную таблицу с помощью статического SQL, который, поскольку он был создан динамическим SQL, можно было бы запросить без проблем. (Я мог бы также изучить возможность передачи нового параметра типа таблицы вместо XML.) Однако я бы хотел избежать передачи потенциально больших списков хранимой процедуре.

2) Я мог выполнять все запросы от клиента.

Первый будет примерно таким:

SELECT Items.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
SELECT Orders.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)

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

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

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


person Chad    schedule 27.05.2010    source источник
comment
TL; DR: urbandictionary.com/define.php?term=TLDR   -  person OMG Ponies    schedule 27.05.2010
comment
Вы определенно можете вернуть результаты из динамического sql обратно в вызывающее приложение. Большинство ответов здесь относятся к другим пунктам, которые вы указали.   -  person Rory    schedule 21.05.2021


Ответы (5)


Сначала вам нужно создать свою таблицу, тогда она будет доступна в динамическом SQL.

Это работает:

CREATE TABLE #temp3 (id INT)
EXEC ('insert #temp3 values(1)')

SELECT *
FROM #temp3

Так не пойдет:

EXEC (
        'create table #temp2 (id int)
         insert #temp2 values(1)'
        )

SELECT *
FROM #temp2

Другими словами:

  1. Создать временную таблицу
  2. Выполнить процедуру
  3. Выбрать из временной таблицы

Вот полный пример:

CREATE PROC prTest2 @var VARCHAR(100)
AS
EXEC (@var)
GO

CREATE TABLE #temp (id INT)

EXEC prTest2 'insert #temp values(1)'

SELECT *
FROM #temp
person SQLMenace    schedule 27.05.2010
comment
Думаю, это тоже работает: вставить в #temptable exec ('select ?? from ??'); - person Zachary Scott; 27.05.2010
comment
проблема в том, что когда мы не знаем определения столбцов, как насчет этого? - person Muflix; 20.02.2017
comment
Предполагая, что вы знаете, что таблица существует, возьмите верхнюю 1 из целевой таблицы в #someTable. Затем обрежьте #someTable. Это быстро и грязно. Отказ от ответственности: размер столбца varchars, например, может не соответствовать наибольшему размеру где-то во всем наборе. Если вы беспокоитесь об этом, создайте временную таблицу из sys.tables и sys.columns. Вам нужно будет сделать это только один раз. Это также можно сделать динамически, если вы хорошо разбираетесь в динамическом SQL. - person natur3; 03.02.2021

1-й метод - заключите несколько операторов в один вызов динамического SQL:

DECLARE @DynamicQuery NVARCHAR(MAX)

SET @DynamicQuery = 'Select * into #temp from (select * from tablename) alias 
select * from #temp
drop table #temp'

EXEC sp_executesql @DynamicQuery

2-й метод - используйте глобальную временную таблицу:
(Осторожно, вам нужно позаботиться о глобальной переменной.)

IF OBJECT_ID('tempdb..##temp2') IS NULL
BEGIN
    EXEC (
            'create table ##temp2 (id int)
             insert ##temp2 values(1)'
            )

    SELECT *
    FROM ##temp2
END

Не забудьте удалить объект ## temp2 вручную, когда закончите с ним:

IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL)
BEGIN
     DROP Table ##temp2
END

Примечание. Не используйте этот метод 2, если вы не знаете полную структуру базы данных.

person Sid    schedule 23.11.2015

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

DECLARE @DynamicSQL NVARCHAR(MAX)
DECLARE @DynamicTable VARCHAR(255) = 'DynamicTempTable_' + CONVERT(VARCHAR(36), NEWID())
DECLARE @DynamicColumns NVARCHAR(MAX)

--Get "@DynamicColumns", example: SET @DynamicColumns = '[Column1], [Column2]'

SET @DynamicSQL = 'SELECT ' + @DynamicColumns + ' INTO [##' + @DynamicTable + ']' + 
     ' FROM [dbo].[TableXYZ]'

EXEC sp_executesql @DynamicSQL

SET @DynamicSQL = 'IF OBJECT_ID(''tempdb..##' + @DynamicTable + ''' , ''U'') IS NOT NULL ' + 
    ' BEGIN DROP TABLE [##' + @DynamicTable + '] END'

EXEC sp_executesql @DynamicSQL

Конечно, не лучшее решение, но, похоже, это работает для меня.

person David Rogers    schedule 28.08.2018

Я настоятельно рекомендую вам прочитать http://www.sommarskog.se/arrays-in-sql-2005.html

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

person Sam Saffron    schedule 27.05.2010
comment
Я бы скорее передал XML, чем CSV. Хотя он более подробный, он позволяет гибко изменять и передавать дополнительные столбцы. А SQL уже умеет разбирать XML. Но я видел пример передачи набора данных клиента в переменную таблицы на стороне сервера. Очень чистый. Даже это, хотя, менее желательно, чем временная таблица IMHO, это подход, который с меньшей вероятностью масштабируется. - person Chad; 27.05.2010
comment
SQL Server 2008+ (или 2005?) Поддерживает значения таблиц, которые могут быть предоставлены через клиент после определения (например, хорошо работает с наборами данных ADO.NET). SQL Server 2016+ поддерживает JSON. - person user2864740; 19.11.2018

Наборы результатов из динамического SQL возвращаются клиенту. Я делал это довольно часто.

Вы правы в вопросах обмена данными через временные таблицы и переменные и тому подобном между SQL и генерируемым им динамическим SQL.

Я думаю, что, пытаясь заставить вашу временную таблицу работать, вы, вероятно, запутались, потому что вы определенно можете получить данные от SP, который выполняет динамический SQL:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + ''''
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO

Также:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * INTO #temp FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + '''; SELECT * FROM #temp;'
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO
person Cade Roux    schedule 27.05.2010
comment
если вы создадите временную таблицу в процессе, это не сработает, вам нужно сначала создать временную таблицу, затем вы можете заполнить ее в процессе ... см. также мой пример - person SQLMenace; 27.05.2010
comment
@SQLMenace - я понимаю, о чем вы говорите. Я хотел сказать, что вы МОЖЕТЕ возвращать наборы из динамического SQL (и они могут использовать свои собственные временные таблицы и возвращаться из них). Добавлю второй пример. - person Cade Roux; 27.05.2010