A procedure to Reverse a String in PL/SQL - sql

I just started learning PL/SQL and I'm not sure how to create a procedure. The logic seems about right but I think there's some syntactical mistake in the first line. Here's my code:-
CREATE OR REPLACE PROCEDURE ReverseOf(input IN varchar2(50)) IS
DECLARE
reverse varchar2(50);
BEGIN
FOR i in reverse 1..length(input) LOOP
reverse := reverse||''||substr(input, i, 1);
END LOOP;
dbms_output.put_line(reverse);
END;
/

Two things - you shouldn't specify the datatype size in procedure's/function's parameter list and you do not need the DECLARE keyword. Try this:
CREATE OR REPLACE PROCEDURE ReverseOf(input IN varchar2) IS
rev varchar2(50):='';
BEGIN
FOR i in reverse 1..length(input) LOOP
rev := rev||substr(input, i, 1);
END LOOP;
dbms_output.put_line(rev);
END;

Try it without PL/SQL!
WITH
params AS
(SELECT 'supercalifragilisticexpialidocious' phrase FROM dual),
WordReverse (inpt, outpt) AS
(SELECT phrase inpt, CAST(NULL AS varchar2(4000)) outpt FROM params
UNION ALL
SELECT substr(inpt,2,LENGTH(inpt)-1), substr(inpt,1,1) || outpt
FROM wordReverse
WHERE LENGTH(inpt) > 0
)
SELECT phrase,outpt AS reversed FROM wordReverse, params
WHERE LENGTH(outpt) = LENGTH(phrase) ;
PHRASE REVERSED
---------------------------------- -----------------------------------
supercalifragilisticexpialidocious suoicodilaipxecitsiligarfilacrepus
Citation: http://rdbms-insight.com/wp/?p=94

Another solution reverse string minimizing loop count
DECLARE
v_str VARCHAR2(100) DEFAULT 'MYSTRING';
v_len NUMBER;
v_left VARCHAR2(100);
v_right VARCHAR2(100);
BEGIN
v_len := LENGTH(v_str)/2;
FOR rec IN 1..v_len
LOOP
v_left := substr(v_str,rec,1) || v_left;
IF rec * 2 <= LENGTH(v_str) THEN
v_right := v_right || substr(v_str,-rec,1);
END IF;
END LOOP;
v_str := v_right || v_left;
dbms_output.put_line(v_str);
END;

set serveroutput on
declare
str1 varchar2(30);
len number(3);
str2 varchar2(30);
i number(3);
begin
str1:='&str1';
len:=length(str1);
for i in reverse 1..len
loop
str2:=str2 || substr(str1,i,1);
end loop;
dbms_output.put_line('Reverse string is: '||str2);
end;
/

create or replace procedure ap_reverse_number(input_no VARCHAR2) as
output_no VARCHAR2(100);
begin
for i in 1 .. length(input_no) loop
output_no := output_no || '' ||
substr(input_no, (length(input_no) - i + 1), 1);
end loop;
dbms_output.put_line('Input no. is :' || input_no);
dbms_output.put_line('Output no. is:' || output_no);
end;
This should do the job correctly.

Try this one line statement to reverse the string in sql
with a as (select 'brahma' k from dual)
select listagg(substr(k,length(k)-level+1,1),'') within group (order by 1) b from a connect by level<length(k)+1

Try this one line query to reverse a string or a number.
select reverse('HelloWorld') from dual;

declare
name varchar2(20):='&name';
rname varchar2(20);
begin
for i in reverse 1..length(name)
loop
rname:=rname||substr(name,i,1);
end loop;
display(rname);
end;

Related

Return SQL Array from string [duplicate]

