Need String Format Validation in Oracle SQL - oracle

I need to validate the given string for a given format.
I have a table as below.
with t as (
select 'AR' country,8 zip_code_length,'A####AAA' zip_code_format,'B6789CDF' zip_code from dual union all
select 'AR' country,8 zip_code_length,'A####AAA' zip_code_format,'6789CDF' zip_code from dual union all
select 'BR' country,9 zip_code_length,'#####-###' zip_code_format,'12345-678' zip_code from dual union all
select 'BR' country,9 zip_code_length,'#####-###' zip_code_format,'BC345-678' zip_code from dual union all
select 'CA' country,7 zip_code_length,'A#A #A#' zip_code_format,'B2C 8A9' zip_code from dual union all
select 'CA' country,7 zip_code_length,'A#A #A#' zip_code_format,'BbC CA9' zip_code from dual union all
select 'CH' country,4 zip_code_length,'####' zip_code_format,'1234' zip_code from dual union all
select 'CH' country,4 zip_code_length,'####' zip_code_format,'123456' zip_code from dual union all
select 'CZ' country,6 zip_code_length,'### ##' zip_code_format,'123 45' zip_code from dual union all
select 'CZ' country,6 zip_code_length,'### ##' zip_code_format,'12345' zip_code from dual union all
select 'EC' country,6 zip_code_length,'A####A' zip_code_format,'B3456C' zip_code from dual union all
select 'EC' country,6 zip_code_length,'A####A' zip_code_format,'BBB56C' zip_code from dual union all
select 'PL' country,6 zip_code_length,'##-###' zip_code_format,'12-345' zip_code from dual union all
select 'PL' country,6 zip_code_length,'##-###' zip_code_format,'12-34567' zip_code from dual
)
select * from t;
I have zip_code_format and zip_code fields in the above query.
I need to validate the zip_code against zip_code_format field and get TRUE/FALSE in SQL or PL/SQL.
I would like to have a PL/SQL function where zip_code and zip_code_format will be the parameters and result would be TRUE/FALSE in return.
Expected output:
country zip_code_length zip_code_format zip_code validation
AR 8 A####AAA B6789CDF TRUE
AR 8 A####AAA 6789CDF FALSE
BR 9 #####-### 12345-678 TRUE
BR 9 #####-### BC345-678 FALSE
CA 7 A#A #A# B2C 8A9 TRUE
CA 7 A#A #A# BbC CA9 FALSE
CH 4 #### 1234 TRUE
CH 4 #### 123456 FALSE
CZ 6 ### ## 123 45 TRUE
CZ 6 ### ## 12345 FALSE
EC 6 A####A B3456C TRUE
EC 6 A####A BBB56C FALSE
PL 6 ##-### 12-345 TRUE
PL 6 ##-### 12-34567 FALSE

