change column names in a dynamic pivot table result - sql

I have a tsql dynamic pivot table query that works fine although I´m not happy with the column names in the final output.
To make it work, the user has to select up to 20 fruit names from a list of 200 fruit names. Then I build the pivot table so I end up with different column names everytime the select is run.
For example:
First time the column names are: apple, orange and pear
Second time is: .orange, banana, kiwi and apple
My question is: Is there a posibility to have static names, like for example: name of the first column always "col_1", second column "col_2" etc?
The select statement is as follows:
DECLARE #idList varchar(800)
DECLARE #sql nvarchar(max)
SELECT #idList = coalesce(#idList + ', ', '') + '['+ltrim(rtrim(id_producto)) +']'
from gestor_val_pos
group by id_producto order by id_producto
SELECT #sql = 'select * from #correlaciones pivot (max (correl)
for codigo2 in (' + #IDlist + ')) AS pvt order by codigo1;'
exec sp_executeSQL #sql

sure.. just created a new variable to hold the column aliases and Row_Number to get the column number.
DECLARE #idList varchar(800)
DECLARE #idListAlias varchar(800)
DECLARE #sql nvarchar(max)
SELECT
#idList = coalesce(#idList + ', ', '') + '['+ltrim(rtrim(id_producto)) +']',
#idListAlias = coalesce(#idListAlias + ', ', '') + '['+ltrim(rtrim(id_producto)) +'] as col_' + CONVERT(VARCHAR(10), ROW_NUMBER() OVER(ORDER BY id_producto))
from gestor_val_pos
group by id_producto order by id_producto
SELECT #sql = 'select ' + #idListAlias + ' from #correlaciones pivot (max (correl)
for codigo2 in (' + #IDlist + ')) AS pvt order by codigo1;'
exec sp_executeSQL #sql

Yes, but it'll make your query significantly more complex.
You'll need to return a list of the possible column names generated from #IDList and convert that into a SELECT clause more sophisticated than your current SELECT *.
When you've got that, use some SQL string splitting code to convert the #IDList into a table of items with a position parameter. Append AS <whatever> onto the end of any you want and use the FOR XML PATH trick to flatten it back, and you've got a SELECT clause that'll do what you want. But, as I said, your code is now significantly more complicated.
As an aside - I really hope that #idList is either completely impossible for any user input to ever reach or hugely simplified from your real code for this demonstration. Otherwise you've got a big SQL injection hole right there.

Related

How to keep column name after sum aggregation in SQL query

I am querying SQL Server where I have multiple columns with sum aggregation. I'm wondering if there is a way to keep column name after doing sum over each of them.
I want to avoid using aliases immediately after the selected column as their numbers and names change over time.
Example of my result:
My query:
DECLARE #SQLQUERY AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
DECLARE #SumColumns as NVARCHAR(MAX)
SELECT #PivotColumns = COALESCE(#PivotColumns + ',','') + QUOTENAME(description)
FROM [example-base].dbo.table
SELECT #SumColumns = 'sum('+Replace(#PivotColumns,',','),sum(')+')'
SET #SQLQUERY = N'SELECT TOP(100) Weeks, Years, Code,'+#SumColumns+ '
FROM [example-base].dbo.table
group by Weeks,Years,Code'
EXECUTE sp_executesql #SQLQUERY
Is there a way to keep column name on which is done sum?
Be aware of the potential performance issues by building a query with text. Moreover, your result may be not predictable while the number of columns can evolve between two executions.
To add an alias to your columns, you may use this statement :
DECLARE #SumColumns AS nvarchar(MAX);
SELECT #SumColumns = CONCAT(COALESCE(#PivotColumns + ',', ''), 'SUM(', QUOTENAME(description), ') AS ', QUOTENAME(description))
FROM [example-base].dbo.table
...a union..?
SET #SQLQUERY = N'
SELECT TOP(0) Weeks, Years, Code,'+#PivotColumns+ '
FROM [example-base].dbo.table
UNION ALL
SELECT TOP(100) Weeks, Years, Code,'+#SumColumns+ '
FROM [example-base].dbo.table
group by Weeks,Years,Code'