I'd like to create an in-memory array variable that can be used in my PL/SQL code. I can't find any collections in Oracle PL/SQL that uses pure memory, they all seem to be associated with tables. I'm looking to do something like this in my PL/SQL (C# syntax):
string[] arrayvalues = new string[3] {"Matt", "Joanne", "Robert"};
Edit:
Oracle: 9i
You can use VARRAY for a fixed-size array:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t('Matt', 'Joanne', 'Robert');
begin
for i in 1..array.count loop
dbms_output.put_line(array(i));
end loop;
end;
Or TABLE for an unbounded array:
...
type array_t is table of varchar2(10);
...
The word "table" here has nothing to do with database tables, confusingly. Both methods create in-memory arrays.
With either of these you need to both initialise and extend the collection before adding elements:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t(); -- Initialise it
begin
for i in 1..3 loop
array.extend(); -- Extend it
array(i) := 'x';
end loop;
end;
The first index is 1 not 0.
You could just declare a DBMS_SQL.VARCHAR2_TABLE to hold an in-memory variable length array indexed by a BINARY_INTEGER:
DECLARE
name_array dbms_sql.varchar2_table;
BEGIN
name_array(1) := 'Tim';
name_array(2) := 'Daisy';
name_array(3) := 'Mike';
name_array(4) := 'Marsha';
--
FOR i IN name_array.FIRST .. name_array.LAST
LOOP
-- Do something
END LOOP;
END;
You could use an associative array (used to be called PL/SQL tables) as they are an in-memory array.
DECLARE
TYPE employee_arraytype IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
employee_array employee_arraytype;
BEGIN
SELECT *
BULK COLLECT INTO employee_array
FROM employee
WHERE department = 10;
--
FOR i IN employee_array.FIRST .. employee_array.LAST
LOOP
-- Do something
END LOOP;
END;
The associative array can hold any make up of record types.
Hope it helps,
Ollie.
You can also use an oracle defined collection
DECLARE
arrayvalues sys.odcivarchar2list;
BEGIN
arrayvalues := sys.odcivarchar2list('Matt','Joanne','Robert');
FOR x IN ( SELECT m.column_value m_value
FROM table(arrayvalues) m )
LOOP
dbms_output.put_line (x.m_value||' is a good pal');
END LOOP;
END;
I would use in-memory array. But with the .COUNT improvement suggested by uziberia:
DECLARE
TYPE t_people IS TABLE OF varchar2(10) INDEX BY PLS_INTEGER;
arrayvalues t_people;
BEGIN
SELECT *
BULK COLLECT INTO arrayvalues
FROM (select 'Matt' m_value from dual union all
select 'Joanne' from dual union all
select 'Robert' from dual
)
;
--
FOR i IN 1 .. arrayvalues.COUNT
LOOP
dbms_output.put_line(arrayvalues(i)||' is my friend');
END LOOP;
END;
Another solution would be to use a Hashmap like #Jchomel did here.
NB:
With Oracle 12c you can even query arrays directly now!
Another solution is to use an Oracle Collection as a Hashmap:
declare
-- create a type for your "Array" - it can be of any kind, record might be useful
type hash_map is table of varchar2(1000) index by varchar2(30);
my_hmap hash_map ;
-- i will be your iterator: it must be of the index's type
i varchar2(30);
begin
my_hmap('a') := 'apple';
my_hmap('b') := 'box';
my_hmap('c') := 'crow';
-- then how you use it:
dbms_output.put_line (my_hmap('c')) ;
-- or to loop on every element - it's a "collection"
i := my_hmap.FIRST;
while (i is not null) loop
dbms_output.put_line(my_hmap(i));
i := my_hmap.NEXT(i);
end loop;
end;
Sample programs as follows and provided on link also https://oracle-concepts-learning.blogspot.com/
plsql table or associated array.
DECLARE
TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
salary_list salary;
name VARCHAR2(20);
BEGIN
-- adding elements to the table
salary_list('Rajnish') := 62000; salary_list('Minakshi') := 75000;
salary_list('Martin') := 100000; salary_list('James') := 78000;
-- printing the table name := salary_list.FIRST; WHILE name IS NOT null
LOOP
dbms_output.put_line ('Salary of ' || name || ' is ' ||
TO_CHAR(salary_list(name)));
name := salary_list.NEXT(name);
END LOOP;
END;
/
Using varray is about the quickest way to duplicate the C# code that I have found without using a table.
Declare your public array type to be use in script
type t_array is varray(10) of varchar2(60);
This is the function you need to call - simply finds the values in the string passed in using a comma delimiter
function ConvertToArray(p_list IN VARCHAR2)
RETURN t_array
AS
myEmailArray t_array := t_array(); --init empty array
l_string varchar2(1000) := p_list || ','; - (list coming into function adding final comma)
l_comma_idx integer;
l_index integer := 1;
l_arr_idx integer := 1;
l_email varchar2(60);
BEGIN
LOOP
l_comma_idx := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_idx = 0;
l_email:= SUBSTR(l_string, l_index, l_comma_idx - l_index);
dbms_output.put_line(l_arr_idx || ' - ' || l_email);
myEmailArray.extend;
myEmailArray(l_arr_idx) := l_email;
l_index := l_comma_idx + 1;
l_arr_idx := l_arr_idx + 1;
END LOOP;
for i in 1..myEmailArray.count loop
dbms_output.put_line(myEmailArray(i));
end loop;
dbms_output.put_line('return count ' || myEmailArray.count);
RETURN myEmailArray;
--exception
--when others then
--do something
end ConvertToArray;
Finally Declare a local variable, call the function and loop through what is returned
l_array t_array;
l_Array := ConvertToArray('email1#gmail.com,email2#gmail.com,email3#gmail.com');
for idx in 1 .. l_array.count
loop
l_EmailTo := Trim(replace(l_arrayXX(idx),'"',''));
if nvl(l_EmailTo,'#') = '#' then
dbms_output.put_line('Empty: l_EmailTo:' || to_char(idx) || l_EmailTo);
else
dbms_output.put_line
( 'Email ' || to_char(idx) ||
' of array contains: ' ||
l_EmailTo
);
end if;
end loop;

