3

My COLUMNS can contain only three values or var chars - economy, basic, luxury. I want to select a ROW and display only those COLUMNS which contain luxury. The problem is that there are many such columns - about 50. I don't want to type the names of all those columns in my select query. Is there a shorter and simpler alternative to this ? Which query should I use ?

I am thinking of something like this (this is a FAKE query) -

@declare Column_Name varchar(30)
select Column_Name where Column_Value = 'luxury' 
from ATable
where rowId = 'row 5';

Table structure -

rowId | Column1 | Column2 | Column3.....
sequel.learner
  • 3,421
  • 7
  • 22
  • 24
  • 1
    Yes I have seen this done using meta from the system tables, and I'm sure someone more knowleagable than me will provide an answer along those lines. But in the mean time, try right click on the table> Script Table As> Create To> New Query Editor Window? The entire list of columns are given in the script. Copy it and use the fields as necessary. (from http://stackoverflow.com/questions/600446/sql-server-how-do-you-return-the-column-names-from-a-table) – Sepster Mar 21 '13 at 03:39
  • Since no-one has stepped up with a meta-based answer... I've added one below that worked for me on a small test db. – Sepster Mar 21 '13 at 04:29

1 Answers1

2

I've created a stored procedure for you.

This procedure examines the MSSQL meta to build a dynamic SQL string that returns a result containing column names N and their values V, and the corresponding row key K from which that value was retrieved, for a specified table.

When this is executed, the results stored in a global temporary table called ##ColumnsByValue, which can then be queried directly.

Create the GetColumnsByValue stored procedure, by executing this script:

-- =============================================
-- Author:      Ben Roberts (sepster@internode.on.net)
-- Create date: 22 Mar 2013
-- Description: Returns the names of columns that contain the specified value, for a given row
-- =============================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF OBJECT_ID ( 'dbo.GetColumnsByValue', 'P' ) IS NOT NULL 
    DROP PROCEDURE dbo.GetColumnsByValue;
GO
CREATE PROCEDURE dbo.GetColumnsByValue
    -- Add the parameters for the stored procedure here
    @idColumn sysname,
    @valueToFind nvarchar(255), 
    @dbName sysname,
    @tableName sysname,
    @schemaName sysname,
    @debugMode int = 0

AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @SQL nvarchar(max);
    DECLARE @SQLUnion nvarchar(max);
    DECLARE @colName sysname;
    DECLARE @dbContext nvarchar(256);
    DECLARE @Union nvarchar(10);

    SELECT @dbContext = @dbName + '.' + @schemaName + '.sp_executeSQL';
    SELECT @SQLUnion = '';
    SELECT @Union = '';

    IF OBJECT_ID ( 'tempdb..##GetColumnsByValueIgnoreList') IS NULL -- no columns to ingore have been specified, need to create an empty list.
    BEGIN
        CREATE TABLE ##GetColumnsByValueIgnoreList (column_name nvarchar(255));
    END

    DECLARE DBcursor CURSOR FOR
        SELECT 
            COLUMN_NAME
        FROM 
            INFORMATION_SCHEMA.COLUMNS
        WHERE 
            TABLE_NAME = @tableName 
            AND 
            TABLE_SCHEMA = @schemaName;

    OPEN DBcursor; 
        FETCH DBcursor INTO @colName;
        WHILE (@@FETCH_STATUS = 0)
        BEGIN
            IF (
                @colName != @idColumn
                AND
                @colName NOT IN (SELECT column_name FROM ##GetColumnsByValueIgnoreList)
            )
            BEGIN
                SELECT @SQL = 'SELECT '+@idColumn+' as K, '''+@colName+''' as N, ' +@colName+ ' as V FROM ' + @dbName + '.' + @schemaName + '.' + @tableName;
                --PRINT @SQL;
                SELECT @SQLUnion = @SQL + @Union + @SQLUnion;
                SELECT @Union = ' UNION ';
            END
            FETCH  DBcursor INTO @colName;
        END; -- while
    CLOSE DBcursor; DEALLOCATE DBcursor;

    IF (@debugMode != 0)
        BEGIN
            PRINT @SQLUnion;
            PRINT @dbContext;
        END
    ELSE
        BEGIN
            -- Delete the temp table if it has already been created.
            IF OBJECT_ID ('tempdb..##ColumnsByValue') IS NOT NULL 
                BEGIN 
                    DROP TABLE ##ColumnsByValue 
                END

            -- Create a new temp table
            CREATE TABLE ##ColumnsByValue (
                K nvarchar(255), -- Key
                N nvarchar(255), -- Column Name
                V nvarchar(255)  -- Column Value
            )

            -- Populate it with the results from our dynamically generated SQL.
            INSERT INTO ##ColumnsByValue EXEC @dbContext @SQLUnion;
        END
END
GO

The SP takes several inputs as parameters, these are explained in the following code.

Note also I've provided a mechanism to add an "ignore list" as an input:

  • This allows you to list any column names that should not be included in the results.
  • You do NOT need to add the columnn that you're using as your key, ie the row_id from your example structure.
  • You MUST include other columns that are not varchar as these will cause an error (as the SP just does a varchar comparison on all columns it looks at).
  • This is done via a temp table that you must create/populate
  • Your example table structure suggests the table contains only columns of interest, so this may not apply to you.

I've included example code for how to do this (but only do this if you need to):

IF OBJECT_ID ( 'tempdb..##GetColumnsByValueIgnoreList') IS NOT NULL
    BEGIN
        DROP TABLE ##GetColumnsByValueIgnoreList;
    END
CREATE TABLE ##GetColumnsByValueIgnoreList (column_name nvarchar(255));
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('a_column');
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('another_column');
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('yet_another_column');

Now, to fire off the procedure that build your temp table of results, use the following code (and modify as appropriate, of course).

-- Build the ##ColumnsByValue table
EXEC dbo.GetColumnsByValue
    @idColumn = 'row_id',   -- The name of the column that contains your row ID (eg probably your PK column)
    @dbName = 'your_db_name',
    @tableName = 'your_table_name',
    @schemaName = 'dbo',
    @debugMode = 0          -- Set this to 1 if you just want a print out of the SQL used to build the temp table, to 0 if you want the temp table populated

This leaves you with ##ColumnsByValue, on which you can perform whatever search you need, eg:

select * from ##ColumnsByValue WHERE v = 'luxury' and k = 5 --some_row_id

You'd need to re-execute the stored procedure (and if relevant, create/modify the ignore list table prior to it) for each table you want to examine.

A concern with this approach is the nvarchar length might get exceeded in your case. You'd prob. need to use different datatype, reduce the column name lengths etc. Or break it up into sub-steps and union the results together to get the resultset you're after.

Another concern I have is that this is complete overkill for your particular scenario, where a one-off script-to-query-window will give you the basis of what you need, then some clever text editing in eg Notepad++ will get you all the way there... and hence this problem will likely (and quite reasonably) put you off doing it this way! But it is a good general-case question, and so deserves an answer for anyone interested in future ;-)

Sepster
  • 4,800
  • 20
  • 38
  • Sepster - I tried your query. Not sure if i did it correctly. I set @dbName = 'my database name', schemaName = 'dbo' and tName = 'dbo.My table name'. The query executed successfully, but nothing happened. – sequel.learner Mar 21 '13 at 06:56
  • does your query make any permanent changes to my table or is it just temporary ? – sequel.learner Mar 21 '13 at 06:56
  • The code runs and returns the result which you mentioned. How do I use that your code inside a select statement. I tried running SELECT Column_Name WHERE Column_Value = 'luxury'; after this query and got an error. – sequel.learner Mar 21 '13 at 07:44
  • The code works, but the result is only 'command completed successfully'. It does not give me a result set. What do i do now ? – sequel.learner Mar 21 '13 at 19:33
  • @sequel.learner I've completely updated the code so it's more "user friendly" I think. Have a read through and run with it if it makes sense. Can you please delete your above comments to get a "clean slate", and then post any further questions you may have about this as new comments? – Sepster Mar 22 '13 at 11:14