SQL Server 2008 - Replace Text Values in Column with Values from Another Table - sql

I've tried flexing my Google-fu to no avail so here I am! Unfortunately I cannot change anything about these tables as they are coming out of an application that I have to report out of.
In SQL Server 2008, I'm trying to replace multiple values in one text string column (Table 1) with the value from another table (Table 2).
Thanks in advance!!
Table 1
id value
-------------
1 a1, a2, a3
2 a2, a3
3 a4
Table 2
id value
---------
a1 Value1
a2 Value2
a3 Value3
a4 Value4
Desired Output
id value
-----------------------------
1 Value1, Value2, Value3
2 Value2, Value3
3 Value4

I'm sorry for this solution in advance :) It does what you need though:
create table TableA(
id int,
string varchar(255)
)
create table table2(
id varchar , text varchar(255)
)
insert into tableA values(1,'a,b,c,d')
insert into tableA values(2,'e,f')
insert into table2 values('a', 'value1')
insert into table2 values('b', 'value2')
insert into table2 values('c', 'value3')
insert into table2 values('d', 'value4')
insert into table2 values('e', 'value5')
insert into table2 values('f', 'value6')
select id, left(myConcat,len(myConcat)-1) from (
select c.id, replace(replace(CAST(CAST('<i'+stuff((select * from(
SELECT A.[id] ,
Split.a.value('.', 'VARCHAR(1000)') AS String
FROM (SELECT [id],
CAST ('<M>' + REPLACE([string], ',', '</M><M>') + '</M>' AS XML) AS String
FROM TableA) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) a
inner join table2 b on a.String = b.id
where a.id = c.id
FOR XML PATH ('')
),1,2,'') AS XML).query('/text') AS VARCHAR(1000)),'<text>',''),'</text>',',') myConcat
from TableA c
group by c.id
) d

Using the DelimitedSplit8K found at http://www.sqlservercentral.com/articles/Tally+Table/72993/ as suggested by #user1221684 you might come up with something like this. Working with delimited data like this is a pain. First you have to parse the string so you can join it to the other table and then ruin by stuffing it back into a denormalized form.
Make sure that if you use this that you understand that function and what this code is doing. This is not entry level t-sql and it will be YOU supporting this at 3am when it breaks in production, not me.
if OBJECT_ID('tempdb..#table1') is not null
drop table #table1;
create table #table1
(
id int,
value varchar(50)
);
insert #table1
select 1, 'a1, a2, a3' union all
select 2, 'a2, a3' union all
select 3, 'a4';
if OBJECT_ID('tempdb..#table2') is not null
drop table #table2;
create table #table2
(
id varchar(50),
value varchar(50)
);
insert #table2
select 'a1', 'Value1' union all
select 'a2', 'Value2' union all
select 'a3', 'Value3' union all
select 'a4', 'Value4';
with parsedValues as
(
select t1.id
, t1.value
, LTRIM(x.item) as item
from #table1 t1
cross apply dbo.DelimitedSplit8K(t1.value, ',') x
)
, swappedVals as
(
select pv.id
, t2.value
from parsedValues pv
join #table2 t2 on t2.id = pv.item
)
select id
, STUFF((select ',' + value
from swappedVals sv2
where sv2.id = sv.id
order by sv2.value --need to make sure to order here so the results are in the right order
for XML path('')), 1, 1, '') as MyValues
from swappedVals sv
group by id
;

This site has a delimited text split function http://www.sqlservercentral.com/articles/Tally+Table/72993/
Use that function to split your values out into a temp table. Replace the values in your temp table with the new values. Then use STUFF..FOR XML to combine the records back together and update your table.
One query with a few cte's should be able to handle all of this after you add the function to your database.
Example using Sql Fiddle

