Reverse of ascii function in Oracle - 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%'

Related

How to insert comma delimited values in one column in oracle?

I have a table by the name of personal_info and it contains id,name and phone_number as columns. So the following is table structure which I want to store data.
id
name
phone_number
1
ali
03434444, 03454544, 0234334
So how to store data in phone_number column in comma delimited format and how to filter that column in where clause for example
Select * from personal_info where phone_number = 03454544 ;
And which datatype is suitable for phone_number column.
Well, the real good practice would rather be to have another table PHONE with a 1xN association (for example a PHONE_ID primary key, and ID and PHONE columns.)
You may then have the result you want with a view based on your two tables and using the LISTAGG operator : https://fr.wikibooks.org/wiki/Oracle_Database/Utilisation_de_fonctions/fonction_LISTAGG, but this will be much efficient to work with, especially if you want WHERE clauses based on your phone numbers.
Use LIKE with the delimiters:
Select *
from personal_info
where ', ' || phone_number || ', ' LIKE '%, ' || '03454544' || ', %';
However
You should consider changing your data structure to store the phone numbers in a separate table:
CREATE TABLE phone_numbers (
person_id REFERENCES personal_info (id),
phone_number VARCHAR2(12)
);
And then you can get the data using a JOIN
SELECT pi.*,
pn.phone_number
FROM personal_info pi
INNER JOIN phone_numbers pn
ON (pi.id = pn.person_id)
WHERE pn.phone_number = '03434444'
or, if you want all the phone numbers:
SELECT pi.*,
pn.phone_numbers
FROM personal_info pi
INNER JOIN (
SELECT person_id,
LISTAGG(phone_number, ', ') WITHIN GROUP (ORDER BY phone_number)
AS phone_numbers
FROM phone_numbers
GROUP BY person_id
HAVING COUNT(CASE WHEN phone_number = '03434444' THEN 1 END) > 0
) pn
ON (pi.id = pn.person_id)
db<>fiddle here
VARCHAR2 is suitable for phone numbers.
You can get the values this way:
WITH personal_info AS
(
SELECT 1 AS ID, 'Ali' AS NAME, '03434444, 03454544, 0234334' AS phone_number FROM dual
)
SELECT *
FROM (SELECT id, name, TRIM(regexp_substr(phone_number, '[^,]+', 1, LEVEL)) AS phone_number
FROM personal_info
CONNECT BY LEVEL <= LENGTH (phone_number) - LENGTH(REPLACE(phone_number, ',' )) + 1)
WHERE phone_number = '03454544';
Wrong data model, it isn't normalized. You should create a new table:
create table phones
(id_phone number constraint pk_phone primary key,
id_person number constraint fk_pho_per references person (id_person),
phone_number varchar2(30) not null
);
Then you'd store as many numbers as you want, one-by-one (row-by-row, that is).
If you want to do it your way, store it just like that:
insert into person (id, name, phone_number)
values (1, 'ali', '03434444, 03454544, 0234334');
One option of querying such data is using the instr function:
select * from person
where instr(phone_number, '03434444') > 0;
or like:
select * from person
where phone_number like '%'% || '03434444' || '%'
or split it into rows:
select * from person a
where '03434444' in (select regexp_substr(b.phone_number, '[^,]+', 1, level)
from person b
where b.id_person = a.id_person
connect by level <= regexp_count(b.phone_number, ',') + 1
)
I'd do it my way, i.e. with a new table that contains only phone numbers.

Building an Oracle view with a sequence

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!!

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;

Using a single select statment to get the next row from a table or return the first row if the end of table is reached

