Function to extract array items to different columns postgresql - arrays

I'm trying to design a function to solve this problem. I have column with cities that looks like this.
1 |Curaçao-Amsterdam
2 |St. Christopher-Essequibo
3 |Texel-Riohacha-Buenos Aires-La Rochelle`
And I have used this query to extract it to an array of elements
select t2.rut1,t2.rutacompleta, t2.id
from (
select regexp_split_to_array(t.rutacompleta, E'[\-]+') as rut1,
t.rutacompleta,t.id
from (
select id, strpos(ruta, '-') as posinic, strpos(ruta, '-') as posfin,
ruta as rutacompleta
from dyncoopnet.todosnavios2
) t
) t2
That gives this result:
{Curaçao,Amsterdam}
{"St. Christopher",Essequibo}
{Texel,Riohacha,"Buenos Aires","La Rochelle"}`
And I want to create a function to extract * array elements to different columns. I have thought of a while function like this:
create or replace function extractpuertos()
returns text as
$body$
declare
i integer;
puerto text;
begin
i := 1
while (i >=1)
loop
with tv as(
select t2.rut1,t2.rutacompleta, t2.id from(
select regexp_split_to_array(t.rutacompleta, E'[\-]+') as rut1,
t.rutacompleta,t.id from(
select id, strpos(ruta, '-') as posinic, strpos(ruta, '-') as posfin,ruta as
rutacompleta from dyncoopnet.todosnavios2) t)t2
)
select tv.rut1[i] as puerto from tv;
end loop;
return puerto;
end;
But I'm not sure it is a proper solution, and how to implement it. Any hint?
Thanks in advance!

is it what you try to do?
create table:
t=# create table so65 (i int, t text);
CREATE TABLE
Time: 55.234 ms
populate data:
t=# copy so65 from stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 1 |Curaçao-Amsterdam
2 |St. Christopher-Essequibo
3 |Texel-Riohacha-Buenos Aires-La Rochelle>> >>
>> \.
COPY 3
Time: 2856.465 ms
split:
t=# select string_to_array(t,'-') from so65;
string_to_array
-----------------------------------------------
{Curaçao,Amsterdam}
{"St. Christopher",Essequibo}
{Texel,Riohacha,"Buenos Aires","La Rochelle"}
(3 rows)
Time: 4.428 ms
to one column:
t=# select unnest(string_to_array(t,'-')) from so65;
unnest
-----------------
Curaçao
Amsterdam
St. Christopher
Essequibo
Texel
Riohacha
Buenos Aires
La Rochelle
(8 rows)
Time: 1.662 ms

Related

Using alternate away instead of staging table to join data to itself

