I have a table with a single column in a Postgres 13.1 database. It consists of many rows with comma-separated values - around 20 elements at most.
I want to split the data into multiple columns. But I have only a limited number of columns say 5 and more than 5 CSV values in a single row, so excess values must be shifted to new/next row). How to do this?
Example:
a1, b1, c1
a2, b2, c2, d2, e2, f2
a3, b3, c3, d3, e3, f3, g3, h3, i3, j3
a4
a5, b5, c5
'
'
'
Columns are only 5, so the output would be like:
c1 c2 c3 c4 c5
---------------
a1 b1 c1
a2 b2 c2 d2 e2
f2
a3 b3 c3 d3 e3
f3 g3 h3 i3 j3
a4
a5 b5 c5
'
'
'
It is typically bad design to store CSV values in a single column. If at all possible, use an array or a properly normalized design instead.
While stuck with your current situation ...
For known small maximum number of elements
A simple solution without trickery or recursion will do:
SELECT id, 1 AS rnk
, split_part(csv, ', ', 1) AS c1
, split_part(csv, ', ', 2) AS c2
, split_part(csv, ', ', 3) AS c3
, split_part(csv, ', ', 4) AS c4
, split_part(csv, ', ', 5) AS c5
FROM tbl
WHERE split_part(csv, ', ', 1) <> '' -- skip empty rows
UNION ALL
SELECT id, 2
, split_part(csv, ', ', 6)
, split_part(csv, ', ', 7)
, split_part(csv, ', ', 8)
, split_part(csv, ', ', 9)
, split_part(csv, ', ', 10)
FROM tbl
WHERE split_part(csv, ', ', 6) <> '' -- skip empty rows
-- three more blocks to cover a maximum "around 20"
ORDER BY id, rnk;
db<>fiddle here
id being the PK of the original table.
This assumes ', ' as separator, obviously.
You can adapt easily.
Related:
Split comma separated column data into additional columns
For unknown number of elements
Various ways. One way use regexp_replace() to replace every fifth separator before unnesting ...
-- for any number of elements
SELECT t.id, c.rnk
, split_part(c.csv5, ', ', 1) AS c1
, split_part(c.csv5, ', ', 2) AS c2
, split_part(c.csv5, ', ', 3) AS c3
, split_part(c.csv5, ', ', 4) AS c4
, split_part(c.csv5, ', ', 5) AS c5
FROM tbl t
, unnest(string_to_array(regexp_replace(csv, '((?:.*?,){4}.*?),', '\1;', 'g'), '; ')) WITH ORDINALITY c(csv5, rnk)
ORDER BY t.id, c.rnk;
db<>fiddle here
This assumes that the chosen separator ; never appears in your strings. (Just like , can never appear.)
The regular expression pattern is the key: '((?:.*?,){4}.*?),'
(?:) ... “non-capturing” set of parentheses
() ... “capturing” set of parentheses
*? ... non-greedy quantifier
{4}? ... sequence of exactly 4 matches
The replacement '\1;' contains the back-reference \1.
'g' as fourth function parameter is required for repeated replacement.
Further reading:
PostgreSQL & regexp_split_to_array + unnest
Apply `trim()` and `regexp_replace()` on text array
PostgreSQL unnest() with element number
Other ways to solve this include a recursive CTE or a set-returning function ...
Fill from right to left
(Like you added in How to put values starting from the right side into columns?)
Simply count down numbers like:
SELECT t.id, c.rnk
, split_part(c.csv5, ', ', 5) AS c1
, split_part(c.csv5, ', ', 4) AS c2
, split_part(c.csv5, ', ', 3) AS c3
, split_part(c.csv5, ', ', 2) AS c4
, split_part(c.csv5, ', ', 1) AS c5
FROM ...
db<>fiddle here
CREATE UNLOGGED TABLE foo( x TEXT );
\copy foo FROM stdin
a1, b1, c1
a2, b2, c2, d2, e2, f2
a3, b3, c3, d3, e3, f3, g3, h3, i3, j3
a4
a5, b5, c5
\.
From lines to single column...
SELECT (ROW_NUMBER() OVER () - 1)/5 AS r, u FROM (SELECT unnest(string_to_array(x,', ')) u from foo) y;
r | u
---+----
0 | a1
0 | b1
0 | c1
0 | a2
0 | b2
1 | c2
1 | d2
...etc
...and back to lines of known length.
SELECT r,array_agg(u) a FROM (
SELECT (ROW_NUMBER() OVER () - 1)/5 AS r, u FROM (
SELECT unnest(string_to_array(x,', ')) u from foo) y) y1
GROUP BY r ORDER BY r;
r | a
---+------------------
0 | {a1,b1,c1,a2,b2}
1 | {c2,d2,e2,f2,a3}
2 | {b3,c3,d3,e3,f3}
3 | {g3,h3,i3,j3,a4}
4 | {a5,b5,c5}
After this you can insert it into a table using a[] for each column. What to do with the last line is left as an exercise to the reader...
Answer to related question: How to put values starting from the right side into columns?
The accepted great answer from #ErwinBrandstetter can be easily adapted to required right-to-left output.
You just need to change to order of the split parts. So you don't return split parts 1-5 and 6-10 but 5-1 and 10-6:
demo:db<>fiddle
SELECT id, 1 AS rnk
, split_part(csv, ', ', 5) AS c1
, split_part(csv, ', ', 4) AS c2
, split_part(csv, ', ', 3) AS c3
, split_part(csv, ', ', 2) AS c4
, split_part(csv, ', ', 1) AS c5
FROM tbl
WHERE split_part(csv, ', ', 1) <> '' -- skip empty rows
UNION ALL
SELECT id, 2
, split_part(csv, ', ', 10)
, split_part(csv, ', ', 9)
, split_part(csv, ', ', 8)
, split_part(csv, ', ', 7)
, split_part(csv, ', ', 6)
FROM tbl
WHERE split_part(csv, ', ', 6) <> '' -- skip empty rows
-- more?
ORDER BY id, rnk;
You need to do this in the whatever backend layer you are using.
First, convert the CSV rows to array of string
Then, use logic something like this to add Values to the database
int row = 0; // database row index - can be used to just have a count
final int MAX_COLUMNS = 5;
for(int i = 0; i<rows.length; i++) {
// Convert csv row string to array of each value.
String [] values = rows[i].split(",");
// Dividing whole row into chunks of size of number of columns
for(int j = 0; j < (values.length/(MAX_COLUMNS)) + 1; j++) {
Add Values [MAX_COLUMNS*j,MAX_COLUMNS*j+(MAX_COLUMNS - 1)] to the row [row + j]
row++;
}
}
Related
I enter in the database the value with this format 1 000.00.
I want to add two values of this format, for example 1 000.00 + 4 000.00 like this:
CAST(SUM(1 000.00 + 4 000.00) AS DECIMAL (15 , 2 )) AS Pago
the returned value is 5 and should be 5 000.00
Sum 2 values and finally add a space
-- MySQL
SELECT INSERT((SUM(CAST(REPLACE('1 000.00', ' ', '') AS decimal(15, 2)) + CAST(REPLACE('4 000.00', ' ', '') AS decimal(15, 2)))), 2, 0, ' ') sum_total
Please check url https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=b8ab1e7c4c1d2ab596fbc1fe58754cf8
Add these two values in a table
SELECT INSERT((SUM(CAST(REPLACE(apago, ' ', '') as decimal(15, 2)))), 2, 0, ' ') pago FROM test;
Please check this url https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=65fa6af1bedd4a1acce01dc0ea700dd6
I resolved it this way. First I removed the space in the string like this:
SELECT Id, Total, Metodo, REPLACE(Pago, ' ', '') AS Pago
FROM dados.Orcamento
Then I added the string:
SELECT B.Id, B.Total, B.Metodo, CAST(SUM(B.Pago) AS DECIMAL (15 , 2 )) AS Pago
FROM
(SELECT Id, Total, Metodo, REPLACE('Pago', ' ', '') AS Pago
FROM dados.Orcamento) AS B GROUP BY B.Id, B.Metodo, B.Total ORDER BY B.Id ASC LIMIT 1
So the 5000.00 returned
I currently have a view that has a column called alt_email_contact and I used the group_concat function in order to get multiple emails associated with one contact. However I want to be able to extract each email and create a separate column for each.
Example:
id email
1 SkyW#gmail.com, SW#gmail.com, WW#gmail.com, WalterW#gmail.com
the amount of emails is subject to change from one user to another therefore there wont always be four emails for each user. I want to create a new column per each email like so:
id email_1 email_2 email_3 email_4
1 SkyW#gmail.com SW#gmail.com WW#gmail.com WalterW#gmail.com
(I am using phpmyadmin) I would like to be able to modify my view to contain the variable amount of emails per user.
You can use substring_index() to achieve what you want. It is not really pretty, but it will work:
select id,
substring_index(emails, ', ', 1) as email_1
(case when length(emails) - length(replace(emails, ',', '')) >= 1
then substring_index(substring_index(emails, ', ', 2), ', ', -1)
end) as email_2,
(case when length(emails) - length(replace(emails, ',', '')) >= 2
then substring_index(substring_index(emails, ', ', 3), ', ', -1)
end) as email_3,
(case when length(emails) - length(replace(emails, ',', '')) >= 3
then substring_index(substring_index(emails, ', ', 4), ', ', -1)
end) as email_4,
(case when length(emails) - length(replace(emails, ',', '')) >= 4
then substring_index(substring_index(emails, ', ', 5), ', ', -1)
end) as email_5
from table t;
You can insert these values into another table, if you like.
Don't use that view, since GROUP_CONCAT() has already ruined the normalization. You want to simulate a pivot table using the limited SQL capabilities of MySQL.
Let's assume that your view is based on a Contacts table that looks like this:
CREATE TABLE Contacts
( id INTEGER NOT NULL
, alt_email_contact VARCHAR(256) NOT NULL
);
Create this helper view instead (which is basically RANK() OVER (PARTITION BY id ORDER BY alt_email_contact), except that MySQL doesn't support RANK()):
CREATE VIEW NumberedContacts AS
SELECT c1.id, c1.alt_email_contact, COUNT(*) AS rank
FROM Contacts c1
INNER JOIN Contacts c2
ON c2.id = c1.id AND
c1.alt_email_contact >= c2.alt_email_contact
GROUP BY c1.id, c1.alt_email_contact;
Then you can write this query or view, which gives you up to 5 alternate e-mail addresses, ordered alphabetically:
CREATE VIEW ContactsForImport AS
SELECT c1.id
, c1.alt_email_contact AS email_1
, c2.alt_email_contact AS email_2
, c3.alt_email_contact AS email_3
, c4.alt_email_contact AS email_4
, c5.alt_email_contact AS email_5
FROM NumberedContacts AS c1
LEFT OUTER JOIN NumberedContacts AS c2
ON c1.id = c2.id AND c2.rank = 2
LEFT OUTER JOIN NumberedContacts AS c3
ON c1.id = c3.id AND c3.rank = 3
LEFT OUTER JOIN NumberedContacts AS c4
ON c1.id = c4.id AND c4.rank = 4
LEFT OUTER JOIN NumberedContacts AS c5
ON c1.id = c5.id AND c5.rank = 5
WHERE c1.rank = 1;
SQL Fiddle
I have two tables
test
and test1
test
id formula
1 A12+C32+D+X
2 K/Y
test1
id Code
6 A12
7 C32
100 A1
10 D
12 X
13 K
14 Y
How can I update formula(text formlas) filed in the table test to get
id formula
1 [6]+[7]+[10]+[12]
2 [13]/[14]
Itry the following script sqlfield but it doesn't rerun the correct result . It returns
RFORMULA
[6]+C32+D+X//need to remove it
[13]/Y//need to remove it
[13]/[14]//the best result
[100]2+[7]+[10]+[12]
[13]/Y//need to remove it
K/[14]//need to remove it
[6]+[7]+[10]+[12]//the best result
I am working on it for hours , any idea ?
Try by using the following:
SQL Fiddle
WITH a AS
( SELECT DISTINCT A.id group_id,
Split.A.value('.', 'VARCHAR(100)') AS Data
FROM
(SELECT id,
CAST ('<M>' + REPLACE(REPLACE(REPLACE(REPLACE(formula, '/', '/</M><M>'), '+','+</M><M>'), '-','+</M><M>'),'*','+</M><M>') + '</M>' AS XML) AS Data
FROM test
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a)
),
b AS
(SELECT group_id,
REPLACE(LEFT(Data, LEN(Data)-1), t.[Code], concat(t.[id],RIGHT(Data,1))) yy,
REPLACE(Data, t.[Code], t.[id])uu
FROM a
LEFT JOIN test1 t
ON t.Code =LEFT(Data, LEN(Data)-1)
OR (t.Code=Data)
)
SELECT group_id,
REPLACE(REPLACE(REPLACE(REPLACE(stuff(
(SELECT uu FROM b WHERE newtable.group_id=group_id FOR XML PATH('')
), 1,1,''), 'uu', ''), '</>', ''),'<', ''), '>', '')
FROM b newtable
GROUP BY group_id
Here, I have also specified multiplication and subtraction, in case they appear in your expression column. You can edit it accordingly if there exist some other ones:
CAST ('<M>' + REPLACE(REPLACE(REPLACE(REPLACE(formula, '/', '/</M><M>'), '+','+</M><M>'), '-','+</M><M>'),'*','+</M><M>') + '</M>' AS XML) AS Data
I am trying to divide data in one onf the tables on my MySQL database.
Column contains data like this:
de:"Sweatjacke*";en:"jacket*";pl:"bluza*";
de:"*";en:"*";pl:"bluza*";
fr:"*";de:"*";en:"*";pl:"dres junior*";cz:"*";
pl:"bluza";
And I am trying to divide all of the translations into separate columns. Already came with solution to do this by using:
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', 1), ';', -1) as tr1,
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', 2), ';', -1) as tr2,
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', 3), ';', -1) as tr3,
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', 4), ';', -1) as tr4,
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', 5), ';', -1) as tr5
FROM product;
statement, but that results in:
tr1 tr2 tr3 tr4 tr5
fr:"*" de:"*" en:"*" pl:"bluza*" cz:"*"
fr:"*" de:"Sweatjacke*" en:"jacket*" pl:"bluza*" cz:"*"
de:"Sweatjacke*" en:"jacket*" pl:"bluza*"
And I want to have the results gruped by translation type (pl/de/en) so in each collumn one type of translatoin is present. For example in column1 = pl:, column2 = en: etc.
Any one came across similar problem and knows a way to solve it?
You need to unpivot the data, then select the first and second part of each value and then re-aggregate it.
However, a better form for the data is really to have language/translation. The following produces this:
select substring_index(tr, ':', 1) as l, substring_index(tr, ':', 2) as t, name
from (select SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', n.n), ';', -1) as tr, n, name
from product p cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5
) n
) n
You would probably want an "id" column or "word" column to identify each row, rather than the name column.
You can now pivot this result to get what you want:
select max(case when l = 'en' then name end) as en,
max(case when l = 'fr' then name end) as fr,
max(case when l = 'de' then name end) as de,
max(case when l = 'pl' then name end) as pl,
max(case when l = 'cz' then name end) as cz
from (select substring_index(tr, ':', 1) as l, substring_index(tr, ':', 2) as t, name
from (select SUBSTRING_INDEX(SUBSTRING_INDEX(name, ';', n.n), ';', -1) as tr, n, name
from product p cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5
) n
) n
) lt
group by name;
Managed to solve it by using some of the string related functions funcitons:
SELECT
SUBSTRING_INDEX( SUBSTRING( name, LOCATE( "pl:", name ) , 150 ) , ';', 1 ) AS pl,
SUBSTRING_INDEX( SUBSTRING( name, LOCATE( "en:", name ) , 150 ) , ';', 1 ) AS en,
SUBSTRING_INDEX( SUBSTRING( name, LOCATE( "de:", name ) , 150 ) , ';', 1 ) AS de,
SUBSTRING_INDEX( SUBSTRING( name, LOCATE( "fr:", name ) , 150 ) , ';', 1 ) AS fr
FROM product
Thanks to everyone for help.
As far as I understand you want to UNPIVOT your data. There is no such function in MySQL, so you might want to export your data into MSSQL (you can use free MSSQL Express) and use UNPIVOT function: http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
I have a input variable
#inputData varchar(Max)
e.g :
Victor:2;John:22;Jo:100
how can I split the variable into two columns?
Col1 Col2
----------
Victor 2
John 22
Jo 100
Here is the script, this gives multiple columns and rows from single string value.
declare #inputData varchar(Max) = 'Victor:2;John:22;Jo:100' + ';'
;with row(c1,c2)
as
(
SELECT LEFT
( #inputData
, CHARINDEX(';', #inputData, 0) - 1
) col1
, SUBSTRING
( #inputData
, CHARINDEX(';', #inputData, 0) + 1
, LEN(#inputData)
) col2
UNION ALL
SELECT LEFT
( c2
, CHARINDEX(';', c2, 0) - 1
) col1
, SUBSTRING
( c2
, CHARINDEX(';', c2, 0) + 1
, LEN(c2)
) col2
FROM row
WHERE CHARINDEX(';', c2, 0) >0
)
select LEFT(C1, CHARINDEX(':', c1, 0) - 1) col1, SUBSTRING( c1 , CHARINDEX(':', c1, 0) + 1, LEN(c1)) col2 from row
Output :
col1 col2
Victor 2
John 22
Jo 100
May not be a very good/efficient solution and typically based on your example; you can try the below:
create table tab1(col varchar(100))
insert into tab1 values ('key1:val1;key2:val2;key3:valu3')
Query:
select SUBSTRING((SUBSTRING((left(col,CHARINDEX(';',Col))),0,
charindex(';',col))),0,charindex(':',col)) as Name,
SUBSTRING((SUBSTRING((left(col,CHARINDEX(';',Col))),0,
charindex(';',col))),(charindex(':',col)+1),4) as Age
from tab1
union
select SUBSTRING((SUBSTRING(right(col,CHARINDEX(';',Col)),0,
charindex(';',col))) ,0,charindex(':',col)) as Name,
SUBSTRING((SUBSTRING(right(col,CHARINDEX(';',Col)),0,
charindex(';',col))),(charindex(':',col)+1),4) as Age
from tab1
union
select SUBSTRING((SUBSTRING(substring(col,(CHARINDEX(';',Col) +
1),10),0,charindex(';',col))),0,charindex(':',col)) as Name,
SUBSTRING((SUBSTRING(substring(col,(CHARINDEX(';',Col) + 1),10),0,
charindex(';',col))),(charindex(':',col)+1),4) as Age
from tab1
The best way to achieve this would be UDF (user Defined Functions). This is just a
example and you may like to take it further from here.