Use this:
DECLARE #t TABLE(id int,value varchar(255))
INSERT INTO #t (id,value)
VALUES(1,'a1'),(2,'a2'),(3,'a3')....
SELECT *,STUFF((SELECT DISTINCT ','+value FROM #t WHERE id=t.id)
FOR XML PATH('')),1,2,' ')
FROM (SELECT DISTINCT ID FROM #t) t
DISTINCT in the case of same id, otherwise let it go

Related

Function to multiple tables

I have this function, but I wanted to pass a table so as to use the same function to get the job done for multiple tables. For example, I want this function work for table1, and table2. But it is just for table1 currently. I was trying to use a dynamic sql in vain; it doesn't pass the parameter selected.
Can someone help? Give me guide on how to pass table as a parameter.
Sample data, table1
CREATE TABLE table1 (id int identity (1,1), name varchar(60))
INSERT INTO table1
VALUES ('a1, a2, a9, a8')
Sample data, table2
CREATE TABLE table2 (id int identity (1,1), name varchar(60))
INSERT INTO table2
VALUES ('a1, a2, a9, a8')
The function:
CREATE FUNCTION f_split
(#id INT)
RETURNS #ab
TABLE (name VARCHAR(20),
ab1 VARCHAR(5)
)
AS
BEGIN
DECLARE #temp TABLE (rn INT, name VARCHAR(5))
INSERT INTO #temp(rn, name)
SELECT ROW_NUMBER() OVER(ORDER BY LTRIM(RTRIM(Split.a.value('.', 'NVARCHAR(MAX)'))) ASC) rn, LTRIM(RTRIM(Split.a.value('.', 'NVARCHAR(MAX)'))) Result
FROM
(
SELECT CAST('<X>'+REPLACE([name], ',', '</X><X>')+'</X>' AS XML) AS String
FROM table1 where id = #id
) AS A
CROSS APPLY String.nodes('/X') AS Split(a)
ORDER BY 1
INSERT INTO #ab
SELECT * FROM #temp
RETURN
END
This gives the result from table1.
SELECT * FROM F_SPLIT(1)
But I want the same function to work for table2 as well.
Any help is appreciated.
Use a partitioned view, which will allow you to specify the table name as a parameter in the where clause.
Start by creating a view that unions the two tables, plus an additional column to indicate which table the row comes from.
CREATE VIEW BothTables AS
SELECT 'Table1' TableName, * FROM Table1
UNION ALL
SELECT 'Table2' TableName, * FROM Table2
Then modify your function. When you pass the table name, use it to select a subset of rows from the view. So instead of
SELECT CAST('<X>'+REPLACE([name], ',', '</X><X>')+'</X>' AS XML) AS String
FROM table1
WHERE id = #id
Use
SELECT CAST('<X>'+REPLACE([name], ',', '</X><X>')+'</X>' AS XML) AS String
FROM BothTables
WHERE TableName = #TableName
AND id = #id

Join by concatenated field (in CSV format)

UPDATE: I am fully aware that this is a poor RDBMS practice, but the question is not asking whether it is and how I can re-train the DBAs who created this architecture. The question is how I can work around the situation that I have on hands. I appreciate the help of the community and must admit that this is an interesting problem indeed.
In SQL Server 2017, I have a lookup table containing codes and a transactions table with CSV-formated codes:
CREATE TABLE #t(cd VARCHAR(100))
CREATE TABLE #cd (id INT, cd VARCHAR (1000))
INSERT INTO #t SELECT 'c1'
INSERT INTO #t SELECT 'c1,c2'
INSERT INTO #t SELECT 'c1,c2,c3'
INSERT INTO #cd SELECT 10, 'c1'
INSERT INTO #cd SELECT 20, 'c2'
INSERT INTO #cd SELECT 30, 'c3'
So, the lookup is
id cd
10 c1
10 c1
20 c2
30 c3
and, the transactions table has:
cd
c1
c1,c2
c1,c2,c3
I need to replace the codes to their respective IDs, while keeping these in CSV format.
I would like to avoid the cursor because it is too slow. Is there a way to parse the codes, do the JOIN, and recombine the IDs somehow efficiently? I suppose COALESCE may be of use, but need help applying it. Perhaps, there is already a function in t-SQL that does this types of lookups.
The output needs to another column in transactions table:
id
10
10,20
10,20,30
You can first strip out comma into a list and then join and get correct ids for codes and then add them back with commas. I used a row_number upfront to get a unique thing to join back on in my query.
See live demo
CREATE TABLE #t(cd VARCHAR(100))
CREATE TABLE #cd (id INT, cd VARCHAR (1000))
INSERT INTO #t SELECT 'c1'
INSERT INTO #t SELECT 'c1,c2'
INSERT INTO #t SELECT 'c1,c2,c3'
INSERT INTO #cd SELECT 10, 'c1'
INSERT INTO #cd SELECT 20, 'c2'
INSERT INTO #cd SELECT 30, 'c3'
; WITH X AS
(
SELECT
C.id,P1.rn
FROM
(
SELECT *, row_number() over( order by (select 1)) rn,
cast('<X>'+replace(P.cd,',','</X><X>')+'</X>' as XML) AS xmlitems FROM #t P
)P1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(100)') AS splitdata
FROM P1.xmlitems.nodes('X') AS fdata(D)) O
LEFT JOIN #cd C
ON C.cd= LTRIM(RTRIM(O.splitdata ))
)
SELECT
rn,
id= STUFF((
SELECT ',' + cast(id as varchar(100)) FROM X AS x2
WHERE x2.rn = x.rn
ORDER BY rn FOR XML PATH,
TYPE).value(N'.[1]',N'varchar(max)'), 1, 1, '')
FROM
X
GROUP BY rn
Note: With SQL server 2017 you can also you SPLIT_STRING() function
and STRING_AGG() functions
SQL SERVER 2017 code:
select
id=STRING_AGG(id,',')
from
(
select V=value, rn
from
(
select
rn=row_number() over( order by (select 1)),
cd
from #T
)T
cross apply STRING_SPLIT(cd, ',')
) T
left join #cd C
on cd= v
group by rn

SQL - string combine based on id

Need suggestion to split string in table 1, match its Ids with table 2 and concatenate the values.
Table - 1
Id Tbl1Col
1 2
2 2,4
3
4 6
5 3
Table - 2
Id Tbl2Col
1 E
2 F
3 M
4 U
5 P
6 C
7 N
8 G
Query -
SELECT T2.Tbl2Col
FROM Table1 AS T1
LEFT JOIN Table2 AS T2 WHERE T1.Tbl1Col= T2.Id
WHERE T1.Id = #Id
Now If #Id = 1, Output is F -- works fine
Now If #Id = 2, Output should be FU -- should not be F,U
Yuck! But you can use LIKE:
SELECT T2.Tbl2Col
FROM Table1 T1 LEFT JOIN
Table2 T2
WHERE ',' + T1.Tbl1Col + ',' LIKE '%,' + CAST(T2.Id as VARCHAR(255)) + ',%'
WHERE T1.Id = #Id;
You have a lousy data format, so this cannot make use of indexes. You should really have a separate table, with one row per Table1.id and Table2.id. Such a table is called a junction table or an association table.
create table dbo.Table01 (
Id int
, Col varchar(100)
);
create table dbo.Table02 (
Id int
, Col varchar(100)
);
insert into dbo.Table01 (Id, Col)
values (1, '2'), (2, '2, 4');
insert into dbo.Table02 (Id, Col)
values (1, 'E'), (2, 'F'), (4, 'U');
select
t.Id
, replace(STRING_AGG (t02.Col, ','), ',', '') as StringAgg
from dbo.Table01 t
cross apply string_split (t.Col, ',') as ss
inner join dbo.Table02 t02 on ss.value = t02.Id
group by t.id
Follow the next approach:-
1) Turning a Comma Separated string into individual rows via using CROSS APPLY with XML
2) Join the two tables with left join.
3) Concatenate many rows with same id via using STUFF & FOR XML
4) Use Replace function for removing comma.
Demo:-
declare #MyTable table (id int , Tbl1Col varchar(10))
insert into #MyTable values (1,'2'),(2,'2,4'),(3,''),(4,'6'),(5,'3')
declare #MyTable2 table (id int , Tbl2Col varchar(10))
insert into #MyTable2 values (1,'E'),(2,'F'),(3,'M'),(4,'U'),(5,'P'),(6,'C'),(7,'N'),(8,'G')
select a.id , Tbl2Col
into #TestTable
from
(
SELECT A.id,
Split.a.value('.', 'VARCHAR(100)') AS Tbl1Col
FROM
(
SELECT id,
CAST ('<M>' + REPLACE(Tbl1Col, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #MyTable
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a) ) a
left join #MyTable2 b
on a.Tbl1Col = b.id
order by a.id
SELECT id, Tbl2Col =
Replace(STUFF((SELECT DISTINCT ', ' + Tbl2Col
FROM #TestTable b
WHERE b.id = a.id
FOR XML PATH('')), 1, 2, ''),',','')
FROM #TestTable a
GROUP BY id
Output:-
1 F
2 F U
3 NULL
4 C
5 M
References:-
Turning a Comma Separated string into individual rows
How to concatenate many rows with same id in sql?
Finally:-
Don't use this approach, and normalize your database instead , just use it as fun/training/trying .... etc code.

SQL inserted value of a table as column of another table

Table1 Table2
Id Name Id Table1Id Value
1 Some 1 1 value1
2 Some1 2 2 value2
3 Some2 3 3 value3
4 Some3
.
.
I want to result this:
Some Some1 Some2 Some3
value1 value2 value3 NULL
When I entered value into the Table1 I want to look like the table2's column, how can I do this?
I guess I'm looking for pivot query.
Thanks for posting the additional information. Based on what you said, you're looking for a PIVOT statement that will turn each name in Table1 into a column header, containing the corresponding value from Table2.
Have a look at the following SQL, which produces the output your sample data indicates.
CREATE TABLE #Table1 (ID INT, Name varchar(5))
CREATE TABLE #Table2 (ID INT, Table1ID INT, Value varchar(6))
INSERT INTO #Table1 (ID, Name) SELECT 1, 'Some'
INSERT INTO #Table1 (ID, Name) SELECT 2, 'Some1'
INSERT INTO #Table1 (ID, Name) SELECT 3, 'Some2'
INSERT INTO #Table1 (ID, Name) SELECT 4, 'Some3'
INSERT INTO #Table2 (ID, Table1ID, Value) SELECT 1, 1, 'Value1'
INSERT INTO #Table2 (ID, Table1ID, Value) SELECT 2, 2, 'Value2'
INSERT INTO #Table2 (ID, Table1ID, Value) SELECT 3, 3, 'Value3'
-- List of values to be columns
declare #cols nvarchar(max)
select #cols = coalesce(#cols+N',', N'') + quotename(Name) from #Table1 order by ID
PRINT #Cols
declare #query varchar(MAX)
SET #query = '
SELECT *
FROM
(
SELECT T1.Name, T2.Value
FROM
#Table1 T1
INNER JOIN
#Table2 T2 ON
T1.ID = T2.Table1ID
) s
PIVOT
(
MAX(Value) FOR Name IN ('+#Cols+')
) p'
PRINT #query
EXEC (#query)
DROP TABLE #Table1
DROP TABLE #Table2

Select only distinct values from two columns from a table

If I have a table such as
1 A
1 B
1 A
1 B
2 C
2 C
And I want to select distinct from the two columns so that I would get
1
2
A
B
C
How can I word my query? Is the only way to concatenate the columns and wrap them around a distinct function operator?
You could use a union to create a table of all values from both columns:
select col1 as BothColumns
from YourTable
union
select col2
from YourTable
Unlike union all, union removes duplicates, even if they come from the same side of the union.
SQL Fiddle
Why even distinct in Union, try this :
select cast(id as char(1)) from test
union
select val from test
Please try:
Select Col1 from YourTable
union
Select Col2 from YourTable
UNION removes duplicate records (where all columns in the results are the same), UNION ALL does not.
Please check What is the difference between UNION and UNION ALL
For multiple columns, you can go for UNPIVOT.
SELECT distinct DistValues
FROM
(SELECT Col1, Col2, Col3
FROM YourTable) p
UNPIVOT
(DistValues FOR Dist IN
(Col1, Col2, Col3)
)AS unpvt;
Try this one -
DECLARE #temp TABLE
(
Col1 INT
, Col2 NVARCHAR(50)
)
INSERT INTO #temp (Col1, Col2)
VALUES (1, 'ab5defg'), (2, 'ae4eii')
SELECT disword = (
SELECT DISTINCT dt.ch
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.number + 1, 1)
FROM [master].dbo.spt_values n
CROSS JOIN (
SELECT mtxt = (
SELECT CAST(Col1 AS VARCHAR(10)) + Col2
FROM #temp
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'
)
) t
WHERE [type] = N'p'
AND number <= LEN(mtxt) - 1
) dt
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'
)
Or try this -
DECLARE #temp TABLE
(
a CHAR(1), b CHAR(1)
)
INSERT INTO #temp (a, b)
VALUES
('1', 'A'), ('1', 'B'), ('1', 'A'),
('1', 'B'), ('2', 'C'), ('2', 'C')
SELECT a
FROM #temp
UNION
SELECT b
FROM #temp
Because what you want select is in different columns, you can use union like below:
select distinct tarCol from
(select distinct column1 as tarCol from table
union
select distinct column2 from table) as tarTab
You can use like this to get multiple distinct column values
(SELECT DISTINCT `enodeb` as res,
"enodeb" as columnname
FROM `raw_metrics`)
UNION
(SELECT DISTINCT `interval` as res,
"interval" as columnname
FROM `raw_metrics`)