How to create random function using a stored procedure? PL/SQL - sql

I have a procedure that adds data
add_price (cust_id customers.id%type,
items_id items.id%type,
price number);
and I want to create a function that for each combination of customers and items to create an additional one at random entry in the table price.
How can I do that?

UPD: Please note, I believe the idea from MT0 is better because you'll need only one insert statement. My solution is for the case when using add_price function is required
So, "each combination of customers and items" means you need a cartesian product:
select cust_id, item_id
from customers
cross join items;
For example if you had following data in "customers" and "items" table:
cust_id
cust_name
1
A
2
B
item_id
item_name
1
a
2
b
the query above would return:
cust_id
item_id
1
1
1
2
2
1
2
2
Thus, all is left is to get random value. Use dbms_random.value for that
begin
for q in (select cust_id, item_id from customers cross join items) loop
add_price(q.cust_id, q.item_id, round(dbms_random.value(10, 1000), 2));
end loop;
end;
The parameters for value are lowes_value and highest_value so the result will be between those numbers. You probably will need to set them somehow. And rounding will be needed too

Don't use a function, create a procedure and use INSERT ... SELECT with the CROSS JOIN of the customers and the items tables:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
INSERT INTO prices (customer_id, item_id, price)
SELECT c.customer_id,
i.item_id,
ROUND(DBMS_RANDOM.VALUE(0,100),2)
FROM customers c
CROSS JOIN items i;
END generate_random_prices;
/
Which, if you have the sample data:
CREATE TABLE customers (customer_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,5,42));
CREATE TABLE items (item_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,3,61));
CREATE TABLE prices (
customer_id REFERENCES customers(customer_id),
item_id REFERENCES items(item_id),
price NUMBER(4,2)
);
Then after:
BEGIN
generate_random_prices();
END;
/
The prices table may (randomly) contain:
CUSTOMER_ID
ITEM_ID
PRICE
1
1
38.91
1
3
39.74
1
61
67.28
5
1
13.92
5
3
48.17
5
61
70.21
42
1
90.33
42
3
5.7
42
61
40.37
If you want to call your ADD_PRICE procedure then just take the same CROSS JOIN query and use a cursor loop:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
FOR rw IN (SELECT c.customer_id,
i.item_id
FROM customers c
CROSS JOIN items i)
LOOP
ADD_PRICE(rw.customer_id, rw.item_id, ROUND(DBMS_RANDOM.VALUE(0,100),2));
END LOOP;
END generate_random_prices;
/
(But it will be more efficient to just use a single INSERT ... SELECT statement.)
db<.fiddle here

Related

Inner Join with Sum in oracle SQL

