How to merger the splited data into same table? - sql

I have a table having data like this
id | Name | Age
---------------
1 | a,b | 10
I want to split that data and merge with the same table like this
id | Name | Age
---------------
1 | a | 10
1 | b | 10
I have a split function.

Test Data
DECLARE #TABLE TABLE(id INT, Name VARCHAR(10), Age INT)
INSERT INTO #TABLE VALUES
(1 ,'a',10), (1 ,'b',10),
(2 ,'d',20), (2 ,'f',20)
Query
SELECT t.ID
,STUFF(( SELECT ',' + Name
FROM #TABLE
WHERE ID = t.ID AND Age = t.Age
FOR XML PATH(''),TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'') AS Name
,t.Age
FROM #TABLE t
GROUP BY t.id, t.Age
Result
╔════╦══════╦═════╗
║ ID ║ Name ║ Age ║
╠════╬══════╬═════╣
║ 1 ║ a,b ║ 10 ║
║ 2 ║ d,f ║ 20 ║
╚════╩══════╩═════╝

It seems to me that OP has the data concatenated and needs to split it. Here's one way to do it (fiddle: http://sqlfiddle.com/#!3/6b2a1/15)
WITH cteSplitCol
AS
(
SELECT id,
Name,
Age,
SUBSTRING(Name, 1, 1) AS Character,
1 AS CharPos,
MAX(LEN(Name)) OVER () AS MaxLen
FROM Table1
UNION ALL
SELECT id,
Name,
Age,
SUBSTRING(Name, CharPos + 1, 1) AS Character,
CharPos + 1,
MaxLen
FROM cteSplitCol
WHERE CharPos + 1 <= MaxLen
)
SELECT id,
Character AS Name,
Age
FROM cteSplitCol
WHERE Character <> ',';

Related

SQL server how to change pipe separated column string to rows

I have 3 columns in a table, in which 2 columns have string separated by a '|' pipe. Both these columns values depend on each other.
For an example: I have data in the table like this :
ID product quantity
1 A|B|C 1|2|3
2 X|Y|Z 7|8|9
I would like to change it to something like this :
ID product quantity
1 A 1
1 B 2
1 C 3
2 X 7
2 Y 8
2 Z 9
As i am working with SSMS, i don't have any other choice except SQL. I try to use cross apply but i am not getting right result. For 1 row i receive 9 rows instead of getting 3.
could anyone suggest me which method should i use?
Thank you in advance!!
JACK
This is rather tricky, because you need for the values to match up. The following takes a recursive CTE approach:
with cte as (
select id,
left(product, charindex('|', product + '|') - 1) as product,
left(quantity, charindex('|', quantity + '|') - 1) as quantity,
substring(product, charindex('|', product + '|') + 1, 1000) as products,
substring(quantity, charindex('|', quantity + '|') + 1, 1000) as quantities
from t
union all
select id,
left(products, charindex('|', products + '|') - 1) as product,
left(quantities, charindex('|', quantities + '|') - 1) as quantity,
substring(products, charindex('|', products + '|') + 1, 1000) as products,
substring(quantities, charindex('|', quantities + '|') + 1, 1000) as quantities
from cte
where products <> '' and quantities <> ''
)
select id, product, quantity
from cte;
Here is a little Rextester.
Test Data
CREATE TABLE #t (ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #t VALUES
(1 ,'A|B|C' , '1|2|3'),
(2 ,'X|Y|Z' , '7|8|9');
Query
WITH Products AS (
SELECT ID
, Product_Split.a.value('.', 'VARCHAR(100)') Products
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(product, '|', '</X><X>')
+ '</X>' AS XML) AS Product_Data
FROM #t
) AS t
CROSS APPLY Product_Data.nodes ('/X') AS Product_Split(a)
),
Quantities AS (
SELECT ID
, Quantity_Split.a.value('.', 'VARCHAR(100)') Quantity
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(quantity, '|', '</X><X>')
+ '</X>' AS XML) AS Quantity_Data
FROM #t
) AS t
CROSS APPLY Quantity_Data.nodes ('/X') AS Quantity_Split(a)
)
SELECT t.ID
, P.Products
, Q.Quantity
FROM #t t
LEFT JOIN Products P ON t.ID = p.ID
LEFT JOIN Quantities Q ON Q.ID = t.ID
AND Q.rn = p.rn
Result Set
╔════╦══════════╦══════════╗
║ ID ║ Products ║ Quantity ║
╠════╬══════════╬══════════╣
║ 1 ║ A ║ 1 ║
║ 1 ║ B ║ 2 ║
║ 1 ║ C ║ 3 ║
║ 2 ║ X ║ 7 ║
║ 2 ║ Y ║ 8 ║
║ 2 ║ Z ║ 9 ║
╚════╩══════════╩══════════╝
splitting strings is easy, there are tons of examples. The tricky part here is to connect the fragments via their position. My suggestion uses XMLs abilities to target an element by its position:
DECLARE #tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9');
--This is the query
WITH CastedToXML AS
(
SELECT *
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM #tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
the result
+----+------+-------------------+--------------------+
| ID | Nmbr | ProductAtPosition | QuantityAtPosition |
+----+------+-------------------+--------------------+
| 1 | 1 | A | 1 |
+----+------+-------------------+--------------------+
| 1 | 2 | B | 2 |
+----+------+-------------------+--------------------+
| 1 | 3 | C | 3 |
+----+------+-------------------+--------------------+
| 2 | 1 | X | 7 |
+----+------+-------------------+--------------------+
| 2 | 2 | Y | 8 |
+----+------+-------------------+--------------------+
| 2 | 3 | Z | 9 |
+----+------+-------------------+--------------------+
Some explanation:
the cast to xml transfers your A|B|C to
<x>A</x>
<x>B</x>
<x>C</x>
This list is joined with a number set created on the fly using the count of <x> as the TOP limit.
Now it is easy to pick the <x> out of your XML by the position.
Try it out!
UPDATE: Non-unique IDs
DECLARE #tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9')
,(3 ,'a|b|c' , '7|8|9')
,(2 ,'D|e|f' , '7|8|9')
;
--This is the query
WITH CastedToXML AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RowIndex
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM #tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);

Update column, non distinct ID with secondary id?

+-----+-----+------+----------+
| id | num | text | combo |
+-----+-----+------+----------+
| 126 | 1 | 12 | 12,58,94 |
| 126 | 2 | 58 | |
| 126 | 3 | 94 | |
| 130 | 1 | 28 | 28,45,64 |
| 130 | 2 | 45 | |
| 130 | 3 | 64 | |
+-----+-----+------+----------+
I have this table, where the column "combo" is currently null. I am trying to update it as such with data from "text" (from min value to max value in "num" and for each distinct id)
Any help formulating this query would be greatly appreciated, pardon my poor phrasing.
Test Data
CREATE TABLE #MyTable(id INT,num INT,text INT,combo VARCHAR(100))
GO
INSERT INTO #MyTable
VALUES
(126,1,12,NULL),
(126,2,58,NULL),
(126,3,94,NULL),
(130,1,28,NULL),
(130,2,45,NULL),
(130,3,64,NULL)
GO
Query
;With UpdateRecord
AS
(
SELECT * , rn = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY [text])
FROM #MyTable
),
UpdateValues
AS
(
SELECT ID, Num, STUFF(List.Numbers, 1 ,2 , '') Vals
, rn2 = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY [text])
FROM #MyTable t CROSS APPLY
(
SELECT ', ' + CAST([text] AS VARCHAR) [text()]
FROM #MyTable
WHERE id = t.id
FOR XML PATH('')
)List(Numbers)
)
UPDATE UpdateRecord
SET combo = (SELECT TOP 1 Vals
FROM UpdateValues
WHERE id = UpdateRecord.id AND rn = UpdateRecord.rn
AND rn = 1)
Result
╔═════════╦═════╦══════╦════════════════╗
║ id ║ num ║ text ║ combo ║
╠═════════╬═════╬══════╬════════════════╣
║ 126 ║ 1 ║ 12 ║ 12, 58, 94 ║
║ 126 ║ 2 ║ 58 ║ NULL ║
║ 126 ║ 3 ║ 94 ║ NULL ║
║ 130 ║ 1 ║ 28 ║ 28, 45, 64 ║
║ 130 ║ 2 ║ 45 ║ NULL ║
║ 130 ║ 3 ║ 64 ║ NULL ║
╚═════════╩═════╩══════╩════════════════╝
You can use a recursive cte:
declare #SourceTable TABLE
(
id int, num int,textvalue varchar(255), combo varchar(255)
)
INSERT INTO #SourceTable(id, num, textvalue) VALUES
(126, 1, 12),(126, 2, 58),(126, 3, 94),(130, 1, 28),(130, 2, 45),(130, 3, 64)
Create a recursive cte that concatenates the values:
;with cte_ConcatenatedText(id, textvalue, num) AS
(
SELECT id, textvalue, num FROM #SourceTable WHERE num = 1
UNION ALL
SELECT st.id, cast(cte.textvalue + ',' + st.textvalue as varchar(255)), st.num
FROM #SourceTable st
INNER JOIN cte_ConcatenatedText cte ON cte.id = st.id and cte.num = st.num -1
)
After that you can update the source table:
UPDATE #SourceTable SET combo = cte.textvalue
FROM #SourceTable st
INNER JOIN
(SELECT id, textvalue,num FROM cte_ConcatenatedText) cte
ON cte.id = st.id and st.num = 1
INNER JOIN
(SELECT id, MAX(num) AS num FROM #SourceTable group by id) maxNum
on maxNum.id = cte.id and maxNum.num = cte.num
SELECT * FROM #SourceTable
Result:
id num textvalue combo
126 1 12 12,58,94
126 2 58 NULL
126 3 94 NULL
130 1 28 28,45,64
130 2 45 NULL
130 3 64 NULL

Select value as column

If I have a table containing:
game powerup used
memory 1 12
memory 2 10
bejeweled 2 88
bejeweled 3 54
...where the (number of) different possible values of powerup are unknown, is there a way to SELECT this as something like
game 1_used 2_used 3_used
memory 12 10
bejeweled 88 54
I'm interested in pure SQL or PostgreSQL answers.
Note: This is an academical question, I don't have a use case at the moment. Answer it for your enjoyment or for the good of the site, not my personal benefit :)
Try this one:
SELECT game
,MAX(CASE WHEN powerup = 1 THEN used ELSE NULL END) AS used1
,MAX(CASE WHEN powerup = 2 THEN used ELSE NULL END) AS used2
,MAX(CASE WHEN powerup = 3 THEN used ELSE NULL END) AS used3
FROM Table1
GROUP BY game;
Output:
╔═══════════╦════════╦═══════╦════════╗
║ GAME ║ USED1 ║ USED2 ║ USED3 ║
╠═══════════╬════════╬═══════╬════════╣
║ bejeweled ║ (null) ║ 88 ║ 54 ║
║ memory ║ 12 ║ 10 ║ (null) ║
╚═══════════╩════════╩═══════╩════════╝
See this SQLFiddle
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE meuk
( game varchar
, powerup INTEGER NOT NULL
, used INTEGER NOT NULL
, PRIMARY KEY (game,powerup)
);
INSERT INTO meuk(game, powerup, used) VALUES
( 'memory' , 1,12)
,( 'memory' , 2,10)
,( 'bejeweled' , 2,88)
,( 'bejeweled' , 3,54)
;
-- using a self-join as
-- a poor man's pivot.
WITH dom AS (
SELECT DISTINCT game AS game
FROM meuk
)
SELECT m0.game AS game
, m1.used AS used_1
, m2.used AS used_2
, m3.used AS used_3
FROM dom m0
LEFT JOIN meuk m1 ON m1.game = m0.game AND m1.powerup = 1
LEFT JOIN meuk m2 ON m2.game = m0.game AND m2.powerup = 2
LEFT JOIN meuk m3 ON m3.game = m0.game AND m3.powerup = 3
;
RESULTS:
game | used_1 | used_2 | used_3
-----------+--------+--------+--------
bejeweled | | 88 | 54
memory | 12 | 10 |
(2 rows)
CREATE TABLE test_1
( game varchar(10)
, powerup INTEGER NOT NULL
, used INTEGER NOT NULL
);
INSERT INTO test_1(game, powerup, used)
SELECT 'memory' , 1,12 UNION ALL
SELECT 'memory' , 2,10 UNION ALL
SELECT 'bejeweled' , 2,88 UNION ALL
SELECT 'bejeweled' , 3,54
select * from test_1
DECLARE #COLUMNS varchar(max)
SELECT #COLUMNS = COALESCE(#COLUMNS+'],[' ,'') + CAST(powerup as varchar)
FROM test_1
GROUP BY powerup
SET #COLUMNS = '[' + #COLUMNS + ']'
EXECUTE ('select * from test_1
pivot (SUM(used) for powerup IN ('+ #COLUMNS +')) as used_t')

Concatenate row record base on group by in SQL Server 2008

I have one table (tblproduct) with fields: dept, product, qty.
sample data below:
dept product qty
IT A 2
IT B 1
PU C 4
SAL D 1
SER D 2
SER A 4
I want to create stored pro in sql server with the result below:
product qty remark
A 6 IT=2,SER=4
B 1 IT=1
C 4 PU=4
D 3 SAL=1,SER=2
this is my stored pro
select product,
sum(qty)
from tblproduct
group by product
order by product
Pls. any help. thanks.
SELECT
[product], SUM(qty) Total_Qty,
STUFF(
(SELECT ',' + dept + '=' + CAST(qty AS VARCHAR(10))
FROM TableName
WHERE [product] = a.[product]
FOR XML PATH (''))
, 1, 1, '') AS Remark
FROM TableName AS a
GROUP BY [product]
SQLFiddle Demo
OUTPUT
╔═════════╦═══════════╦═════════════╗
║ PRODUCT ║ TOTAL_QTY ║ REMARK ║
╠═════════╬═══════════╬═════════════╣
║ A ║ 6 ║ IT=2,SER=4 ║
║ B ║ 1 ║ IT=1 ║
║ C ║ 4 ║ PU=4 ║
║ D ║ 3 ║ SAL=1,SER=2 ║
╚═════════╩═══════════╩═════════════╝
Please try:
SELECT product, SUM(qty) Qty,
STUFF(
(SELECT ','+b.dept+'='+CAST(qty as nvarchar(10))
FROM YourTable b where b.product=a.product
FOR XML PATH(''),type).value('.','nvarchar(max)'), 1, 1, '')
from YourTable a
group by product

Update duplicate fields in table

I have table with about 100000 records.I need update same fields like this.
For example this is my table
id name
1 sss
2 bbb
3 ccc
4 avg
5 bbb
6 bbb
7 sss
8 mmm
9 avg
After executing script I need get
id name
1 sss
2 bbb
3 ccc
4 avg
5 bbb-5
6 bbb-6
7 sss-7
8 mmm
9 avg-9
How can I do that?
By using CTE
WITH greaterRecord
AS
(
SELECT id,
name,
ROW_NUMBER() OVER(PARTITION BY name ORDER BY id) RN
FROM TableName
)
UPDATE greaterRecord
SET name = name + '-' + CAST(id AS VARCHAR(10))
WHERE RN > 1
SQLFiddle Demo
This is the common query that works on most RDBMS
UPDATE a
SET a.Name = a.Name + '-' + CAST(ID AS VARCHAR(10))
FROM tableName a
LEFT JOIN
(
SELECT MIN(ID) min_ID, name
FROM tableName
GROUP BY name
) b ON a.name = b.name AND
a.ID = b.Min_ID
WHERE b.Name IS NULL
SQLFiddle Demo
OUTPUT after the update statement has been executed
╔════╦═══════╗
║ ID ║ NAME ║
╠════╬═══════╣
║ 1 ║ sss ║
║ 2 ║ bbb ║
║ 3 ║ ccc ║
║ 4 ║ avg ║
║ 5 ║ bbb-5 ║
║ 6 ║ bbb-6 ║
║ 7 ║ sss-7 ║
║ 8 ║ mmm ║
║ 9 ║ avg-9 ║
╚════╩═══════╝
This should do:
;WITH CTE AS
(
SELECT id,
name,
RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id)
FROM YourTable
)
UPDATE CTE
SET name = name + '-' + CAST(id AS VARCHAR(8))
WHERE RN > 1