You can replace the # in the zip_code_format with \d and A with [A-Z] and wrap it in ^ and $ to match the start and end of the strings and then just compare it to the zip_code using REGEXP_LIKE:
with t as (
select 'AR' country,8 zip_code_length,'A####AAA' zip_code_format,'B6789CDF' zip_code from dual union all
select 'AR' country,8 zip_code_length,'A####AAA' zip_code_format,'6789CDF' zip_code from dual union all
select 'BR' country,9 zip_code_length,'#####-###' zip_code_format,'12345-678' zip_code from dual union all
select 'BR' country,9 zip_code_length,'#####-###' zip_code_format,'BC345-678' zip_code from dual union all
select 'CA' country,7 zip_code_length,'A#A #A#' zip_code_format,'B2C 8A9' zip_code from dual union all
select 'CA' country,7 zip_code_length,'A#A #A#' zip_code_format,'BbC CA9' zip_code from dual union all
select 'CH' country,4 zip_code_length,'####' zip_code_format,'1234' zip_code from dual union all
select 'CH' country,4 zip_code_length,'####' zip_code_format,'123456' zip_code from dual union all
select 'CZ' country,6 zip_code_length,'### ##' zip_code_format,'123 45' zip_code from dual union all
select 'CZ' country,6 zip_code_length,'### ##' zip_code_format,'12345' zip_code from dual union all
select 'EC' country,6 zip_code_length,'A####A' zip_code_format,'B3456C' zip_code from dual union all
select 'EC' country,6 zip_code_length,'A####A' zip_code_format,'BBB56C' zip_code from dual union all
select 'PL' country,6 zip_code_length,'##-###' zip_code_format,'12-345' zip_code from dual union all
select 'PL' country,6 zip_code_length,'##-###' zip_code_format,'12-34567' zip_code from dual
)
SELECT COUNTRY,
LENGTH( zip_code_format ) AS zip_code_length,
zip_code_format,
zip_code,
CASE
WHEN REGEXP_LIKE(
zip_code,
'^'
|| REPLACE(
REPLACE(
zip_code_format,
'#',
'\d'
),
'A',
'[A-Z]'
)
|| '$'
)
THEN 'TRUE'
ELSE 'FALSE'
END AS validation
FROM t;
Which outputs:
COUNTRY | ZIP_CODE_LENGTH | ZIP_CODE_FORMAT | ZIP_CODE | VALIDATION
:------ | --------------: | :-------------- | :-------- | :---------
AR | 8 | A####AAA | B6789CDF | TRUE
AR | 8 | A####AAA | 6789CDF | FALSE
BR | 9 | #####-### | 12345-678 | TRUE
BR | 9 | #####-### | BC345-678 | FALSE
CA | 7 | A#A #A# | B2C 8A9 | TRUE
CA | 7 | A#A #A# | BbC CA9 | FALSE
CH | 4 | #### | 1234 | TRUE
CH | 4 | #### | 123456 | FALSE
CZ | 6 | ### ## | 123 45 | TRUE
CZ | 6 | ### ## | 12345 | FALSE
EC | 6 | A####A | B3456C | TRUE
EC | 6 | A####A | BBB56C | FALSE
PL | 6 | ##-### | 12-345 | TRUE
PL | 6 | ##-### | 12-34567 | FALSE
If you want it in PL/SQL then just do exactly the same inside a function:
CREATE FUNCTION validate_zip_code(
in_zip_code IN VARCHAR2,
in_zip_code_format IN VARCHAR2
) RETURN VARCHAR2 DETERMINISTIC
IS
BEGIN
IF REGEXP_LIKE(
in_zip_code,
'^'
|| REPLACE(
REPLACE(
in_zip_code_format,
'#',
'\d'
),
'A',
'[A-Z]'
)
|| '$'
)
THEN RETURN 'TRUE';
ELSE RETURN 'FALSE';
END IF;
END;
/
Then, for the same data:
SELECT COUNTRY,
LENGTH( zip_code_format ) AS zip_code_length,
zip_code_format,
zip_code,
validate_zip_code( zip_code, zip_code_format ) AS validation
FROM t;
Outputs:
COUNTRY | ZIP_CODE_LENGTH | ZIP_CODE_FORMAT | ZIP_CODE | VALIDATION
:------ | --------------: | :-------------- | :-------- | :---------
AR | 8 | A####AAA | B6789CDF | TRUE
AR | 8 | A####AAA | 6789CDF | FALSE
BR | 9 | #####-### | 12345-678 | TRUE
BR | 9 | #####-### | BC345-678 | FALSE
CA | 7 | A#A #A# | B2C 8A9 | TRUE
CA | 7 | A#A #A# | BbC CA9 | FALSE
CH | 4 | #### | 1234 | TRUE
CH | 4 | #### | 123456 | FALSE
CZ | 6 | ### ## | 123 45 | TRUE
CZ | 6 | ### ## | 12345 | FALSE
EC | 6 | A####A | B3456C | TRUE
EC | 6 | A####A | BBB56C | FALSE
PL | 6 | ##-### | 12-345 | TRUE
PL | 6 | ##-### | 12-34567 | FALSE
db<>fiddle here

Comment to MT0's answer but formatting in a comment just does not work so putting this here. Since the length of the zip_code is supplied, I'd like to suggest doing the more expensive string operations only if the length of the zip_code = the length supplied, otherwise it's automatically false. Note this would allow you better error reporting too, as now you can differentiate between just FALSE in general with FALSE for length didn't match vs FALSE for format didn't match. Your support folks will thank you.
...
CASE
WHEN LENGTH(ZIP_CODE) = zip_code_length THEN
CASE
WHEN REGEXP_LIKE(ZIP_CODE,'^'||
REPLACE(REPLACE(zip_code_format,'#','\d'),'A','[A-Z]')||'$')
THEN 'TRUE'
ELSE 'FALSE' -- Format didn't match
END
ELSE 'FALSE' -- Length of zip not correct
END AS VALIDATION

Related

How to merge consecutive column values in Oracle into one and save initial order?

