Joining a table based on comma separated values - sql-server

How can I join two tables, where one of the tables has multiple comma separated values in one column that reference an id in another column?
1st table
Name | Course Id
====================
Zishan | 1,2,3
Ellen | 2,3,4
2nd table
course id | course name
=======================
1 | java
2 | C++
3 | oracle
4 | dot net

Maybe this uglyness, I have not checked results:
select names.name, courses.course_name
from names inner join courses
on ',' + names.course_ids + ',' like '%,' + cast(courses.course_id as nvarchar(20)) + ',%'

First of all your Database structure is not normalized and should have been. Since it is already set up this way , here's how to solve the issue.
You'll need a function to split your string first:
CREATE FUNCTION SPLIT_STRING(str VARCHAR(255), delim VARCHAR(12), pos INT) RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(str, delim, pos),
LENGTH(SUBSTRING_INDEX(str, delim, pos-1)) + 1), delim, '');
Then you'll need to create a view in order to make up for your structure:
CREATE VIEW database.viewname AS
SELECT SPLIT_STRING(CourseID, ',', n) as firstField,
SPLIT_STRING(CourseID, ',', n) as secondField,
SPLIT_STRING(CourseID, ',',n) as thirdField
FROM 1stTable;
Where n is the nth item in your list.
Now that you have a view which generates your separated fields, you can make a normal join on your view, just use your view like you would use a table.
SELECT *
FROM yourView
JOIN table1.field ON table2.field
However since I don't think you'll always have 3 values in your second field from your first table you'll need to tweak it a little more.
Inspiration of my answer from:
SQL query to split column data into rows
and
Equivalent of explode() to work with strings in MySQL

SELECT f.name,s.course_name FROM table1 AS f
INNER JOIN table2 as s ON f.course_id IN (s.course_id)

Use the Below Query For Solution
Select * from table_2 t2 INNER JOIN table_1 t1 on t1.Course Id = t2.course id

Related

Lookup delimited values in a table in sql-server

In a table A i have a column (varchar*30) city-id with the value e.g. 1,2,3 or 2,4.
The description of the value is stored in another table B, e.g.
1 Amsterdam
2 The Hague
3 Maastricht
4 Rotterdam
How must i join table A with table B to get the descriptions in one or maybe more rows?
Assuming this is what you meant:
Table A:
id
-------
1
2
3
Table B:
id | Place
-----------
1 | Amsterdam
2 | The Hague
3 | Maastricht
4 | Rotterdam
Keep id column in both tables as auto increment, and PK.
Then just do a simple inner join.
select * from A inner join B on (A.id = B.id);
Ideal way to deal with such scenarios is to have a normalized table as Collin. In case that can't be done here is the way to go about -
You would need to use a table-valued function to split the comma-seperated value. If you are having SQL-Server 2016, there is a built-in SPLIT_STRING function, if not you would need to create one as shown in this link.
create table dbo.sCity(
CityId varchar(30)
);
create table dbo.sCityDescription(
CityId int
,CityDescription varchar(30)
);
insert into dbo.sCity values
('1,2,3')
,('2,4');
insert into dbo.sCityDescription values
(1,'Amsterdam')
,(2,'The Hague')
,(3,'Maastricht')
,(4,'Rotterdam');
select ctds.CityDescription
,sst.Value as 'CityId'
from dbo.sCity ct
cross apply dbo.SplitString(CityId,',') sst
join dbo.sCityDescription ctds
on sst.Value = ctds.CityId;

Split Single Column into multiple and Load it to a Table or a View

