How to create a `Union` based on a comma-separated string list - sql-server-2008

Suppose I am building a stored procedure in my SQL Server for table my_table.
The purpose of this stored procedure is to take an input keyword parameter #keyword nvarchar(255) and a comma-separated string '#vehicleList nvarchar(266)'.
#catList basically contains the values of of what filter checkboxes were checked on the front end (the options are car, truck, motorcycle). So, vehicle list might look list this:
#vehicleList = "car, truck, motorcycle"
or this
"car, motorcycle"
or this
"truck"
These checkbox names are the same as column names in my_table. I want to return certain columns from my_table based my on #keyword parameter being found in one of the columns, but ONLY if that column is in #vehicleList.
If the #vehicleList was always 'Car, Truck, Motorcyle', I could do this:
SELECT ID, T_Number, Car, Truck, Motorcycle, Date
FROM T_Ticket
WHERE Car Like '%' + #Keyword + '%'
UNION ALL
SELECT ID, T_Number, Car, Truck, Motorcycle, Date
FROM T_Ticket
WHERE Truck Like '%' + #Keyword + '%'
UNION ALL
SELECT ID, T_Number, Car, Truck, Motorcycle, Date
FROM T_Ticket
WHERE Motorcycle Like '%' + #Keyword + '%'
My question is, how would I do this based on a variable, comma-separated string that will be different every time the stored procedure is called?