Trying to Optimize my code not to using a temptable, im new to sql server, the code i wrote gives me the info i need
Create table FI (
tipodoc numeric,
nmdoc varchar(20),
fno numeric,
rdata datetime,
fnoft numeric,
design varchar(60);
Insert into Fi (tipodoc ,
nmdoc,
fno,
rdata ,
fnoft ,
design)
values(1,'FT','18',2017-31-12,null,null),
(2,'NC',1,2018-31-12,18,'FT 18 18-12-2017'),
(1,'FT',1,2018-01-01,null,null),
(2,'NC',1,2018-31-12,1,'FT 1 01-01-2018'),
(1,'FT',2,2018-31-12,null,null),
(2,'NC',1,2019-31-12,5,'FT 2 31-12-2018')
select
Cod_final = convert(int,fi.fnoft+fi.tipodoc+fi.rdata),
fi.tipodoc,
fi.nmdoc,
fi.fno,
fi.rdata,
fi.fnoft,
ltrim(rtrim(CONVERT(varchar(30),replace(right(design,10 ),'.','-')))) as Ftdata
into tempFI
from fi
where fi.nmdoc Like '%Credito' and design like '%factura%';
select
tempFI.Cod_final,
fi.nmdoc,
fi.fno,
convert(date,fi.rdata) as DataDoc,
Fi.fnoft,
tempFI.rdata,
tempfi.Ftdata
from fi
inner join tempfi on fi.fnoft=tempFI.fnoft
where convert(int,fi.fnoft+fi.tipodoc+fi.rdata) = tempFI.Cod_final
and fi.tipodoc =3 and tempfi.ftdata like '%-%-%'
order by tempFI.Cod_final
go
DROP TABLE IF EXISTS tempfi
raw data
https://docs.google.com/spreadsheets/d/1VCd8ZQVt6ztLNNhUb-FRj1WIAGW8zYZP3AZbx3GaGTs/edit?usp=sharing
Data Output
Brief explanation of what i pretend and i have done
Tempfi.cod_final - since the fnoft, tipodoc repeat themselves over the years
the same fnoft and tipodoc( this is associated with nmdoc there is a number associated for document type ) this are both int , so i converted rdata which is datatime when document is created and this is my new number so i can identify correctly.
fno and fnoft - cod_final 42726 is fno 8 and was originited from fnoft 1268 is the same number in fno from different document type tipodoc
tempfi.rdata - i was testing in getting the correct value for tempfi.codfinal date time when document was created, which i failed miserably
tempfi.FTdata . is a string that contains the number of fnoft and date of creation so my tempfi.rdata should be the same as tempfi.ftdata
thanks for your help , and im sorry if this is a bit messy
Update
So after reading about CTE i Have built this
with cte_fi2 as
(
select
Cod_final = convert(int,fi.fnoft+fi.tipodoc+fi.rdata),
fi.tipodoc,
fi.nmdoc,
fi.fno,
fi.rdata,
fi.eteliquido
from fi
where fi.tipodoc = 1
),
cte_fi as
(
select
Cod_final = convert(int,fi.fnoft+fi.tipodoc+fi.rdata),
fi.tipodoc,
fi.nmdoc,
fi.fno,
fi.rdata,
fi.fnoft,
ltrim(rtrim(CONVERT(varchar(30),replace(right(design,10 ),'.','-')))) as Ftdata
from fi
where fi.tipodoc=3 and design like '%factura%'
)
select
fi.nmdoc,
fi.fno as NºNC,
convert(date,fi.rdata) as DataNC,
Fi.fnoft as NºFT,
cte_fi2.rdata as DataFTcte2,
cte_fi.ftdata,
from fi
inner join cte_fi on fi.fno=cte_fi.fno
inner join cte_fi2 on fi.fno = cte_fi2.fno
where fi.tipodoc =3 and cte_fi.ftdata like '%-%-%'
order by cte_fi.Cod_final
Im still doing something wrong with with ct2_fi2.rdata it should return same value as cte_fi.tdata, ct2_fi2.rdata is date creation of fi.tipodoc = 1,
guess im doing something wrong in the inner join. i dont understand what yet
Nmdoc FNo fi.rdata Fnoft cte_fi2.rdata cte_fi.ftdata
NC 3 2013-02-08 0 08-02-2013 2016-01-04
Output

PostgreSQL JSON building an array without null values

The following query
SELECT jsonb_build_array(jsonb_build_object('use', 'Home'),
CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work')
END)
produces
[{"use":"Home"},null]
When I actually want
[{"use":"Home"}]
How do I go about doing this? json_strip_nulls() does not work for me.
By using a PostgreSQL array like that:
SELECT array_to_json(array_remove(ARRAY[jsonb_build_object('use', 'Home'),
CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END], null))
which does produce:
[{"use": "Home"}]
while, to be sure:
SELECT array_to_json(array_remove(ARRAY[jsonb_build_object('use', 'Home'),
CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END,
jsonb_build_object('real_use', 'NotHome')], null))
does produce:
[{"use": "Home"},{"real_use": "NotHome"}]
Creating a custom function seems to be the simplest way.
create or replace function jsonb_build_array_without_nulls(variadic anyarray)
returns jsonb language sql immutable as $$
select jsonb_agg(elem)
from unnest($1) as elem
where elem is not null
$$;
select
jsonb_build_array_without_nulls(
jsonb_build_object('use', 'home'),
case when 1 = 2 then jsonb_build_object('use', 'work') end
)
jsonb_build_array_without_nulls
---------------------------------
[{"use": "home"}]
(1 row)
I'm assuming this query is generaetd dynamically, somehow. If you're in control of the SQL generation, you can also use ARRAY_AGG(...) FILTER(...) instead, which, depending on your real world query, might be more convenient than using all the different array conversion functions suggested by Patrick.
SELECT (
SELECT json_agg(v) FILTER (WHERE v IS NOT NULL)
FROM (
VALUES
(jsonb_build_object('use', 'Home')),
(CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END)
) t (v)
)
Or also:
SELECT (
SELECT json_agg(v)
FROM (
VALUES
(jsonb_build_object('use', 'Home')),
(CASE WHEN 1 = 2 THEN jsonb_build_object('use', 'Work') END)
) t (v)
WHERE v IS NOT NULL
)
One other way that this can be handled is the following:
SELECT jsonb_build_array(
jsonb_build_object('use', 'Home'),
CASE
WHEN 1 = 2 THEN jsonb_build_object('use', 'Work')
ELSE '"null"'
END
) - 'null'
(Unfortunately it's not really possible to do much with null by itself in postgres -- or most other DBs)
In the case above, '"null"' could be replaced with just about any unique string that would not be mistaken for live data in the array. I would not use numbers since - 0 would actually try to remove the first item from the array, rather than a number within the array. But you could probably use '"0"' and remove using something like - '0' if you wanted.
For those not using CASE, a COALESCE could be used to convert nulls into the desired string (alas, no NVL, IFNULL or ISNULL in postgres, but at least COALESCE is portable)

Listagg for large data and included values in quotes

I would like to get all the type names of a user seperated in commas and included in single quotes. The problem I have is that &apos ; character is displayed as output instead of '.
Trial 1
SELECT LISTAGG(TYPE_NAME, ''',''') WITHIN GROUP (ORDER BY TYPE_NAME)
FROM ALL_TYPES
WHERE OWNER = 'USER1';
ORA-01489: result of string concatenation is too long
01489. 00000 - "result of string concatenation is too long"
*Cause: String concatenation result IS more THAN THE maximum SIZE.
*Action: Make sure that the result is less than the maximum size.
Trial 2
SELECT '''' || RTRIM(XMLAGG(XMLELEMENT(E,TYPE_NAME,q'$','$' ).EXTRACT('//text()')
ORDER BY TYPE_NAME).GetClobVal(),q'$','$') AS LIST
FROM ALL_TYPES
WHERE OWNER = 'USER1';
&apos ;TYPE1&apos ;,&apos ;TYPE2&apos ;, ............... ,'TYPE3&apos ;,&apos ;
Trial 3
SELECT
dbms_xmlgen.CONVERT(XMLAGG(XMLELEMENT(E,TYPE_NAME,''',''').EXTRACT('//text()')
ORDER BY TYPE_NAME).GetClobVal())
AS LIST
FROM ALL_TYPES
WHERE OWNER = 'USER1';
TYPE1&amp ;apos ;,&amp ;apos ;TYPE2&amp ;apos ;, ......... ,&amp ;apos ;TYPE3&amp ;apos ;,&amp ;apos ;
I don;t want to call replace function and then make substring as follow
With tbla as (
SELECT REPLACE('''' || RTRIM(XMLAGG(XMLELEMENT(E,TYPE_NAME,q'$','$' ).EXTRACT('//text()')
ORDER BY TYPE_NAME).GetClobVal(),q'$','$'),''',''') AS LIST
FROM ALL_TYPES
WHERE OWNER = 'USER1')
select SUBSTR(list, 1, LENGTH(list) - 2)
from tbla;
Is there any other way ?
use dbms_xmlgen.convert(col, 1) to prevent escaping.
According to Official docs, the second param flag is:
flag
The flag setting; ENTITY_ENCODE (default) for encode, and
ENTITY_DECODE for decode.
ENTITY_DECODE - 1
ENTITY_ENCODE - 0 default
Try this:
select
''''||substr(s, 1, length(s) - 2) list
from (
select
dbms_xmlgen.convert(xmlagg(xmlelement(e,type_name,''',''')
order by type_name).extract('//text()').getclobval(), 1) s
from all_types
where owner = 'USER1'
);
Tested the similar code below with 100000 rows:
with t (s) as (
select level
from dual
connect by level < 100000
)
select
''''||substr(s, 1, length(s) - 2)
from (
select
dbms_xmlgen.convert(xmlagg(XMLELEMENT(E,s,''',''') order by s desc).extract('//text()').getClobVal(), 1) s
from t);

