How to get values from table using multiple ID's in single column in SQL? - sql

I am working in an existing stored procedure. In that, they fetch some data using select Queries. Now I am trying to fetch some more data. The problems I have is, the column I am using to fetch data from another table have more than one Id saved in with comma(,) in between.
Procedure with 2 tables as Tbl_av,Tbl_aa.
I have one column in Tbl_av as processId that is in Varchar and it stores data like 1,2,4 etc.. (that is, it stores the Id of the Process).
Now I am trying to get the Process Name from one more table Tbl_Process using this ProcessId under the table Tbl_av. In that table, the process Id is Unique one and it is in INTEGER.
Tbl_Process as follows:
Tbl_av as follows:
I have procedure few part as follows:
SET #strQuery= 'SELECT isnull(av.ID, 0) AS ID
,isnull(ItemNumber, '''') AS Number
,isnull(av.ItemName,'''') as Item Name
,av.Description
,(select top 1 Name from TBL_Names where Id =aa.id)as Person Incharge from Tbl_AV av, Tbl_aa aa WHERE av.S_number = aa.S_number'
Now what I am trying to do is , I need to fetch that Process names from Tbl_Process using this Tbl_av ProcessId's inside the procedure.
I dont know how to achieve it, as it both columns are in different datatype and more than 1 id is saved in one column
NOTE: We can simply achieve this as 'SELECT Process_Name from Tbl_Process WHERE Id in (av.ProcessId)' - But doing this ,displays data in column format..
I want data to be selected as if Id is 1,2 means I want my output as Process_Name = Item 1,Item 2.
Kindly help.

Two tricks are needed. One is joining based on a comma-separated list of IDs. That can easily be done poorly resulting in unwanted matches such as 1 and 2 matching 12. The article Stephen Jennings referenced has some good reliable solutions.
The second is concatenating a collection of results into a single string. For recent versions of SQL Server, the STRING_AGG is the preferred solution. For older versions (such as 2014) the most common method is the "FOR XML" trick.
I've combined the two techniques below.
DECLARE #Tbl_Process TABLE(ID INT, process_Name VARCHAR(100))
INSERT #Tbl_Process
VALUES (1, 'Item 1'), (2, 'Item 2'), (3, 'Item 3'), (4, 'Item 4'), (5, 'Item 5'), (12, 'Item 12')
DECLARE #Tbl_av TABLE(ID INT, ProcessId VARCHAR(100))
INSERT #Tbl_av
VALUES (1, '1,3,5'), (2, '2,4'), (3, '1,2,3'), (4, '1,5'), (5, ''), (6, '3,4,12')
SELECT AV.*, PL.*
FROM #Tbl_av AV
CROSS APPLY (
SELECT ISNULL(STUFF(
(
SELECT ',' + P.process_Name
FROM #Tbl_Process P
--(bad) WHERE CHARINDEX(CONVERT(VARCHAR, P.ID), AV.ProcessId) > 0
WHERE ',' + AV.ProcessId + ',' LIKE '%,' + CONVERT(VARCHAR, P.ID) + ',%'
ORDER BY P.ID -- alternately ORDER BY P.process_Name
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)')
, 1, 1, '')
, '(none)')
AS Process_Names
) PL
Results
ID
ProcessId
Process_Names
1
1,3,5
Item 1,Item 3,Item 5
2
2,4
Item 2,Item 4
3
1,2,3
Item 1,Item 2,Item 3
4
1,5
Item 1,Item 5
5
(none)
6
3,4,12
Item 3,Item 4,Item 12
The FOR XML PATH('') trick causes the results to concatenate into a single XML string with no tags. The , TYPE together with .value('text()[1]','nvarchar(max)') safely extracts the resulting text, undoing any XML specific text encodings such as &, <, or >. The STUFF() removes the leading comma, and the ISNULL() provides a default if there are no values.
See How Stuff and 'For Xml Path' work in SQL Server? for more on how the FOR XML trick works.
If you prefer comma-space list separators, you can update the ',', but will also need to adjust the STUFF to strip two characters instead of one.
The contents of the cross apply could be moved directly into the SELECT clause, but as a matter of style, the CROSS APPLY allows separation of complex logic from the rest of the query.

Related

How to fetch only a part of string

I have a column which has inconsistent data. The column named ID and it can have values such as
0897546321
ABC,0876455321
ABC,XYZ,0873647773
ABC,
99756
test only
The SQL query should fetch only Ids which are of 10 digit in length, should begin with a 08 , should be not null and should not contain all characters. And for those values, which have both digits and characters such as ABC,XYZ,0873647773, it should only fetch the 0873647773 . In these kind of values, nothing is fixed, in place of ABC, XYZ , it can be anything and can be of any length.
The column Id is of varchar type.
My try: I tried the following query
select id
from table
where id is not null
and id not like '%[^0-9]%'
and id like '[08]%[0-9]'
and len(id)=10
I am still not sure how should I deal with values like ABC,XYZ,0873647773
P.S - I have no control over the database. I can't change its values.
SQL Server generally has poor support regular expressions, but in this case a judicious use of PATINDEX is viable:
SELECT SUBSTRING(id, PATINDEX('%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%', ',' + id + ','), 10) AS number
FROM yourTable
WHERE ',' + id + ',' LIKE '%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%';
Demo
If you normalise your data, and split the delimited data into parts, you can achieve this some what more easily:
SELECT SS.value
FROM dbo.YourTable YT
CROSS APPLY STRING_SPLIT(YT.YourColumn,',') SS
WHERE LEN(SS.value) = 10
AND SS.value NOT LIKE '%[^0-9]%';
If you're on an older version of SQL Server, you'll have to use an alternative String Splitter method (such as a XML splitter or user defined inline table-value function); there are plenty of examples on these already on Stack Overflow.
db<>fiddle

When concatenating using COALESCE, number more than 9 displays as asterisk *

I want to concatenate values from multiple rows into one. I am using COALESCE for this purpose. One of the columns I have is an ID column. When concatenating ID column, values up to 9 are displayed correctly but after nine, asterisk is displayed. Anyone knows why this is? See my code below using COALESCE to concatenate all rows in one:
CREATE TABLE #test
(id int, name varchar(50))
insert into #test
values(1, 'ana'),
(2, 'bob'),
(3, 'steph'),
(4, 'bill'),
(5, 'john'),
(6, 'jose'),
(7, 'kerry'),
(8, 'frank'),
(9, 'noah'),
(10, 'melissa')
--SELECT * FROM #test
DECLARE #NameAndID VARCHAR(1000)
SELECT #NameAndID = COALESCE(#NameAndID +'; ', '') + CAST(ID AS VARCHAR(1))+'. ' + name
FROM #test
SELECT #NameAndID
You are casting the number to varchar(1) - and any number that have more than a single digit will overflow the one char and therefor will be turned into an asterisks (*).
When casting ints, I find it best to use varchar(11), since this covers the maximum amount of chars that might be needed to display an int.
The int minimum value is -2,147,483,648 - removing the thousands separators it's 10 digits and a minus sign:
-2147483648
123456789 1 (10 is missing in the chars count to make it more clear)
By the way, there are better ways of doing string aggregation in T-Sql.
For versions prior to 2017, use a combination of stuff and for xml path, like this:
SELECT STUFF(
(
SELECT '; ' + CAST(id as varchar(11)) + '. ' + name
FROM #test
FOR XML PATH('')
),1 ,2, '')
For version 2017 or higher, use the built in string_agg function, like this:
SELECT STRING_AGG(CAST(id as varchar(11)) + '. '+ name, '; ')
FROM #Test
for more information, check out this SO post.
The * is an indicator that the result length (was) too short to display. In your example you're trying to fit a two digit number into VARCHAR(1). In this particular case the result is the * instead of throwing an error.
The behavior is described in the docs.

PIVOT row values into rows and columns side by side dynamically and concatenate 2 values separated by comma in a cell

I need to display my row values into rows and columns side by side dynamically and concatenate 2 values separated by comma in a cell. See example below. Any helps is appreciated.
Table1
Toys CostPrice SellingPrice
---- --------- -------------
A 10.55 12.60
B 7.60 8.90
C 8.90 10.50
D 11.50 13.40
E 17.50 20.30
F 2.57 3.50
I need to display in this format. Thanks.
Toys A B C D E F
---- ----- ------- ---- --- ---- ---
A 10.55,12.60
B 7.60,8.90
C 8.90,10.50
D 11.50,13.40
E 17.50,20.30
F 2.57,3.50
Alright, once more then! First, you'll notice I made a Temp Table instead of a table variable for the example, the #tablecolumns variable required that. Doesn't really apply to you but it was a change I made to be aware of if you copy/paste the code to play with it.
#tablecolumns is the dynamic piece you need, it creates a value that ends up looking like this "[A], [B], [C]...." for the pivot table.
Now you'll need to run the query as Dynamic SQL to toss in your #tablecolumns variable correctly. (Depending on how intensive your actual query gets you may decide to put the Query itself in a view and select/concatenate what you need from the view instead, I find Dynamic SQL a bit odd so sometimes that's easier)
create table #toystab (Toys nvarchar(1), Cost decimal (5,2), Sell decimal (5,2))
insert into #toystab values ('A', 5.50, 6.60)
insert into #toystab values ('B', 6.50, 7.60)
insert into #toystab values ('C', 7.50, 8.60)
insert into #toystab values ('D', 8.50, 9.60)
insert into #toystab values ('E', 11.50, 12.60)
insert into #toystab values ('F', 13.50, 14.60)
DECLARE #tablecolumns nvarchar(max)
select #tablecolumns = (COALESCE( #tablecolumns +',', '')
+ '[' + convert(nvarchar(10), #Toystab.Toys) +']')
from #toystab
declare #Query nvarchar(max)
set #Query = '
select *
from (
select Toys Toy1, Toys ToyName,
CONCAT(cast(Cost as varchar(10)), '','', cast(Sell as varchar(10))) as [ConcatenatedPrice]
from #toystab) Toyz
pivot
(
MAX(ConcatenatedPrice)
for Toy1 in (' + #tablecolumns+ ')
)
as [Pivot]
'
exec(#Query)
drop table #toystab
Notice a couple things. First its that I'm selecting the Toys column twice, once to pivot and once to be a column still. Second, in the CONCAT statement the separator '','' is not a quote (") it is two apostrophes (''). If you make it a quote "," then you'll get an Invalid column error. Last as a reminder, make sure Exec (#query) includes the parens.
Thanks you Bwoods. Your format is what I wanted but I need to make the Toy names dynamically in columns across and rows top down because I do not know how many Toy names I have each time. It can be from A to F the first month and next month it can be A to Z. Also, the toy names changes it might not always be in Alphabet. I used A to F just for example the real names are different. Let me know if there is a way to do it dynamically. Thanks.

Concatenate values based on ID

I Have a table called Results and the data looks like:
Response_ID Label
12147 It was not clear
12458 Did not Undersstand
12458 Was not resolved
12458 Did not communicate
12586 Spoke too fast
12587 Too slow
Now I want the ouput to display one row per ID and the values from Label to be concatenated and seperated by comma
My Output should look like:
Response_ID Label
12147 It was not clear
12458 Did not Undersstand,Was not resolved,Did not communicate
12586 Spoke too fast
12587 Too Slow
How can I do this:
You can not be sure about the order of the strings concatenated without an order by statement in the sub query. The .value('.', 'varchar(max)') part is there to handle the case where Label contains XML-unfriendly characters like &.
declare #T table(Response_ID int, Label varchar(50))
insert into #T values
(12147, 'It was not clear'),
(12458, 'Did not Undersstand'),
(12458, 'Was not resolved'),
(12458, 'Did not communicate'),
(12586, 'Spoke too fast'),
(12587, 'Too slow')
select T1.Response_ID,
stuff((select ','+T2.Label
from #T as T2
where T1.Response_ID = T2.Response_ID
for xml path(''), type).value('.', 'varchar(max)'), 1, 1, '') as Label
from #T as T1
group by T1.Response_ID
Check the link below, it approaches your problem with many different solutions
http://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/
Given this sample data:
CREATE TABLE #Results(Response_ID int, Label varchar(80));
INSERT #Results(Response_ID, Label) VALUES
(12147, 'It was not clear'),
(12458, 'Did not Undersstand'),
(12458, 'Was not resolved'),
(12458, 'Did not communicate'),
(12586, 'Spoke too fast'),
(12587, 'Too slow');
On older versions you can use FOR XML PATH for (grouped) string aggregation:
SELECT r.Response_ID, Label = STUFF((SELECT ',' + Label
FROM #Results WHERE Response_ID = r.Response_ID
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'varchar(max)'), 1, 1, '')
FROM #Results AS r
GROUP BY r.Response_ID;
If you are on SQL Server 2017 or greater, the query is much simpler:
SELECT r.Response_ID, Label = STRING_AGG(Label, ',')
FROM #Results AS r
GROUP BY r.Response_ID;
Consider this, it is very performant:
http://jerrytech.blogspot.com/2010/04/tsql-concatenate-strings-1-2-3-and.html
Avoid XML functions because they are not performant.
This will take some effort to implement, but millions of rows => milliseconds to run.

Return string of description given string of IDs (separated by commas)

I have one table A that has a column C and a lookup table (lookup) that provides a description given an ID.
Here the setup:
table A with column C values:
1,2,3
2,3,4
table lookup:
1, 'This'
2, 'is'
3, 'tricky'
4, 'SQL'
Provide a SQL (SQL Server 2005) statement that returns the following strings:
Input: 1,2,3 Output: 'This','Is','tricky'
Input: 2,3,4 Output: 'Is','tricky','SQL'
basically turning the string of IDs (from an input table A) into a string of descriptions
The Samples that come with SQL Server 2005 include a CLR function called Split(). It's the best way of splitting comma-separated lists like this by far.
Suppose you have a table called inputs, with a column called input.
I forget what the particular outputs of dbo.Split() are... so work with me here. Let's call the fields id and val, where id tells us which entry it is in the list.
WITH
separated AS (
SELECT i.input, s.id, s.val
FROM
dbo.inputs AS i
CROSS APPLY
dbo.Split(i.input) AS s
)
, converted AS (
SELECT s.input, s.id, m.string
FROM
separated AS s
JOIN
dbo.mapping AS m
ON m.number = CAST(s.val AS varchar(5))
)
SELECT c.input, (SELECT string + ' ' FROM converted AS c2 WHERE c2.input = c.input ORDER BY id FOR XML PATH('')) AS converted_string
FROM converted AS c
GROUP BY c.input;