Split one row into multiple rows (fixed width) - sql

One row of incoming record has multiple sub-records concatenated. Each sub record is 9 characters long. I have 8 such sub-records in each row. So each row is (8x9=72 char). Is there a way i can split 1 record into 8 records here?
Input
123456789123456789123456789123456789123456789123456789123456789123456789
Output
123456789
123456789
123456789
123456789
123456789
123456789
123456789
123456789
I know i can do this with 8 sub queries and union them .. is there a better way?

or how about this:
SELECT substr(t1.astr,n*9+1,9)
FROM t1,
(SELECT ROWNUM-1 n FROM dual CONNECT BY LEVEL <= 8) t2
table t1 contains your strings, table t2 is a generated list of sequential numbers from 0 to 7 by cross joining it to the t1 table we can use it to cut up the string column.

I've seen people use a secondary table containing only the values 1 through 8, then joining with that table. You can use the value to substring your record.
Something like
SELECT SUBSTRING(long_val, eight.value * 9, 9)
FROM mytable, eight
Note: Query untested, but I hope the idea gets across.

If it is predictable that each line will always split into a fixed number of lines, something like the following should be possible:
select
b.iterator,
substr(a.mystring,b.iterator*9-8,9) as split
from
(select '123456789123456789123456789123456789123456789123456789123456789123456789' as mystring from dual) a,
(select 1 as iterator from dual union
select 2 as iterator from dual union
select 3 as iterator from dual union
select 4 as iterator from dual union
select 5 as iterator from dual union
select 6 as iterator from dual union
select 7 as iterator from dual union
select 8 as iterator from dual) b
EDIT: Kevin's iterator was simpler and better than my brute force version. The b subquery shoud be (SELECT ROWNUM as iterator FROM dual CONNECT BY LEVEL <= 8)

Related

SQL Query to select a specific part of the values in a column

I have a table in a database and one of the columns of the table is of the format AAA-BBBBBBB-CCCCCCC(in the table below column Id) where A, B and C are all numbers (0-9). I want to write a SELECT query such that for this column I only want the values in the format BBBBBBB-CCCCCCC. I am new to SQL so not sure how to do this. I tried using SPLIT_PART on - but not sure how to join the second and third parts.
Table -
Id
Name
Age
123-4567890-1234567
First Name
199
456-7890123-4567890
Hulkamania
200
So when the query is written the output should be like
Output
4567890-1234567
7890123-4567890
As mentioned in the request comments, you should not store a combined number, when you are interested in its parts. Store the parts in separate columns instead.
However, as the format is fixed 'AAA-BBBBBBB-CCCCCCC', it is very easy to get the substring you are interested in. Just take the string from the fifth position on:
select substr(col, 5) from mytable;
You can select the right part of a column starting at the 4th character
SELECT RIGHT(Id, LEN(Id)-4) AS TrimmedId;
Another option using regexp_substr
with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ;
Demo
SQL> with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ; 2 3 4 5 6
RESULT
--------------------------------------------------------------------------------
4567890-1234567
7890123-4567890
SQL>

Update numbers with a counter in SQL

Suppose I have a table containing increasing and irregularly incremented numbers (3, 4, 7, 11, 16,...) in the first column col1.
I want to update col1 such that the numbers will be 1000 plus the row number. So something like:
UPDATE tab1 SET col1 = 1000 + row_number()
I am using Oracle SQL and would appreciate any help! Many thanks in advance.
In Oracle, the simplest method might be to create a sequence and use that:
create sequence temp_seq_rownum;
update tab1
set col1 = 1000 + temp_seq_rownum.nextval;
drop sequence temp_seq_rownum;
At the time I am posting this, a different answer is already marked as "correct". That answer assigns values 1001, 1002 etc. with no regard to the pre-existing values in col1.
To me that makes no sense. The problem is more interesting if the OP actually meant what he wrote - the new values should follow the pre-existing values in col1, so that while the numbers 1001, 1002, 1003 etc. are not the same as the pre-existing values, they still preserve the pre-existing order.
In Oracle, row_number() is not allowed in a straight update statement. Perhaps the simplest way to achieve this task is with a merge statement, as demonstrated below.
For testing I created a table with two columns, with equal values before the update, simply so that we can verify that the new values in col1 preserve the pre-existing order after the update. I start with the test table, then the merge statement and a select statement to see what merge did.
create table t (col1, col2) as
select 3, 3 from dual union all
select 4, 4 from dual union all
select 16, 16 from dual union all
select 7, 7 from dual union all
select 1, 1 from dual
;
Table T created.
merge into t
using ( select rowid as rid, 1000 + row_number() over (order by col1) as val
from t) s
on (t.rowid = s.rid)
when matched then update set col1 = val;
5 rows merged.
select *
from t
order by col1
;
COL1 COL2
------- -------
1001 1
1002 3
1003 4
1004 7
1005 16