accessing second element in regexp_matches array

I have a table with a field that has strings like this:
US 19;PA 65
I need to split this out into four new fields like:
'US ','19','PA','65'
regexp_matches seems to be the ticket. I can use the following statements to extract 'US' into one field and '19' into another.
UPDATE osm_motorway SET shieldcl1 = (regexp_matches(ref, '^[A-Z]+', 'i'))[1];
UPDATE osm_motorway SET shieldlbl1 = (regexp_matches(ref, '\d+', 'i'))[1];
But I can't get 'PA' and '65' into their own fields with the following. They return empty:
UPDATE osm_motorway SET shieldcl2 = (regexp_matches(ref, '^[A-Z]+', 'i'))[2];
UPDATE osm_motorway SET shieldlbl2 = (regexp_matches(ref, '\d+', 'i'))[2];
How do I access the second match with regexp_matches?
Use both patterns in an alternative and the flag 'g' for global searching to get all matches at once:
select regexp_matches('US 19;PA 65', '[A-Z]+|\d+', 'ig');
regexp_matches
----------------
{US}
{19}
{PA}
{65}
(4 rows)
Use this query to convert the result into an array:
select array(select (regexp_matches('US 19;PA 65', '[A-Z]+|\d+', 'ig'))[1]);
array
---------------
{US,19,PA,65}
(1 row)
Create the function for convenience:
create or replace function split_ref(ref text)
returns text[] language sql as $$
select array(select (regexp_matches(ref, '[A-Z]+|\d+', 'ig'))[1])
$$;
and use it in your update statement:
update osm_motorway
set
shieldcl1 = (split_ref(ref))[1],
shieldlbl1 = (split_ref(ref))[2],
shieldcl2 = (split_ref(ref))[3],
shieldlbl2 = (split_ref(ref))[4];
An alternative way to split the string (without regexp):
select string_to_array(translate('US 19;PA 65', ' ', ';'), ';');
string_to_array
-----------------
{US,19,PA,65}
(1 row)