I'm using Oracle SQL and I have two tables, invoice and invoice_item.
invoice:
id(pk) total_invoice_price
1
2
invoice_item:
invoice total_item_price
1 10
1 20
2 25
2 35
I need that total_invoice_price be the sum of every total_item_price where invoice = id.
invoice_item.invoice is a fk that references to invoice.id
The best I could make was in the lines of:
update(
select invoice.total_invoice_price as old, SUM(invoice_item.total_item_price) as total
from invoice
inner join invoice_item
on invoice.id = invoice_item.invoice
) t
set t.old = t.total;
but it obviously doesn't work.
Tables creation:
create table invoice(
id number(5) not null,
customer_name varchar2(50) not null,
issue_date date not null,
due_date date not null,
comments varchar2(50) ,
total_invoice_price number(9) ,
constraint pk_invoice
primary key (id)
);
create table invoice_item(
id number(5) not null,
product_name varchar2(50) not null,
unit_price number(9) not null,
quantity number(9) not null,
total_item_price number(9) ,
invoice number(5) not null,
constraint pk_invoice_item
primary key (id),
constraint fk_invoice_item_invoice
foreign key (invoice)
references invoice(id)
);
I would use Merge. See below
MERGE INTO invoice tb1
USING ( SELECT invoice, SUM (total_item_price) tot_price
FROM invoice_item
GROUP BY invoice) tb2
ON (tb1.id = tb2.invoice)
WHEN MATCHED
THEN
UPDATE SET tb1.total_invoice_price = tb2.tot_price;
update
( select i.total_invoice_price, x.total_price
from invoice i
join
(
select invoice as id, sum(total_item_price) as total_price
from invoice_item
group by invoice
) x
on i.id = x.id
)
set total_invoice_price = total_price
;
Comments:
You need to aggregate within the second table, before joining. Then you join by id. In this arrangement, you will never run into issues with "uniqueness" or "primary key" being defined; the only condition that matters is that the id be unique in the "other" table, which in this case is the subquery x. Since it is an aggregation where you group by id, that uniqueness is guaranteed by the very definition of GROUP BY.
Then: It is unfortunate that you have a table invoice and a column (in a different table) also called invoice; the invoice id column should be called something like invoice_id in both tables. In my subquery, I changed the column name (from the second table) from invoice to id, by giving it that alias in the SELECT clause of the subquery.
Further comment: In a comment below this replies, the OP says he ran into an error. That means he didn't use the solution as I wrote it above. Since this is really annoying, I decided to present a full SQL*Plus session to prove that the solution is correct as written.
Create table INVOICE:
SQL> create table invoice ( id, total_invoice_price ) as
2 select 1, cast(null as number) from dual union all
3 select 2, null from dual;
Table created.
Elapsed: 00:00:00.01
SQL> select * from invoice;
ID TOTAL_INVOICE_PRICE
---------- -------------------
1
2
2 rows selected.
Create table INVOICE_ITEM:
Elapsed: 00:00:00.00
SQL> create table invoice_item ( invoice, total_item_price ) as
2 select 1, 10 from dual union all
3 select 1, 20 from dual union all
4 select 2, 25 from dual union all
5 select 2, 35 from dual;
Table created.
Elapsed: 00:00:00.01
SQL> select * from invoice_item;
INVOICE TOTAL_ITEM_PRICE
---------- ----------------
1 10
1 20
2 25
2 35
4 rows selected.
Elapsed: 00:00:00.00
UPDATE statement:
SQL> update
2 ( select i.total_invoice_price, x.total_price
3 from invoice i
4 join
5 (
6 select invoice as id, sum(total_item_price) as total_price
7 from invoice_item
8 group by invoice
9 ) x
10 on i.id = x.id
11 )
12 set total_invoice_price = total_price
13 ;
2 rows updated.
Elapsed: 00:00:00.01
SQL> select * from invoice;
ID TOTAL_INVOICE_PRICE
---------- -------------------
1 30
2 60
2 rows selected.
Elapsed: 00:00:00.02
Clean-up:
SQL> drop table invoice purge;
Table dropped.
Elapsed: 00:00:00.02
SQL> drop table invoice_item purge;
Table dropped.
Elapsed: 00:00:00.01
SQL>

Cursor and update in Postgresql

I've written a function in postgresql for updating a table using two cursors, the code executes fine without errors but doesn't do anything.
I have three tables
product master
sales data table
target table
So in the below function I'm getting product key from 1 and then in the sales table calculating no of stores/outlets the product was sold and then update the third table accordingly,
I need to calc the no of outlets for each product every week. but they should be unique, e.g If a product was sold in outlets A and B in first week the no of stores in first week should be 2, if it is sold in B, C, D next week the total should be 4 for 2nd week, If it is sold in A and D in third week the total should be still 4 for 3rd week. How do I do this.
Any help will be greatly appreciated.
create or replace function StCount() returns void as
$BODY$
Declare
ik1 integer ;
wk1 integer ;
Cur1 CURSOR FOR SELECT ik From table1 ;
Cur2 CURSOR FOR SELECT distinct(wk) FROM table2 order by wk asc;
begin
OPEN Cur1 ;
WHILE (Found) Loop
FETCH next from Cur1 INTO ik1 ;
OPEN Cur2 ;
WHILE (Found) Loop
FETCH next from Cur2 INTO wk1;
update table3 set skly =(select count(sk)from table2 a
where a.ik = ik1 and a.wk = wk1
and a.sk not in (select distinct (sk) from table2
where ik = ik1 and wk <= wk1 - 1 ))
where ik = ik1 and wk = w1
;
End loop ;
CLOSE Cur2;
end loop ;
CLOSE Cur1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
You didn't show us how your tables look like or what exactly the data is you are dealing with, so I am assuming a lot here.
Using this setup:
create table product (id integer primary key);
create table sales (id serial primary key, product_id integer, store text, wk integer);
insert into product values (1),(2),(3),(4);
insert into sales (product_id, store, wk)
values
(1, 'A', 1),
(1, 'B', 1),
(1, 'B', 2),
(1, 'C', 2),
(1, 'D', 2),
(1, 'A', 3),
(1, 'D', 3);
The cumulative sales per product, store and week can be calculated like this:
select product_id,
store,
wk,
count(store) over (partition by product_id order by wk) as sofar
from (
select product_id,
store,
wk,
row_number() over (partition by product_id, store order by wk) as rn
from sales
order by product_id, store
) t
where rn = 1;
Note that the product table isn't actually used in this, but it can easily be joined to the inner query.
For the above sample data, this returns:
product_id | store | wk | sofar
-----------+-------+----+------
1 | A | 1 | 2
1 | B | 1 | 2
1 | C | 2 | 4
1 | D | 2 | 4
Now this result can be used to update the target table:
update target_table
set slky = t.sofar
from (
select product_id,
store,
wk,
count(store) over (partition by product_id order by wk) as sofar
from (
select product_id,
store,
wk,
row_number() over (partition by product_id, store order by wk) as rn
from sales
order by product_id, store
) t
where rn = 1
) s
where s.wk = target_table.wk
and s.product_id = target_table.product_id
and s.store = target_table.store;
This assumes that the combination of (product_id, store, wk) is unique in target_table. As you have obfuscated the names of your tables and columns in your example it is hard to tell though.