I have the following table.
Table 1
+---------+-----------+
| col_key | col_value |
+---------+-----------+
| key1 | value1 |
| key1 | value2 |
| key1 | value3 |
| key1 | value2 |
| key1 | value2 |
| key1 | value2 |
| key1 | value1 |
| key1 | value1 |
| key1 | value3 |
| key1 | value3 |
| key1 | value3 |
| key1 | value2 |
+---------+-----------+
I want to get the following output:
Table 2
+---------+-------------------------------------------------------------------+
| col_key | col_value |
+---------+-------------------------------------------------------------------+
| key1 | value1 | value2 | value 3 | value 2 | value 1 | value 3 | value 2 |
+---------+-------------------------------------------------------------------+
Algorithm - if consecutive values same - merge them into one.
I am using Oracle and listagg function:
select
col_key,
listagg(distinct col_value, ' | ') within group (order by col_key) as col_value
from
sample_table
group by col_key
order by col_key
But, listagg function with distinct keyword removes duplicates.
So, is it possible to make this in Oracle (like in table 2)?
Oracle versions (12c and 18c)
From Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row comparisons and aggregate the adjacent duplicates and then you can use LISTAGG to aggregate the unique values:
SELECT col_key,
LISTAGG(value, ' | ') WITHIN GROUP (ORDER BY mno) AS col_value
FROM (SELECT t.*,
ROWNUM AS rn -- You need to provide a way of getting this order!
FROM table1 t)
MATCH_RECOGNIZE(
PARTITION BY col_key
ORDER BY rn
MEASURES
MATCH_NUMBER() AS mno,
FIRST(col_value) AS value
PATTERN (same_value+)
DEFINE
same_value AS FIRST(col_value) = col_value
)
GROUP BY col_key;
Which, for the sample data:
CREATE TABLE Table1 (col_key, col_value) AS
SELECT 'key1', 'value1' FROM DUAL UNION ALL
SELECT 'key1', 'value2' FROM DUAL UNION ALL
SELECT 'key1', 'value3' FROM DUAL UNION ALL
SELECT 'key1', 'value2' FROM DUAL UNION ALL
SELECT 'key1', 'value2' FROM DUAL UNION ALL
SELECT 'key1', 'value2' FROM DUAL UNION ALL
SELECT 'key1', 'value1' FROM DUAL UNION ALL
SELECT 'key1', 'value1' FROM DUAL UNION ALL
SELECT 'key1', 'value3' FROM DUAL UNION ALL
SELECT 'key1', 'value3' FROM DUAL UNION ALL
SELECT 'key1', 'value3' FROM DUAL UNION ALL
SELECT 'key1', 'value2' FROM DUAL;
Outputs:
COL_KEY
COL_VALUE
key1
value1 | value2 | value3 | value2 | value1 | value3 | value2
db<>fiddle here
You said you'll edit the question and provide sorting column; so far, you didn't do that so I used ROWID instead. Change it with your own column (once you find out which one to use).
Read comments within code.
SQL> with
2 temp as
3 -- find COL_VALUE and the value that follows it (NEXT_VALUE in this query.
4 -- As I already said, I used ROWID for sorting purposes)
5 (select col_key,
6 col_value,
7 rowid rid,
8 lead(col_value) over (partition by col_key order by rowid) next_value
9 from sample_table
10 ),
11 temp2 as
12 -- "new" COL_VALUE will be the "original" COL_VALUE if it is different from its
13 -- next value (or - for the last row - if there's no next value)
14 (select col_key,
15 rid,
16 case when col_value <> next_value or next_value is null then col_value
17 else null
18 end col_value
19 from temp
20 )
21 -- finally, aggregate the result
22 select col_key,
23 listagg(col_value, ' | ') within group (order by rid) col_value
24 from temp2
25 group by col_key;
COL_KEY COL_VALUE
---------- ------------------------------------------------------------
1 val1 | val2 | val3 | val2 | val1 | val3 | val2
SQL>
Here's another take on it:
WITH
cte1 AS (SELECT COL_KEY,
COL_VALUE,
LAG(COL_VALUE) OVER (ORDER BY ROWNUM) AS PREV_COL_VALUE
FROM TEST_TAB),
cte2 AS (SELECT COL_KEY,
COL_VALUE,
CASE
WHEN COL_VALUE = PREV_COL_VALUE THEN 0
ELSE 1
END AS FLAG
FROM cte1),
cte3 AS (SELECT COL_KEY,
COL_VALUE
FROM cte2
WHERE FLAG = 1)
SELECT COL_KEY,
LISTAGG(COL_VALUE, ' | ') WITHIN GROUP (ORDER BY COL_KEY, ROWNUM) AS COL_VALUES
FROM cte3
GROUP BY COL_KEY
This produces:
COL_KEY COL_VALUES
------- ------------------------------------------------------------
key1 value1 | value2 | value3 | value2 | value1 | value3 | value2
which matches your desired output, but without an ordering column in your data you're at the mercy of the database and how it chooses to return the data. Remember - a basic rule of relational databases is that tables are unordered collections of rows, and there must an ORDER BY clause to impose an order on those rows when they are returned by a query.
db<>fiddle here

xsd format number , 5 decimal places

I have xsd code which is printing LINE_AMOUNT value as 14,952.59 , now i want to display this as 14,952.59000(5 decimal places).
How to achieve this?
Thank you
SQL Fiddle
Query 1 Either (if you want to use the current NLS values for decimal and thousands characters):
SELECT TO_CHAR(
14952.59,
'FM9G999G999G999G990D00000'
)
FROM DUAL
Results:
| TO_CHAR(14952.59,'FM9G999G999G999G990D00000') |
|-----------------------------------------------|
| 14,952.59000 |
Query 2 or:
SELECT TO_CHAR(
14952.59,
'FM9,999,999,999,990.00000'
)
FROM DUAL
Results:
| TO_CHAR(14952.59,'FM9,999,999,999,990.00000') |
|-----------------------------------------------|
| 14,952.59000 |
Update: SQL Fiddle
Query 3:
SELECT TO_CHAR(
TO_NUMBER(
'13,214,952.59',
'FM9G999G999G999G990D99999'
),
'FM9G999G999G999G990D00000'
) AS formatted_value
FROM DUAL
Results:
| FORMATTED_VALUE |
|------------------|
| 13,214,952.59000 |
Query 4:
SELECT TO_CHAR(
TO_NUMBER(
'13,214,952.59',
'FM9,999,999,999,990.99999'
),
'FM9,999,999,999,990.00000'
) AS formatted_value
FROM DUAL
Results:
| FORMATTED_VALUE |
|------------------|
| 13,214,952.59000 |
Try this, it will work for you I think.
select trim(to_char(14952.59,9999999999.99999)) from dual
OUTPUT
14952.59000

Oracle: How to use pivot muilti column?

I want Pivot multi column. What use oracle pivot table?
SQL:
SELECT * FROM
(
SELECT *
FROM IRO_SIM A
WHERE A.COM_CODE = 'AAQ'
AND A.PCODE = 'AKIOP'
)
PIVOT
(
LISTAGG(SIMTYPE,',')
WITHIN GROUP (ORDER BY SIMTYPE)
FOR SIMTYPE IN ('H','V')
)
Sample Data:
COM_CODE | PCODE | L_VALUE | A_SIM | AMT_SIM | SIMTYPE
A | AKIOP | 1700 | TOTAL | 50 | H
A | AKIOP | 500 | EACH | 100 | V
A | BHUIO | 200 | TOTAL | 500 | H
A | BHUIO | 600 | TOTAL | 400 | V
i need Result:
COM_CODE | PCODE | H_VALUE | H_ASIM | H_AMTSIM | V_VALUE | V_ASIM | V_AMTSIM
A | AKIOP | 1700 | TOTAL | 50 | 500 | EACH | 100
A | BHUIO | 200 | TOTAL | 500 | 600 | TOTAL | 400
thanks advance :)
Just list the multiple columns. Every expression in your PIVOT clause will be matched with every value in the FOR clause. So, what you want is this:
SELECT * FROM d
PIVOT ( sum(l_value) as value, max(a_sim) as asim, sum(amt_sim) as amtsim
FOR simtype in ('H' AS "H", 'V' AS "V") )
With data...
with d as (
SELECT 'A' com_code, 'AKIOP' pcode, 1700 l_value, 'TOTAL' a_sim, 50 amt_sim, 'H' simtype FROM DUAL UNION ALL
SELECT 'A' com_code, 'AKIOP' pcode, 500 l_value, 'EACH' a_sim, 100 amt_sim, 'V' simtype FROM DUAL UNION ALL
SELECT 'A' com_code, 'BHUIO' pcode, 200 l_value, 'TOTAL' a_sim, 500 amt_sim, 'H' simtype FROM DUAL UNION ALL
SELECT 'A' com_code, 'BHUIO' pcode, 600 l_value, 'TOTAL' a_sim, 400 amt_sim, 'V' simtype FROM DUAL)
SELECT * FROM d
PIVOT ( sum(l_value) as value, max(a_sim) as asim, sum(amt_sim) as amtsim
FOR simtype in ('H' AS "H", 'V' AS "V") )

