Reputation: 687
Intro
I'm creating a SQL Server stored procedure to dynamically get data from large table(s). There are many (30+) columns which may / may not need to be returned with selected data.
I have a dynamic SQL select query, using an unpivot (so columns are listed as rows), working as expected... Now, I need to filter these results.
Input table:
Column
A
B
Table:
A B C
---------------------------
value1 valueZ
value2 valueY
value3 valueX
value0 valueA
value4
valueX
Expected results:
Col Value Count
-------------------
A value1 1
A value2 1
A value3 1
A value0 1
A value4 1
B valueZ 1
B valueY 1
B valueX 2
(no C due input column selection)
Question
As an input, I have a table type listing the columns I need to return. I intend to extend this to include values to filter the given columns.
Example table type input:
Column Value
-----------------
A value1
A value2
A value3
B valueX
B valueY
From this, I need to build a where clause like this. Same columns are ORed; when column changes, it is ANDed.
WHERE (A = 'value1'
OR A = 'value2'
OR A = 'value3')
AND (B = 'valueX' OR B = 'valueY')
So far, I've got a COALESCE, but don't know how to determine the column change - I really don't want a cursor.
As a C# dev - can SQL offer me any 'magic' loops to achieve this?
I can also use C# to parse the clause as a string, but kind of need to keep that bit easy!
Many thanks.
Upvotes: 0
Views: 1857
Reputation: 687
With most thanks to @user2114537, I came up with this (it's not pretty!). I do need to apply the dynamic filter BEFORE the pivot.
We are using SQL Server 2008, so LEAD() is unavailable, I also used COALESCE instead of @user2114537's WHILE (please review their answer, https://stackoverflow.com/a/54592090/4616330, for SQLServer >= 2012).
-- create a tmp table var for and/or where data
DECLARE @t TABLE([Column] nvarchar(50), [Value] nvarchar(50), [andOr] nvarchar(7))
-- create a table with row numbers (for joining to self)
;WITH cte AS
(SELECT f.[Column], f.[Value], ROW_NUMBER() OVER (ORDER BY f.[Column], f.[Value]) AS [RowNo]
FROM @InputTable f
)
-- join table to itself to reproduce 2012's LEAD function and insert AND/OR based on col being same as previous
INSERT INTO @t([Column], [Value], [andOr])
SELECT c1.[Column], c1.[Value], CASE WHEN c1.[Column] = c2.[Column] THEN ') AND (' ELSE ' OR ' END AS [andOr]
FROM cte c1 LEFT OUTER JOIN cte c2 ON c1.[RowNo] = c2.[RowNo] + 1
-- build a where clause from andOr info
DECLARE @where AS NVARCHAR(MAX),
@whereBuilt AS NVARCHAR(MAX)
SELECT @where = COALESCE(@where, '') + (QUOTENAME([Column]) + '=''' + [Value] + ''' ' + [andOr] + ' ')
FROM @t
-- fiddle to get a valid where...
SET @whereBuilt = '(' + SUBSTRING(@where, 1, LEN(@where) - 4) + ')'
PRINT @whereBuilt
Thank you for all comments and answers.
Upvotes: 0
Reputation: 161
You can try this. First create temp table with script below. COLUNM_A is your column and VALUE_A is your value column.
SELECT ROW_NUMBER() OVER (ORDER BY A.COLUMN_A) AS ID, A.COLUMN_A, A.VALUE_A,
CASE WHEN LEAD(A.COLUMN_A,1) OVER (ORDER BY A.COLUMN_A) = A.COLUMN_A THEN 1 ELSE 0 END AS IDEN
INTO #T1
FROM dbo.YOUR_TABLE A
temp table look like this, and you have a new column IDEN for define 'OR' and 'AND' statment
SELECT * FROM #T1
after that you try this:
DECLARE @SQL_WHERE NVARCHAR(1500),
@val1 INT,
@valmax INT
SET @valmax = (SELECT COUNT(*) FROM #T1)
SET @val1 = 1
SET @SQL_WHERE = '('
WHILE @val1 <= @valmax
BEGIN
SET @SQL_WHERE = @SQL_WHERE + (SELECT COLUMN_A + ' = ''' + VALUE_A + CASE WHEN IDEN = 1 THEN ''' OR ' ELSE ''') AND (' END
FROM #T1 WHERE ID = @val1)
SET @val1 = @val1 + 1
END
SELECT 'WHERE ' + SUBSTRING(@SQL_WHERE, 1 , LEN(@SQL_WHERE) - 5)
Last select will return correct WHERE clause
Upvotes: 1
Reputation: 12243
If I understand your situation correctly, you already have the unpivot
ed output as you require, you just need to filter it from a given set of values?
If this is the case, you can put those values into a derived table and filter the first dataset through an inner join
:
declare @t table(Col varchar(1),Val varchar(6),Cnt int);
insert into @t values('A','value1',1),('A','value2',1),('A','value3',1),('A','value0',1),('A','value4',1),('B','valueZ',1),('B','valueY',1),('B','valueX',2);
declare @f table(Col varchar(1),Val varchar(6));
insert into @f values('A','value1'),('A','value2'),('A','value3'),('B','valueX'),('B','valueY');
select t.*
from @t as t
join @f as f
on t.Col = f.Col
and t.Val = f.Val;
+-----+--------+-----+
| Col | Val | Cnt |
+-----+--------+-----+
| A | value1 | 1 |
| A | value2 | 1 |
| A | value3 | 1 |
| B | valueY | 1 |
| B | valueX | 2 |
+-----+--------+-----+
Upvotes: 1