Handle a very large string in pl/sql script

I am trying to run below code which reads the index definition for table A so that it can be created again after I delete/create that in this script. This script runs fine when the returned value(ddl) is small but in other environments where the value is large with 140K characters in one row this script fails with below mentioned error. Please note that I cannot use spool in this case due to some restrictions. Could someone help on how to resolve this issue or suggest some another approach?
Thanks in advance.
"An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2)."
SET SERVEROUTPUT ON;
DECLARE
my_cursor SYS_REFCURSOR;
TYPE clob_array IS VARRAY(15) OF CLOB;
index_array clob_array := clob_array();
v_clob CLOB;
--index_array SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
BEGIN
OPEN my_cursor FOR 'select replace(dbms_metadata.get_ddl (''INDEX'', index_name), ''"C",'', '''')
from user_indexes
where table_name = ''A''';
LOOP FETCH my_cursor INTO v_clob;
EXIT WHEN my_cursor%NOTFOUND;
index_array.extend;
index_array(index_array.count) := v_clob;
dbms_output.put_line(index_array(index_array.count));
END LOOP;
CLOSE my_cursor;
END;
/
I simulated this issue you are getting this error because of the dbms_output.put_line which displays the output.Try switching to UTL_FILE at the server side OR Try for any alternatives
By the way, the code can be simplified to:
declare
type clob_array is table of clob;
index_array clob_array := clob_array();
begin
for r in (
select replace(dbms_metadata.get_ddl('INDEX', index_name), '"C",') as index_ddl
from user_indexes
where table_name = 'A'
)
loop
index_array.extend;
index_array(index_array.count) := r.index_ddl;
dbms_output.put_line(substr(index_array(index_array.count), 1, 32767));
end loop;
end;
I used substr() to limit the value passed to dbms_output.put_line to its documented limit. You could probably work around it by splitting the text into smaller chunks, and maybe finding the position of the last blank space before position 32767 in order to avoid splitting a word.
Here's what I came up with:
declare
type clob_array is table of clob;
index_array clob_array := clob_array();
procedure put_line
( p_text clob )
is
max_len constant simple_integer := 32767;
line varchar2(max_len);
remainder clob := p_text;
begin
while dbms_lob.getlength(remainder) > max_len loop
line := dbms_lob.substr(remainder,max_len);
line := substr(line, 1, instr(line, ' ', -1));
remainder := substr(remainder, length(line) +1);
dbms_output.put_line(line);
end loop;
if length(trim(remainder)) > 0 then
dbms_output.put_line(remainder);
end if;
end put_line;
begin
for r in (
select replace(dbms_metadata.get_ddl('INDEX', index_name), '"C",') as index_ddl
from user_indexes
where table_name = 'A'
)
loop
index_array.extend;
index_array(index_array.count) := r.index_ddl;
put_line(index_array(index_array.count));
end loop;
end;