Oracle split text into multiple rows

Inside a varchar2 column I have text values like :
aaaaaa. fgdfg.
bbbbbbbbbbbbbb ccccccccc
dddddd ddd dddddddddddd,
asdasdasdll
sssss
if i do select column from table where id=... i get the whole text in a single row, normally.
But i would like to get the result in multiple rows, 5 for the example above.
I have to use just one select statement, and the delimiters will be new line or carriage return (chr(10), chr(13) in oracle)
Thank you!
Like this, maybe (but it all depends on the version of oracle you are using):
WITH yourtable AS (SELECT REPLACE('aaaaaa. fgdfg.' ||chr(10)||
'bbbbbbbbbbbbbb ccccccccc ' ||chr(13)||
'dddddd ddd dddddddddddd,' ||chr(10)||
'asdasdasdll ' ||chr(13)||
'sssss '||chr(10),chr(13),chr(10)) AS astr FROM DUAL)
SELECT REGEXP_SUBSTR ( astr, '[^' ||chr(10)||']+', 1, LEVEL) data FROM yourtable
CONNECT BY LEVEL <= LENGTH(astr) - LENGTH(REPLACE(astr, chr(10))) + 1
see: Comma Separated values in Oracle
The answer by Kevin Burton contains a bug if your data contains empty lines.
The adaptation below, based on the solution invented here, works. Check that post for an explanation on the issue and the solution.
WITH yourtable AS (SELECT REPLACE('aaaaaa. fgdfg.' ||chr(10)||
'bbbbbbbbbbbbbb ccccccccc ' ||chr(13)||
chr(13)||
'dddddd ddd dddddddddddd,' ||chr(10)||
'asdasdasdll ' ||chr(13)||
'sssss '||chr(10),chr(13),chr(10)) AS astr FROM DUAL)
SELECT REGEXP_SUBSTR ( astr, '([^' ||chr(10)||']*)('||chr(10)||'|$)', 1, LEVEL, null, 1) data FROM yourtable
CONNECT BY LEVEL <= LENGTH(astr) - LENGTH(REPLACE(astr, chr(10))) + 1;

Resources