Is it possible to Insert multiple rows with same user and differrent id - oracle11g

I just started with Oracle-Sql and apex , I have stumbled into this code/answer that another user made (credits to the user) and tweaked it a bit. http://sqlfiddle.com/#!4/fa7342/11
this code create 'x' rows/appointments defined by the multiple conditions
what I'm trying to do is to insert into a table 'x' rows/appointments with :
same user_id(not pk)
different appointment_id( pk - sequence and incremented by 'x' appointments')
If the input is :
interval_type VARCHAR2(20) := 'daily';
start_date DATE := DATE '2021-07-15';
number_of_appointments number := 3;
appointent_id_number number := sequence.nextval;
id_client number := 1012;
output should be :
APPOINTMENT_DATE
APPOINTMENT_ID
ID_CLIENT
2021-07-16T00:00:00Z
1
1012
2021-07-17T00:00:00Z
2
1012
2021-07-18T00:00:00Z
3
1012
query :
DECLARE
interval_type VARCHAR2(20) := 'daily';
start_date DATE := DATE '2021-07-15';
number_of_appointments number := 3;
appointent_id_number number := sequence.nextval
id_client number := 1012;
BEGIN
INSERT INTO table_name (appointment_date)
SELECT CASE interval_type
WHEN 'daily'
THEN start_date + INTERVAL '1' DAY * LEVEL
WHEN 'weekly'
THEN start_date + INTERVAL '7' DAY * LEVEL
WHEN 'monthly'
THEN ADD_MONTHS( start_date, LEVEL )
END
FROM DUAL
CONNECT BY
LEVEL <= number_of_appointments;
END;
/

It is easier to let the database generate your primary key so you don't have to worry about that in your code. In the code below I'm using an identity column as primary key - in that case a sequence doesn't need to be created manually. However if you're really on 11g (consider upgrading - that version is very very old) identity columns won't work. Code for that case is at the bottom.
create table appointments_table (
id number generated by default on null as identity
constraint appointments_table_id_pk primary key,
appointment_date date,
id_client number
)
;
DECLARE
interval_type VARCHAR2(20) := 'daily';
start_date DATE := DATE '2021-07-15';
number_of_appointments number := 3;
l_id_client number := 1012;
BEGIN
INSERT INTO appointments_table (appointment_date, id_client)
SELECT CASE interval_type
WHEN 'daily'
THEN start_date + INTERVAL '1' DAY * LEVEL
WHEN 'weekly'
THEN start_date + INTERVAL '7' DAY * LEVEL
WHEN 'monthly'
THEN ADD_MONTHS( start_date, LEVEL )
END,
l_id_client
FROM DUAL
CONNECT BY
LEVEL <= number_of_appointments;
END;
/
select * from appointments_table;
ID APPOINTMENT ID_CLIENT
---------- ----------- ----------
1 16-JUL-2021 1012
2 17-JUL-2021 1012
3 18-JUL-2021 1012
If the version of oracle you are on does not support identity columns (available from 12.1) then this is how you can create your table:
create sequence appointments_table_seq;
create table appointments_table (
id number default appointments_table_seq.NEXTVAL
constraint appointments_table_id_pk primary key,
appointment_date_dt date,
id_client number
)
;

Related

PL/Sql procedure: my cursor is ordered by date but the output that is written to a table is not ordered the same

Only getting my head around cursor loops and the likes lately, so might be something very simple with my code that's causing the problem
I am using a cursor to spool through customer data to create an xml file. It needs to be sorted by date so that the most recent data is at the bottom of the xml file.
when I run the sql for the cursor, i can see the data is ordered by date. But when I run the entire procedure and check the output, it seems to be ordered by date but on closer inspection some of the records are not in the correct order.
here is the code I'm running. I've omitted a lot of the query as its just xml padding, but I don't think that should make a difference.
the output is written to a table, which i then copy and paste into notepad++. When checking the output table I can see that the order is wrong
drop table recs_xml_output;
create table recs_xml_output (XML_STRING VARCHAR2 (4000 char));
declare
PROCEDURE p_generate_ohmpi_record
IS
lv_string VARCHAR2(10000 CHAR) := NULL;
lv_date_format VARCHAR2(20 CHAR) := 'YYYY-MM-DD';
lv_time_format VARCHAR2(20 CHAR) := 'HH24:MI:SS';
n_id PLS_INTEGER := NULL;
CURSOR c_patient_xml IS
select *
from sbyn_transaction T
where timestamp >= '07-JAN-22 11.58.02.139977000'
and timestamp <= '07-JAN-22 17.51.26.054240000'
ORDER BY TIMESTAMP;
begin
for v_patient_xml in c_patient_xml
loop
lv_string := n_id||'<Person><SourceID>';
lv_string := lv_string||v_patient_xml.lid||'</SourceID><PPSN>'||v_patient_xml.lid||'</PPSN>';
lv_string := lv_string||'<PPSNLastUpdated>';
lv_string := lv_string||TO_CHAR( v_patient_xml.pps_number_updated,lv_date_format )||'T'||TO_CHAR( v_patient_xml.pps_number_updated,lv_time_format)||'</PPSNLastUpdated>';
lv_string := lv_string||'<Birth>';
IF v_patient_xml.date_of_birth IS NOT NULL THEN
lv_string := lv_string||'<DateOfBirth>'||TO_CHAR( v_patient_xml.date_of_birth,lv_date_format )||'T'||TO_CHAR( v_patient_xml.date_of_birth,lv_time_format)||'</DateOfBirth>';
else lv_string := lv_string||'<DateOfBirth></DateOfBirth>';
END IF;
...
insert into recs_xml_output VALUES (lv_string);
END LOOP;
COMMIT;
end p_generate_ohmpi_record;
begin
p_generate_ohmpi_record;
end;
/
The main issue with your code is that you aren't storing the ordering column in your output table, and you're relying on the rows being returned from that table in the order they were inserted.
Unfortunately, as it's a heap table, the order of insertion is not necessarily going to be the same as the order you retrieve them. In order to guarantee a specific ordering of the rows when selecting from a table, you need to have an order by clause.
Therefore you could do something like:
create table recs_xml_output (tstamp timestamp, XML_STRING VARCHAR2 (4000 char));
PROCEDURE p_generate_ohmpi_record
IS
...
CURSOR c_patient_xml IS
select *
from sbyn_transaction T
where timestamp >= '07-JAN-22 11.58.02.139977000'
and timestamp <= '07-JAN-22 17.51.26.054240000'
ORDER BY TIMESTAMP;
begin
for v_patient_xml in c_patient_xml
loop
...
insert into recs_xml_output (tstamp, xml_string)
VALUES (v_patient_xml.timestamp, lv_string);
END LOOP;
COMMIT;
end p_generate_ohmpi_record;
select *
from recs_xml_output
order by tstamp;
However, if your ultimate goal is simply to take your rows and output them as XML, you can do it in a single SQL statement:
WITH sbyn_transaction AS (SELECT 1 lid,
to_timestamp('11/01/2022 11:25:57.136468', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
to_date('01/01/2000', 'dd/mm/yyyy') date_of_birth,
'info 1' info_column
FROM dual
UNION ALL
SELECT 2 lid,
to_timestamp('11/01/2022 11:23:46.115329', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
to_date('06/10/1979', 'dd/mm/yyyy') date_of_birth,
'info 2' info_column
FROM dual
UNION ALL
SELECT 3 lid,
to_timestamp('11/01/2022 11:24:08.951232', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
NULL date_of_birth,
'info 3' info_column
FROM dual
UNION ALL
SELECT 4 lid,
to_timestamp('11/01/2022 11:23:17.468329', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
to_date('29/03/1957', 'dd/mm/yyyy') date_of_birth,
'info 4' info_column
FROM dual)
-- end of mimicking your table with data in it; main query below:
SELECT st.*,
XMLELEMENT("Person",
XMLFOREST(lid AS "SourceID",
lid AS "PPSN",
to_char(pps_number_updated, 'yyyy-mm-dd"T"hh24:mi:ss') AS "PPSNLastUpdated"),
XMLELEMENT("Birth",
XMLFOREST(to_char(date_of_birth, 'yyyy-mm-dd"T"hh24:mi:ss') AS "DateOfBirth") AS "Birth"),
XMLFOREST(info_column AS "SomeData")).getclobval() xml_record
FROM sbyn_transaction st
ORDER BY pps_number_updated;
LID PPS_NUMBER_UPDATED DATE_OF_BIRTH INFO_COLUMN XML_RECORD
---------- ------------------------------------------------- ------------- ----------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4 11-JAN-22 11.23.17.468329000 29/03/1957 info 4 <Person><SourceID>4</SourceID><PPSN>4</PPSN><PPSNLastUpdated>2022-01-11T11:23:17</PPSNLastUpdated><Birth><DateOfBirth>1957-03-29T00:00:00</DateOfBirth></Birth><SomeData>info 4</SomeData></Person>
2 11-JAN-22 11.23.46.115329000 06/10/1979 info 2 <Person><SourceID>2</SourceID><PPSN>2</PPSN><PPSNLastUpdated>2022-01-11T11:23:46</PPSNLastUpdated><Birth><DateOfBirth>1979-10-06T00:00:00</DateOfBirth></Birth><SomeData>info 2</SomeData></Person>
3 11-JAN-22 11.24.08.951232000 info 3 <Person><SourceID>3</SourceID><PPSN>3</PPSN><PPSNLastUpdated>2022-01-11T11:24:08</PPSNLastUpdated><Birth></Birth><SomeData>info 3</SomeData></Person>
1 11-JAN-22 11.25.57.136468000 01/01/2000 info 1 <Person><SourceID>1</SourceID><PPSN>1</PPSN><PPSNLastUpdated>2022-01-11T11:25:57</PPSNLastUpdated><Birth><DateOfBirth>2000-01-01T00:00:00</DateOfBirth></Birth><SomeData>info 1</SomeData></Person>

SQLite: Running balance with an ending balance

I have an ending balance of $5000. I need to create a running balance, but adjust the first row to show the ending balance then sum the rest, so it will look like a bank statement. Here is what I have for the running balance but how can I adjust row 1 to not show a sum of the first row, but the ending balance instead.
with BalBefore as (
select *
from transactions
where ACCT_NAME = 'Real Solutions'
ORDER BY DATE DESC
)
select
DATE,
amount,
'$' || printf("%.2f", sum(AMOUNT) over (order by ROW_ID)) as Balance
from BalBefore;
This gives me"
DATE AMOUNT BALANCE
9/6/2019 -31.00 $-31.00 <- I need this balance to be replaced with $5000 and have the rest
9/4/2019 15.00 $-16.00 sum as normal.
9/4/2019 15.00 $-1.00
9/3/2019 -16.00 $-17.00
I have read many other questions, but I couldn't find one that I could understand so I thought I would post a simpler question.
The following is not short and sweet, but using the WITH statement and CTEs, I hope that the logic is apparent. Multiple CTEs are defined which refer to each other to make the overall query more readable. Altogether the goal was just to add a beginning balance record that could be :
/*
DROP TABLE IF EXISTS data;
CREATE temp TABLE data (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
date DATETIME NOT NULL,
amount NUMERIC NOT NULL
);
INSERT INTO data
(date, amount)
VALUES
('2019-09-03', -16.00),
('2019-09-04', 15.00),
('2019-09-04', 15.00),
('2019-09-06', -31.00)
;
*/
WITH
initial_filter AS (
SELECT id, date, amount
FROM data
--WHERE ACCT_NAME = 'Real Solutions'
),
prepared AS (
SELECT *
FROM initial_filter
UNION ALL
SELECT
9223372036854775807 as id, --largest signed integer
(SELECT MAX(date) FROM initial_filter) AS FinalDate,
-(5000.00) --ending balance (negated for summing algorithm)
),
running AS (
SELECT
id,
date,
amount,
SUM(-amount) OVER
(ORDER BY date DESC, id DESC
RANGE UNBOUNDED PRECEDING
EXCLUDE CURRENT ROW) AS balance
FROM prepared
ORDER BY date DESC, id DESC
)
SELECT *
FROM running
WHERE id != 9223372036854775807
ORDER BY date DESC, id DESC;
This produces the following
id date amount balance
4 2019-09-06 -31.00 5000
3 2019-09-04 15.00 5031
2 2019-09-04 15.00 5016
1 2019-09-03 -16.00 5001
UPDATE: The first query was not producing the correct balances. The beginning balance row and the windowing function (i.e. OVER clause) were updated to accurately sum over the correct amounts.
Note: The balance on each row is determined completely from the previous rows, not from the current row's amount, because this works backward from an ending balance, not forward from the previous row balance.

Function to generate random date from period [duplicate]

I have this anonymous block:
DECLARE
V_DATA DATE;
BEGIN
V_DATA := '01-GEN-2000';
HR.STATISTICHE.RATINGOPERATORI (V_DATA);
COMMIT;
END;
but I would to generate the date in a random way. How can I do?
You can generate random dates between two dates ,as displayed in the query below .Random Dates are generated between 1-jan-2000 and 31-dec-9999
SELECT TO_DATE(
TRUNC(
DBMS_RANDOM.VALUE(TO_CHAR(DATE '2000-01-01','J')
,TO_CHAR(DATE '9999-12-31','J')
)
),'J'
) FROM DUAL;
OR you can use
SELECT TO_DATE (
TRUNC (
DBMS_RANDOM.VALUE (2451545, 5373484)
)
, 'J'
)
FROM DUAL
In the above example ,the first value is 01-Jan-2000 and the second value id 31-dec-9999
To generate random date you can use
select to_date('2010-01-01', 'yyyy-mm-dd')+trunc(dbms_random.value(1,1000)) from dual
or for random datetime
select to_date('2010-01-01', 'yyyy-mm-dd')+dbms_random.value(1,1000) from dual
If you want to see it's logic, you can also use this code.
create or replace procedure genDate(result out nvarchar2) IS
year number;
month number;
day number;
Begin
year:=FLOOR(DBMS_RANDOM.value(2000,2100));
month:=FLOOR(DBMS_RANDOM.value(1,12));
IF month=2 and (year/4)=0 and (year/100)!=0 then
day:=FLOOR(DBMS_RANDOM.value(1,29));
ELSIF month=2 or (year/100)=0 then
day:=FLOOR(DBMS_RANDOM.value(1,28));
ELSIF MOD(month,2)=1 then
day:=FLOOR(DBMS_RANDOM.value(1,31));
ELSIF MOD(month,2)=0 and month!=2 then
day:=FLOOR(DBMS_RANDOM.value(1,30));
END IF;
result:=month||'-'||day||'-'||year;
End;
here is one more option to generate date going back from now where 365 - days quanitity to move back from today, 'DD.MM.YYYY'- mask
to_char(sysdate-dbms_random.value()*365, 'DD.MM.YYYY')
I needed to generate employee data for testing. Each employee needed a date of birth that put them between 16 and 65 years of age, and a date of hire sometime between their 16th birthday and SYSDATE. Here's how...
FUNCTION randomDateInRange(alpha IN DATE, omega IN DATE) RETURN DATE IS
BEGIN
RETURN alpha + DBMS_RANDOM.VALUE(0, omega - alpha);
END;
...and then, to use this function...
-- an employee can be any age from 16 to 65 years of age
DoB := randomDateInRange(
SYSDATE - INTERVAL '65' YEAR,
SYSDATE - INTERVAL '16' YEAR
);
-- an employee could have been hired any date since their sixteenth birthday
DoH := randomDateInRange(
DoB + INTERVAL '16' YEAR,
SYSDATE
);

How to convert the Long value to String using sql

I am doing a long to string conversion using java in following way.
Long longValue = 367L;
String str = Long.toString(longValue, 36).toUpperCase();
this is returning me as value A7. how can achieve this in doing oracle sql.
UPDATED:
Hi, I have analyzed how java code is working then wanted to implement the same thing in procedure.
First point is Input vaues. LONG and Radix. in my case Radix is 36. so i will have values from 1..9A...Z0 It picks up the values from this set only.
Second point Long value as input. we have to divide this value with radix. if the quotient is more than 36 again we need to divide.
For eaxmple 367 then my converted value is 10(quotient) 7(remainder) that is A7.
3672 converted value is 102 0 i need to do again for 102 that is 2 -6 so my final value will be 2-6 0 that is 2U0(- means reverse the order).
UPDATE 2:
Using oracle built in functions we can do this. this was solved by my friend and gave me a function.I want to thank my friend. this will give me an out put as follows.
367 then my converted value is 10(quotient) 7(remainder) that is *A*7.(I modified this to my requirement).
FUNCTION ENCODE_STRING(BASE_STRING IN VARCHAR2,
FROM_BASE IN NUMBER,
TO_BASE IN NUMBER)
RETURN VARCHAR2
IS
V_ENCODED_STRING VARCHAR(100);
BEGIN
WITH N1 AS (
SELECT SUM((CASE
WHEN C BETWEEN '0' AND '9'
THEN TO_NUMBER(C)
ELSE
ASCII(C) - ASCII('A') + 10
END) * POWER(FROM_BASE, LEN - RN)
) AS THE_NUM
FROM (SELECT SUBSTR(BASE_STRING, ROWNUM, 1) C, LENGTH(BASE_STRING) LEN, ROWNUM RN
FROM DUAL
CONNECT BY ROWNUM <= LENGTH(BASE_STRING))
),
N2 AS (
SELECT (CASE
WHEN N < 10
THEN TO_CHAR(N)
ELSE CHR(ASCII('A') + N - 10)
END) AS DIGI, RN
FROM (SELECT MOD(TRUNC(THE_NUM/POWER(TO_BASE, ROWNUM - 1)), TO_BASE) N, ROWNUM RN
FROM N1
CONNECT BY ROWNUM <= TRUNC(LOG(TO_BASE, THE_NUM)) + 1)
)
SELECT SYS_CONNECT_BY_PATH(DIGI, '*') INTO V_ENCODED_STRING
FROM N2
WHERE RN = 1
START WITH RN = (SELECT MAX(RN) FROM N2)
CONNECT BY RN = PRIOR RN - 1;
RETURN V_ENCODED_STRING;
IN PL/SQL (or Oracle SQL) you have the a function called TO_CHAR.
http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions181.htm
It is not possible to do it in the pure SQL. You have to use PL/SQL.
Simple example how to do it PL/SQL:
CREATE TABLE long_tbl
(
long_col LONG
);
INSERT INTO long_tbl VALUES('How to convert the Long value to String using sql');
DECLARE
l_varchar VARCHAR2(32767);
BEGIN
SELECT long_col
INTO l_varchar
FROM long_tbl;
DBMS_OUTPUT.PUT_LINE(l_varchar);
END;
-- How to convert the Long value to String using sql
There is TO_LOB function but it can only by used when you insert data into table.
http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions185.htm
You can apply this function only to a LONG or LONG RAW column, and
only in the select list of a subquery in an INSERT statement.
There is also other, more proper way to do it by using "dbms_sql.column_value_long" but this gets complicated (fetching of the LONG column and appending to the CLOB type.)
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#i1025399
(Oracle Database PL/SQL Packages and Types Reference)

Pl/SQL - oracle 9i - Manual Pivoting

We have a table which has three columns in it:
Customer_name, Age_range, Number_of_people.
1 1-5 10
1 5-10 15
We need to return all the number of people in different age ranges as rows of a single query. If we search for customer #1, the query should just return one row:
Header- Age Range (1-5) Age Range (5-10)
10 15
We needed to get all the results in a single row; When I query for customer 1, the result should be only number of people in a single row group by age_range.
What would be the best way to approach this?
You need to manually perform a pivot:
SELECT SUM(CASE WHEN age_range = '5-10'
THEN number_of_people
ELSE NULL END) AS nop5,
SUM(CASE WHEN age_range = '10-15'
THEN number_of_people
ELSE NULL END) AS nop10
FROM customers
WHERE customer_name = 1;
There are easy solutions with 10g and 11g using LISTGAGG, COLLECT, or other capabilities added after 9i but I believe that the following will work in 9i.
Source (http://www.williamrobertson.net/documents/one-row.html)
You will just need to replace deptno with customer_name and ename with Number_of_people
SELECT deptno,
LTRIM(SYS_CONNECT_BY_PATH(ename,','))
FROM ( SELECT deptno,
ename,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS seq
FROM emp )
WHERE connect_by_isleaf = 1
CONNECT BY seq = PRIOR seq +1 AND deptno = PRIOR deptno
START WITH seq = 1;
DEPTNO CONCATENATED
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.
This will create a stored FUNCTION which means you can access it at any time.
CREATE OR REPLACE FUNCTION number_of_people(p_customer_name VARCHAR2)
RETURN VARCHAR2
IS
v_number_of_people NUMBER;
v_result VARCHAR2(500);
CURSOR c1
IS
SELECT Number_of_people FROM the_table WHERE Customer_name = p_customer_name;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_number_of_people;
EXIT WHEN c1%NOTFOUND;
v_result := v_result || v_number_of_people || ' ' || CHR(13);
END;
END;
To run it, use:
SELECT number_of_people(1) INTO dual;
Hope this helps, and please let me know if there are any errors, I didn't testrun the function myself.
Just do
select Number_of_people
from table
where Customer_name = 1
Are we missing some detail?

Resources