How to check if the length of a string is more than one word and keep only the first word else keep the entire string in SQL?

I have the following table in sql.
I want to keep only the first word in the Name column. I have written the code below however when I run it it extracts the first word for strings longer that one word but returns empty cell for strings which consist of one word only. Could you please advise me how should I modify it to achieve the desired result of keeping only the first word of all strings.
SELECT ID,substr(Name, 1, instr ( Name, ' ' ) -1 ) AS Name FROM names_list
DBMS Toad for Oracle
How about regexp_substr()?
select regexp_substr(name, '^[^ ]+')
from names_list;
This is more flexible than instr(), because you have more control over the separators. For instance, if a comma is sometimes used as well:
select regexp_substr(name, '^[^ ,]+')
from names_list;
This would select the first word out of the name column:
SQL> with names_list (id, name) as
2 (select 1, 'John Smith' from dual union all
3 select 2, 'One' from dual union all
4 select 3, 'Nikola O''Neil' from dual union all
5 select 4, 'Rose Ann Lee' from dual union all
6 select 5, 'Neil' from dual union all
7 select 6, 'William Hugh Forest' from dual union all
8 select 7, 'Andrew' from dual
9 )
10 select id,
11 regexp_substr(name, '^\w+') name
12 from names_list;
ID NAME
---------- --------------------
1 John
2 One
3 Nikola
4 Rose
5 Neil
6 William
7 Andrew
7 rows selected.
SQL>

Make in (SQL) dynamic for incoming values