Dynamically Identify Table (Table Identified by Variable)

I'm trying to create a procedure that will allow me to write an existing row to another table dynamically but the row declaration and insert-statement in the following snippet don't work. The error message indicates that the view hasn't been identified although outputting the target_table.table_name works just fine.
More will be added to the block later on - such as a column with the operation (e.g. INSERT or UPDATE). This is just a simple example and the last procedure (pass_reference) is used to trigger the procedure.
Any help would be much appreciated.
CREATE OR REPLACE PROCEDURE denormalize (new_cursor sys_refcursor, target_table_name varchar)
IS
target_table user_tables%rowtype;
sql_target_table varchar(200) := 'select * from user_tables where table_name = :target_table_name';
row target_table%rowtype;
BEGIN
execute immediate sql_target_table into target_table using target_table_name;
LOOP
fetch new_cursor into row;
exit when new_cursor%notfound;
insert into target_table values row;
commit;
END LOOP;
END denormalize;
/
CREATE OR REPLACE PROCEDURE pass_reference
AS
new_cursor sys_refcursor;
BEGIN
open new_cursor for select * from sales where sales_id=1;
denormalize(new_cursor, 'NEW_SALES');
END;
/
please check this code, it's not working only as for example, as you see for working columns in your cursor should be named as columns in destination table.
I take this code from package that create html table in mail base on view, hope you found this example useful
good luck
declare
in_view_name varchar2(30);
in_table_name varchar2(30) := 'your_new_table';
out_rc number;
out_rc_txt varchar2(1000);
l_cursor number;
l_sql varchar2(50) := 'select * from ' || in_view_name;
l_col_cnt binary_integer;
l_col_tab dbms_sql.desc_tab;
l_column_value varchar2(4000);
l_is_empty boolean := true;
l_insert_header varchar2(1000);
l_insert varchar2(32000);
begin
out_rc := 0;
out_rc_txt := 'OK';
l_cursor := dbms_sql.open_cursor;
dbms_sql.parse(l_cursor, l_sql, dbms_sql.native);
l_col_cnt := dbms_sql.execute(l_cursor);
dbms_sql.describe_columns(l_cursor, l_col_cnt, l_col_tab);
l_insert_header := 'insert into '||in_table_name||'(';
if l_col_cnt > 0 then
-- header
for i in l_col_tab.first .. l_col_tab.last loop
dbms_lob.append(l_insert_header, l_col_tab(i).col_name);
if i != l_col_tab.last then
dbms_lob.append(l_insert_header, ',');
end if;
dbms_sql.define_column(l_cursor, i, l_column_value, 4000);
end loop;
l_insert_header := l_insert_header || ') values(';
-- data
while dbms_sql.fetch_rows(l_cursor) > 0 loop
l_is_empty := false;
l_insert := l_insert_header;
for i in l_col_tab.first .. l_col_tab.last loop
dbms_sql.column_value(l_cursor, i, l_column_value);
l_insert := l_insert || '''' || l_column_value || ''','
if not in_attachment then
dbms_lob.append(out_table, l_td);
end if;
if (not in_attachment) or (l_column_value is not null) then
dbms_lob.append(out_table, nvl(l_column_value, l_nbsp));
end if;
if (not in_attachment) or (i != l_col_tab.last) then
dbms_lob.append(out_table, l_tdc);
end if;
end loop;
l_insert := substr(l_insert, 1, length(l_insert) - 1) || ')';
execute immediate l_insert;
end loop;
end if;
dbms_sql.close_cursor(l_cursor);
end;

How do I view a CLOB output parameter in TOAD from an Oracle Stored Procedure?

