I have a table that has around 200 different boolean columns. These are basically On/Off switches used to blacklist data in another application. This also has multiple rows for different functionalities within said application.
As you can imagine, keeping a good overview over which columns are "turned on" for a specific function is rather tiresome when you have to manually check them against some excel sheet, so I want to make my life easier by only displaying columns that are turned on/set to true.
Something like:
select [columns with value '1']
from table
where function = 'function1'
Where this table:
+-----------+------+------+------+------+------+
| function | Col1 | Col2 | Col3 | Col4 | Col5 |
+-----------+------+------+------+------+------+
| function1 | 1 | 0 | 1 | 1 | 0 |
| function2 | 0 | 1 | 0 | 0 | 0 |
+-----------+------+------+------+------+------+
returns this:
+----------+------+-------+-----+
| function | Col1 | Col3 | Col4 |
+-----------+------+------+------+
| function1 | 1 | 1 | 1 |
+-----------+------+------+------+
Is there any way to do something like this?
As is mentioned in the comments, result columns are defined independent of table data, but the following approach, which returns the columns names as a single column, is a possible solution:
Table:
CREATE TABLE Data (
[Function] varchar(3),
Col1 bit,
Col2 bit,
Col3 bit,
Col4 bit,
Col5 bit
)
INSERT INTO Data ([Function], Col1, Col2, Col3, Col4, Col5)
VALUES ('xyz', 1, 0, 1, 1, 1), ('abc', 0, 0, 0, 0, 1)
Dynamic statement:
DECLARE #stm nvarchar(max) = N''
SELECT #stm = CONCAT(#stm, ',(','''', col.[name], ''', ', col.[name], ')')
FROM sys.columns col
JOIN sys.tables tab ON col.object_id = tab.object_id
JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
JOIN sys.types typ ON col.system_type_id = typ.system_type_id
WHERE
tab.[name] = 'Data' AND
sch.[name] = 'dbo' AND
col.[name] != 'Function'
ORDER By col.[name]
SELECT #stm = CONCAT(
'SELECT d.[Function], STRING_AGG(v.ColName, '','') AS [Columns] FROM Data d CROSS APPLY (VALUES ',
STUFF(#stm, 1, 1, ''),
') v(ColName, ColVal) WHERE v.ColVal = 1 GROUP BY d.[Function]'
)
PRINT #stm
EXEC sp_executesql #stm
Result:
Function Columns
abc Col5
xyz Col1,Col3,Col4,Col5
This is an example, I hope it helps, it's a little tricky, but gets what you want, at least could it helps to your progress, if you have any doubts send me a message or comment, good luck.
IF OBJECT_ID('dbo.TestMr') IS NOT NULL DROP TABLE TestMr
IF OBJECT_ID('tempdb.dbo.#TestMrColumns') IS NOT NULL DROP TABLE #TestMrColumns
CREATE TABLE TestMr(x_function varchar(50),col1 numeric, col2 numeric, col3 numeric)
CREATE TABLE #TestMrColumns(y_function varchar(50),valor varchar(50),columna varchar(50))
--TEST VALUES
INSERT INTO TestMr VALUES('fun1',1,0,1)
INSERT INTO TestMr VALUES('fun2',0,1,0)
DECLARE #script nvarchar(max)
DECLARE #cols nvarchar(max)
--We get the columns from our table, this columns have to be of the same data_type or this wont work.
SET #cols = STUFF((SELECT ',' + QUOTENAME(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='TestMr' and DATA_TYPE='numeric' FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'),1,1,'')
--we make that columns rows to save them on a tempTable
set #script='select * from TestMr unpivot(valor for columna in ('+#cols+')) unpiv'
insert into #TestMrColumns exec (#script);
--we get the final columns for our select, here we can apply conditions for the columns that we want, in this case, we get
--the columns that had valor=1 and y_function=fun1/
SET #cols = STUFF((SELECT ',' + QUOTENAME(columna) FROM #TestMrColumns where valor=1 and y_function='fun1' FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'),1,1,'')
--final select
set #script='select x_function,'+#cols+' from TestMr where x_function=''fun1'' '
exec(#script)
Related
I have a fixed query on which I can add only new columns, let's say this:
SELECT '1', '2' , 'Place I can insert my subquery1', 'Place I can insert my subquery1'
FROM FixedQueryTable
I also have a table with a column that stores some values and another that stores the name of the columns from which I have to get the other values.
TABLE1
----------------------------
ValuesColumn | NameColumn
_________________________
Val1 | Col1
Val2 | Col2
Val3 | Col3
Table2
----------------------------
Col1|Col2|Col3
______________
Val4|Val5|Val6
Now, what I want to return is
1|2|Val1Val4|Val2Val5|Val3Val6
I've written this
declare #qry nvarchar(max) =
'SELECT top 1 ' +
STUFF((
SELECT ', ' +''''+ ValueColumn +'''+ CONVERT(NVARCHAR,' +NameColumn +') '
FROM Table1
FOR XML PATH('')
), 1, 1, '') + ' FROM Table2'
select #qry
execute sp_executesql #qry
And it works great as a stand alone, but I cannot use it to return the result from a function and I cannot use it directly as subquery (declare, exec, etc... obviously)
Do you guy know what I could do?
I have a requirement where my query will return output something like this:
PermissionType IsAllowed
-------------------------
IsEdit | 1
IsDelete | 0
isRemove | 1
isPrint | 1
isReport | 0
-- | -
-- | -
-- | -
--------------------------
These rows can be dynamic depending upon the filter criteria I will pass.
So now I want to convert the above resultset to the following:
IsEdit | IsDelete | IsRemove | IsPrint | IsReport | -- | -- | --
--------------------------------------------------------------------
1 | 0 | 1 | 1 | 0 | - | - | -
I tried to use the pivot here but it asks the Column Names to be pivoted into the output and this is dynamic in my case, also it needed an aggregate function for FOR but I don't have any calculation in my case.
Anyone please help me on this.
Then try this Dynamic sql
IF OBJECT_ID('dbo.temp')IS NOT NULL
DROP TABLE temp
;WITH Cte(PermissionType, IsAllowed)
AS
(
SELECT 'IsEdit' , 1 UNION ALL
SELECT 'IsDelete' , 0 UNION ALL
SELECT 'isRemove' , 1 UNION ALL
SELECT 'isPrint' , 1 UNION ALL
SELECT 'isReport' , 0
)
SELECT *,ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS Seq INTO
temp FROM Cte
DECLARE #Sql nvarchar(max),
#Sqlcol nvarchar(max),
#ISNULLSqlcol nvarchar(max)
SELECT #Sqlcol=STUFF((SELECT ', '+QUOTENAME(PermissionType)
FROM temp ORDER BY Seq FOR XML PATH ('')),1,1,'')
SELECT #ISNULLSqlcol=STUFF((SELECT ', '+'MAX('+QUOTENAME(PermissionType) +') AS '+QUOTENAME(PermissionType)
FROM temp ORDER BY Seq FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT '+#ISNULLSqlcol+'
FROM(
SELECT * FROM temp
)AS SRC
PIVOT
(
MAX(IsAllowed) FOR PermissionType IN ('+#Sqlcol+')
) AS PVT '
PRINT #Sql
EXEC (#Sql)
IsEdit IsDelete isRemove isPrint isReport
--------------------------------------------------
1 0 1 1 0
With pivot and dynamic sql you can create a query that will include a different number of columns:
if OBJECT_ID('Test') is not null
drop table [dbo].[Test]
CREATE TABLE [dbo].[Test](PermissionType varchar(20), IsAllowed int)
insert into [dbo].[Test] values
('IsEdit' , 1)
,('IsDelete', 0)
,('isRemove', 1)
,('isPrint' , 1)
,('isReport', 0)
--this variable holds all the dates that will become column names
declare #permissionTypes nvarchar(max) = ''
--this variable contains the TSQL dinamically generated
declare #sql nvarchar(max) = ''
select #permissionTypes = #permissionTypes + ', ' + quotename(PermissionType)
from [dbo].[Test]
set #permissionTypes = RIGHT(#permissionTypes, len(#permissionTypes)-2)
set #sql = concat(
'select *
from [dbo].[Test]
pivot
(
max(isallowed)
for PermissionType in (', #permissionTypes, ')
) piv '
)
exec(#sql)
Result:
Adding a new row:
insert into [dbo].[Test] values
('IsNew' , 1)
Causes a new column to be created:
I am having trouble sorting a pivot based on a quite large set of data. I have looked at many examples, but none of them seems to address the issue of volume - or perhaps I am just missing something. I have had a very good look here: Sort Columns For Dynamic Pivot and PIVOT in sql 2005 and found much good advise, but I still cannot find the correct way to sort my pivot.
I am using the following sql. It pivots the columns, but the result needs to be sorted for readability:
SELECT a.* INTO #tempA
FROM (SELECT top (5000) id, email, CONVERT(varchar,ROW_NUMBER() OVER
(PARTITION BY email ORDER BY id)) AS PIVOT_CODE FROM Email) a
order by PIVOT_CODE
DECLARE #cols AS NVARCHAR(MAX),
#sql AS NVARCHAR(MAX)
SELECT #cols =STUFF((SELECT DISTINCT ', ' + QUOTENAME(col)
FROM #tempA WITH (NOLOCK)
cross apply
(
SELECT 'id_' + PIVOT_CODE, id
) c (col, so)
group by col, so
--order by col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #sql = 'SELECT email, '
+#cols+
'INTO ##AnotherPivotTest FROM
(
SELECT email,
col,
value
FROM #tempA WITH (NOLOCK)
cross apply
(
values
(''id_'' + PIVOT_CODE, id)
) c (col, value)
) d
pivot
(
max(value)
for col in ('
+ #cols+
')
) piv'
EXEC (#sql)
SELECT * FROM ##AnotherPivotTest
The result is a chaos to look at:
==============================================================================================
| email | id_19 | id_24 | id_2 | id_16 | id_5 | id_9 | id_23 | .... | id_1 | .... | id_10 |
==============================================================================================
| xx#yy.dk | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 1234 | NULL | NULL |
==============================================================================================
I would very much like the Ids to be sorted - beginning with id_1.
As you can see, I have attempted to place an 'order by' in the selection for 'cols', but that gives me the error: "ORDER BY items must appear in the select list if SELECT DISTINCT is specified." And without DISTINCT, I get another error: "The number of elements in the select list exceeds the maximum allowed number of 4096 elements."
I'm stuck, so any help will be greatly appreciated!
Not sure what causes the problem but I've solved my order problem in my pivot table by inserting the data coming from tempA into another temp table and ordering them there
INSERT INTO #tempB
SELECT * FROM #tempA
ORDER BY PIVOT_CODE
Then selecting distinct ones like so:
SELECT #cols = #cols + QUOTENAME(PIVOT_CODE) + ',' FROM (SELECT DISTINCT PIVOT_CODE FROM #tempB ORDER BY PIVOT_CODE)
SELECT #cols = SUBSTRING(#cols, 0, LEN(#cols)) --trims "," at end
You can also just use a cursor to determine your cols and the order them
Cursor with cols ordered
declare #gruppe nvarchar(max)
declare #gruppeSql nvarchar(max)
declare #SQL nvarchar(max)
DECLARE myCustomers CURSOR FOR
select top 10 FirstName from [dbo].[DimCustomer] Order by FirstName
set #gruppeSql = ''
OPEN myCustomers
FETCH NEXT FROM myCustomers INTO #gruppe
IF (##FETCH_STATUS>=0)
BEGIN
SET #gruppeSql = #gruppeSql +'[' +#gruppe+']'
FETCH NEXT FROM myCustomers INTO #gruppe
END
WHILE (##FETCH_STATUS<>-1)
BEGIN
IF (##FETCH_STATUS<>-2)
SET #gruppeSql = #gruppeSql + ',[' +#gruppe+']'
FETCH NEXT FROM myCustomers INTO #gruppe
END
CLOSE myCustomers
DEALLOCATE myCustomers
SET #gruppeSql = replace(#gruppesql,'''','')
/*Select to preview your cols*/
select #gruppeSql
Dynamic pivot
SET #SQL = '
Select *
from
(
SELECT SalesAmount, FirstName
FROM [AdventureWorksDW2014].[dbo].[FactInternetSales] a inner join dbo.DimCustomer b on a.CustomerKey = b.CustomerKey
) x
pivot
(
sum(SalesAmount)
for FirstName in ('+#gruppesql+')
) p'
print #sql
exec(#sql)
I'm working with a legacy DB that has a table that houses field names from other tables.
So I have this structure:
Field_ID | Field_Name
*********************
1 | Col1
2 | Col2
3 | Col3
4 | Col4
and I need to pull a list of this field metadata along with the values of that field for a given user. So I need:
Field_ID | Field_Name | Value
1 | Col1 | ValueOfCol1onADiffTable
2 | Col2 | ValueOfCol2onADiffTable
3 | Col3 | ValueOfCol3onADiffTable
4 | Col4 | ValueOfCol4onADiffTable
I'd like to use the Field_Name in a subquery to pull that value, but can't figure out how to get SQL to evaluate Field_Name as a column in the sub-query.
So something like this:
select
Field_ID
,Field_Name
,(SELECT f.Field_Name from tblUsers u
where u.User_ID = #userId) as value
from
dbo.tblFields f
But that just returns Field_Name in the values column, not the value of it.
Do I need to put the sub-query in a separate function and evaluate that? Or some kind of dynamic SQL?
In SQL server this would require dynamic SQL and UNPIVOT notation.
see working demo
create table tblFields (Field_ID int ,Field_Name varchar(10));
insert into tblFields values
(1,'Col1')
,(2,'Col2')
,(3,'Col3')
,(4,'Col4');
declare #userId int
set #userId=1
create table tblUsers (User_ID int, col1 varchar(10),col2 varchar(10));
insert into tblUsers values
(1, 10,100),
(2,20,200);
declare #collist varchar(max)
declare #sqlquery varchar(max)
select #collist= COALESCE(#collist + ', ', '') + Field_Name
from dbo.tblFields
where exists (
select * from sys.columns c join sys.tables t
on c.object_id=t.object_id and t.name='tblUsers'
and c.name =Field_Name)
select #sqlquery=
' select Field_ID ,Field_Name, value '+
' from dbo.tblFields f Join '+
' ( select * from '+
'( select * '+
' from tblUsers u '+
' where u.User_ID = '+ cast(#userId as varchar(max)) +
' ) src '+
'unpivot ( Value for field in ('+ #collist+')) up )t'+
' on t.field =Field_Name'
exec(#sqlquery)
I've been stuck with this for a while and I've not found something on the website that answers something like this so please point me to the right direction if an existing question exists. In SQL Server 2012, I have a table with ID as the primary key:
ID col1 col2 col3 ....
--- ---- ----- -----
1 a z k
2 g b p
3 k d a
I don't know the length of the table nor the amount of columns/ column names
but I want to be able to get a table that gives me something like:
ID ColName Value
--- ---- -----
1 col1 a
1 col2 z
1 col3 k
2 col1 g
2 col2 b
2 col3 p
3 col1 k
3 col2 d
3 col3 a
...
I know that
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'table'
gets me my columns and I've tried trying to use that to create a temp table to
insert my desired format into a temp table but I'm not sure how to go through each row in a table and then grab the desired values dynamically for each column name and display it. I've been able to kind of achieve this with double cursors but that is painfully slow and I'm not sure how else to approach this since I'm relatively new at SQL. Any help would be appreciated!
Edit
Thank you so very much Lamak! I did have varying data types and coverting them to varchars for now shows me that the concept does work. However, I have 4 common datatypes (varchar, float, int, datetime) that I want to account for so I have 4 value fields for each of those where I would insert the column value into one of those 4 depending on it and leave the other 3 blank. I know that INFORMATION_SCHEMA.COLUMNS also provides the data_types so I was wondering what the syntax would be to convert the datatype in the "STUFF" variables based on a simple IF statement. I tried mapping the data_types to the column names but having any type of conditional statement breaks the query. If anyone has a simple example, that would be great :)
Edit
Actually, I've been able to figure out that I would need to create 4 variables to each data type rather than do them all in just one of them. Thank you all for your help!
As the comments said, you'll need to use dynamic unpivot.
If every column aside ID have the same datatype, you can use the following query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #colsUnpivot = STUFF((SELECT ', ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SET #query = '
SELECT ID,
ColName,
Value
FROM
(
SELECT *
FROM dbo.table
) x
UNPIVOT
(
Value FOR ColName IN (' + #colsunpivot + ')
) u
';
EXEC(#query);
Now, if the datatypes are different, then you'll need to first convert every column to a common datatype. In the following example, I'll use NVARCHAR(1000), but you'll need to convert them to the right datatype:
DECLARE #colsUnpivot1 AS NVARCHAR(MAX),
#colsUnpivot2 as NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #colsUnpivot1 = STUFF((SELECT ', ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SELECT #colsUnpivot2 = STUFF((SELECT ', CONVERT(NVARCHAR(1000),' + QUOTENAME(C.name)
+ ') ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SET #query = '
SELECT ID,
ColName,
Value
FROM
(
SELECT ID, ' + #colsUnpivot2 + '
FROM dbo.table
) x
UNPIVOT
(
Value FOR ColName IN ('+ #colsunpivot1 +')
) u
';
EXEC(#query);
You don't have to go DYNAMIC. Another Option with with a CROSS APPLY and a little XML.
UnPivot is more performant, but you will find the performance of this approach very respectable.
An added bonus of this approach is that the From #YourTable A could be any query (not limited to a table). For example From ( -- Your Complex Query --) A
Declare #YourTable table (ID int,Col1 varchar(25),Col2 varchar(25),Col3 varchar(25))
Insert Into #YourTable values
(1,'a','z','k')
,(2,'g','b','p')
,(3,'k','d','a')
Select C.*
From #YourTable A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select ID = r.value('#ID','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','OtherFieldsToExclude')
) C
Returns
ID Item Value
1 Col1 a
1 Col2 z
1 Col3 k
2 Col1 g
2 Col2 b
2 Col3 p
3 Col1 k
3 Col2 d
3 Col3 a