I would like to make data in this table more optimise. I have now like this:
And I tried
SELECT *
FROM T_TEST
PIVOT (
MAX(cnt) FOR ASSIGNMENT_ROLE IN ('2nd case','1st case','3rd case')
);
I got this result which has nulls and doesn't work for me
Instead I would like something like this:
I guess you have a table with at least some date column and the assignment_role. For example:
create table role_whatever(
log_date date,
assignment_role varchar2(30),
whatever varchar2(42));
Then you can get the desired report with:
select *
from (
select assignment_role,trunc(log_date) dt
from role_whatever
) pivot (
count(*) for assignment_role in (
'1st case' as FIRST_CASE,
'2nd case' as SECOND_CASE,
'3rd case' as THIRD_CASE))
order by dt;
Note, since the log_date column may contain a time part, I used trunc() on it.
Test data for this table was generated with:
insert into role_whatever
select date '2022-07-15' + dbms_random.value(1,5) log_date,
decode(round(dbms_random.value(1,3)),1,'1st',2,'2nd',3,'3rd') || ' case' assignment_role,
dbms_random.string('A',10) whatever
from dual
connect by level < 10000;
If you have a table with the aggregated result as shown in your first image, then this query gives the same result:
select *
from (
select assignment_role,dt,cnt
from role_aggregated
) pivot (
sum(cnt) for assignment_role in (
'1st case' as FIRST_CASE,
'2nd case' as SECOND_CASE,
'3rd case' as THIRD_CASE))
order by dt;
The table above was created with:
create table role_aggregated as
select count(*) cnt,assignment_role,trunc(log_date) dt
from role_whatever
group by assignment_role,trunc(log_date);
Related
I am experimenting with a recursive CTE to split a string into multiple values. The data is then inserted into another table.
The following works in PostgreSQL:
CREATE TABLE test(data varchar(255));
WITH RECURSIVE
cte(genres) AS (SELECT 'apple,banana,cherry,date'),
split(genre,rest,genres) AS (
SELECT '', genres||',',genres FROM cte
UNION ALL
SELECT
substring(rest,0,position(',' IN rest)),
substring(rest,position(',' IN rest)+1),
genres
FROM split WHERE rest<>''
)
INSERT INTO test(data)
SELECT genre
FROM split;
However, the Oracle version:
CREATE TABLE test(data varchar(255));
WITH
cte(genres) AS (SELECT 'apple,banana,cherry,date' FROM dual),
split(genre,rest,genres) AS (
SELECT '', genres||',',genres FROM cte
UNION ALL
SELECT
substr(rest,1,instr(rest,',')-1),
substr(rest,instr(rest,',')+1),
genres
FROM split WHERE rest IS NOT NULL
)
INSERT INTO test(data)
SELECT genre
FROM split WHERE genre IS NOT NULL;
gives me the error message:
ORA-00928: missing SELECT keyword.
Now I’m pretty sure that I’ve got one, there between the INSERT and the following FROM.
If you comment out the INSERT, the rest of it will give the results. Elsewhere, I know that a simple INSERT … SELECT does work.
Is it something to do with the recursive CTE? How can I get this to work properly using a standard recursive CTE?
Using Oracle 18c in a Docker image. There is a fiddle here: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=d24f3105634933af1b7072bded1dacdf .
An INSERT-SELECT means "INSERT" command first, then the SELECT part, and the WITH is part of the SELECT not the insert.
SQL> create table t ( x int );
Table created.
SQL> with blah as ( select 1 c from dual)
2 insert into t
3 select * from blah;
insert into t
*
ERROR at line 2:
ORA-00928: missing SELECT keyword
SQL> insert into t
2 with blah as ( select 1 c from dual)
3 select * from blah;
1 row created.
I have a table with one column giving me dates in the form 24-JUL-17 and another column a string telling me the hour in the form hh:mm:ss.several_decimals but they are in 24 hours and not AM/PM, could someone point me on how I can add a column in oracle that combines them both into a timestamp?
CAST the date to a timestamp data type and use TO_DSINTERVAL to convert the time column to a DAY TO SECOND INTERVAL and then add one to the other:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name (
date_column DATE,
time_column VARCHAR2(15)
);
INSERT INTO table_name ( date_column, time_column )
VALUES ( DATE '2017-06-24', '12:34:56.789012' );
Query 1:
SELECT CAST( date_column AS TIMESTAMP )
+ TO_DSINTERVAL( '+0 ' || time_column ) AS datetime
FROM table_name
Results:
| DATETIME |
|----------------------------|
| 2017-06-24 12:34:56.789012 |
Here is an example with a fabricated table.
WITH test_data AS (
SELECT
SYSDATE date_val,
'14:23:15.123' AS time_val
FROM
dual
) SELECT
to_timestamp(TO_CHAR(date_val,'MM/DD/YYYY')
|| ' '
|| time_val,'MM/DD/YYYY HH24:MI:SS.FF3')
FROM
test_data
Just concatenate both input strings with || and convert it with TO_TIMESTAMP. The format is a bit tricky, took me a while to get it right.
CREATE TABLE mytable (mydate VARCHAR2(9), mytime VARCHAR2(20), mytimestamp TIMESTAMP);
INSERT INTO mytable (mydate, mytime) VALUES ('24-JUL-17', '22:56:47.1915010');
Test it
SELECT TO_TIMESTAMP(mydate||mytime, 'DD-MON-YYHH24:MI:SS.FF9') FROM mytable;
2017-07-24 22:56:47,191501000
Or, in a table:
UPDATE mytable
SET mytimestamp = TO_TIMESTAMP(mydate||mytime, 'DD-MON-YYHH24:MI:SS.FF9');
I was doing a select operation on my table in oracle but was getting ORA-01795 so,
then I try inserting my values in the list of order 1000+ (890623250,915941020,915941021,....1000+ times) into temp table and I can't figure it out how to do it so that later I can do a select from a temp table
So basically my objective is to insert those 1000 id into the temp table of schema
TEMP_L{ID INTEGER} like INSERT INTO TEMP_LINK SELECT(890623254,915941020,1000+ values )
Use a collection. SYS.ODCINUMERLIST is a built-in VARRAY:
INSERT INTO TEMP_LINK ( value )
SELECT COLUMN_VALUE
FROM TABLE( SYS.ODCINUMBERLIST( 890623254,915941020,1000 /* + values */ ) );
Or you can define your own collection:
CREATE TYPE NumberList IS TABLE OF NUMBER;
INSERT INTO TEMP_LINK ( value )
SELECT COLUMN_VALUE
FROM TABLE( NumberList( 890623254,915941020,1000 /* + values */ ) );
However, if you are going to use collections then you don't need to load them into a temp table:
SELECT *
FROM your_table
WHERE your_id MEMBER OF NumberList( 890623254,915941020,1000 /* + values */ )
or
SELECT *
FROM your_table
WHERE your_id IN (
SELECT COLUMN_VALUE
FROM TABLE( 890623254,915941020,1000 /* + values */ )
);
Preferably use SQL* Loader for bulk inserts. One other option would be to construct a query using Excel or notepad++ for all the ids.
INSERT INTO mytable(id)
select 890623250 FROM DUAL UNION ALL
select 915941020 FROM DUAL UNION ALL
...
..
I have a function, which will get greatest of three dates from the table.
create or replace FUNCTION fn_max_date_val(
pi_user_id IN number)
RETURN DATE
IS
l_modified_dt DATE;
l_mod1_dt DATE;
l_mod2_dt DATE;
ret_user_id DATE;
BEGIN
SELECT MAX(last_modified_dt)
INTO l_modified_dt
FROM table1
WHERE id = pi_user_id;
-- this table contains a million records
SELECT nvl(MAX(last_modified_ts),sysdate-90)
INTO l_mod1_dt
FROM table2
WHERE table2_id=pi_user_id;
-- this table contains clob data, 800 000 records, the table 3 does not have user_id and has to fetched from table 2, as shown below
SELECT nvl(MAX(last_modified_dt),sysdate-90)
INTO l_mod2_dt
FROM table3
WHERE table2_id IN
(SELECT id FROM table2 WHERE table2_id=pi_user_id
);
execute immediate 'select greatest('''||l_modified_dt||''','''||l_mod1_dt||''','''||l_mod2_dt||''') from dual' into ret_user_id;
RETURN ret_user_id;
EXCEPTION
WHEN OTHERS THEN
return SYSDATE;
END;
this function works perfectly fine and executes within a second.
-- random user_id , just to test the functionality
SELECT fn_max_date_val(100) as max_date FROM DUAL
MAX_DATE
--------
27-02-14
For reference purpose i have used the table name as table1,table2 and table3 but my business case is similar to what i stated below.
I need to get the details of the table1 along with the highest modified date among the three tables.
I did something like this.
SELECT a.id,a.name,a.value,fn_max_date_val(id) as max_date
FROM table1 a where status_id ='Active';
The above query execute perfectly fine and got result in millisecods. But the problem came when i tried to use order by.
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt,fn_max_date_val(id) as max_date
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc ;
-- It took almost 300 seconds to complete
I tried using index also all the values of the status_id and last_modified, but no luck. Can this be done in a right way?
How about if your query is like this?
select a.*, fn_max_date_val(id) as max_date
from
(SELECT a.id,a.name,a.value,a.status_id,last_modified_dt
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc) a;
What if you don't use the function and do something like this:
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt x.max_date
FROM table1 a
(
select max(max_date) as max_date
from (
SELECT MAX(last_modified_dt) as max_date
FROM table1 t1
WHERE t1.id = a.id
union
SELECT nvl(MAX(last_modified_ts),sysdate-90) as max_date
FROM table2 t2
WHERE t2.table2_id=a.id
...
) y
) x
where a.status_id ='Active'
order by status_id desc,last_modified_dt desc;
Syntax might contain errors, but something like that + the third table in the derived table too.
What is the easiest way to INSERT a row if it doesn't exist, in PL/SQL (oracle)?
I want something like:
IF NOT EXISTS (SELECT * FROM table WHERE name = 'jonny') THEN
INSERT INTO table VALUES ("jonny", null);
END IF;
But it's not working.
Note: this table has 2 fields, say, name and age. But only name is PK.
INSERT INTO table
SELECT 'jonny', NULL
FROM dual -- Not Oracle? No need for dual, drop that line
WHERE NOT EXISTS (SELECT NULL -- canonical way, but you can select
-- anything as EXISTS only checks existence
FROM table
WHERE name = 'jonny'
)
Assuming you are on 10g, you can also use the MERGE statement. This allows you to insert the row if it doesn't exist and ignore the row if it does exist. People tend to think of MERGE when they want to do an "upsert" (INSERT if the row doesn't exist and UPDATE if the row does exist) but the UPDATE part is optional now so it can also be used here.
SQL> create table foo (
2 name varchar2(10) primary key,
3 age number
4 );
Table created.
SQL> ed
Wrote file afiedt.buf
1 merge into foo a
2 using (select 'johnny' name, null age from dual) b
3 on (a.name = b.name)
4 when not matched then
5 insert( name, age)
6* values( b.name, b.age)
SQL> /
1 row merged.
SQL> /
0 rows merged.
SQL> select * from foo;
NAME AGE
---------- ----------
johnny
If name is a PK, then just insert and catch the error. The reason to do this rather than any check is that it will work even with multiple clients inserting at the same time. If you check and then insert, you have to hold a lock during that time, or expect the error anyway.
The code for this would be something like
BEGIN
INSERT INTO table( name, age )
VALUES( 'johnny', null );
EXCEPTION
WHEN dup_val_on_index
THEN
NULL; -- Intentionally ignore duplicates
END;
I found the examples a bit tricky to follow for the situation where you want to ensure a row exists in the destination table (especially when you have two columns as the primary key), but the primary key might not exist there at all so there's nothing to select.
This is what worked for me:
MERGE INTO table1 D
USING (
-- These are the row(s) you want to insert.
SELECT
'val1' AS FIELD_A,
'val2' AS FIELD_B
FROM DUAL
) S ON (
-- This is the criteria to find the above row(s) in the
-- destination table. S refers to the rows in the SELECT
-- statement above, D refers to the destination table.
D.FIELD_A = S.FIELD_A
AND D.FIELD_B = S.FIELD_B
)
-- This is the INSERT statement to run for each row that
-- doesn't exist in the destination table.
WHEN NOT MATCHED THEN INSERT (
FIELD_A,
FIELD_B,
FIELD_C
) VALUES (
S.FIELD_A,
S.FIELD_B,
'val3'
)
The key points are:
The SELECT statement inside the USING block must always return rows. If there are no rows returned from this query, no rows will be inserted or updated. Here I select from DUAL so there will always be exactly one row.
The ON condition is what sets the criteria for matching rows. If ON does not have a match then the INSERT statement is run.
You can also add a WHEN MATCHED THEN UPDATE clause if you want more control over the updates too.
Using parts of #benoit answer, I will use this:
DECLARE
varTmp NUMBER:=0;
BEGIN
-- checks
SELECT nvl((SELECT 1 FROM table WHERE name = 'john'), 0) INTO varTmp FROM dual;
-- insert
IF (varTmp = 1) THEN
INSERT INTO table (john, null)
END IF;
END;
Sorry for I don't use any full given answer, but I need IF check because my code is much more complex than this example table with name and age fields. I need a very clear code. Well thanks, I learned a lot! I'll accept #benoit answer.
In addition to the perfect and valid answers given so far, there is also the ignore_row_on_dupkey_index hint you might want to use:
create table tq84_a (
name varchar2 (20) primary key,
age number
);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', 77);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Pete' , 28);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Sue' , 35);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', null);
select * from tq84_a;
The hint is described on Tahiti.
you can use this syntax:
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
if its open an pop for asking as "enter substitution variable" then use this before the above queries:
set define off;
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
You should use Merge:
For example:
MERGE INTO employees e
USING (SELECT * FROM hr_records WHERE start_date > ADD_MONTHS(SYSDATE, -1)) h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
or
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
https://oracle-base.com/articles/9i/merge-statement
CTE and only CTE :-)
just throw out extra stuff. Here is almost complete and verbose form for all cases of life. And you can use any concise form.
INSERT INTO reports r
(r.id, r.name, r.key, r.param)
--
-- Invoke this script from "WITH" to the end (";")
-- to debug and see prepared values.
WITH
-- Some new data to add.
newData AS(
SELECT 'Name 1' name, 'key_new_1' key FROM DUAL
UNION SELECT 'Name 2' NAME, 'key_new_2' key FROM DUAL
UNION SELECT 'Name 3' NAME, 'key_new_3' key FROM DUAL
),
-- Any single row for copying with each new row from "newData",
-- if you will of course.
copyData AS(
SELECT r.*
FROM reports r
WHERE r.key = 'key_existing'
-- ! Prevent more than one row to return.
AND FALSE -- do something here for than!
),
-- Last used ID from the "reports" table (it depends on your case).
-- (not going to work with concurrent transactions)
maxId AS (SELECT MAX(id) AS id FROM reports),
--
-- Some construction of all data for insertion.
SELECT maxId.id + ROWNUM, newData.name, newData.key, copyData.param
FROM copyData
-- matrix multiplication :)
-- (or a recursion if you're imperative coder)
CROSS JOIN newData
CROSS JOIN maxId
--
-- Let's prevent re-insertion.
WHERE NOT EXISTS (
SELECT 1 FROM reports rs
WHERE rs.name IN(
SELECT name FROM newData
));
I call it "IF NOT EXISTS" on steroids. So, this helps me and I mostly do so.