I have a stored procedure in a package in an Oracle database that has 2 input parameters + 1 output CLOB parameter. How do I view the output in Toad? (Preferably with the user only having execute/select permissions)
Solution:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
DBMS_OUTPUT.PUT_LINE(my_output_parameter);
END;
Don't forget to execute as script, rather than just execute statement, and results appear in the DBMS Output window, not the datagrid.
I guess DBMS_OUTPUT.PUT_LINE has an internal line limit of 255 chars. However it has been removed from 10g Release 2 onwards. You can try inserting the column data in a table and view it later on by querying that table.
Please refer -
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:146412348066
Would you consider printing the CLOB as a result set? You could then use a PIPELINED function (more about them here: PIPELINED functions by Tim Hall) which would return the CLOB line by line, take a look at the example below:
CREATE TABLE my_clob_tab (
id NUMBER,
clob_col CLOB
)
/
INSERT INTO my_clob_tab
VALUES (1,
to_clob('first line' || chr(10) ||
'second line, a longer one' || chr(10) ||
'third'))
/
CREATE OR REPLACE TYPE t_my_line_str AS TABLE OF VARCHAR2(2000)
/
CREATE OR REPLACE FUNCTION print_clob_func(p_id IN NUMBER)
RETURN t_my_line_str PIPELINED
AS
v_buffer VARCHAR2(32767);
v_clob CLOB;
v_len NUMBER;
v_offset NUMBER := 1;
v_line_break_pos NUMBER;
v_amount NUMBER;
BEGIN
SELECT clob_col
INTO v_clob
FROM my_clob_tab
WHERE id = p_id;
IF v_clob IS NOT NULL THEN
v_len := dbms_lob.getlength(v_clob);
WHILE v_offset < v_len
LOOP
v_line_break_pos := instr(v_clob, chr(10), v_offset);
IF v_line_break_pos = 0 THEN
v_amount := v_len - v_offset + 1;
ELSE
v_amount := v_line_break_pos - v_offset;
END IF;
dbms_lob.read(v_clob, v_amount, v_offset, v_buffer);
v_offset := v_offset + v_amount + 1;
PIPE ROW (v_buffer);
END LOOP;
END IF;
END;
/
(the function can be changed so that it takes as a parameter the CLOB you get from your procedure)
The code reads the content of the CLOB line by line (I assumed that the line separator is CHR(10) - if you are on Windows, you can change it to CHR(10) || CHR(13)) and PIPEs each line to the SELECT statement.
The function that reads the clob could also print the output to the standard output via dbms_output.put_line, but it would be trickier, because you'd have to take into account that standard output's maximal line length is limitied to, correct me if I'm wrong, 2000 characters, but it is doable (can't try that solution right now, unfortunately). In the meanwhile, please check above proposal and give me some feedback if that would work for you.
Back to the solution, now we can issue this SELECT statement:
SELECT COLUMN_VALUE AS clob_line_by_line FROM TABLE(print_clob_func(1));
Which will give us the following output:
CLOB_LINE_BY_LINE
-------------------------
first line
second line, a longer one
third
Check it at SQLFiddle: SQLFiddle example
Approach with inserting PL/SQL block and dbms_output:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
declare
vClob CLOB := my_output_parameter;
vPos number;
vLen number;
begin
vLen := DBMS_LOB.GetLength(vClob);
vPos := 1;
while vPos < vLen loop
DBMS_OUTPUT.Put(DBMS_LOB.Substr(vCLOB, 200, vPos));
vPos := vPos + 200;
end loop;
DBMS_OUTPUT.new_line;
end;
END;

How to remove more than one space in Oracle

