I have a SourceTable and a table variable @TQueries containing various T-SQL predicates that target SourceTable.
The expected result is to dynamically generate SELECT statements that return a list of Id’s as specified by the predicates in @TQueries. Each dynamically generated SELECT statement also needs to execute in a particular order, and the final set of values needs to be unique and the ordering must be preserved.
Fortunately, there’s a limit to how many values need to be retrieved and how many dynamic queries need to be generated. The Id list should contain at most 10 Ids, and we don’t expect more than 7 queries.
The following is a sample of this setup, not the actual data/database:
-- Set up some test data, this is quick and dirty just to provide some data to test against
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SourceTable]') AND type in (N'U'))
BEGIN
-- Create a numbers table, sorta
SELECT TOP 20
IDENTITY(INT,1,1) AS Id,
ABS(CHECKSUM(NewId())) % 100 AS [SomeValue]
INTO [SourceTable]
FROM sysobjects a
END
DECLARE @TQueries TABLE (
[Ordinal] INT,
[WherePredicate] NVARCHAR(MAX),
[OrderByPredicate] NVARCHAR(MAX)
);
-- Simulate SELECTs with different order by that get different data due to varying WHERE clauses and ORDER conditions
INSERT INTO @TQueries VALUES ( 1, N'[Id] IN (6,11,13,7,10,3,15)', '[SomeValue] ASC' ) -- Sort Asc
INSERT INTO @TQueries VALUES ( 2, N'[Id] IN (9,15,14,20,17)', '[SomeValue] DESC' ) -- Sort Desc
INSERT INTO @TQueries VALUES ( 3, N'[Id] IN (20,10,1,16,11,19,9,15,17,6,2,3,13)', 'NEWID()' ) -- Sort Random
My main issue has been avoiding the use of a CURSOR or iterating through the rows one by one. The closest I’ve come to a set operation that meets this criteria is using a table variable to store the results of each query or a massive CTE.
Suggestions and comments are welcome.
Here’s a solution that builds a single statement both to run all the queries and to return the results.
It uses a similar approach as in your answer when iterating over the
@TQueriestable, i.e. it also uses{...}tokens where column values from@TQueryshould go, and it puts the values there with nestedREPLACE()calls.Other than that, it heavily depends on ranking functions, and I’m not sure if doesn’t really abuse them. You’d need to test this method before deciding if it’s better or worse than the one you’ve got so far.
Basically, every subquery gets these auxiliary columns:
QueryRank– a constant value (within the subquery’s result set) derived from[Ordinal];RowRank– a ranking assigned to a row based on the[OrderByPredicate].The result sets are UNIONed and then every entry of every unique value is again ranked (
ValueRank) based on the query ranking.When pulling the final result set, duplicates are suppressed (by the condition
ValueRank = 1), andQueryRankandRowRankare used in theORDER BYclause to preserve the original row order.I used
EXECUTE sp_executesql @queryinstead ofEXECUTE (@query), because the former allows you to add parameters to the query. In particular, I parametrised the number of results to return (the argument ofTOP). But you could certainly concatenate that value into the dynamic script directly, just like other things, if you preferEXECUTE ()overEXECUTE sq_executesql.If you like, you can try this query at SQL Fiddle. (Note: the SQL Fiddle version replaces the
@TQueriestable variable with theTQueriestable.)