Building an Oracle view with a sequence - oracle

I'm trying to build a new Oracle view off of a table. The only difference between the two is that I want to add a new column with a unique ID. The IDs have to be unique, but does not need to be ordered.
I tried to run a script like this:
CREATE VIEW <VIEW_NAME>
(
ID, VALUE1, VALUE2,...
)
AS
SELECT SEQ1.NEXTVAL, VAL1, VAL2,... FROM <TABLE>
However, I'm running into errors. A previous post mentions it's not really possible but didn't elaborate so I was hoping to get some clarity. Doing an INSERT doesn't seem useful because I'd have to populate all the other values too, at least from what I've been reading.
Edit: IDs should be consistent every time I look at the view.
Picture of error:

If you just want a unique value for each row then you can use ROWNUM or the ROW_NUMBER analytic function:
CREATE VIEW view_name ( ID, VALUE1, VALUE2,... ) AS
SELECT ROWNUM,
VAL1,
VAL2,
...
FROM table_name
or
CREATE VIEW view_name ( ID, VALUE1, VALUE2,... ) AS
SELECT ROW_NUMBER() OVER ( ORDER BY val1, val2 ),
VAL1,
VAL2,
...
FROM table_name
IDs should be consistent every time I look at the view.
I do not think this is possible; you would need to store the IDs somewhere and that requires a table rather than a view/sequence.
For example:
Oracle Setup:
CREATE TABLE table_name ( val1, val2 ) AS
SELECT 1, 'a' FROM DUAL UNION ALL
SELECT 2, 'b' FROM DUAL
CREATE SEQUENCE view_name__seq;
CREATE FUNCTION seq_value RETURN NUMBER
IS
BEGIN
RETURN view_name__seq.NEXTVAL;
END;
/
CREATE VIEW view_name ( id, value1, value2 ) AS
SELECT seq_value, val1, val2 FROM table_name;
If you select from the view then first time:
SELECT * FROM view_name;
you get:
ID | VALUE1 | VALUE2
-: | -----: | :-----
1 | 1 | a
2 | 2 | b
and second time you get:
ID | VALUE1 | VALUE2
-: | -----: | :-----
3 | 1 | a
4 | 2 | b
db<>fiddle here
and the IDs are not consistent.

I would suggest you to use INVISIBLE column in your base table.
ALTER TABLE MY_TABLE ADD MY_UNIQUE_ID NUMBER INVISIBLE;
Now, assign sequential number to it or use as GENERATED AS IDENTITY (try this)
This will not break you application and you can use it in your view to achieve the desired result and return consistent values in your view.
Cheers!!

Related

Reverse of ascii function in Oracle

When I do following query
select asciistr(first_name) from person where id = 1
the result contains '\200D'.
So what is the reverse function of converting a '\200D' to character, so I can find all names with that specific character.
You want UNISTR (and, maybe, CAST it to a VARCHAR2). If you have the table:
CREATE TABLE person ( first_name, id ) AS
SELECT 'A', 1 FROM DUAL UNION ALL
SELECT CAST( UNISTR( '\200D' ) AS VARCHAR2(20) ), 1 FROM DUAL;
Then the output from your query:
select first_name,
asciistr(first_name)
from person
where id = 1
Is:
FIRST_NAME | ASCIISTR(FIRST_NAME)
:--------- | :-------------------
A | A
? | \200D
db<>fiddle here
\200D is the U+200D ZERO WIDTH JOINER
If you like to find names with this (non printable) character try
SELECT *
FROM person
WHERE first_name LIKE '%'||UNISTR('\200D')||'%'
or
WHERE asciistr(first_name) LIKE '%\200D%'

Return first row in each group from Oracle SQL