MS SQL Store Procedure to Merge Multiple Rows into Single Row based on Variable Table and Column Names

I'm working with MS SQL Server 2008. I'm trying to create a stored procedure to Merge (perhaps) several rows of data (answers) into a single row on target table(s). This uses a 'table_name' field and 'column_name' field from the answers table. The data looks like something like this:
answers table
--------------
id int
table_name varchar
column_name varchar
answer_value varchar
So, the target table (insert/update) would come from the 'table_name'. Each row from the anwsers would fill one column on the target table.
table_name_1 table
--------------
id int
column_name_1 varchar
column_name_2 varchar
column_name_3 varchar
etc...
Note, there can be many target tables (variable from answers table: table_name_1, table_name_2, table_name_3, etc.) that insert into many columns (column_name_1...2...3) on each target table.
I thought about using a WHILE statement to loop through the answers table. This could build a variable which would be the insert/update statement(s) for the target tables. Then executing those statements somehow. I also noticed Merge looks like it might help with this problem (select/update/insert), but my MS SQL Stored Procedure experience is very little. Could someone suggestion a strategy or solution to this problem?
Note 6/23/2014: I'm considering using a single Merge statement, but I'm not sure it is possible.
I'm probably missing something, but the basic idea to solve the problem is to use meta-programming, like a dynamic pivot.
In this particular case there is another layer to make the solution more difficult: the result need to be in different execution instead of beeing grouped.
The backbone for a possible solution is
DECLARE #cols AS NVARCHAR(MAX)
DECLARE #query AS NVARCHAR(MAX)
--using a cursor on SELECT DISTINCT table_name FROM answers iterate:
--*Cursor Begin Here*
--mock variable for the first value of the cursor
DECLARE #table AS NVARCHAR(MAX) = 't1'
-- Column list
SELECT #cols = STUFF((SELECT distinct
',' + QUOTENAME(column_name)
FROM answers with (nolock)
WHERE table_name = #table
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '')
--Query definition
SET #query = '
SELECT ' + #cols + '
INTO ' + #table + '
FROM (SELECT column_name, answer_value
FROM answers
WHERE table_name = ''' + #table + ''') b
PIVOT (MAX(answer_value) FOR column_name IN (' + #cols + ' )) p '
--select #query
EXEC sp_executesql #query
--select to verify the execution
--SELECT * FROM t1
--*Cursor End Here*
SQLFiddle Demo
The cursor definition is omitted, because I'm not sure if it'll work on SQLFiddle
In addition to the template for a Dynamic Pivot the columns list is filtered by the new table name, and in the query definition there is a SELECT ... INTO instead of a SELECT.
This script does not account for table already in the database, if that's a possibility the query can be divided in two:
SET #query = '
SELECT TOP 0 ' + #cols + '
INTO ' + #table + '
FROM (SELECT column_name, answer_value
FROM answers
WHERE table_name = ''' + #table + ''') b
PIVOT (MAX(answer_value) FOR column_name IN (' + #cols + ' )) p '
to create the table without data, if needed, and
SET #query = '
INSERT INTO ' + #table + '(' + #cols + ')'
SELECT ' + #cols + '
FROM (SELECT column_name, answer_value
FROM answers
WHERE table_name = ''' + #table + ''') b
PIVOT (MAX(answer_value) FOR column_name IN (' + #cols + ' )) p '
or a MERGE to insert/update the values in the table.
Another possibility will be to DROP and recreate every table.
Approach I took to this complex problem:
Create several temporary tables to work with your data
Select and populate the temporary tables with the data
Use dynamic pivoting to pivot the rows into one row
Use a CURSOR with WHILE loop for multiple table entries
SET #query with the dynamically built MERGE statement
EXECUTE(#query)
Drop temporary tables

how do I select records that are like some string for any column in a table?

I know that I can search for a term in one column in a table in t-sql by using like %termToFind%. And I know I can get all columns in a table with this:
SELECT *
FROM MyDataBaseName.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'MyTableName`
How can I perform a like comprparison on each of the columns of a table? I have a very large table so I can't just spell out LIKE for each column.
As always, I'll suggest xml for this (I'd suggest JSON if SQL Server had native support for it :) ). You can try to use this query, though it could perform not so well on large number of rows:
;with cte as (
select
*,
(select t.* for xml raw('data'), type) as data
from test as t
)
select *
from cte
where data.exist('data/#*[local-name() != "id" and contains(., sql:variable("#search"))]') = 1
see sql fiddle demo for more detailed example.
Important note by Alexander Fedorenko in comments: it should be understood that contains function is case-sensitive and uses xQuery default Unicode code point collation for the string comparison.
More general way would be to use dynamic SQL solution:
declare #search nvarchar(max)
declare #stmt nvarchar(max)
select #stmt = isnull(#stmt + ' or ', '') + quotename(name) + ' like #search'
from sys.columns as c
where c.[object_id] = object_id('dbo.test')
--
-- also possible
--
-- select #stmt = isnull(#stmt + ' or ', '') + quotename(column_name) + ' like #search'
-- from INFORMATION_SCHEMA.COLUMNS
-- where TABLE_NAME = 'test'
select #stmt = 'select * from test where ' + #stmt
exec sp_executesql
#stmt = #stmt,
#params = N'#search nvarchar(max)',
#search = #search
sql fiddle demo
I'd use dynamic SQL here.
Full credit - this answer was initially posted by another user, and deleted. I think it's a good answer so I'm re-adding it.
DECLARE #sql NVARCHAR(MAX);
DECLARE #table NVARCHAR(50);
DECLARE #term NVARCHAR(50);
SET #term = '%term to find%';
SET #table = 'TableName';
SET #sql = 'SELECT * FROM ' + #table + ' WHERE '
SELECT #sql = #sql + COALESCE('CAST('+ column_name
+ ' as NVARCHAR(MAX)) like N''' + #term + ''' OR ', '')
FROM INFORMATION_SCHEMA.COLUMNS WHERE [TABLE_NAME] = #table
SET #sql = #sql + ' 1 = 0'
SELECT #sql
EXEC sp_executesql #sql
The XML answer is cleaner (I prefer dynamic SQL only when necessary) but the benefit of this is that it will utilize any index you have on your table, and there is no overhead in constructing the XML CTE for querying.
In case someone is looking for PostgreSQL solution:
SELECT * FROM table_name WHERE position('your_value' IN (table_name.*)::text)>0
will select all records that have 'your_value' in any column. Didn't try this with any other database.
Unfortunately this works as combining all columns to a text string and then searches for a value in that string, so I don't know a way to make it match "whole cell" only. It will always match if any part of any cell matches 'your_value'.

