TSQL How to build a string dynamically? - sql-server

let's say that I have a table with columns like this:
| Name | Cat_id |
I cannot remember the function which can build a varchar like this
1,24|4,56|5,67
where the number before the comma is the Cat_id, and the number after comma is the count of rows with that ID. Any ideas ?

This could do the trick:
declare #s varchar(8000)
set #s = ''
select #s = #s + cast(cat_id as varchar(20)) + ',' + cast(count(*) as varchar(20)) + '|'
from SomeTable
group by cat_id
option(maxdop 1) -- this sure there are no funny side-effects because of parallelism
print #s
Alternatively you could use 'for xml', or a cursor, but this should be faster.
Regards GJ

Hope this will help(sql server 2005+)... I have not tested the program as I donot have SQL Server at present
With Cte1 As(
Select Cat_ID, Count(Cat_ID),Cast(Cat_ID as Varchar) + Cast(Count(Cat_ID)) as MergedColumn from tbl
group by Cat_ID),
cte2 as(
Select
Cat_ID
, mergedData = stuff((Select cast(MergedColumn + '|' as varchar) from tbl t2 where t2.Cat_ID = t1.Cat_ID
for xml path('')),1,1,'')
from tbl t1)
select mergedData from Cte2

Related

Get data from every column in every table in a database | SQL Server