one way would be to UNPIVOT as below.
Unlike your UNION ALL this won't return duplicates in the event of multiple columns matching but I doubt you want that anyway (if you do use a JOIN instead of EXISTS).
SELECT *
FROM T_Ticket
WHERE EXISTS (SELECT *
FROM (VALUES ('Car', Car),
('Truck', Truck),
('Motorcycle', Motorcycle) ) V(colname, colvalue)
JOIN STRING_SPLIT(#vehicleList, ',') vl
ON ltrim(vl.value) = colname
WHERE colvalue LIKE '%' + #Keyword + '%')

Going down the dynamic SQL route, you could do:
DECLARE #vehicleList nvarchar(266);
DECLARE #keyword nvarchar(255);
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT ID, T_Number, Car, Truck, Motorcycle, Date' + NCHAR(10) + NCHAR(13) +
N'FROM T_Ticket' + NCHAR(10) + NCHAR(13) +
N'WHERE ' +
STUFF((SELECT NCHAR(10) + NCHAR(13) +
N' OR ' + QUOTENAME([value]) + N' LIKE N''%'' + #Keyword + N''%'''
FROM STRING_SPLIT(#vehicleList,',')
FOR XML PATH(N''),TYPE).value(N'.','nvarchar(MAX)'),1,8,N'') + N';';
--PRINT #SQL; --Your debugging best friend
EXEC sp_executesql #SQL, N'#keyword nvarchar(255)', #keyword = #keyword;

I think this is the simplest way to achieve what you need
SELECT ID, T_Number, Car, Truck, Motorcycle, Date
FROM T_Ticket
WHERE (#vehicleList LIKE '%car%' AND Car LIKE '%' + #Keyword + '%')
OR (#vehicleList LIKE '%truck%' AND Truck LIKE '%' + #Keyword + '%')
OR (#vehicleList LIKE '%motorcycle%' AND Motorcycle LIKE '%' + #Keyword + '%')

Related

T-SQL query to return JSON array of strings

I'm hoping to build an optimized data JSON structure that only includes data, no names. I'll included the names in another JSON.
For example
[["1", "William"],["2", "Dylan"]]
I'm looking at "for json auto", running a query like this.
declare #t table(id int, name varchar(20))
insert into #t (id, name) values( 1, 'William')
insert into #t (id, name) values( 2, 'Dylan')
declare #result as varchar(max)
select id, name from #t for json auto
However it includes the names with every value.
[{"id":1,"name":"William"},{"id":2,"name":"Dylan"}]
Is there a way to instruct SQL Server to omit the names and just return a string array?
I'll need to update a couple hundred queries, so I'm hoping for an answer that doesn't require too much modification on a basic query.
Unfortunately, SQL Server does not support the JSON_AGG function or similar. You can hack it with STRING_AGG and STRING_ESCAPE.
You can either do this with a single aggregation and concatenating the row together
SELECT '[' + STRING_AGG(CONCAT(
'["',
id,
'","',
STRING_ESCAPE(name, 'json'),
'"]'
), ',') + ']'
FROM #t t;
Or with a nested aggregation, aggregating first each row in an unpivoted subquery, then all rows together
SELECT '[' + STRING_AGG('[' + j.json + ']', ',') + ']'
FROM #t t
CROSS APPLY (
SELECT STRING_AGG('"' + STRING_ESCAPE(value, 'json') + '"', ',')
FROM (VALUES
(CAST(id AS nvarchar(max))),
(name)
) v(value)
) j(json);
db<>fiddle
I've assumed columns are not nullable. Nullable columns will need special handling, I leave it as an exercise to the reader.
Not all that different from Charlie's but uses CONCAT_WS to remove some of the explicit " characters:
SELECT [json] = '['
+ STRING_AGG('["' + CONCAT_WS('", "', id,
STRING_ESCAPE(COALESCE(name,''), N'JSON'))
+ '"]', ',') + ']'
FROM #t;
Output (after adding a 3rd row, values(3, NULL):
json
[["1", "William"],["2", "Dylan"],["3", ""]]
Example db<>fiddle
If you want the literal string null with no quotes:
SELECT [json] = '['
+ STRING_AGG('['
+ CONCAT_WS(', ', CONCAT('"', id, '"'),
COALESCE('"' + STRING_ESCAPE(name, N'JSON') + '"', 'null'))
+ ']', ',') + ']'
FROM #t;
Output:
json
[["1", "William"],["2", "Dylan"],["3", null]]
Example db<>fiddle
If you don't want the NULL value to present a column in the JSON, just remove the COALESCE:
SELECT [json] = '['
+ STRING_AGG('["' + CONCAT_WS('", "', id,
STRING_ESCAPE(name, N'JSON'))
+ '"]', ',') + ']'
FROM #t;
Output:
json
[["1", "William"],["2", "Dylan"],["3"]]
Example db<>fiddle
If you don't want that row present in the JSON at all, just filter it out:
FROM #t WHERE name IS NOT NULL;
If that column doesn't allow NULLs, state it explicitly so we don't have to guess (probably doesn't hurt to confirm id is unique, either):
declare #t table(id int UNIQUE, name varchar(20) NOT NULL);

How to round numbers within dynamic pivot?

So i have this:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(period)
FROM (SELECT DISTINCT period FROM atbv_Accounting_Transactions WHERE lAccountNO LIKE '6%' AND Period LIKE '2017%') AS Periods
SET #DynamicPivotQuery =
N'SELECT lAccountNo, ' + #ColumnName + '
FROM (SELECT
lAccountNo, period, SUM(Amount) As Total
FROM atbv_Accounting_Transactions
WHERE lAccountNO LIKE ''6%'' AND Period LIKE ''2017%''
GROUP BY lAccountNo, period
) AS T
PIVOT(SUM(TOTAL)
FOR period IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
It returns me this:
How do i remove decimal places within select itself. I cannot edit column in the table and reduce the decimal places.So i need to edit this query to return values without decimals.
Thanks!
Should just change SUM(Amount) to cast(SUM(Amount) as int) or perhaps floor(SUM(Amount)) and it will do the trick.

SQL find text in string

I have a text field in my database:
DECLARE #vchText varchar(max) =
This is a string<>Test1<>Test2<>Test
That #vchText parameter should return like this:
This is a string:
1. Test1
2. Test2
3. Test
Anyone think of a good way to correct this. I was thinking the STUFF and CHARINDEX Functions with a WHILE LOOP...?
Something I should also note would be that there might not be only 1,2,3 items in the list there could be lots more so I can't build it so its static and only handles 1,2,3 it should be able to work for any number of items in the list.
Try this. Break the string into parts.
First part - This is a list:
Second part - 1.Test1 1.Test2 1.Test3
Convert the second part into rows using the delimiter Space. Then add row_number to the rows. Append the row_number and column data.
Finally convert the different rows into single row delimited by space and append it with the first part
DECLARE #NOTE VARCHAR(max) = 'This is a list: 1.Test1 1.Test2 1.Test3',
#temp VARCHAR(max),
#output VARCHAR(max)
SELECT #temp = Substring(#NOTE, Charindex(':', #NOTE) + 2, Len(#note))
SELECT #output = LEFT(#NOTE, Charindex(':', #NOTE) + 1)
SELECT #output += CONVERT(VARCHAR(10), Row_number() OVER (ORDER BY col))
+ Substring(col, Charindex('.', col), Len(col))
+ ' '
FROM (SELECT Split.a.value('.', 'VARCHAR(100)') col
FROM (SELECT Cast ('<M>' + Replace(#temp, ' ', '</M><M>') + '</M>' AS XML) AS Data) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)) ou
SELECT #output -- This is a list: 1.Test1 2.Test2 3.Test3
I was able to do it with a loop and use the stuff and charindex below.
DECLARE #vchText varchar(max) =
This is a string<>Test1<>Test2<>Test
DECLARE #positionofNextX INT = CHARINDEX('<>', #vchText)
DECLARE #nbrOFListItems INT = 1
WHILE #positionofNextX != 0
BEGIN
SET #NOTE = STUFF( #vchText, #positionofNextX, 4, CAST(#nbrOFListItems AS VARCHAR(1)) + '. ')
SET #positionofNextX = CHARINDEX('<>', #vchText)
--increment the list item number
SET #nbrOFListItems = #nbrOFListItems + 1
END
print #vchText

Space separated string to create dynamic Sql query

I have a space separated string as parameter to my SP. I need to split the strings and compare each one against a column in the database along with wildcard and return the results.
For example:
I have CompanyName 'ABC DataServices Pvt Ltd' I need to split the string by 'space' and compare each word with database field company name with an OR condition.
Something like this:
select *
from CompanyTable
where companyname like '%ABC%'
or companyname like '%DataServices%'
or companyname like '%Pvt%'
or companyname like '%Ltd%'
Can some one help me out to achieve this?
Thanks in advance
Try this SQL User Defined Function to Parse a Delimited String helpful .
For Quick Solution use this ,
SELECT PARSENAME(REPLACE('ABC DataServices Pvt Ltd', ' ', '.'), 2) // return DataServices
PARSENAME takes a string and splits it on the period character. It takes a number as it's second argument, and that number specifies which segment of the string to return (working from back to front).
you can put the index you want in place of 2 in above like
SELECT PARSENAME(REPLACE('ABC DataServices Pvt Ltd', ' ', '.'), 3) --return Pvt
declare a string in your store procedure and use set this value to it. use where you want.
The only problem is when the string already contains a period. One thing should be noted that PARSENAME only expects four parts, so using a string with more than four parts causes it to return NULL
Try this function:
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
And use it like this:
select * from dbo.fnSplitString('Querying SQL Server','')
This will scale to any number of search terms and does not require dynamic SQL.
declare #a table (w varchar(50)) -- holds original string
declare #b table (s varchar(50)) -- holds parsed string
insert into #a -- load string into temp table
values ('ABC DataServices Pvt Ltd')
--parse string as XML
;WITH Vals AS (
SELECT w,
CAST('<d>' + REPLACE(w, ' ', '</d><d>') + '</d>' AS XML) XmlColumn
FROM #a
)
--Insert results to parsed string
insert into #b
SELECT
C.value('.','varchar(max)') ColumnValue
FROM Vals
CROSS APPLY Vals.XmlColumn.nodes('/d') AS T(C)
--Join on results
select * from companytable
join #b b on b.s like '%'+companyname+'%'
You can also try this.
First in your parameter's value replace space with comma (,) and then replace comma with desired where condition like this -
DECLARE #companyname VARCHAR(1000)
,#where VARCHAR(max)
SET #companyname = 'ABC DataServices Pvt Ltd'
SET #companyname = replace(#companyname, ' ', ', ')
SELECT #where = 'companyname like ''%' +
REPLACE(#companyname, ', ', '%'' OR companyname like ''%') + '%'''
PRINT #where
PRINT ('SELECT * FROM CompanyTable WHERE' + #where)
--EXEC ('SELECT * FROM CompanyTable WHERE' + #where)

Replace views with tables

I have numerous tables I that I want to have created and populated dynamically based on views.
I want to perform something like a combination of these two posts:
Create Table from View
Is there a way to loop through a table variable in TSQL without using a cursor?
Select *
Into dbo.##tblTemp
From databasename.sys.views
Declare #TableName NVARCHAR(128)
While (Select COUNT(*) From #Temp) > 0
Begin
Select Top 1 #TableName = name from databasename.sys.views
Select * into #TableName from databasename.sys.views
Delete databasename.sys.views Where name = #TableName
End
Am I better off with a stored procedure that dynamically creates the sql statement to create the table?
EDIT:
Per Sebastian, I am running the below code to accomplish this:
DECLARE #cmd NVARCHAR(MAX) = ( SELECT TOP 10 'exec sp_rename '
+ '#objname =''' + OBJECT_SCHEMA_NAME(object_id)
+ '.'
+ OBJECT_NAME(object_id) + ''
+ ''', #newname = '
+ '''v_' + name + ''
+ ''';'
+ 'SELECT * INTO '
+ OBJECT_SCHEMA_NAME(object_id)
+ '.'
+ OBJECT_NAME(object_id)
+ ' FROM '
+ OBJECT_SCHEMA_NAME(object_id)
+ '.v_'
+ OBJECT_NAME(object_id)
+ ';'
+ 'DROP VIEW '
+ OBJECT_SCHEMA_NAME(object_id)
+ '.v_'
+ OBJECT_NAME(object_id)
+ ';'
FROM db.sys.views
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)');
EXEC (#cmd)
--Select #cmd
To copy all the data seems to be the wrong approach to solve your problem. If your problem is poor performance do to too many reads caused by views there are two things to look out for first.
1) Check that you tables have appropriate indexes. You could even add indexes to your views. There are many resources out there that tell you how to go about index tuning. Or you could hire a consultant (like me) to help you out with that.
2) If your queries join views, it often happens that unnecessary tables make it into the mix. For example if view v1 joins table a and b and view v2 joins table b and c and your query then joins v1 with v2, it effectively joins a with b with b with c. Such a query often can be rewritten to join to b only once helping tremendously with performance. So if you have queries joining views with views you should review those.
If after all that you still want to go forward with copying the data, you can use this:
DECLARE #cmd NVARCHAR(MAX) = ( SELECT 'SELECT * INTO '
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.'
+ QUOTENAME('tbl_'+OBJECT_NAME(object_id))
+ ' FROM '
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.' + QUOTENAME(OBJECT_NAME(object_id))
+ ';'
FROM sys.views
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)');
EXEC (#cmd);
It creates a command that uses SELECT INTO to create a table for each view in the database. Because SQL Server does not allow name collisions even for objects of differing type, I prefixed the names of the tables with "tbl_".
If you need to create the tables in a different database, you need to prefix the table names with "dbname.". In that case you can remove the "tbl_"prefix.
EDIT:
You had a few missing quotes in your version. Try this:
DECLARE #cmd NVARCHAR(MAX) = ( SELECT TOP 1 'exec sp_rename '''
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.'
+ QUOTENAME(OBJECT_NAME(object_id))
+ ''', '''
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.'
+ QUOTENAME('v_' +OBJECT_NAME(object_id))
+ ''';'
+ 'SELECT * INTO '
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.'
+ QUOTENAME(OBJECT_NAME(object_id))
+ ' FROM '
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.'
+ QUOTENAME('v_' +OBJECT_NAME(object_id))
+ ';'
+ 'DROP VIEW '
+ QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
+ '.'
+ QUOTENAME('v_' +OBJECT_NAME(object_id))
+ ';'
FROM sys.views
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)');
You also can use PRINT #cmd instead of EXEC(#cmd) to see if the command put together makes sense.