I have an Oracle table which contains data like 'Shiv------Shukla' (consider '-' as space).
Now I need to write a program which leaves just one space and removes all other spaces.
Here is the program which I've made but it is not giving me expected result.
DECLARE
MAX_LIMIT VARCHAR2(50):=NULL;
REQ VARCHAR2(20):=NULL;
CURSOR C1 IS
SELECT *
FROM ASSET_Y;
BEGIN
FOR REC IN C1
LOOP
MAX_LIMIT:=LENGTH(REC.NAME)-LENGTH(REPLACE(REC.NAME,'-'));
FOR I IN 1..MAX_LIMIT
LOOP
UPDATE ASSET_Y
SET NAME=REPLACE(REC.NAME,'--','-')
WHERE REC.SNO=ASSET_Y.SNO;
COMMIT;
SELECT ASSET_Y.NAME INTO REQ FROM ASSET_Y WHERE ASSET_Y.SNO=REC.SNO;
DBMS_OUTPUT.PUT_LINE(REQ);
END LOOP;
END LOOP;
COMMIT;
END;
/
My table is
SQL> select * from asset_y;
SNO NAME FL
---------- -------------------- --
1 Shiv------Shukla y
2 Jinesh y
after running the procedure i m getting the following output.
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
PL/SQL procedure successfully completed.
Since regexp_replace is not available in Oracle 9i maybe you can use owa_pattern routines for simple regex replaces:
owa_pattern.change(fStr, '\s+', ' ', 'g');
More info about owa_pattern package can be found here
Bear in mind, that "\s" will match tabs and newlines as well.
With Oracle 9 you could write your own function:
CREATE FUNCTION remove_multi_spaces( in_value IN VARCHAR2 )
RETURN VARCHAR2
AS
v_result VARCHAR2(32767);
BEGIN
IF( in_value IS NOT NULL ) THEN
FOR i IN 1 .. ( LENGTH(in_value) - 1 ) LOOP
IF( SUBSTR( in_value, i, 2 ) <> ' ' ) THEN
v_result := v_result || SUBSTR( in_value, i, 1 );
END IF;
END LOOP;
v_result := v_result || SUBSTR( in_value, -1 );
END IF;
RETURN v_result;
END;
and call it in a single update-statement:
UPDATE asset_y
SET name = replace_multi_spaces( name );
BTW: With Oracle 10 you could use REGEXP_REPLACE.
Your problem is this part:
SET NAME=REPLACE(REC.NAME,'--','-')
However many times you do that within the inner loop it starts with the same value of REC.NAME as before. Changing it to this would fix it:
SET NAME=REPLACE(NAME,'--','-')
However, it is a pretty inefficient way to do this job if the table is large. You could instead do this:
BEGIN
LOOP
UPDATE ASSET_Y
SET NAME=REPLACE(NAME,'--','-')
WHERE NAME LIKE '%--%';
EXIT WHEN SQL%ROWCOUNT = 0;
END LOOP;
END;
/
Another way:
CREATE OR REPLACE
FUNCTION remove_multi_spaces( in_value IN VARCHAR2 )
RETURN VARCHAR2 IS
v_result VARCHAR2(32767) := in_value;
BEGIN
LOOP
EXIT WHEN INSTR(v_result,' ') = 0;
v_result := REPLACE(v_result, ' ', ' ');
END LOOP;
RETURN v_result;
END remove_multi_spaces;
Ack loops! No need to loop this
This will work in T-SQL...unfortunately I have no pl/sql environment to write this in. PL/SQL will have equivlents to everything used here (I think substr instead of substring and | instead of +)
declare #name varchar(200)
set #name = 'firstword secondword'
select left(#name,(patindex('% %',#name)-1)) + ' ' + ltrim(substring(#name,(patindex('% %',#name)+1),len(#name)))
You'll have to retool it to work for oracle and you'll need to replace any reference to #name to asset_y.name
select left(asset_y.name,(patindex('% %',asset_y.name)-1)) || ' ' || ltrim(substring(asset_y.name,(patindex('% %',asset_y.name)+1),len(asset_y.name)))
Sorry if it won't run as is, as I mentioned I lack an oracle install here to confirm...
Just to add...I normally turn that query above into a function named formatname and call it as select formatname(array_y.name) from... This allows me to include some form of error handling. The query will fail if patindex('% %',array_v.name) returns a null...meaning there is no space. You could do the same in a select statement using cases I guess:
select case when patindex('% %',array_v.name) > 0 then
left(asset_y.name,(patindex('% %',asset_y.name)-1)) || ' ' || ltrim(substring(asset_y.name,(patindex('% %',asset_y.name)+1),len(asset_y.name)))
else asset_y.name
from...