Hi I need to come up with a query that efficiently returns just one record per group(I might be thinking about it wrong) and stop searching for more records in that group as soon as it has found one record.
This is my table:
|col1 | col2|
|-----|-----|
| A | 1 |
| A | 2 |
| B | 3 |
| B | 4 |
I want to return.
|col1 | col2|
|-----|-----|
| A | 1 |
| B | 3 |
Note that I don't actually care if in row one I have A,1 or A,2(same applies to second row).
What I want is to get one record that has A in first column could be any record that matches that criteria, and similarly I want one record that has B in col1.
The closes that I know to getting this are two queries
SELECT col1, MIN(col2)
FROM tablename
GROUP BY col1
and the other:
SELECT *
FROM tablename
WHERE col1 = 'A'
AND ROWNUM = 1
First query is not good enough because it will try to find all records that have A in col1(in the actual table I'm looking at this means searching though millions of rows, and my indicies won't be of much help here). Second query will return just one value of col1 at a time, so I'd have to run it thousands of times to get all the records I need.
NOTE:
I did see similar question in here but the answers were focused on just getting the right query results, I my case issue is how long do I need to wait for these results.
Sounds like this is the query you are looking for:
select col1
, min(col2) keep (dense_rank first order by rownum) col2
from tablename
group by col1;
I feel the below query is returning the result quick.
SELECT col1,col2 FROM (
SELECT col1,col2, ROW_NUMBER () over (partition by col1 order by col2 asc)
minseq FROM tablename
--where rownum < 1000000000
)
where minseq = 1;
Normal query
SELECT col1, MIN(col2)
FROM tablename
GROUP BY col1
took 1 min to fetch 500 records out of 100000000 records and this query took 0.03 seconds

Order by: Special characters before ABC

I have this sql:
SELECT -1 AS ID, '(None)' AS NAME
FROM TABLE_1 WHERE ID=1
UNION
SELECT ID, NAME
FROM TABLE_2
ORDER BY 2
Table data:
ID | NAME
1 | Direct
2 | Personal
3 | Etc
So if i execute this sql in Oracle 10 it returns these:
Result:
ID | NAME
1 | Direct
3 | Etc
-1 | (None)
2 | Personal
How is it possible to sort the "(None)" always to the top?
If i use
' (None) ' as Name
instead of
'(None)' as Name
It works, because the space before the (None), but that is not a solution.
You can add a dummy column ORDER_COL and then order on that column
select ID, NAME from
(
SELECT -1 AS ID, '(None)' AS NAME, 1 as ORDER_COL FROM TABLE_1 WHERE ID=1
UNION
SELECT ID, NAME, 2 as ORDER_COL FROM TABLE_2
)
order by ORDER_COL, NAME;
Try this. NULLS LAST is the default for ascending order in Oracle. make it NULLS FIRST for '(None)'. Also, use UNION ALL as UNION removes duplicates and is less efficient.
SELECT *
FROM (
SELECT -1 AS ID
,'(None)' AS NAME
FROM TABLE_1
WHERE ID = 1
UNION ALL
SELECT ID
,NAME
FROM TABLE_2
)
ORDER BY CASE
WHEN NAME = '(None)'
THEN NULL
ELSE NAME -- or id if you want
END NULLS FIRST;

Understanding MultiSet in Oracle & advantages,importance of it