I have seen multiple questions on how to retrieve every column from every table along with its data type, among many other pieces of information which can be summarised in the shortest way with this query:
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
However, is it possible to get all the data from the columns and the rows they belong to get the first row in the table alongside this? I have not found a way to do so thus far. Is it possible to do such, maybe also having a WHERE condition such as checking if the table contains a list of specific columns before returning it e.g.:
SELECT <AllTablesAndColumns+FirstRow>
FROM <WhereTheyCanBeSelectedFrom>
WHERE <TheTableHasTheseSpecificColumns>
Which would return the table name, column name and the data contained within those columns for each row.
If you are looking for more of an EAV structure
Let's say that we're looking for all tables with a column name of ZIPCODE
Example
Declare #S varchar(max) = ''
SELECT #S = #S +'+(Select top 1 SourceTable='''+A.Table_Name+''',* from '+quotename(A.Table_Name)+' for XML RAW)'
FROM INFORMATION_SCHEMA.COLUMNS A
Where COLUMN_NAME in ('ZipCode')
Declare #SQL varchar(max) = '
Declare #XML xml = '+stuff(#S,1,1,'')+'
Select SourceTable = r.value(''#SourceTable'',''varchar(100)'')
,Item = attr.value(''local-name(.)'',''varchar(100)'')
,Value = attr.value(''.'',''varchar(max)'')
From #XML.nodes(''/row'') as A(r)
Cross Apply A.r.nodes(''./#*'') AS B(attr)
Where attr.value(''local-name(.)'',''varchar(100)'') not in (''SourceTable'')
'
Exec(#SQL)
Returns
You could build dynamic query:
DECLARE #sql NVARCHAR(MAX) =
N'SELECT *
FROM (VALUES (1)) AS s(n)
<joins>';
DECLARE #joins NVARCHAR(MAX)= '';
SELECT #joins += FORMATMESSAGE('LEFT JOIN (SELECT TOP 1 * FROM %s ) AS sub%s
ON 1=1' + CHAR(10), table_schema + '.' + table_name,
CAST(ROW_NUMBER() OVER(ORDER BY 1/0) AS VARCHAR(10)))
FROM (SELECT DISTINCT table_schema, table_name
FROM INFORMATION_SCHEMA.COLUMNS
-- WHERE ... -- custom logic based on column type/name/...
) s;
SET #sql = REPLACE(#sql, '<joins>', #joins);
PRINT #sql;
EXEC(#sql);
DBFiddle Demo
The dynamic query has structure:
SELECT *
FROM (VALUES (1)) AS s(n) -- always 1 row
LEFT JOIN (SELECT TOP 1 * FROM dbo.tab1 ) AS sub1 ON 1=1 -- get single row
LEFT JOIN (SELECT TOP 1 * FROM dbo.tab2 ) AS sub2 ON 1=1
LEFT JOIN (SELECT TOP 1 * FROM dbo.tabC ) AS sub3 ON 1=1
Please treat it as starting point. You could easily extend it with WHERE condition for each subquery and return specific columns instead of *.
EDIT:
Version with UNION ALL:
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = COALESCE(#sql + ' UNION ALL', '') +
FORMATMESSAGE(' SELECT TOP 1 tab_name=''%s'',col_name=''%s'',col_val=%s FROM %s'+CHAR(10)
,table_name, column_name, column_name, table_schema + '.' + table_name)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE column_name LIKE 'colV%';
PRINT #sql;
EXEC(#sql);
DBFiddle Demo2

SQL Server 2016 T-SQL convert comma string to inner join database name(s)

I have a value for one of my columns that looks like this:
ID | userPerms | Name | DOB
-----+-----------+-----------+----------
5985 |1,3,4 |Bob Barker |12/12/1923
895 |1,2 |Bill Gates |10/14/1955
5897 |1,2,4 |Steve Jobs |02/24/1955
That column being the userPerms column.
I need to be able to Inner Join with the userPerm table associated with those numbers.
My query is currently:
SELECT
uT.employeeID + '|' + uT.lastFirstMiddle + '|' + uT.ntName + '|' + uT.email + '|' +
uT.firstName + '|' + uT.lastName + '|' + uT.active + '|' + uT.userPerms + '|' +
uT.userPermPages
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP ON uP.id = uT.userPerms
WHERE
uT.id = 1
Naturally it won't work since the data has commas in it.
So what I am looking for in the output:
ID | userPerms | Name | DOB
-----+------------------+-----------+------------
5985 |Read,Upload,Admin |Bob Barker |12/12/1923
895 |Read,Write |Bill Gates |10/14/1955
5897 |Read,Write,Admin |Steve Jobs |02/24/1955
Does anyone know how to split these out so that the inner join would then work as designed?
UPDATE 1
I got it working but:
It does not combine the userPerms into the original string
SELECT
uT.employeeID + '|' + uT.lastFirstMiddle + '|' + uT.ntName + '|' +
uT.email + '|' + uT.firstName + '|' + uT.lastName + '|' + uT.active,
(
SELECT
',' + uP.type
FROM
usersPermissions AS uP
WHERE
',' + uT.userPerms + ','
LIKE
'%,' + cast(uP.id AS nvarchar(20)) + ',%'
FOR
XML PATH(''), TYPE
).value('substring(text()[1], 2)', 'varchar(max)') AS userPerms,
(
SELECT
',' + uP.name
FROM
pagePermissions AS uP
WHERE
',' + uT.userPerms + ','
LIKE
'%,' + cast(uP.id AS nvarchar(20)) + ',%'
FOR
XML PATH(''), TYPE
).value('substring(text()[1], 2)', 'varchar(max)') AS userPermPages
FROM
usersTbl as uT
WHERE
uT.id = '1';
Not great DB Design to a list of IDs like that. It's better to use a relation table to store the many to many relationship. That said, this might work:
SELECT
uT.employeeID + '|' + uT.lastFirstMiddle + '|' + uT.ntName + '|' + uT.email + '|' +
uT.firstName + '|' + uT.lastName + '|' + uT.active + '|' + uT.userPerms + '|' +
uT.userPermPages
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
ON ','+uT.userPerms+',' like '%,'+uP.id+',%'
WHERE
uT.id = 1
The idea is to add commas to the beginning and end of uT.userPerms and use the like operator to join to any occurance of uP.id (also surrounded with commas) in uT.userPerms.
Well, you asked me... :-)
I do not have an installation of SQL-Server 2016 working at the moment, so this is untested air code. But I think you need something like this:
SET DATEFORMAT mdy;
DECLARE #tblData TABLE(ID INT, userPerms VARCHAR(100),Name VARCHAR(100),DOB DATE);
INSERT INTO #tblData VALUES
(5985,'1,3,4','Bob Barker','12/12/1923')
,(895,'1,2','Bill Gates','10/14/1955')
,(5897,'1,2,4','Steve Jobs','02/24/1955');
DECLARE #tblPerm TABLE(ID INT, Perm VARCHAR(100));
INSERT INTO #tblPerm VALUES
(1,'Read')
,(2,'Write')
,(3,'Upload')
,(4,'Admin');
SELECT d.ID,d.Name,d.DOB
,STRING_AGG(p.Perm,',') AS Perms
FROM #tblData AS d
CROSS APPLY STRING_SPLIT(d.userPerms,',') AS A
INNER JOIN #tblPerm AS p ON CAST(A.value AS INT)=p.ID --<-- added cast to int
GROUP BY d.ID,d.Name,d.DOB;
The function string_split() will separate the values at commas, which allows you to JOIN your catalog table. GROUP BY in combination with STRING_AGG() should return what you want. But I've never used this before...
Hope this helps!
UPDATE: A working solution without string-split and string-agg
SET DATEFORMAT mdy;
DECLARE #tblData TABLE(ID INT, userPerms VARCHAR(100),Name VARCHAR(100),DOB DATE);
INSERT INTO #tblData VALUES
(5985,'1,3,4','Bob Barker','12/12/1923')
,(895,'1,2','Bill Gates','10/14/1955')
,(5897,'1,2,4','Steve Jobs','02/24/1955');
DECLARE #tblPerm TABLE(ID INT, Perm VARCHAR(100));
INSERT INTO #tblPerm VALUES
(1,'Read')
,(2,'Write')
,(3,'Upload')
,(4,'Admin');
WITH Joined As
(
SELECT d.ID
,d.Name
,d.DOB
,p.Perm
FROM #tblData AS d
CROSS APPLY(SELECT CAST('<x>' + REPLACE(d.userPerms,',','</x><x>') + '</x>' AS XML)) AS A(casted)
CROSS APPLY A.casted.nodes(N'/x') AS B(numbers)
INNER JOIN #tblPerm AS p ON CAST(B.numbers.value(N'text()[1]',N'int') AS INT)=p.ID
)
SELECT j.ID
,j.Name
,j.DOB
,STUFF((
SELECT ',' + x.perm
FROM Joined AS x
WHERE x.ID=j.ID
FOR XML PATH('')
),1,1,'') AS Perm
FROM Joined AS j
GROUP BY ID,Name,DOB
create table #tblData (ID INT, userPerms VARCHAR(100),Name VARCHAR(100),DOB DATE);
INSERT INTO #tbldata VALUES
(5985,'1,3,4','Bob Barker','12/12/1923')
,(895,'1,2','Bill Gates','10/14/1955')
,(5897,'1,2,4','Steve Jobs','02/24/1955');
create table #tblPerm (ID INT, Perm VARCHAR(100));
INSERT INTO #tblPerm VALUES
(1,'Read')
,(2,'Write')
,(3,'Upload')
,(4,'Admin');
Create FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
go
;with cte
as
(
select t1.ID,t1.userPerms,t1.Name,t3.Perm ,t1.DOB from #tblData as t1
cross apply
dbo.fnsplitstring(t1.userPerms,',') as t2
join #tblPerm as t3
on t3.ID=t2.splitdata
)
select id,STUFF((select ','+perm from cte t1
where t1.ID=t2.id
for XML path('')),1,1,'') as userperms,Name,DOB
from cte t2
group by id ,name,DOB

Comparing multiple columns in SQL

Let's say I have 2 columns:
Column1: 'Silverado'
Column2: '1500 Vortec Max Crew Cab'
And I want to match up the record to a string that is 'Silverado 1500'
How would I go about this?
I'm running SQL Server 2014
Thank you
You could try something like this:
SELECT * FROM MyTable
WHERE Col1 + ' ' + Col2 LIKE '%Silverado 1500%'
Not exactly what you are asking for, but this is the kind of behavior I would expect for a search like this. It will give you matching results for multiple terms and how many terms were matched.
DECLARE #SearchTerms TABLE(
Term VARCHAR(200)
)
DECLARE #Table TABLE (
Col1 VARCHAR(200),
Col2 VARCHAR(200)
)
INSERT INTO #Table SELECT 'Silverado','1500 Vortec Max Crew Cab'
INSERT INTO #Table SELECT 'Honda','Accord'
DECLARE #xml as xml,#str as varchar(100),#delimiter as varchar(10)
SET #str='Silverado 1500 Crew'
SET #delimiter =' '
SET #xml = cast(('<X>'+replace(#str,#delimiter ,'</X><X>')+'</X>') as xml)
INSERT INTO #SearchTerms
SELECT N.value('.', 'varchar(10)') as value FROM #xml.nodes('X') as T(N)
SELECT t.Col1,t.Col2,count(*) [Matching Terms]
FROM #Table t
INNER JOIN #SearchTerms st on t.Col1 like '%' + st.Term + '%' OR t.Col2 like '%' + st.Term + '%'
GROUP BY t.Col1,t.Col2
ORDER BY count(*) DESC

Error in exec SQL Server query

I'm a beginner at SQL Server.
I write this query and pass to this table name and count number with #tblName and #count
DECLARE #Base nvarchar(200)
if (object_ID('tempdb..##temp')) is not NULL
DROP TABLE ##temp
SET #Base = 'WITH Base AS (SELECT [picName],[Address],ID, ROW_NUMBER() OVER (ORDER BY Id DESC) RN FROM'
+ Quotename(#tblName) + ' GROUP BY [picName],[Address],ID)
SELECT * INTO ##temp FROM Base'
EXEC (#Base)
SELECT *
FROM ##temp
declare #command nvarchar(max)
set #command='SELECT TOP 15 [picName],[Address],(SELECT TOP 1 COUNT(Id) FROM ' + QUOTENAME(#tblName) + ') as AllSampleCount FROM ' + QUOTENAME(#tblName) +
'WHERE [Id] IN (SELECT TOP 15 Id From ##temp WHERE RN >'+ ((QUOTENAME(#Count)-1)*15)+ 'ORDER BY Id DESC) ORDER BY Id DESC'
exec (#command)
drop table ##temp
but I get this error
Conversion failed when converting the nvarchar value 'SELECT TOP 15 [picName],[Address],(SELECT TOP 1 COUNT(Id) FROM [Brochure]) as AllSampleCount FROM [Brochure]WHERE [Id] IN (SELECT TOP 15 Id From ##temp WHERE RN >' to data type int.
COUNT IS NUMBER SO CONVERTION TO STR IS NEEDED
QUOTENAME(CONVERT(NVARCHAR(10),#Count-1)*15 ) + 'ORDER BY Id DESC) ORDER BY Id DESC'
replace this:
You need to Convert #Count to nvarchar(20)
set #command='SELECT TOP 15 [picName],[Address],(SELECT TOP 1 COUNT(Id) FROM ' + QUOTENAME(#tblName) + ') as AllSampleCount FROM ' + QUOTENAME(#tblName) +
'WHERE [Id] IN (SELECT TOP 15 Id From ##temp WHERE RN >'+
QUOTENAME(CAST((#Count-1)*15 as NVARCHAR(20))) + ' ORDER BY Id DESC) ORDER BY Id DESC'
In this fragment:
((QUOTENAME(#Count)-1)*15)
the argument of QUOTENAME is #Count but my guess is the actual argument was supposed be (#Count-1)*15. The problem is how the brackets are placed. The -1 (and later *15) is logically applied to the result of QUOTENAME rather than to #Count. That causes the entire concatenation expression to be treated as numeric and SQL Server, therefore, tries to convert all the arguments in it to numbers. It fails to convert the very first one, 'SELECT TOP 15 ...', and that is what the error is about.
If you rewrite the above QUOTENAME fragment like this:
QUOTENAME((#Count-1)*15)
the issue will be gone.
Note that QUOTENAME expects a string and your argument is numeric. In this situation you can either convert the numeric expression's result to a string explicitly (using CAST or CONVERT), as others have suggested, or leave it without explicit conversion, as above – it will be converted automatically.

Concatenating Column Values into a Comma-Separated List

What is the TSQL syntax to format my output so that the column values appear as a string, seperated by commas.
Example, my table CARS has the following:
CarID CarName
----------------
1 Porsche
2 Mercedes
3 Ferrari
How do I get the car names as : Porsche, Mercedes, Ferrari
SELECT LEFT(Car, LEN(Car) - 1)
FROM (
SELECT Car + ', '
FROM Cars
FOR XML PATH ('')
) c (Car)
You can do a shortcut using coalesce to concatenate a series of strings from a record in a table, for example.
declare #aa varchar (200)
set #aa = ''
select #aa =
case when #aa = ''
then CarName
else #aa + coalesce(',' + CarName, '')
end
from Cars
print #aa
If you are running on SQL Server 2017 or Azure SQL Database you do something like this :
SELECT STRING_AGG(CarName,',') as CarNames
FROM CARS
You can do this using stuff:
SELECT Stuff(
(
SELECT ', ' + CARS.CarName
FROM CARS
FOR XML PATH('')
), 1, 2, '') AS CarNames
DECLARE #CarList nvarchar(max);
SET #CarList = N'';
SELECT #CarList+=CarName+N','
FROM dbo.CARS;
SELECT LEFT(#CarList,LEN(#CarList)-1);
Thanks are due to whoever on SO showed me the use of accumulating data during a query.
Another solution within a query :
select
Id,
STUFF(
(select (', "' + od.ProductName + '"')
from OrderDetails od (nolock)
where od.Order_Id = o.Id
order by od.ProductName
FOR XML PATH('')), 1, 2, ''
) ProductNames
from Orders o (nolock)
where o.Customer_Id = 525188
order by o.Id desc
(EDIT: thanks #user007 for the STUFF declaration)
Please try this with the following code:
DECLARE #listStr VARCHAR(MAX)
SELECT #listStr = COALESCE(#listStr+',' , '') + CarName
FROM Cars
SELECT #listStr
DECLARE #SQL AS VARCHAR(8000)
SELECT #SQL = ISNULL(#SQL+',','') + ColumnName FROM TableName
SELECT #SQL

Resources