Is it possible for in statement to be dynamic? like dynamic comma separation
for example:
DATA=1
select * from dual
where
account_id in (*DATA);
DATA=2
select * from dual
where
account_id in (*DATA1,*DATA2);
FOR DATA=n
how will i make the in statement dynamic/flexible (comma) for unknown quantity.
select * from dual
where
account_id in (*DATAn,*DATAn+1,etc);
A hierarchical query might help.
acc CTE represents sample data
lines #9 - 11 are what you might be looking for; data is concatenated with level pseudocolumn's value returned by a hierarchical query
Here you go:
SQL> with acc (account_id) as
2 (select 'data1' from dual union all
3 select 'data2' from dual union all
4 select 'data3' from dual union all
5 select 'data4' from dual
6 )
7 select *
8 from acc
9 where account_id in (select 'data' || level
10 from dual
11 connect by level <= &n
12 );
Enter value for n: 1
ACCOU
-----
data1
SQL> /
Enter value for n: 3
ACCOU
-----
data1
data2
data3
SQL>
As I can see, You are using the number in Where clause, Substitution will be enough to solve your problem.
See the example below:
CREATE table t(col1 number);
insert into t values(1);
insert into t values(2);
insert into t values(3);
-- Substitution variable initialization
define data1=1;
define data2='1,2';
--
-- With data1
select * from t where col1 in (&data1);
Output:
-- With data2
select * from t where col1 in (&data2);
Output:
Hope, This will be helpful to you.
Cheers!!
The basic problem is not the listagg function but a major misconception that just because elements in a string list are comma separated that a string with commas in it is a list. Not So. Consider a table with the following rows.
Key
- Data1
- Data2
- Data1,Data2
And the query: Select * from table_name where key = 'wanted_key'; Now if all commas separate independent elements then what value in for "wanted_Key" is needed to return only the 3rd row above? Even with the IN predicate 'Data1,Data2' is still just 1 value, not 2. For 2 values it would have to be ('Data1','Data2').
The problem you're having with Listagg is not because of the comma but because it's not the appropriate function. Listagg takes values from multiple rows and combines then into a single comma separated string but not comma separated list. Example:
with elements as
( select 'A' code, 'Data1' item from dual union all
select 'A', 'Data2' from dual union all
select 'A', 'Data3' from dual
)
select listagg( item, ',') within group (order by item)
from elements group by code;
(You might also want to try 'Data1,Data2' as a single element. Watch out.
What you require is a query that breaks out each element separately. This can be done with
with element_list as
(select 'Data1,Data2,Data3' items from dual) -- get paraemter string
, parsed as
(select regexp_substr(items,'[^,]+',1,level) item
from element_list connect by regexp_substr(items,'[^,]+',1,level) is not null -- parse string to elements
)
The CTE "parsed" can now be used as table/view in your query.
This will not perform as well as querying directly with a parameter, but performance degradation is the cost of dynamic/flexible queries.
Also as set up this will NOT handle parameters which contain commas within an individual element. That would require much more code as you would have to determine/design how to keep the comma in those elements.

Oracle db query for regex is not giving correct result

I am using Oracle database and table
-----------------------------
ID : NAME
------------------------------
Now I need to know know how many name follow folling condition
Can contain A to Z
Can contain a to z
Can contain _
I have written query
SELECT *
FROM REGEX_TEST
WHERE REGEXP_LIKE (name,'[A-Za-z0-9_]')
But this is not giving me correct result
Sample result which I want
ID Text Expected result
1 PLAN_20001 PASS
2 A937AH PASS
3 556679815 PASS
4 A93_7AH PASS
5 PLANavd20001 PASS
6 A93*7AH FAIL
7 A93%7AH FAIL
8 A93^7AH FAIL
9 A93$7AH FAIL
10 A93#7AH FAIL
11 A93!7AH FAIL
12 A93~7AH FAIL
13 A93+7AH FAIL
//------------ RESULT -----------
1 PLAN20001
2 A937AH
3 556679815
4 A93 7AH
5 PLANavd20001
6 A93*7AH
7 A93%7AH
8 A93^7AH
9 A93$7AH
10 A93#7AH
11 A93!7AH
12 A93~7AH
13 A93+7AH
At the moment your pattern is matching any character in the name; it will only exclude values that do not contain any of those ranges at all, rather than what you seem to want which is to exclude values that contain anything else. So you need to anchor the pattern with ^ and $, and add a quantifier to allow the pattern to be repeated, with either * or ?. So the pattern becomes '^[A-Za-z0-9_]*$'.
Demo with a CTE to represent your sample data:
WITH REGEX_TEST (id, name) AS (
select 1, 'PLAN20001' from dual
union all select 2,'A937AH' from dual
union all select 3, '556679815' from dual
union all select 4, 'A93 7AH' from dual
union all select 5, 'PLANavd20001' from dual
union all select 6, 'A93*7AH' from dual
union all select 7, 'A93%7AH' from dual
union all select 8, 'A93^7AH' from dual
union all select 9, 'A93$7AH' from dual
union all select 10, 'A93#7AH' from dual
union all select 11, 'A93!7AH' from dual
union all select 12, 'A93~7AH' from dual
union all select 13, 'A93+7AH' from dual
)
SELECT *
FROM REGEX_TEST
WHERE REGEXP_LIKE (name, '^[A-Za-z0-9_]*$');
ID NAME
---------- ------------
1 PLAN20001
2 A937AH
3 556679815
5 PLANavd20001
I've left the numeric range 0-9 in despite you saying twice that you only want letters and underscores, as your reply to David Aldridge then said you did want numbers too.
You could also use the alphanumeric class instead of your ranges, which handles other languages more safely (depending on what you want to match, of course):
WHERE REGEXP_LIKE (name, '^[[:alnum:]_]*$');
Or the Perl-influenced \w operator:
WHERE REGEXP_LIKE (name, '^\w*$');
Or the not-word operator, which doesn't need to be anchored:
WHERE NOT REGEXP_LIKE (name, '\W');