I have a stored procedures that accepts a comma separated list, then makes copies of that list with quoted strings and brackets, then uses those in a dynamic sql statement to build a pivot table with flexible number of columns.
My problem is that sometimes my users submit a list with duplicates, and that makes the pivot query fail. So I want to somehow select distinct from my comma separated strings.
Here's how I manipulate the initial string:
Declare #data varchar(max) = '150593, 150593, 150603, 150578, 150604'
Declare #bracketed varchar(max) = ''
Declare #quoted varchar(max) = ''
select #bracketed = #bracketed + quotename(rtrim(ltrim(Value))) + ', ',
#quoted = #quoted + quotename(rtrim(ltrim(Value)), '''') + ', '
from [dbo].[fnSplitStringAsTable](#data, ',')
Select #bracketed = LEFT(#bracketed, len(#bracketed) - 1),
#quoted = LEFT(#quoted, len(#quoted) - 1)
I'm thinking I should be able to add DISTINCT somewhere in this query,
but I can't make it work. How can I select distinct from comma separated lists?
as an alternative solution you can dedupe in xml and convert back to varchar
Declare #data varchar(max) = '150593, 150593, 150603, 150578, 150604'
set #data= (select '''' + cast(cast('<d>'+replace(#data, ', ',',</d><d>')+'</d>' as xml).query('distinct-values(/d)') as varchar) +'''')
select #data
I guess we can add in distinct after you make the table, like this:
select #bracketed = #bracketed + quotename(rtrim(ltrim(Value))) + ', ',
#quoted = #quoted + quotename(rtrim(ltrim(Value)), '''') + ', '
from (
SELECT DISTINCT Value FROM [dbo].[fnSplitStringAsTable](#data, ',')
) T
If this fails try this:
select #bracketed = #bracketed + quotename(Value) + ', ',
#quoted = #quoted + quotename(Value), '''') + ', '
from (
SELECT DISTINCT RTRIM(LTRIM(Value)) AS Value FROM [dbo].[fnSplitStringAsTable](#data, ',')
) T
With a little dynamic sql, you can select distinct values from your string variable into a table variable, and then put those values back into the original variable:
declare #data varchar(max) = '150593, 150593, 150603, 150578, 150604'
declare #table table(data varchar(10))
set #data = 'select distinct value from (values (''' +
replace(#data,', ','''),(''') + ''')) as v(value)'
insert into #table
exec(#data)
set #data = ''
select #data = #data + data +
case row_number() over(order by data desc)
when 1 then ''
else ','
end
from #table
order by data asc
select #data
Related
I am trying to do my best to avoid using cursors. There is a comma separated list of columns in nvarchar variable that looks like this:
#columnList = 'col1,col2,col5'
There is a table with lots of varchar columns:
myTable: [col1],[col2],[col3],[col4],[col5],[col6],[col7]
This is how I select data using a dynamic sql:
exec ('select ' + #columnList + ' from myTable')
This query returns the following results:
[col1], [col2] , [col5]
null , "txt1" , null
"txt2", null , null
null , "txt3" , "txt4"
This is what I need to get:
#resultList = "txt1,txt2,txt3,txt4"
How do I select a comma separated string containing not-null values only? I know how to convert a table to comma separated string, so getting something like:
[column]
"txt1"
"txt2"
"txt3"
"txt4"
Is also fine. Any suggestions? Feel free to suggest a basic approach, I don't expect you to write the actual code for me.
You can use a solution like the following, using just a REPLACE to create the SQL query:
DECLARE #columnList VARCHAR(100)
SET #columnList = 'col1,col2,col5'
SET #columnList = REPLACE(#columnList, ',', ' AS colName FROM myTable UNION ALL SELECT ');
EXEC('SELECT * FROM (SELECT ' + #columnList + ' AS colName FROM myTable)t WHERE NOT t.colName IS NULL');
You can also use a solution using UNPIVOT:
DECLARE #columnList VARCHAR(100);
SET #columnList = 'col1,col2,col5';
EXEC('SELECT colName FROM (SELECT ' + #columnList + ' FROM myTable) t1 UNPIVOT (colName FOR columnNames IN (' + #columnList + ')) AS t2');
demo on dbfiddle.uk
Since you mention you already know how to aggregate into comma-seperated, here's how to unpivot your table with cross apply:
select unpivoted.*
from myTable
cross apply
( values
('col1',col2)
,('col2',col2)
,('col3',col3) -- etc
)unpivoted(colname,colval)
This would work for SQL Server 2017, on earlier version STRING_AGG usage should be replaced with XML-based solution.
You could first concatenate your results into a single line and then use STRING_AGG to aggregate them into a single string:
;with t as
(
SELECT * FROM (VALUES
(null , 'txt1' , null),
('txt2', null , null),
(null , 'txt3' , 'txt4')) x(col1, col2, col5)
)
SELECT STRING_AGG(STUFF(CONCAT(',' + col1, ',' + col2, ',' + col5), 1, 1, ''), ',')
FROM t
CTE is just for showcasing, you can simply do it in your dynamic query:
DECLARE #columnList NVARCHAR(MAX) = 'col1,col2,col5'
DECLARE #query NVARCHAR(MAX) = ''
SELECT #query = 'SELECT STRING_AGG(STUFF(CONCAT('','' + ' + REPLACE(#columnList, ',', ', '','' + ') + '), 1, 1, ''''), '','') from mytable'
EXEC sp_executesql #query
Working example on dbfiddle
I am using SQL Server 2012. i have a table with 90 columns. I am trying to select only columns that contains data. After searching i used the following procedure:
1- Getting all columns count using one select query
2- Pivoting Result Table into a Temp table
3- Creating Select query
4- Executing this query
Here is the query i used:
DECLARE #strTablename varchar(100) = 'dbo.MyTable'
DECLARE #strQuery varchar(max) = ''
DECLARE #strSecondQuery varchar(max) = 'SELECT '
DECLARE #strUnPivot as varchar(max) = ' UNPIVOT ([Count] for [Column] IN ('
CREATE TABLE ##tblTemp([Column] varchar(50), [Count] Int)
SELECT #strQuery = ISNULL(#strQuery,'') + 'Count([' + name + ']) as [' + name + '] ,' from sys.columns where object_id = object_id(#strTablename) and is_nullable = 1
SELECT #strUnPivot = ISNULL(#strUnPivot,'') + '[' + name + '] ,' from sys.columns where object_id = object_id(#strTablename) and is_nullable = 1
SET #strQuery = 'SELECT [Column],[Count] FROM ( SELECT ' + SUBSTRING(#strQuery,1,LEN(#strQuery) - 1) + ' FROM ' + #strTablename + ') AS p ' + SUBSTRING(#strUnPivot,1,LEN(#strUnPivot) - 1) + ')) AS unpvt '
INSERT INTO ##tblTemp EXEC (#strQuery)
SELECT #strSecondQuery = #strSecondQuery + '[' + [Column] + '],' from ##tblTemp WHERE [Count] > 0
DROP TABLE ##tblTemp
SET #strSecondQuery = SUBSTRING(#strSecondQuery,1,LEN(#strSecondQuery) - 1) + ' FROM ' + #strTablename
EXEC (#strSecondQuery)
The problem is that this query is TOO SLOW. Is there a best way to achieve this?
Notes:
Table have only one clustered index on primary key Column ID and does not contains any other indexes.
Table is not editable.
Table contains very large data.
Query is taking about 1 minute to be executed
Thanks in advance.
I do not know if this is faster, but you might use one trick: FOR XML AUTO will ommit columns without content:
DECLARE #tbl TABLE(col1 INT,col2 INT,col3 INT);
INSERT INTO #tbl VALUES (1,2,NULL),(1,NULL,NULL),(NULL,NULL,NULL);
SELECT *
FROM #tbl AS tbl
FOR XML AUTO
This is the result: col3 is missing...
<tbl col1="1" col2="2" />
<tbl col1="1" />
<tbl />
Knowing this, you could find the list of columns, which are not NULL in all rows, like this:
DECLARE #ColList VARCHAR(MAX)=
STUFF
(
(
SELECT DISTINCT ',' + Attr.value('local-name(.)','nvarchar(max)')
FROM
(
SELECT
(
SELECT *
FROM #tbl AS tbl
FOR XML AUTO,TYPE
) AS TheXML
) AS t
CROSS APPLY t.TheXML.nodes('/tbl/#*') AS A(Attr)
FOR XML PATH('')
),1,1,''
);
SELECT #ColList
The content of #ColList is now col1,col2. This string you can place in a dynamically created SELECT.
UPDATE: Hints
It would be very clever, to replace the SELECT * with a column list created from INFORMATION_SCHEMA.COLUMNS excluding all not-nullable. And - if needed and possible - types, wich contain very large data (BLOBs).
UPDATE2: Performance
Don't know what your very large data means actually... Just tried this on a table with about 500.000 rows (with SELECT *) and it returned correctly after less than one minute. Hope, this is fast enough...
Try using this condition:
where #columnname IS NOT NULL AND #columnname <> ' '
I have the following Sql Server 2016 SELECT statement that returns only 1 row:
SELECT TOP 1 * FROM tempdb.dbo.IMTD
How can I concatenate the values as a comma delimited string? NOTE: the column names of this temporary table are unknown as they can variate.
Thank you.
Something like this perhaps:
-- Sample data
DECLARE #someTable TABLE (SomeID int identity, SomeTxt varchar(100));
INSERT #someTable VALUES ('row1'),('row2'),('row3');
-- Solution
SELECT ConcatinatedString =
STUFF
((
SELECT ','+SomeTxt
FROM #someTable
FOR XML PATH(''), TYPE
).value('.','varchar(100)'),1,1,'');
You can use Dynamic query as below:
DECLARE #COLS VARCHAR(MAX) = ''
SELECT #COLS = #COLS + ',' + COLUMN_NAME
FROM tempdb.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME LIKE '#table[_]%' -- Dynamic Table (here, Temporary table)
DECLARE #COLNAMES VARCHAR(MAX) = REPLACE(STUFF(#COLS, 1, 1, ''), ',', '+ '','' +')
Declare #cmd varchar(max) = 'Select ' + #COLNAMES + ' as CSVCol from #table'
-- will generate
-- Select Column1+ ',' +Column2+ ',' +Column3 as CSVCol from #table
EXEC (#cmd)
Another solution you can try is this.
SELECT LTRIM(RTRIM(<ColumnName1>)) + ',',
LTRIM(RTRIM(<ColumnName2>)) + ',',
...
LTRIM(RTRIM(<ColumnNamen>)) + ','
FROM tempdb.dbo.IMTD
If you only want one row keep that top 1 In there like
SELECT TOP 1
LTRIM(RTRIM(<ColumnName1>)) + ',',
LTRIM(RTRIM(<ColumnName2>)) + ',',
...
LTRIM(RTRIM(<ColumnNamen>)) + ','
FROM tempdb.dbo.IMTD
The LTRIM and RTRIM will remove any white space and this should allow you to copy and paste the result set anywhere you may need it. You will need to do this for each columnname.
You can use the query below to get the column names from your temp table.
DECLARE #ColumnNames NVARCHAR(MAX)
SELECT
#ColumnNames= COALESCE(#ColumnNames +',','')+COLUMN_NAME
FROM
TempDB.INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = '#TempTableName'
I want to fetch all the records on the basis of Search string
E.G.
Column name: FileName
MasterRoomTwo.jpg
BedRoom.png
MasterbedRoom.gif
and in simple scenario I can use
Declare #FileName nvarchar(60) = NULL
set #FileName = '.jpg'
SELECT *
FROM JobAttachment
WHERE AND Tags LIKE '%' + ISNULL(#FileName ,FileName ) + '%'
ORDER BY updated DESC
but in my case I will get like
set #FileName = '.jpg,.Png,gif'
So how to make query like this?
Any help will be appreciated.
Thanks
Try this. Split the input string and use charindex
SELECT 'MasterRoomTwo.jpg' a INTO #temp UNION
SELECT 'BedRoom.png' UNION
SELECT 'MasterbedRoom.gif'
DECLARE #FileName NVARCHAR(60)
SET #FileName = '.jpg,.Png,gif'
SELECT *
FROM #temp
JOIN (SELECT Rtrim(Ltrim(Split.a.value('.', 'VARCHAR(100)'))) fs
FROM (SELECT Cast ('<M>' + Replace(#FileName, ',', '</M><M>')
+ '</M>' AS XML) AS Data) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)) ad
on Charindex(fs, a) > 0
Try this :
SELECT *
FROM JobAttachment a
JOIN (SELECT t1.nod.value('.', 'varchar(50)') tags
FROM (SELECT Cast('<N>.'
+ Replace(Replace(#FileName, '.', ''), ',', '</N><N>.')
+ '</N>' AS XML) AS format) t
CROSS APPLY format.nodes('/N') AS t1(nod)
WHERE t1.nod.value('.', 'varchar(50)') <> '.') fileformat
ON a.tag LIKE ( '%' + fileformat.tags + '%' )
You can create dynamic condition as
Declare #FileName nvarchar(60) = NULL
set #FileName = '.jpg,.Png,gif'
--append a comma to the string to get correct results with empty strings
--or strings with a single value (no commas)
SET #FileName = #FileName + ',';
declare #x XML
declare #FileSearch nvarchar(max)
select #x = cast( '<F>' + replace ( #FileName,',','</F><F>') + '</F>' as xml)
select #FileSearch = stuff( isnull(#FileSearch , '') +
' OR FileName Like ''%'+ isnull(t.value('.','nvarchar(60)'),'')
+'%''' ,1,3,'')
from #x.nodes('/F') as x(t)
And then create dynamic query to get desired results:
set #sql = 'select * from test where ' -- entire query goes here
+ #FileSearch
exec sp_executesql #sql
DEMO
I am inserting select statement result to Temporary table. I want its one column value comma separated, so I tried following but it is not working.
SELECT #IdList = COALESCE(#IdList+',' ,'') + s.Id
FROM (SELECT Id FROM #_TempStudentTable) as s
I have checked data in #_TempStudentTable, it is having data. But when i try to display IdList, it returns NULL.
Help me. I don't know what's the problem.
Try this :
DECLARE #IdList VARCHAR(MAX)
SELECT #IdList = COALESCE(#IdList + ',','') + CAST(Id AS VARCHAR(100))
FROM #_TempStudentTable
SELECT #IdList
You can get it without variable also using XML like below
SELECT STUFF(( SELECT ',' + CAST(Id AS VARCHAR(10))
FROM #_TempStudentTable
FOR XML PATH (''))
, 1, 1, '')
In SQL Server, you may use the below query to get a list of comma separated column names/headers:
SELECT STUFF((
SELECT ',' + CAST(name AS VARCHAR(50))
FROM (
SELECT name
FROM tempdb.sys.columns
WHERE [object_id] = OBJECT_ID(N'tempdb..#temptablename')
) k
FOR XML PATH('')
), 1, 1, '')