I'm using SQL Server 2008. I have a source table with a few columns (A, B) containing string data to split into a multiple columns. I do have function that does the split already written.
The data from the Source table (the source table format cannot be modified) is used in a View being created. But I need to have my View have already split data for Column A and B from the Source table. So, my view will have extra columns that are not in the Source table.
Then the View populated with the Source table is used to Merge with the Other Table.
There two questions here:
Can I split column A and B from the Source table when creating a View, but do not change the Source Table?
How to use my existing User Defined Function in the View "Select" statement to accomplish this task?
Idea in short:
String to split is also shown in the example in the commented out section. Pretty much have Destination table, vStandardizedData View, SP that uses the View data to Merge to tblStandardizedData table. So, in my Source column I have column A and B that I need to split before loading to tblStandardizedData table.
There are five objects that I'm working on:
Source File
Destination Table
vStandardizedData View
tblStandardizedData table
Stored procedure that does merge
(Update and Insert) form the vStandardizedData View.
Note: all the 5 objects a listed in the order they are supposed to be created and loaded.
Separately from this there is an existing UDFunction that can split the string which I was told to use
Example of the string in column A (column B has the same format data) to be split:
6667 Mission Street, 4567 7rd Street, 65 Sully Pond Park
Desired result:
User-defined function returns a table variable:
CREATE FUNCTION [Schema].[udfStringDelimeterfromTable]
(
#sInputList VARCHAR(MAX) -- List of delimited items
, #Delimiter CHAR(1) = ',' -- delimiter that separates items
)
RETURNS #List TABLE (Item VARCHAR(MAX)) WITH SCHEMABINDING
/*
* Returns a table of strings that have been split by a delimiter.
* Similar to the Visual Basic (or VBA) SPLIT function. The
* strings are trimmed before being returned. Null items are not
* returned so if there are multiple separators between items,
* only the non-null items are returned.
* Space is not a valid delimiter.
*
* Example:
SELECT * FROM [Schema].[udfStringDelimeterfromTable]('abcd,123, 456, efh,,hi', ',')
*
* Test:
DECLARE #Count INT, #Delim CHAR(10), #Input VARCHAR(128)
SELECT #Count = Count(*)
FROM [Schema].[udfStringDelimeterfromTable]('abcd,123, 456', ',')
PRINT 'TEST 1 3 lines:' + CASE WHEN #Count=3
THEN 'Worked' ELSE 'ERROR' END
SELECT #DELIM=CHAR(10)
, #INPUT = 'Line 1' + #delim + 'line 2' + #Delim
SELECT #Count = Count(*)
FROM [Schema].[udfStringDelimeterfromTable](#Input, #Delim)
PRINT 'TEST 2 LF :' + CASE WHEN #Count=2
THEN 'Worked' ELSE 'ERROR' END
What I'd ask you, is to read this: How to create a Minimal, Complete, and Verifiable example.
In general: If you use your UDF, you'll get table-wise data. It was best, if your UDF would return the item together with a running number. Otherwise you'll first need to use ROW_NUMBER() OVER(...) to create a part number in order to create your target column names via string concatenation. Then use PIVOT to get the columns side-by-side.
An easier approach could be a string split via XML like in this answer
A quick proof of concept to show the principles:
DECLARE #tbl TABLE(ID INT,YourValues VARCHAR(100));
INSERT INTO #tbl VALUES
(1,'6667 Mission Street, 4567 7rd Street, 65 Sully Pond Park')
,(2,'Other addr1, one more addr, and another one, and even one more');
WITH Casted AS
(
SELECT *
,CAST('<x>' + REPLACE(YourValues,',','</x><x>') + '</x>' AS XML) AS AsXml
FROM #tbl
)
SELECT *
,LTRIM(RTRIM(AsXml.value('/x[1]','nvarchar(max)'))) AS Address1
,LTRIM(RTRIM(AsXml.value('/x[2]','nvarchar(max)'))) AS Address2
,LTRIM(RTRIM(AsXml.value('/x[3]','nvarchar(max)'))) AS Address3
,LTRIM(RTRIM(AsXml.value('/x[4]','nvarchar(max)'))) AS Address4
,LTRIM(RTRIM(AsXml.value('/x[5]','nvarchar(max)'))) AS Address5
FROM Casted
If your values might include forbidden characters (especially <,> and &) you can find an approach to deal with this in the linked answer.
The result
+----+---------------------+-----------------+--------------------+-------------------+----------+
| ID | Address1 | Address2 | Address3 | Address4 | Address5 |
+----+---------------------+-----------------+--------------------+-------------------+----------+
| 1 | 6667 Mission Street | 4567 7rd Street | 65 Sully Pond Park | NULL | NULL |
+----+---------------------+-----------------+--------------------+-------------------+----------+
| 2 | Other addr1 | one more addr | and another one | and even one more | NULL |
+----+---------------------+-----------------+--------------------+-------------------+----------+

SQL: Where Clause Match Fullname with First & LastName

I have two tables, which I need to match on
"Fullname"
against
"FirstName" & "LastName"
and extract the userID from the "FirstName"/"LastName" table.
If there's a match retrieve the UserID if not Just Null
Example:
Table1 (With fullname)
|Sam Smith|
Must match with
Table2 (with first and last name)
| Sam | Smith |
And I would like to take into account if a person has three names.
(Fullname)
|Sam Samual Smith|
vs. (First & Last Name)
|Sam Samual | Smith |
Any help needed, not sure how to go around it,
As Lasse V. Karlsen suggested,
SELECT *
FROM [MainTable] M
INNER JOIN [SubTable] S
ON M.Fullname=S.Firstname + ' ' + S.Lastname; -- check if fullname is a of combination
-- firstname and lastname from other table
Replace the table names with your table names and put the fields you want in the select query as selecting all the fields could compromise the perfomance.
Try like this,
SELECT *
FROM Table1 T1
INNER JOIN Table2 T2 ON T1.FullName = T2.FirstName + ' ' + T2.LastName

combining groups in sql containing substrings

I apologize in advance for not explaining this very well.
I have a sql database with some data like this:
column1 | groups
3323052 | 3323052,3324794,3324795
3324794 | 3323052,3324794
3324794 | 3324794
3324794 | 3324794,3763369
3353586 | 3353586
3763369 | 3324794,3763369
I want to combine groups so that if a number is in two groups, the groups will combine and the number will only show up once in the list.
For example, the final result would look like this:
groups
3323052,3324794,3324795,3763369
3353586
I have been googling around without much luck. Any help is greatly appreciated.
Thanks.
So you want to recursively replace any items in groups -column with any values found from other rows with that value in column1? At least you can do it this way:
Split the data into rows, so there's just column1 -> group relation
Fetch any values that can be used as root nodes, my approach takes the smallest value because your data has a circle (3323052 -> 3324794 -> 3323052)
Fetch recursively all the value that can be found from the hierarchy under these root nodes
Put it back together into the original format
This example uses DelimitedSplit8k by Jeff Moden:
-- Step 1:
select distinct
d.column1,
convert(int, s.Item) as item
into #tmp
from
data d
cross apply DelimitedSplit8k(d.groups, ',') s
-- Step 2:
select distinct
column1
into #root
from #tmp t1
where not exists
(select 1 from #tmp t2 where t2.item = t1.column1 and t2.item > t2.column1)
-- Step 3:
;with CTE (root, parent, child) as (
select r.column1, r.column1, r.column1 from #root r
union all
select C.root, t.column1, t.item
from CTE C join #tmp t on t.column1 = C.child and t.item > C.parent
)
select distinct * into #results from CTE
-- Step 4:
SELECT r.column1, STUFF((SELECT distinct ', ' + convert(varchar(50), r2.child)
FROM #results r2
WHERE r2.root = r.column1
ORDER BY ', ' + convert(varchar(50), r2.child)
FOR XML PATH(N'')), 1, 2, '') as groups
FROM #root r
GROUP BY column1
ORDER BY column1
Result:
column1 groups
3323052 3323052, 3324794, 3324795, 3763369
3353586 3353586
I used temp. tables to be sure each of the steps is executed just once, but I believe it would be possible to do the whole thing with just one select and using CTEs instead of temp tables.
You can test this in SQL Fiddle

T-SQL trying to determine the largest string from a set of concatenated strings in a database

I have two tables. One has an Order number, and details about the order:
CREATE TABLE #Order ( OrderID int )
and the second contains comments about the order:
CREATE TABLE #OrderComments ( OrderID int
Comment VarChar(500) )
Order ID Comments
~~~~~~~~ ~~~~~~~~
1 Loved this item!
1 Could use some work
1 I've had better
2 Try the veal
I'm tasked with determining the maximum length of the output, then returning output like the following:
Order ID Comments Length
~~~~~~~~ ~~~~~~~~ ~~~~~~
1 Loved this item! | Could use some work | I've had better 56
2 Try the veal 12
So, in this example, if this is all of the data, I'm looking for "56").
The main purpose is to determine the maximum length of all comments when appended together, including the | delimiter. This will be used when constructing the table this output will be put into, to determine if we can get the data within the 8,060 size limit for a row or if we need to use varchar(max) or text to hold the data.
I have tried a couple of subqueries that can generate this output to variables, but I haven't found one yet that could generate the above output. If I could get that, then I could just do a SELECT TOP 1 ... ORDER BY 3 DESC to get the number I'm looking for.
To find out what the length of the longest string will be if you trim and concatenate all the (not null) comments belonging to an OrderId with a delimiter of length three you can use
SELECT TOP(1) SUM(LEN(Comment)) + 3* (COUNT(Comment) - 1) AS Length
FROM OrderComments
GROUP BY OrderId
ORDER BY Length DESC
To actually do the concatenation you can use XML PATH as demonstrated in many other answers on this site.
WITH O AS
(
SELECT DISTINCT OrderID
FROM #Order
)
SELECT O.OrderID,
LEFT(y.Comments, LEN(y.Comments) - 1) AS Comments
FROM O
CROSS APPLY (SELECT ltrim(rtrim(Comment)) + ' | '
FROM #OrderComments oc
WHERE oc.OrderID = O.OrderID
AND Comment IS NOT NULL
FOR XML PATH(''), TYPE) x (Comments)
CROSS APPLY (SELECT x.Comments.value('.', 'VARCHAR(MAX)')) y(Comments)
All you need is STUFF function and XML PATH
Check out this sql fiddle
http://www.sqlfiddle.com/#!3/65cc6/5

Resources