Error with Dynamic SQL statement cutting off with exec command

I am attempting to pivot a column with 400 or so unique values with the following code:
Declare #t VARCHAR(10)
Declare #A VARCHAR(1000)
Declare #B VARCHAR(1000)
set #A='SELECT Name, IRIS_ID__c'
SET #B='('
SELECT #A=#A+',['+Question_Concept_With_ImpactArea__c+'] as ['+Question_Concept_With_ImpactArea__c+']',#B=#B+'['+Question_Concept_With_ImpactArea__c+']'
FROM (SELECT DISTINCT Question_Concept_With_ImpactArea__c
FROM Company_Number_Response) cur
-- removing last ',' from both variables
SET #B=SUBSTRING(#B,1,LEN(#B)-1)
SET #A=#A+ + '
FROM
(SELECT NAME, IRIS_ID__c, Selected, Question_Concept_With_ImpactArea__c
FROM Company_Number_Response) s PIVOT (max(Unified_Response__c)
FOR Question_Concept_With_ImpactArea__c IN ' +#B+')) p ORDER BY [IRIS_ID__c];'
exec(#A);
Running this code throws an error that "Unclosed quotation mark after the character string 'CM_PcCOGSSup'
Incorrect Syntax near 'CM_PcCOGSSup'
When I change the last command from exec to print I can see that many of the values are being read, but the statement simply cuts off at the value in the above error. I checked the data and do not see any characters that would be causing this. Additionally, I thought the result might be too long and changed the max number for results to text to 8192.
Any help would be greatly appreciated.
Assuming you're using SQL Server 2005 or up, change your variable declarations to varchar(max). It's cutting off because you've only supplied varchar(1000), and string concatenation does not automatically lengthen char data types--they get truncated.
In SQL 2000 you can only use varchar(8000). If you need longer than that you will have to get creative--a pretty difficult task.
You also have SET #A=#A+ + '. If that's working, it's a surprise to me, but in any case there should only be one + there.
And, you should not just put square brackets around columns to make them a valid sysname data type. You should use the QuoteName function:
SELECT #A = #A + ',' + QuoteName(Question_Concept_With_ImpactArea__c) ...
Try this one -
DECLARE #Columns VARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
SELECT #Columns = STUFF((
SELECT DISTINCT ', ['+ Question_Concept_With_ImpactArea__c + ']'
FROM dbo.Company_Number_Response
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
SELECT #SQL = 'SELECT Name, IRIS_ID__c, ' + #Columns + '
FROM
(
SELECT NAME, IRIS_ID__c, Selected, Question_Concept_With_ImpactArea__c
FROM Company_Number_Response
) s
PIVOT (
max(Unified_Response__c)
FOR Question_Concept_With_ImpactArea__c IN (' + #Columns + ')
) p
ORDER BY [IRIS_ID__c];'
EXEC sys.sp_executesql #SQL

How to select output of XML Path query in SQL?

Doc table contains a lot of columns (even not used ones):
Doc_DdfID Doc_RentDate Doc_ReturnDate etc.
--------- ------------ -------------- ----
1 2012-07-28 2012-07-28
But I want to query just the used ones within Doc's table.
DocDefinitionFields list columsn that are in use by document:
SELECT Dfl_ColName
FROM DocDefinitionFields
WHERE Dfl_DdfID = 1
DocDefinitionFields:
Dfl_ColName
-----------
Doc_RentDate
Doc_ReturnDate
...........
So I want to select all columns (listed by second query) from Doc table.
Example (if 2 columns are added to document definition form I want to select just them):
Doc:
Doc_RentDate Doc_ReturnDate
------------ --------------
2012-07-28 2012-07-28
Tried to do that by subquerying select with concatenation of fields using XML PATH:
SELECT
(SELECT
Dfl_ColName + ', '
FROM DocDefinitionFields
FOR XML PATH('')
)
FROM Doc
It's not that simple tho. What do you suggest?
What you need here is dynamic SQL, something like this:
DECLARE #sql VARCHAR(MAX)
SET #sql = 'SELECT ' + STUFF((SELECT ', ' + Dfl_ColName FROM DocDefinitionFields FOR XML PATH('') ),1,1,'') + ' FROM Doc'
EXEC (#sql)
Also, in order to eliminate additional comma(,) at the end of columns I have added STUFF function along with FOR XML PATH.
To get the column names for a query dynamically and use these in a query you will need to use Dynamic SQL. Below is an example of how to create the string of available columns
DECLARE #Columns VARCHAR(MAX);
SELECT #Columns =
COALESCE(#Columns + ',
[' + CAST(COLUMN_NAME AS VARCHAR) + ']',
'[' + CAST(COLUMN_NAME AS VARCHAR) + ']')
FROM (SELECT DISTINCT COLUMN_NAME
FROM [SomeDatabase].INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'SomeTableName') AS H
ORDER BY COLUMN_NAME;
GO
You can now use the string of available columns in a Dynamic SQL query. Below we have adopted the above in an INSERT query that build the required fields dynamically. The reason why we need to do it in the below is the inclusion of the set field SomeFieldA along with the others.
DECLARE #SQL NVARCHAR(MAX);
SET #SQL =
N'INSERT INTO [' + #DbName + N']..[SomeOtherTable] ([SomeFieldA], ' + #Columns + N')
SELECT SomeFieldA, ' + #Columns + N'
FROM [SomeTableName];';
EXEC sp_executesql #SQL;
GO
You should be able to amend the above to provide what you need.
I hope this helps.