I have a table say STAFF like below:
STAFF_NAME
============
ALEX
BERNARD
CARL
DOMINIC
EMMA
Now, I want to write a stored function with a single argument. E.g. GET_NEXT_STAFF(CURRENT_STAFF).
The input and output should be like:
Input | Output
=====================
NULL | ALEX
ALEX | BERNARD
BERNARD | CARL
EMMA | ALEX (Start from the beginning of the table again)
I know how to handle this problem using PL/SQL, but is it possible to deal with this problem with a single select statement?
In the solution below, I assume the rows are ordered alphabetically by names. They may be ordered by another column in the same table (for example by hire date, or by salary, etc. - it doesn't matter) - then the name of that column should be used in the ORDER BY clause of the two analytic functions.
The input name is passed in as a bind variable, :input_staff_name. The solution uses pure SQL, with no need for functions (PL/SQL), but if you must make it into a function, you can adapt it easily.
Edit: In my original answer I missed the required behavior when the input is null. The last line of code (excluding the semicolon) takes care of that. As written currently, the query returns ALEX (or in general the first value in the table) when the input is null, and it returns no rows when the input is not null and not in the table. If instead the requirement is to return the first name when the input is null or not found in the table, then it can be accommodated easily by removing and :input_staff_name is null from the last line.
with
tbl ( staff_name ) as (
select 'ALEX' from dual union all
select 'BERNARD' from dual union all
select 'CARL' from dual union all
select 'DOMINIC' from dual union all
select 'EMMA' from dual
),
prep ( staff_name, next_name, first_name ) as (
select staff_name,
lead(staff_name) over (order by staff_name),
first_value (staff_name) over (order by staff_name)
from tbl
)
select nvl(next_name, first_name) as next_staff_name
from prep
where staff_name = :input_staff_name
or (next_name is null and :input_staff_name is null)
;
Based on the answer from #mathguy I have made a few changes that seem to work. I have added the follow
UNION ALL
SELECT NULL
FROM DUAL
and
WHERE NVL (staff_name, 'X') = NVL (NULL, 'X');
The full code
WITH tbl (staff_name) AS
(SELECT 'ALEX' FROM DUAL
UNION ALL
SELECT 'BERNARD' FROM DUAL
UNION ALL
SELECT 'CARL' FROM DUAL
UNION ALL
SELECT 'DOMINIC' FROM DUAL
UNION ALL
SELECT 'EMMA' FROM DUAL
UNION ALL
SELECT NULL
FROM DUAL),
prep (staff_name,
next_name,
first_name,
last_name) AS
(SELECT staff_name,
LEAD (staff_name) OVER (ORDER BY staff_name),
FIRST_VALUE (staff_name) OVER (ORDER BY staff_name),
LAG (staff_name) OVER (ORDER BY staff_name)
FROM tbl)
SELECT NVL (next_name, first_name) AS next_staff_name
FROM prep
WHERE NVL (staff_name, 'X') = NVL (:input_staff_name, 'X');

Counting null dates in Oracle

I have a question with null dates in Oracle
I've got a table like this:
ID DATE
1 '02/08/2015'
1 NULL
1 '02/06/2014'
2 NULL
2 '06/02/2013'
This is just an example of the real table. Now what I need is something like this:
ID DAY_INAC
1 1
2 1
I mean, I need to count only the null values present in the DATE column.
But when I execute my query
Select id, count(date)
from table
where date is null
group by Id
having count(date)>0
As a result I'm getting nothing. What do I need to with the date value in order to generate the corresponding counting.
Regards
Because your query is already filtering by date is null, you just need to use count(*)
Select id, count(*)
from table
where date is null
group by Id
having count(*) > 0
COUNT does not count NULL values. You can use CASE to change them:
Select id, count(CASE WHEN date IS NULL THEN 1 END) AS DAY_INAC
from table
where date is null
group by Id;
LiveDemo
Please do not name column as datatypes. Otherwise you may need to quote them.
A more condensed query:
Select id, count(nvl2(date_column,null, sysdate)) as cnt
from table
group by Id;
COUNT will not count NULL values - instead get it to count a literal value (i.e. 1) for those rows that are NULL:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( ID, "DATE" ) AS
SELECT 1, DATE '2015-08-02' FROM DUAL
UNION ALL SELECT 1, NULL FROM DUAL
UNION ALL SELECT 1, DATE '2014-06-02' FROM DUAL
UNION ALL SELECT 2, NULL FROM DUAL
UNION ALL SELECT 2, DATE '2013-02-06' FROM DUAL
Query 1:
SELECT id,
COUNT(1)
FROM table_name
WHERE "DATE" IS NULL
GROUP BY id
Results:
| ID | COUNT(1) |
|----|----------|
| 1 | 1 |
| 2 | 1 |

Resources