oracle sql - merge table with nested table as column

I have 2 tables and one nested table:
1.stores data about products which include following columns:
ITEM - product id(key)
STORE - store id(key)
PRICE
NORMAL_PRICE
DISCOUNTS - nested table with info about discounts include columns:
PromotionId(key)
PromotionDescription
PromotionEndDate
MinQty
DiscountedPrice
DiscountedPricePerMida
2- temp table with new discounts include columns:
PROMOTIONID(key)
PRODUCTID(key)
PROMOTIONDESCRIPTION
PROMOTIONENDDATE
MINQTY
DISCOUNTEDPRICE
DISCOUNTEDPRICEPERMIDA
What i need to do is merge table 2 into table 1 - if no match insert else ignore
(when match is: product id matching in table 1 and 2 and for this product sub table PROMOTIONID match PROMOTIONID from table 2)
This is where I got so far and I have difficulty with nested part - ON clause and Insert clause
MERGE INTO PRICES P
USING(SELECT * FROM TMP_PROMO)T
ON(P.ITEM=T.PRODUCTID AND P.STORE=50 AND P.DISCOUNTS.PROMOTIONID=T.PROMOTIONID)
WHEN NOT MATCHED THEN INSERT (P.DISCOUNTS)
VALUES(T.PROMOTIONID,
T.PROMOTIONDESCRIPTION,
T.PROMOTIONENDDATE,
T.MINQTY,
T.DISCOUNTEDPRICE,
T.DISCOUNTEDPRICEPERMIDA);
I know that this is wrong but I can't find anywhere how to do it
example:
Prices table:
row1(1,50,...,nested_table[(11,...),(12,...)])
row2(2,50,...,nested_table[(10,...),(12,...)])
new promo table:
(15,1,...)
(11,1,...)
new promo with id 15 will be added to row1 and row2
and promo with id 11 will not be added
Please help,
thanks
What you intend to do is not realy a MERGE. You are adding a new promotion in each record that doesn't contain it.
Below is an answer how yu would approach it if you would use not a nested table but a conventional child table.
Setup (simplified to a minimum)
create table ITEM
(ITEM_ID NUMBER PRIMARY KEY);
create table ITEM_PROMO
(ITEM_ID NUMBER REFERENCES ITEM(ITEM_ID),
PROMO_ID NUMBER);
create table TMP_PROMO
(PROMO_ID NUMBER);
insert into ITEM values (1);
insert into ITEM values (2);
insert into ITEM_PROMO values (1,11);
insert into ITEM_PROMO values (1,12);
insert into ITEM_PROMO values (2,10);
insert into ITEM_PROMO values (2,12);
insert into TMP_PROMO values (15);
insert into TMP_PROMO values (11);
commit;
The first thing you need to find is which promotions are missing for an item.
Use a cross join to get all combination and constrain those promotions that EXISTS for a particular ITEM_ID:
select ITEM.ITEM_ID, TMP_PROMO.PROMO_ID
from ITEM cross join TMP_PROMO
where NOT EXISTS (select NULL from ITEM_PROMO where ITEM_ID = ITEM.ITEM_ID and PROMO_ID = TMP_PROMO.PROMO_ID)
;
This gives as expected
ITEM_ID PROMO_ID
---------- ----------
2 11
1 15
2 15
Now simple add those new promotions
INSERT INTO ITEM_PROMO
select ITEM.ITEM_ID, TMP_PROMO.PROMO_ID
from ITEM cross join TMP_PROMO
where NOT EXISTS (select NULL from ITEM_PROMO where ITEM_ID = ITEM.ITEM_ID and PROMO_ID = TMP_PROMO.PROMO_ID)
;
This should give you a hint how to approach while using nested tables (or how to change the DB design:)