Oracle group by SQL query by matching varchar field

I have a table structure (that I did not design nor can I change) that uses a varchar field to store an attribute about the entity. I would like to write a SQL query to search for two attributes in particular and combine multiple result rows into single rows. To illustrate, my tables are similar to this:
company
=============
| id | name |
-------------
| 1 | co1 |
| 2 | co2 |
| 3 | co3 |
=============
agent
====================================
| id | name | company_id | type |
------------------------------------
| 1 | Tom | 1 | 'type1' |
| 2 | Bob | 1 | 'type2' |
| 3 | Bill | 2 | 'type1' |
| 4 | Jack | 2 | 'type2' |
| 5 | John | 3 | 'type1' |
| 6 | Joe | 3 | 'type2' |
====================================
type1 and type2 are hard-coded into the software as valid values (again, I didn't write it), so a search for these values should be successful (null is permitted). So, I must base my search off of these values.
As a novice, I could write this SQL:
select c.name, a.name, a.type
from company c
inner join agent a on c.id = a.company_id
and sort through these results in my software (Java program):
===========================
| c.name | a.name | type |
---------------------------
| co1 | Tom | type1 |
| co1 | Bob | type2 |
| co2 | Bill | type1 |
| co2 | Jack | type2 |
| co3 | John | type1 |
| co3 | Joe | type2 |
===========================
But, I was hoping there would be a way to combine the rows into something more efficient:
-- my failed attempt at writing this query
select c.name, a.name as type_1_agent, a.name as type_2_agent
from company c
inner join agent a on c.id = a.company_id
group by c.id -- ?
where -- ?
results:
======================================
| name | type_1_agent | type_2_agent |
--------------------------------------
| co1 | Tom | Bob |
| co2 | Bill | Jack |
| co3 | John | Joe |
======================================
Is this possible?
Oracle version:
WITH company (id, name) AS (
SELECT 1, 'co1' FROM DUAL UNION ALL
SELECT 2, 'co2' FROM DUAL UNION ALL
SELECT 3, 'co3' FROM DUAL
),
agent (id, name, company_id, type) AS (
SELECT 1, 'Tom', 1, 'type1' FROM DUAL UNION ALL
SELECT 2, 'Bob', 1, 'type2' FROM DUAL UNION ALL
SELECT 3, 'Bill', 2, 'type1' FROM DUAL UNION ALL
SELECT 4, 'Jack', 2, 'type2' FROM DUAL UNION ALL
SELECT 5, 'John', 3, 'type1' FROM DUAL UNION ALL
SELECT 6, 'Joe', 3, 'type2' FROM DUAL
)
SELECT
company_name, type_1_agent, type_2_agent
FROM
(SELECT company.name company_name, agent.name agent_name, type FROM company JOIN agent ON company.id = agent.company_id)
PIVOT (
MAX(agent_name) agent
FOR type IN ('type1' type_1, 'type2' type_2)
)
ORDER BY
company_name
You can do this with PIVOT functionality, like so:
select company,type_1_agent,type_2_agent
from
(select a.name as agentname, a.type as agenttype,c.name as company
from agent a
inner join company c on c.id = a.company_id
) s
pivot
(max(agentname) for agenttype in ('type1' type_1_agent,'type2' type_2_agent)) p
order by company
Demo
Of course, in this case we hard coded the values for type. This can be made dynamic to accomodate an unknown number of these values.

add column check for format number to number oracle

I need to add a column to a table that check for input to be a max value of 999 to 999, like a soccer match score. How do I write this statement?
example:
| Score |
---------
| 1-2 |
| 10-1 |
|999-999|
| 99-99 |
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE SCORES (Score ) AS
SELECT '1-2' FROM DUAL
UNION ALL SELECT '10-1' FROM DUAL
UNION ALL SELECT '999-999' FROM DUAL
UNION ALL SELECT '99-99' FROM DUAL
UNION ALL SELECT '1000-1000' FROM DUAL;
Query 1:
SELECT SCORE,
CASE WHEN REGEXP_LIKE( SCORE, '^\d{1,3}-\d{1,3}$' )
THEN 'Valid'
ELSE 'Invalid'
END AS Validity
FROM SCORES
Results:
| SCORE | VALIDITY |
|-----------|----------|
| 1-2 | Valid |
| 10-1 | Valid |
| 999-999 | Valid |
| 99-99 | Valid |
| 1000-1000 | Invalid |

Resources