function to return a column separated list of column names - sql

I would like to create a function that returns a comma separated list of field name for any given table. The function should accept database, schema and table name as input as return the comma separated list.
I can do this in a stored procedure but I want to do this in a function so I can join it into datasets. However I am problems with dynamic sql is not allowed in function - so how can I do this?
here is the proc which i want to duplicate in a function
alter proc dbo.usp_generate_column_name_string
#database varchar(100),#schema varchar(100), #table varchar(100)
as
declare #str varchar(max) = '
select stuff((select '','' + name as [text()] from
(
select c.name from ' + #database + '.sys.tables a
inner join ' + #database + '.sys.schemas b on a.schema_id = a.schema_id
inner join ' + #database + '.sys.columns c on c.object_id= a.object_id
where b.name = '''+#schema+''' and a.name ='''+#table+''') x
for xml path ('''')),1,1,'''')
'
exec (#str)
go
exec dbo.usp_generate_column_name_string 'test' , 'dbo','jl1_tmp'

There are so many ways to do it, one easier way is to insert the proc result into a temp table and use it in join
create table #coltemp(colList varchar(max))
insert into #coltemp
exec dbo.usp_generate_column_name_string 'test' , 'dbo','jl1_tmp'
select * from #coltemp
check the following question to know about diff ways to insert proc results into temp table Insert results of a stored procedure into a temporary table

Here is the basic idea:
create function usp_generate_column_name_string (
#schema varchar(100),
#table varchar(100)
)
returns varchar(max) as
begin
return (select stuff( (select ',' + column_name
from information_schema.columns
where table_name = #table and table_schema = #schema
for xml path ('')
), 1, 1, ''
)
);
end;
Notes:
This doesn't handle special characters in the column names. I'm not sure how you want to escape those, but the logic is easily adjusted.
Database is left out. That is much harder in SQL Server, because the system tables are organized by database. If that is a requirement, you basically cannot do this (easily).

Related

Dynamic SQL Server Query loop through schema find primary key duplicate

EDIT: There seems to be some confusion around the create table statements. These are there solely as a demonstration of what tables *might come in to our synapse instance, not as actual code that will run. The important part of the question is contained in the latter half.
I am trying to create a stored procedure that loops through every table in a supplied schema and outputs the count of duplicate primary key rows for each table. Assume that the data is being supplied from elsewhere and the primary keys are not being enforced. For example I may have three tables in the stack schema:
CREATE TABLE stack.table1(
id int,
name NVARCHAR(MAX),
color NVARCHAR(20)
PRIMARY KEY (id))
INSERT INTO stack.table1 VALUES(1,'item1','yellow')
(2,'item2','blue')
(2,'item2','blue')
CREATE TABLE stack.table2(
id int,
name NVARCHAR(MAX),
size NVARCHAR(1)
PRIMARY KEY (id,size))
INSERT INTO stack.table2 VALUES(1,'item1','L')
(2,'item2','M')
(3,'item2','S')
CREATE TABLE stack.table3(
id int,
name NVARCHAR(MAX),
weight NVARCHAR(20)
PRIMARY KEY (id))
INSERT INTO stack.table1 VALUES(1,'item1','200lb')
(2,'item2','150lb')
(3,'item2','125lb')
I want to supply a variable to a stored procedure to indicate the schema (in this case 'stack') and have that procedure spit out a table with the names of the tables in the schema and the counts of duplicate primary key rows. So in this example a stored procedure called 'loopcheck' would look like this:
Query:
EXEC loopcheck #schema = 'stack'
Output:
table
duplicate_count
table1
1
table2
0
table3
0
I am using an Azure Synapse instance so there are several functions that are not available (such as FOR XML PATH and others.) Since each table may have a single column primary key or a composite primary key I need to join to the system provided tables to get primary key info. My general idea was like so:
CREATE procedure loopcheck #schema= NVARCHAR(MAX)
AS
BEGIN
create table #primarykey(
SCHEMA_NAME nvarchar(400),
TABLE_NAME nvarchar(500),
COLUMN_NAME nvarchar(500)
)
insert into #primarykey
select l.TABLE_SCHEMA,
l.TABLE_NAME,
l.COLUMN_NAME
from INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE l
inner join INFORMATION_SCHEMA.TABLE_CONSTRAINTS t on l.constraint_Name = t.CONSTRAINT_NAME
where
l.table_schema = #schema
CREATE TABLE #groupBy2(
TABLE_NAME nvarchar(50),
groupby nvarchar(200)
)
INSERT INTO #groupBy2
SELECT TABLE_NAME, STRING_AGG(CONVERT(NVARCHAR(max), COLUMN_NAME), ',') as groupby
FROM #primarykey
GROUP BY TABLE_NAME
DECLARE #currentTable NVARCHAR(MAX)=''
DECLARE #currentGroup NVARCHAR(MAX)=''
create table #work4(
TABLE_NAME nvarchar(400),
COUNT int)
DECLARE #final NVARCHAR(MAX)=
'INSERT INTO #work4
SELECT '+#currentTable+', COUNT(*) FROM '+#currentTable+'GROUP BY'+#currentGroup
WHILE (SELECT COUNT(*) FROM #groupby2)>0
BEGIN
SET #currentTable =(SELECT TOP 1 TABLE_NAME FROM #groupby2 ORDER BY TABLE_NAME)
SET #currentGroup =(SELECT TOP 1 groupby FROM #groupby2 ORDER BY TABLE_NAME)
exec #final
DELETE #groupby2 where TABLE_NAME =#currentTable
END
END
This code gives me an error:
Incorrect syntax near 'SELECT'
but doesn't give me the actual line it has the error on.
Your primary issue was syntax errors: parameter declaration should not have = between name and type name, and missing spaces in the dynamic SQL.
Also
A schema name (or any object) can be up to nvarchar(128), you can use the alias sysname
You don't need to do any loops or use temp tables, you can build one big dynamic statement to execute
CREATE procedure loopcheck
#schema sysname
AS
DECLARE #sql nvarchar(max) = (
SELECT STRING_AGG(CAST('
SELECT
TableName = ' + QUOTENAME(t.name, '''') + ',
IndexName = ' + QUOTENAME(i.name, '''') + ',
Duplicates = COUNT(*)
FROM (
SELECT 1 n
FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' t
GROUP BY ' + cols.agg + '
HAVING COUNT(*) > 1
) t
'
AS nvarchar(max)), 'UNION ALL')
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
AND s.name = #schema
JOIN sys.indexes i ON i.object_id = t.object_id
AND i.is_unique = 1
CROSS APPLY (
SELECT STRING_AGG('t.' + QUOTENAME(c.name), ', ')
FROM sys.index_columns ic
JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = t.object_id
WHERE ic.index_id = i.index_id
AND ic.object_id = i.object_id
AND ic.is_included_column = 0
) cols(agg)
);
PRINT #sql; -- for testing
EXEC sp_executesql #sql;
db<>fiddle
I feel like there might be a slightly more efficient method using GROUPING SETS in cases where there are multiple unique indexes/constraints on a single table, but I'll leave that to you.

Remove all tables from schema except specific ones in SQL?

I have a SQL database and I would like to remove almost all tables related to a specific schema except a couple of them. Therefore I think I would need to edit the following sql query (which removes all tables of a specific schema):
EXEC sp_MSforeachtable
#command1 = 'DROP TABLE ?'
, #whereand = 'AND SCHEMA_NAME(schema_id) = ''your_schema_name'' '
Would you be able to suggest a smart and elegant way so that I can add a list of tables that I would like to keep and remove everything else?
If you want to keep using sp_msforeachtable, pass in the set of tables names to keep using a temp table. Here's an example using the boo schema:
create schema boo;
create table boo.t(i int);
create table #keep (name sysname);
insert #keep values ('myFirsttable'), ('mySecondTable'), ('myThirdTable');
exec sp_msforeachtable
#command1='drop table ?; print ''dropped ?''',
#whereand = 'and schema_name(schema_id) = ''your_schema_name'' and object_name(object_id) not in (select name from #keep)';
But personally, I'd probably just write my own stored procedure with a cursor. It's harder to mess up.
Note that this solution expects you to put the tables you want to keep into the temp table. Charlieface's solution expects you to put the names of tables you want to drop into the table variable.
You could place a list of tables you want to delete stored in a table variable or Table-Valued Parameter #tables then you can simply execute dynamic SQL with it.
DECLARE #tables TABLE (tablename sysname);
INSERT #tables (tablename)
SELECT t.name
FROM sys.tables t
WHERE t.schema_id = SCHEMA_ID('your_schema_name');
DECLARE #sql nvarchar(max) =
(
SELECT STRING_AGG(CAST(
'DROP TABLE ' + QUOTENAME('your_schema_name') + '.' + QUOTENAME(tablename) + ';'
AS nvarchar(max)), '
' )
FROM #tables
);
EXEC sp_executesql #sql;
Alternatively, select it directly from sys.tables
DECLARE #sql nvarchar(max) =
(
SELECT STRING_AGG(CAST(
'DROP TABLE ' + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) + ';'
AS nvarchar(max)), '
' )
FROM sys.tables t
WHERE t.schema_id = SCHEMA_ID('your_schema_name')
);
EXEC sp_executesql #sql;

How can i extend the code to be able to show drop list of values from selected column in SSRS report

I'm new to sql and i'm trying to create SSRS.
I found this code in internet to create SSRS report and it works good to me. However i need to adjust this code to get the value as well from selected column
USE [project]
GO
/****** Object: StoredProcedure [dbo].[Report] Script Date: 26-1-2020 01:19:45 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[Report]
#SchemaName VARCHAR(128)='sys',
#TableName VARCHAR(128)='columns',
#ColumnList VARCHAR(MAX)='object_id,column_id,name,max_length,system_type_id'
AS
BEGIN
DECLARE #ColumnNames VARCHAR(MAX)
DECLARE #ColumnNamesVAR VARCHAR(MAX)
--drop ##Temp_Data Table
IF OBJECT_ID('tempdb..##Temp_Data') IS NOT NULL
DROP TABLE ##Temp_Data
--drop ##Temp_Data_Final Table
IF OBJECT_ID('tempdb..##Temp_Data_Final') IS NOT NULL
DROP TABLE ##Temp_Data_Final
--drop #Temp_Columns Table
IF OBJECT_ID('tempdb..#Temp_Columns') IS NOT NULL
DROP TABLE #Temp_Columns
Create table #ColumnList (Data NVARCHAR(MAX))
insert into #ColumnList values (#ColumnList)
--convert all column list to VARCHAR(1000) for unpivot
;with Cte_ColumnList as (
SELECT
'['+LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)')))+']' AS ColumnList
FROM
(
SELECT CAST('<XMLRoot><RowData>' + REPLACE(Data,',','</RowData><RowData>')
+ '</RowData></XMLRoot>' AS XML) AS x
FROM #ColumnList
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n))
,CTE_ColumnListVarchar as
(Select 'CAST('+ColumnList+' as VARCHAR(1000)) AS '+ColumnList AS ColumnListVAR,ColumnList from Cte_ColumnList)
SELECT #ColumnNamesVAR = COALESCE(#ColumnNamesVAR + ', ', '') + ColumnListVAR,
#ColumnNames = COALESCE(#ColumnNames + ', ', '') + ColumnList
FROM CTE_ColumnListVarchar
--Insert data into ##Temp_Data Table
DECLARE #SQL NVARCHAR(MAX)
DECLARE #TempTbleSQL NVARCHAR(MAX)
SET #TempTbleSQL='Select ROW_NUMBER()
OVER (order by (Select 1)) AS R,'+#ColumnNames +' into ##Temp_Data from ['+#SchemaName+'].['+#TableName+']'
--Print #TempTbleSQL
EXEC(#TempTbleSQL)
SET #SQL='
select
R,columnname,value into ##Temp_Data_Final from
(select R,'+#ColumnNamesVAR+' from ##Temp_Data )u
unpivot
(value for columnname in ('+#ColumnNames+'))v'
--Print #SQL
EXEC(#SQL)
Select * From ##Temp_Data_Final
END
SO, Now i can select Schema, Table & column. but i couldn't know how i get drop list for values in selected column.
And one more thing. how i can deploy this report to web form.Or if there any way to create dynamic sql with cascading parameters where i can select schema, table, column and values
PLEASE SOMEBODY HELP ME WITH THIS IT REALLY IMPORTANT
Here i can choose Schema, then table and the column. So i want to extend the code to be able to get another drop list with value of selected column
I used also the following datasets for each parameter
--ds_schema
SELECT NAME AS schemaname FROM sys.schemas
WHERE NAME not in (
'guest',
'information_schema',
'sys',
'db_owner',
'db_accessadmin',
'db_securityadmin',
'db_ddladmin',
'db_backupoperator',
'db_datareader',
'db_datawriter',
'db_denydatareader',
'db_denydatawriter')
----DSTables
Select Distinct Table_Name as TableName from INFORMATION_SCHEMA.TABLES
where TABLE_SCHEMA=#SchemaName
order by Table_Name
----DS_Columns
Select COLUMN_NAME as ColumnName from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA=#SchemaName
and TABLE_NAME=#TableName
To get a list of values in a column you need to build a SQL statement then execute it.
As you have your parameters you can do something like this...
SET #SQL = 'SELECT DISTINCT ' + QUOTENAME(#ColumnName) + ' FROM ' + QUOTENAME(#SchemaName) + '.' + QUOTENAME(#TableName) + ' ORDER BY ' + QUOTENAME(#ColumnName)
EXEC (#SQL)
notes
This gives a DISTINCT list of values and also sorts them using the ORDER BY clause, just edit the SET #SQL = line to adjust the query that is executed.
I've used QUOTENAME() to put square brackets around the schema, table and column names e.g. SELECT DISTINCT [myColumnName] FROM .....
You can add PRINT #SQL at the end to see the generated SQL if you like.

isnull for dynamically Generated column

I am getting temp table with dynamically generated columns let say it is columns A,B,C,D etc from other source.
Now in my hand I have temp table with column generated. I had to write stored procedure with the use of temp table.
So my stored procedure is like
create proc someproc()
as
begin
Insert into #searchtable
select isnull(#temp.*,0.00)
End
Now #searchresult is table created by me to store temp table columns. The problem arises when I want to check isnull for #tempdb columns. Because from source it comes it may be 3 columns, again next time it may be 4 columns. It changes.
Since it is dynamically generated I cannot use each column name and use like below:
isnull(column1,0.00)
isnull(column2,0.00)
I had to use all column generated and check if value is empty use 0.00
I tried this below but not working:
isnull(##temp.*,0.00),
Try with Dynamic code by fetching the column name for your dynamic table from [database].NFORMATION_SCHEMA.COLUMNS
--Get the Column Names for the your dynamic table and add the ISNULL Check:
DECLARE #COLS VARCHAR(MAX) = ''
SELECT #COLS = #COLS + ', ISNULL(' + COLUMN_NAME + ', 0.00) AS ' + COLUMN_NAME
FROM tempdb.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME LIKE '#temp[_]%' -- Dynamic Table (here, Temporary table)
DECLARE #COLNAMES VARCHAR(MAX) = STUFF(#COLS, 1, 1, '')
--Build your Insert Command:
DECLARE #cmd VARCHAR(MAX) = '
INSERT INTO #temp1
SELECT ' + #COLNAMES + ' FROM #temp'
--Execute:
EXEC (#cmd)
Hope, I understood your comment right:
CREATE PROCEDURE someproc
AS
IF OBJECT_ID(N'#searchtable') IS NOT NULL DROP TABLE #searchtable
IF OBJECT_ID(N'#temp') IS NOT NULL
BEGIN
DECLARE #sql nvarchar(max),
#cols nvarchar(max)
SELECT #cols = (
SELECT ',COALESCE('+QUOTENAME([name])+',0.00) as '+QUOTENAME([name])
FROM sys.columns
WHERE [object_id] = OBJECT_ID(N'#temp')
FOR XML PATH('')
)
SELECT #sql = N'SELECT '+STUFF(#cols,1,1,'')+' INTO #searchtable FROM #temp'
EXEC sp_executesql #sql
END
This SP checks if #temp table exists. If exists then it takes all column names from sys.columns table and we make a string like ,COALESCE([Column1],0.00) as [Column1], etc. Then we make a dynamic SQL query like:
SELECT COALESCE([Column1],0.00) as [Column1] INTO #searchtable FROM #temp
And execute it. This query result will be stored in #searchtable.
Notes: Use COALESCE instead of ISNULL, and sp_executesql instead of direct exec. It is a good practice.

How do I create a select statement to return distinct values, column name and table name?

I would like to create a SQL Statement that will return the distinct values of the Code fields in my database, along with the name of the column for the codes and the name of the table on which the column occurs.
I had something like this:
select c.name as 'Col Name', t.name as "Table Name'
from sys.columns c, sys tables t
where c.object_id = t.object_id
and c.name like 'CD_%'
It generates the list of columns and tables I want, but obviously doesn't return any of the values for each of the codes in the list.
There are over 100 tables in my database. I could use the above result set and write the query for each one like this:
Select distinct CD_RACE from PERSON
and it will return the values, but it won't return the column and table name, plus I have to do each one individually. Is there any way I can get the value, column name and table name for EACH code in my database?
Any ideas? THanks...
Just generate your selects and bring in the column and table names as static values. Here's an Oracle version:
select 'select distinct '''||c.column_name||''' as "Col Name", '''||t.table_name||''' as "Table Name", '||c.column_name||' from '||t.table_name||';'
from all_tab_columns c, all_tables t
where c.table_name = t.table_name;
This will give you a bunch of separate statements, you can modify the query a bit to put a union between each select if you really want one uber query you can execute to get all your code values at once.
Here's an approach for SQL Server since someone else covered Oracle (and specific DBMS not mentioned. The following steps are completed:
Setup table to receive the schema, table, column name, and column value (in example below only table variable is used)
Build the list of SQL commands to execute (accounting for various schemas and names with spaces and such)
Run each command dynamically inserting values into the setup table from #1 above
Output results from table
Here is the example:
-- Store the values and source of the values
DECLARE #Values TABLE (
SchemaName VARCHAR(500),
TableName VARCHAR(500),
ColumnName VARCHAR(500),
ColumnValue VARCHAR(MAX)
)
-- Build list of SQL Commands to run
DECLARE #Commands TABLE (
Id INT PRIMARY KEY NOT NULL IDENTITY(1,1),
SchemaName VARCHAR(500),
TableName VARCHAR(500),
ColumnName VARCHAR(500),
SqlCommand VARCHAR(1000)
)
INSERT #Commands
SELECT
[TABLE_SCHEMA],
[TABLE_NAME],
[COLUMN_NAME],
'SELECT DISTINCT '
+ '''' + [TABLE_SCHEMA] + ''', '
+ '''' + [TABLE_NAME] + ''', '
+ '''' + [COLUMN_NAME] + ''', '
+ '[' + [COLUMN_NAME] + '] '
+ 'FROM [' + [TABLE_SCHEMA] + '].[' + [TABLE_NAME] + ']'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME LIKE 'CD_%'
-- Loop through commands
DECLARE
#Sql VARCHAR(1000),
#Id INT,
#SchemaName VARCHAR(500),
#TableName VARCHAR(500),
#ColumnName VARCHAR(500)
WHILE EXISTS (SELECT * FROM #Commands) BEGIN
-- Get next set of records
SELECT TOP 1
#Id = Id,
#Sql = SqlCommand,
#SchemaName = SchemaName,
#TableName = TableName,
#ColumnName = ColumnName
FROM #Commands
-- Add values for that command
INSERT #Values
EXEC (#Sql)
-- Remove command record
DELETE #Commands WHERE Id = #Id
END
-- Return the values and sources
SELECT * FROM #Values