I have seen a lot of answer's using Multiset & AS SYS.ODCIVARCHAR2LIST etc.. and I am not able to understand the logic behind/ how it works out. Please find below example (actually taken from another question). Request you to please explain the workflow/working for understanding multiset. And yes I tried to read the documents related to it.
CREATE TABLE table_name ( Id, Column1, Column2 ) AS
SELECT 1, 'A,B,C', 'H' FROM DUAL UNION ALL
SELECT 2, 'D,E', 'J,K' FROM DUAL UNION ALL
SELECT 3, 'F', 'L,M,N' FROM DUAL;
Query:
SELECT t.id,
c1.COLUMN_VALUE AS c1,
c2.COLUMN_VALUE AS c2
FROM table_name t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column1, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column1, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c1
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column2, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column2, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c2
Result::
| ID | C1 | C2 |
|----|----|----|
| 1 | A | H |
| 1 | B | H |
| 1 | C | H |
| 2 | D | J |
| 2 | D | K |
| 2 | E | J |
| 2 | E | K |
| 3 | F | L |
| 3 | F | M |
| 3 | F | N |
Thanks in advance.
Oracle offers three different types of collection, on other languages term "array" is more common.
Associative array (or index-by table)
VARRAY (variable-size array)
Nested table
Have a look at Collections Types in order to see the differences and where which one can be used.
Start with something simple, e.g.
create type number_table_type as table of number;
You can use them in several ways, the three selects are equivalent. They all select table emp and stores emp_id values into Nested table emp_T.
create table emp (
emp_id number,
emp_name varchar2(100));
insert into emp values (10, 'Scott');
insert into emp values (20, 'King');
insert into emp values (30, 'Tiger');
declare
emp_T number_table_type:
begin
select cast(collect(emp_id) as number_table_type)
into emp_T
from emp;
select emp_id
bulk collect into emp_T
from emp;
SELECT CAST(MULTISET(SELECT emd_id FROM emp) AS number_table_type)
into emp_T
FROM dual;
emp_T := number_table_type(10,20,30);
for i in 1..3 loop
emp_T.EXTEND;
emp_T(i) := 10*i;
end loop;
end;
For the opposite way, i.e. transform a Nested table into "normal" table use TABLE function:
select *
from TABLE(emp_T);
Oracle provides Multiset Operators and Multiset Conditions. They offer function like join two arrays, make values distinct, etc. Many times I see developers writing a LOOP in their code where a Mulitset Operator/Condition would do the same stuff with a single command.
Once you are familiar with such basic array you could also try more complex structures. However, according to my feeling such complex structures are quite common in training material and documentation but hardly used in "real life". Oracle strength is to store and manipulate relational data, rather than object oriented data.
SYS.ODCIVARCHAR2LIST, et al. are just some predefined types. Actually it does not matter wether you use them or create you own types.
Well, you should review such a code from the deepest level and navigate up, in order to figure out what's going on.
This is based on the first row of your table: as you can see, that "regexp" magic converts your comma separated values (i.e. column) into rows.
SQL> with test as
2 (select 1, 'A,B,C' column1 from dual)
3 select regexp_substr(t.column1, '[^,]+', 1, level)
4 from test t
5 connect by level <= regexp_count(t.column1, '[^,]+');
REGEXP_SUBSTR(T.COLU
--------------------
A
B
C
SQL>
MULTISET creates a "collection" of those values, while CAST "converts" it into a SYS.ODCIVARCHAR2LIST type. It is, as you can see, owned by SYS and acts as if you created your own type (using the CREATE TYPE command) which contains VARCHAR2 values. When types are "simple" as this one, or the one that contains numbers (so you'd use SYS.ODCINUMBERLIST), you can use predefined one instead of creating your own. Therefore, that collection contains A, B and C.
Finally, TABLE function produces a collection of rows that can be queried, just as if it was an "ordinary" table.

Oracle SQL - Returning the count from a delimited field

I'm fairly inexperienced with SQL so hopefully this question is not too silly. Here is the scenario:
I have a VARCHAR2 column that stores a series of values delimited by product. Depending on on the account, they can have one or multiple products. I'm trying to write a query that will return the values but also provide a count for each type or product.
For example:
ProductColumn: P1, P2, P3, P4
Table: TableAccount
Sample Value 1: P1:P2:P3
Sample Value 2: P1
Sample Value 3: P2:P3
My current query only returns a count of all different value types including the delimited values:
select
ProductColumn,
count(8) cnt
from TableAccount
group by ProductColumn
Any suggestions would be appreciated!
If the product codes are reliable separated by colons, you can use substring to pull the code values, separate from the separator colons. That allows you to return then to the caller, each in separate fields, so summing, grouping, etc. However, that will get messy if any of the values are longer than two
bytes. This is why data normalization rules specifically spell out not putting more than one piece of data into a single table column. If it were me, I'd write a PL SQL that splits them out and writes it all cleanly to a NORMALIZED table, then queries from that table. And I would be all over my boss about getting this design flaw FIXED.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableAccount ( value ) AS
SELECT 'P1:P2:P3' FROM DUAL
UNION ALL SELECT 'P1' FROM DUAL
UNION ALL SELECT 'P2:P3' FROM DUAL
UNION ALL SELECT 'P1:P3' FROM DUAL
UNION ALL SELECT 'P1:P4' FROM DUAL
UNION ALL SELECT 'P5' FROM DUAL;
Query 1:
SELECT item,
COUNT(1) AS frequency
FROM (
SELECT REGEXP_SUBSTR( value, '[^:]+', 1, COLUMN_VALUE ) AS item
FROM TableAccount t,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '[^:]+')
) AS sys.OdciNumberList
)
)
)
GROUP BY item
ORDER BY item
Results:
| ITEM | FREQUENCY |
|------|-----------|
| P1 | 4 |
| P2 | 2 |
| P3 | 3 |
| P4 | 1 |
| P5 | 1 |

Resources