SQL adds rows on top of eachother rather than aside

For the following tables;
PRICE_TABLE FACT_TABLE
P_ID P_ID
1 0
2 0
3 0
... ....
I am trying to get the values of PRICE_TABLE P_ID into the FACT_TABLE. I have gone with using the following query;
INSERT INTO FACT_TABLE(P_ID) SELECT P_ID FROM PRICE_TABLE
I have several more similar fields in the FACT_TABLE corresponding to other tables ie. SALES_TABLE of which the S_ID would need to come over in the same manner.
However running the queries like so;
INSERT INTO FACT_TABLE(P_ID) SELECT P_ID FROM PRICE_TABLE
INSERT INTO FACT_TABLE(S_ID) SELECT S_ID FROM SALES_TABLE
Will add the second INSERT statement on top of the other, so the second IDs only start when the first set have finished.
eg.
FACT_TABLE
P_ID S_ID
1 NULL
2 NULL
3 NULL
NULL 1
NULL 2
NULL 3
... ...
Anyone have any suggestions?
If you want them all
You cannot associate if there is nothing to base the association on
INSERT INTO FACT_TABLE (P_ID, S_ID)
SELECT P_ID, S_ID
FROM PRICE_TABLE
CROSS JOIN SALES_TABLE
You're running two separate inserts. So the first one runs and populates P_ID and then the second runs and populates S_ID. You want to combine the two inserts into one.
INSERT INTO FACT_TABLE (P_ID, S_ID)
VALUES (pt.P_ID, st.S_ID)
FROM PRICE_TABLE pt
INNER JOIN SALES_TABLE st
on st.P_ID = pt.P_ID
The above is assuming PRICE_TABLE and SALES_TABLE have a common column (in this case P_ID). If they don't you'll need to give more info on what you're trying to accomplish (do you want every P_ID for every S_ID, do you only want when P_ID = S_ID, etc).

How to generate rows for table Order_details, for each row in the Order_header table?

I have two tables. Order_header and Order_detail. For each row in the Order_header table, I want to generate 1 or more Order_detail rows and insert them into the Order_detail table. The number of detail rows for each header row is random, up to the number in the constant max_detailrows. How to proceed with the generate rows for Order_detail table?
You can make use of DBMS_RANDOM.VALUE function, to generate random value.
Use this in hierarchical query to generate rows for each row in your Order_Header table.
Schema Setup:
create table order_header(
order_id number,
order_desc varchar2(10)
);
insert into order_header values(1,'lorem');
insert into order_header values(2,'ewroc');
insert into order_header values(3,'tdsfg');
commit;
create table order_details(
order_id number,
order_detail_nr number,
order_detail_desc varchar2(20)
);
Query:
insert into order_details
select
order_id,
level,
order_desc || '_' || level
from order_header
connect by level <= dbms_random.value(1,7) --max number of rows needed should be given here.
and prior order_id = order_id
and prior sys_guid() is not null;
Output:
select * from order_details
order by 1,2;
ORDER_ID ORDER_DETAIL_NR ORDER_DETAIL_DESC
---------- --------------- ------------------
1 1 lorem_1
1 2 lorem_2
1 3 lorem_3
2 1 ewroc_1
2 2 ewroc_2
2 3 ewroc_3
2 4 ewroc_4
3 1 tdsfg_1