add column check for format number to number oracle - 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 |

Related

(Nested?) Select statement with MAX and WHERE clause

I'm cranking my head on a set of data in order to generate a report from a Oracle DB.
Data are in two tables:
SUPPLY
DEVICE
There is only one column that links the two tables:
SUPPLY.DEVICE_ID
DEVICE.ID
In SUPPLY, there are these data: (Markdown is not working well. it's supposed to show a table)
| DEVICE_ID | COLOR_TYPE | SERIAL | UNINSTALL_DATE |
|----------- |------------ |-------------- |--------------------- |
| 1232 | 1 | CAP857496 | 08/11/2016,19:10:50 |
| 5263 | 2 | CAP57421 | 07/11/2016,11:20:00 |
| 758 | 3 | CBO753421869 | 07/11/2016,04:25:00 |
| 758 | 4 | CC9876543 | 06/11/2016,11:40:00 |
| 8575 | 4 | CVF75421 | 05/11/2016,23:59:00 |
| 758 | 4 | CAP67543 | 30/09/2016,11:00:00 |
In DEVICE, there are columns that I've to select all (more or less), but each row is unique.
What i need to achieve is:
for each SUPPLY.DEVICE_ID and SUPPLY.COLOR_TYPE, I need the most recent ROW -> MAX(UNINSTALL_DATE)
JOINED with
more or less all the columns in DEVICE.
At the end I should have something like this:
| ACCOUNT_CODE | MODEL | DEVICE.SERIAL | DEVICE_ID | COLOR_TYPE | SUPPLY.SERIAL | UNINSTALL_DATE |
|-------------- |------- |--------------- |----------- |------------ |--------------- |--------------------- |
| BUSTO | MS410 | LM753 | 1232 | 1 | CAP857496 | 08/11/2016,19:10:50 |
| MACCHI | MX310 | XC876 | 5263 | 2 | CAP57421 | 07/11/2016,11:20:00 |
| ASL_COMO | MX711 | AB123 | 758 | 3 | CBO753421869 | 07/11/2016,04:25:00 |
| ASL_COMO | MX711 | AB123 | 758 | 4 | CC9876543 | 06/11/2016,11:40:00 |
| ASL_VARESE | X950 | DE8745 | 8575 | 4 | CVF75421 | 05/11/2016,23:59:00 |
So far, using a nested select like:
SELECT DEVICE_ID,COLOR_TYPE,SERIAL,UNINSTALL_DATE FROM
(SELECT SELECT DEVICE_ID,COLOR_TYPE,SERIAL,UNINSTALL_DATE
FROM SUPPLY WHERE DEVICE_ID = '123456' ORDER BY UNINSTALL_DATE DESC)
WHERE ROWNUM <= 1
I managed to get the highest value on the UNISTALL_DATE column after trying MAX(UNISTALL_DATE) or HIGHEST(UNISTALL_DATE).
I tried also:
SELECT SUPPLY.DEVICE_ID, SUPPLY.COLOR_TYPE, ....
FROM SUPPLY,DEVICE WHERE SUPPLY.DEVICE_ID = DEVICE.ID
and it works, but gives me ALL the items, basically it's a merge of the two tables.
When I try to narrow the data selected, i get errors or a empty result.
I'm starting to wonder that it's not possible to obtain this data and i'm starting to export the data in excel and work from there, but I wish someone can help me before giving up...
Thank you in advance.
for each SUPPLY.DEVICE_ID and SUPPLY.COLOR_TYPE, I need the most recent ROW -> MAX(UNINSTALL_DATE)
Use ROW_NUMBER function in this way:
SELECT s.*,
row_number() OVER (
PARTITION BY DEVICE_ID, COLOR_TYPE
ORDER BY UNINSTALL_DATE DESC
) As RN
FROM SUPPLY s
This query marks most recent rows with RN=1
JOINED with more or less all the columns in DEVICE.
Just join the above query to DEVICE table
SELECT d.*,
x.COLOR_TYPE,
x.SERIAL,
x.UNINSTALL_DATE
FROM (
SELECT s.*,
row_number() OVER (
PARTITION BY DEVICE_ID, COLOR_TYPE
ORDER BY UNINSTALL_DATE DESC
) As RN
FROM SUPPLY s
) x
JOIN DEVICE d
ON d.DEVICE_ID = x.DEVICE_ID AND x.RN=1
OK - so you could group by device_id, color_type and select max(uninstall_date) as well, and join to the other table. But you would miss the serial value for the most recent row (for each combination of device_id, color_type).
There are a few ways to fix that. Your attempt with rownum was close, but the problem is that you need to order within each "group" (by device_id, color_type) and get the first row from each group. I am sure someone will post a solution along those lines, using either row_number() or rank() or perhaps the analytic version of max(uninstall_date).
When you just need the "top" row from each group, you can use keep (dense_rank first/last) - which may be slightly more efficient - like so:
select device_id, color_type,
max(serial) keep (dense_rank last order by uninstall_date) as serial,
max(uninstall_date) as uninstall_date
from supply
group by device_id, color_type
;
and then join to the other table. NOTE: dense_rank last will pick up the row OR ROWS with the most recent (max) date for each group. If there are ties, that is more than one row; the serial will then be the max (in lexicographical order) among those rows with the most recent date. You can also select min, or add some order so you pick a specific one (you didn't discuss this possibility).
SELECT
d.ACCOUNT_CODE, d.DNS_HOST_NAME,d.IP_ADDRESS,d.MODEL_NAME,d.OVERRIDE_SERIAL_NUMBER,d.SERIAL_NUMBER,
s.COLOR, s.SERIAL_NUMBER, s.UNINSTALL_TIME
FROM (
SELECT s.DEVICE_ID, s.LAST_LEVEL_READ, s.SERIAL_NUMBER,TRUNC(s.UNINSTALL_TIME), row_number()
OVER (
PARTITION BY DEVICE_ID, COLOR
ORDER BY UNINSTALL_TIME DESC
) As RN
FROM SUPPLY s
WHERE s.UNINSTALL_TIME IS NOT NULL AND s.SERIAL_NUMBER IS NOT NULL
)
JOIN DEVICE d
ON d.ID = s.DEVICE_ID AND s.RN=1;
#krokodilko: thank you very much for your help. First query works. Modified it in order to remove junk, putting real columns name i need (yesterday evening i had no access to the DB) and getting only the data I need.
Unfortunately, when I join the two tables as you suggested I get error:
ORA-00904: "S"."RN": invalid identifier
00904. 00000 - "%s: invalid identifier"
If i remove s. before RN, the ORA-00904 moves back to s.DEVICE_ID.

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.

How I can distribute values in Oracle

Regards.
Can you help me with the following?
I have the following records:
**ITEM VALUES**
A001 29440
A002 29440
A003 29440
A004 29440
A005 29440
Σ of the field values is equal to 147200, but the real value is 148 200.
How I can distribute each value in the VALUES column for the Σ is exactly equal to 148 200.
Thank you very much.
Without a better guide as to how you got the "real" value and how the difference should be distributed, you could do something trivial to fix the data - such as adding an equal share of the difference to each value.
Like this:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( ITEM, VALUE ) AS
SELECT 'A001', 29440 FROM DUAL
UNION ALL SELECT 'A002', 29440 FROM DUAL
UNION ALL SELECT 'A003', 29440 FROM DUAL
UNION ALL SELECT 'A004', 29440 FROM DUAL
UNION ALL SELECT 'A005', 29440 FROM DUAL;
UPDATE TEST
SET VALUE = VALUE + ( SELECT (148200 - SUM( VALUE ))/COUNT(1) FROM TEST );
Query 1:
SELECT * FROM TEST
Results:
| ITEM | VALUE |
|------|-------|
| A001 | 29640 |
| A002 | 29640 |
| A003 | 29640 |
| A004 | 29640 |
| A005 | 29640 |

Oracle subquery internal error

The query in listing 1 joins two subqueries, both of which are computed from two named subqueries (ANIMAL and SEA_CREATURE). The output should list animals that don't live in the sea, and list animals that do live in the sea.
When run in a console window (SQL Navigator 5.5), the server returns error:
15:21:30 ORA-00600: internal error code, arguments: [evapls1], [], [], [], [], [], [], []
Why? And how to get around it?
Interesting to note, I can run the same query in a program written in Delphi XE7 (using TSQLQuery component), and it works ok. But this is not a problem with SQL Navigator. If I create a view containing the expression in listing 1, selecting from the view does not output an error. The problem is in the oracle server.
If I make the ANIMAL subquery really simple, like in Listing 2, it works. but anything else, even just selecting from a table, results in this internal error.
Listing 1: (Outputs error)
with ANIMAL as (
select ANIMAL_NAME
from xmltable( 't/e' passing xmltype( '<t><e>Tuna</e><e>Cat</e><e>Dolphin</e><e>Swallow</e></t>')
columns
ANIMAL_NAME varchar2(100) path 'text()')),
SEA_CREATURE as (
select 'Tuna' as CREATURE_NAME from dual
union all select 'Shark' from dual
union all select 'Dolphin' from dual
union all select 'Plankton' from dual)
select NONSEA_ANIMALS, SEA_ANIMALS
from (
select stringagg( ANIMAL_NAME) as NONSEA_ANIMALS
from ( (select * from ANIMAL)
minus (select CREATURE_NAME as ANIMAL_NAME from SEA_CREATURE))),
(select stringagg( ANIMAL_NAME) as SEA_ANIMALS
from ANIMAL
where ANIMAL_NAME in
(select CREATURE_NAME as ANIMAL_NAME from SEA_CREATURE))
Listing 2: (This works)
with ANIMAL as (
select 'Tuna' as ANIMAL_NAME from dual
union all select 'Cat' from dual
union all select 'Dolphin' from dual
union all select 'Swallow' from dual),
SEA_CREATURE as (
select 'Tuna' as CREATURE_NAME from dual
union all select 'Shark' from dual
union all select 'Dolphin' from dual
union all select 'Plankton' from dual)
select NONSEA_ANIMALS, SEA_ANIMALS
from (
select stringagg( ANIMAL_NAME) as NONSEA_ANIMALS
from ( (select * from ANIMAL)
minus (select CREATURE_NAME as ANIMAL_NAME from SEA_CREATURE))),
(select stringagg( ANIMAL_NAME) as SEA_ANIMALS
from ANIMAL
where ANIMAL_NAME in
(select CREATURE_NAME as ANIMAL_NAME from SEA_CREATURE));
Listing 3: Expected output for expressions in both Listings 1 & 2:
NONSEA_ANIMALS SEA_ANIMALS
-------------------------------
'Cat,Swallow' 'Tuna,Dolphin'
The Oracle banner is shown in Listing 4.
Listing 4: select * from v$version
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
PL/SQL Release 10.2.0.4.0 - Production
CORE 10.2.0.4.0 Production
TNS for IBM/AIX RISC System/6000: Version 10.2.0.4.0 - Productio
NLSRTL Version 10.2.0.4.0 - Production
How is this craziness explained?
Update
Here is the explain plan ...
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
| Id | Operation | Name |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TEMP TABLE TRANSFORMATION | |
| 2 | LOAD AS SELECT | |
| 3 | VIEW | |
| 4 | COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE |
| 5 | LOAD AS SELECT | |
| 6 | UNION-ALL | |
| 7 | FAST DUAL | |
| 8 | FAST DUAL | |
| 9 | FAST DUAL | |
| 10 | FAST DUAL | |
| 11 | NESTED LOOPS | |
| 12 | VIEW | |
| 13 | SORT AGGREGATE | |
| 14 | VIEW | |
| 15 | MINUS | |
| 16 | SORT UNIQUE | |
| 17 | VIEW | |
| 18 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6666_765BCCBD |
| 19 | SORT UNIQUE | |
| 20 | VIEW | |
| 21 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6667_765BCCBD |
| 22 | VIEW | |
| 23 | SORT AGGREGATE | |
| 24 | HASH JOIN RIGHT SEMI | |
| 25 | VIEW | VW_NSO_1 |
| 26 | VIEW | |
| 27 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6667_765BCCBD |
| 28 | VIEW | |
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6666_765BCCBD |
----------------------------------------------------------------------------
ORA-03113 and ORA-6000 usually happens on using WITH clause query when
something fatal happened on execution.
Oracle's subquery factoring or WITH clause, can be overused at times. Oracle may create a global temporary table for every query inside WITH clause, for reusing the results. So, XMLTABLE() here, could have created another GTT here, and perhaps this crash the database.
COLLECTION ITERATOR PICKLER FETCH is something when fetched from a
PL/SL object. It returns pickled(packed and formatted) data
It might involve creation of some temp table beneath like I mentioned previously. So the subquery factoring and the PL/Sql array selection didnt go well.
I have also seen queries with nested UNION ALL in WITH getting crashed.
This is most a bug in Oracle, and should be reported to them.
Only way to get around this now, would be reforming the query. In our application, usage of WITH is strictly restricted(due to high CPU usage) for report only purposes executed as batch.

LISTAGG function with two columns

I have one table like this (report)
--------------------------------------------------
| user_id | Department | Position | Record_id |
--------------------------------------------------
| 1 | Science | Professor | 1001 |
| 1 | Maths | | 1002 |
| 1 | History | Teacher | 1003 |
| 2 | Science | Professor | 1004 |
| 2 | Chemistry | Assistant | 1005 |
--------------------------------------------------
I'd like to have the following result
---------------------------------------------------------
| user_id | Department+Position |
---------------------------------------------------------
| 1 | Science,Professor;Maths, ; History,Teacher |
| 2 | Science, Professor; Chemistry, Assistant |
---------------------------------------------------------
That means I need to preserve the empty space as ' ' as you can see in the result table.
Now I know how to use LISTAGG function but only for one column. However, I can't exactly figure out how can I do for two columns at the sametime. Here is my query:
SELECT user_id, LISTAGG(department, ';') WITHIN GROUP (ORDER BY record_id)
FROM report
Thanks in advance :-)
It just requires judicious use of concatenation within the aggregation:
select user_id
, listagg(department || ',' || coalesce(position, ' '), '; ')
within group ( order by record_id )
from report
group by user_id
i.e. aggregate the concatentation of department with a comma and position and replace position with a space if it is NULL.

Resources