how can I do it - oracle

`
SELECT TXNID,
TOTAL_AMOUNT,
ACCOUNT_CATEGORY,
DEBIT
FROM ACCOUNTING_BOOK
WHERE TXNID like 'M003%' ;
TXNID:M003 TOTAL_AMT:25 ACCOUNT_CATEGORY:Revenue Debit:null Credit:null
TXNID:M003 TOTAL_AMT:25 ACCOUNT_CATEGORY:Asset Debit:null Credit:null
Rules for populating debit column:[Total-Amount if account-category=Expense or if account-category=Asset for corresponding Revenue item matched by TxnId; 0 otherwise]
Want to validate the result inside this table into debit column.How can I do it?
Rules for populating credit column:[Total-Amount if account-category=Revenue or if account-category=Asset for corresponding Expense item matched by TxnId will return 1 ; 0 otherwise]
Want to validate the result inside this table into credit column.How can I do it?

If I understood rules correctly:
update accounting_book ab
set debit = case when account_category in ('Expense', 'Asset')
then (select sum(total_amt)
from accounting_book
where account_category = 'Revenue'
and txnid = ab.txnid)
else 0
end
Edit:
Rules for populating credit column:[Total-Amount if
account-category=Revenue or if account-category=Asset for
corresponding Expense item matched by TxnId will return 1 ; 0
otherwise]
Similar to previous update:
update accounting_book ab
set credit = case when account_category in ('Revenue', 'Asset')
and exists (select 1
from accounting_book
where account_category = 'Expense'
and txnid = ab.txnid)
then 1
else 0
end

CASE WHEN A.ACCOUNTCATEGORY IN('Revenue','Asset')
AND EXISTS (SELECT TOTAL_AMOUNT
FROM ACCOUNTING_BOOK
WHERE ACCOUNTCATEGORY='Expense'
AND TXNID=A.TXNID)
THEN A.TOTAL_AMOUNT
ELSE 0
END,
CASE WHEN A.ACCOUNTCATEGORY IN('Expense','Asset')
AND EXISTS (SELECT TOTAL_AMOUNT
FROM ACCOUNTING_BOOK
WHERE ACCOUNTCATEGORY='Revenue'
AND TXNID=A.TXNID)
THEN A.TOTAL_AMOUNT
ELSE 0
end);
END LOOP;
END;
/

Related

Pivoting a table in SQL, where the new columns are based on every possible combination of values from two columns

To illustrate, the following table:
ID
Model
Series
Amount
001
productX
SeriesZ
1000
001
productX
SeriesABC
2000
001
productX
SeriesABC
8000
002
productY
SeriesABC
5000
should be transformed such that each record captures a unique id and the total amount it has contributed to each model-series possible combination.
ID
productX_SeriesZ
productX_SeriesABC
productY_SeriesABC
001
1000
10000
0
002
0
0
5000
Can I use the pivot function to pivot on for each possible combination of values in two columns?
SELECT ID,
SUM( CASE WHEN model = 'productX' and series = 'SeriesZ' THEN amount ELSE 0 END) productX_SeriesZ,
SUM( CASE WHEN model = 'productX' and series = 'SeriesABC' THEN amount ELSE 0 END) productX_SeriesABC,
SUM( CASE WHEN model = 'productY' and series = 'SeriesABC' THEN amount ELSE 0 END) productX_SeriesABC
FROM TABLE
GROUP BY ID;
EDIT:
"Works for this particular case, but what if we have hundreds of models and series?"
You can try this.
DECLARE
TYPE model_rec IS RECORD ( mr mytable.model%type);
TYPE series_rec IS RECORD ( sr mytable.series%type);
TYPE model_tab IS TABLE OF model_rec;
TYPE series_tab IS TABLE OF series_rec;
mv model_tab;
sv serie_tab;
query varchar2(32767) := 'SELECT ID';
BEGIN
SELECT DISTINCT model, series INTO mv, sv FROM mytable WHERE model IS NOT NULL AND series IS NOT NULL;
FOR i IN 1..mv.COUNT
LOOP
query := query||', SUM( CASE WHEN model = '|| DBMS_ASSERT.ENQUOTE(mv(i).mr)
|| ' and series = ' || DBMS_ASSERT.ENQUOTE(sv(i).sr)||' THEN amount ELSE 0 END) ' || mv(i).mr||'_'||sv(i).sr||' '
END LOOP;
query := query || ' FROM mytable GROUP BY id;'
EXECUTE IMMEDIATE query;
END;
This may contain syntactic errors and can be optimized and refactored but this is the basic idea. I am in hurry so wrote it down without testing.
I think below query should work for you -
SELECT *
FROM (SELECT ID
,Model
,Series
FROM YOUR_TABLE)
PIVOT(SUM(AMOUNT) FOR Model IN ('productX' productx, 'productY' producty),
SUM(AMOUNT) FOR Series IN ('SeriesABC' Seriesabc, 'SeriesZ' Seriesz))

Group by on Key columns and get the records having one valid value and zero value

we have a scenario where as per group by key columns i have to get only record having combination of 0 & any valid number for column "ID"
with the below example
TransId,CustomerNm,Date ,Number,Gender,ID
1 ,Surya ,2020-01-01,123456,M ,1234
1 ,Surya ,2020-01-01,123456,M ,0
2 ,Naren ,2020-01-20,123456,M ,3456
2 ,Naren ,2020-01-20,123456,M ,6789
When i try with the below query (key columns: TransId,CustomerNm,Date,Number,Gender)
select TransId,CustomerNm,Date,Number,Gender from INS.Transaction
group by TransId,CustomerNm,Date,Number,Gender having count(*) > 1
i will get both the records
1 ,Surya ,2020-01-01,123456,M
2 ,Naren ,2020-01-20,123456,M
but i am trying to get the records having ID =0 . Expecting output as
1 ,Surya ,2020-01-01,123456,M
Pls suggest if i can add any change in the above query
while doing group by add MIN(ID) in select query and then fetch id 0
select * from (
select TransId,CustomerNm,Date,Number,Gender,MIN(ID) ID from INS.Transaction
group by TransId,CustomerNm,Date,Number,Gender having count(*) > 1 ) where ID = 0
Just add another AND to the having clause to check if min(id) = 0 . I have renamed some columns.
select transid,customernm,transaction_date,some_number,gender
from transaction
group by transid,customernm,transaction_date,some_number,gender
having count(*) > 1 and min(id) = 0;
Note:- Do not use reserved words as column name like date and number

PL/SQL procedure that will go through inventory one by one looking for item and updates accordingly

I have an inventory table like below:
warehouse_no|item_no|item_quantity
------------|-------|-------------
1 | 1000 | 300
------------|-------|-------------
2 | 1000 | 500
------------|-------|-------------
3 | 1000 | 200
------------|-------|-------------
1 | 2000 | 100
------------|-------|-------------
2 | 2000 | 200
------------|-------|-------------
3 | 2000 | 0
------------|-------|-------------
1 | 3000 | 100
------------|-------|-------------
2 | 3000 | 200
------------|-------|-------------
3 | 3000 | 0
------------|-------|-------------
Now if someone for example orders 400 units item no. 1000, the pl/sql should go through the table, take 300 from warehouse 1 and 100 from warehouse 2 and update it. The resulting table should look like below:
warehouse_no|item_no|item_quantity
------------|-------|-------------
1 | 1000 | 0
------------|-------|-------------
2 | 1000 | 400
------------|-------|-------------
3 | 1000 | 200
------------|-------|-------------
The procedure I wrote is below
PROCEDURE upd_inventory(p_item_no inventory.item_no%TYPE, p_quantity number)
AS
CURSOR inventory_cur IS
select MAX(item_quantity)as quantity
from inventory
where item_no=p_item_no;
v_order_quantity number:=p_quantity;
BEGIN
FOR v_inventory_cur IN inventory_cur LOOP
UPDATE INVENTORY
SET item_quantity = ((v_inventory_cur.quantity) - p_quantity );
COMMIT;
END LOOP;
END upd_inventory;
But as you would have noticed this will update the whole quantity column and does not solve the issue of checking through row by row taking item and updating accordingly.
Thanks
It is possible to do with one SQL query. The following query shows, how to calculate quantity to take from each warehouse, and returns a list of warehouses where we take in total 400 units of item no. 1000:
select warehouse_no, item_no, item_quantity,
case when running_sum < 400 then item_quantity
else 400 - (running_sum - item_quantity) end how_much_to_take
from (select warehouse_no, item_no, item_quantity,
sum(item_quantity) over (partition by item_no
order by warehouse_no) running_sum
from inventory
where item_no = 1000)
where running_sum - item_quantity < 400
Column how_much_to_take contains quantity, how much items we need to take from the warehouse.
So we can write the following MERGE statement:
merge into inventory i
using ( select warehouse_no, item_no, item_quantity, running_sum,
case when running_sum < 400 then item_quantity
else 400 - (running_sum - item_quantity) end to_take
from (select warehouse_no, item_no, item_quantity,
sum(item_quantity) over (partition by item_no
order by warehouse_no) running_sum
from inventory
where item_no = 1000)
where running_sum - item_quantity < 400
) how_much
on (i.warehouse_no = how_much.awrehouse_no and i.item_no = how_much.item_no)
when matched then update
set i.item_quntity = i.item_quntity - how_much.to_take
This statement will update your table as you need. And, if you still need a procedure:
PROCEDURE upd_inventory(p_item_no inventory.item_no%TYPE, p_quantity number) AS
BEGIN
merge into inventory i
using ( select warehouse_no, item_no, item_quantity, running_sum,
case when running_sum < p_quantity then item_quantity
else p_quantity - (running_sum - item_quantity) end to_take
from (select warehouse_no, item_no, item_quantity,
sum(item_quantity) over (partition by item_no
order by warehouse_no) running_sum
from inventory
where item_no = p_item_no)
where running_sum - item_quantity < p_quantity
) how_much
on (i.warehouse_no = how_much.awrehouse_no and i.item_no = how_much.item_no)
when matched then update
set i.item_quntity = i.item_quntity - how_much.to_take;
END upd_inventory;
Use item_no and warehouse_no as where condition to update item_quantity in inventory table. In which you need to find a pattern to use warehouse_no in where condition.
Below function will work for your requirement:
CREATE OR REPLACE PROCEDURE
DEDUCT_INV (in_item_no In Number,in_quantity In Number)
Is
TYPE someRefCursor IS REF CURSOR;
fetchWareHouseQuantitiesCursor someRefCursor;
tempWareHouseNo temp_inventory.warehouse_no%type;
tempItemQuantity temp_inventory.item_quantity%type;
requiredQuantity temp_inventory.item_quantity%type := in_quantity;
Begin
Open fetchWareHouseQuantitiesCursor For
Select warehouse_no, item_quantity
From temp_inventory
Where item_no = in_item_no And item_quantity != 0
order by warehouse_no;
/* Ignoring 0 quantity warehouses
& also ordering by warehouse
but if required can be ordered by item_quantity desc so
minimum warehouses are touched which is more efficient*/
LOOP
Fetch fetchWareHouseQuantitiesCursor Into tempWareHouseNo,tempItemQuantity;
Dbms_Output.Put_Line('Required:'||requiredQuantity||'.');
Exit When fetchWareHouseQuantitiesCursor%NotFound;
Dbms_Output.Put_Line('Fetched:'||tempWareHouseNo||','||tempItemQuantity||'.');
if(requiredQuantity > tempItemQuantity)
then
Dbms_Output.Put_Line('Updating:'||tempWareHouseNo||','||tempItemQuantity||' by 0.');
update temp_inventory set item_quantity = 0 where warehouse_no = tempWareHouseNo And item_no = in_item_no;
requiredQuantity:= requiredQuantity - tempItemQuantity;
else
Dbms_Output.Put_Line('Updating:'||tempWareHouseNo||','||tempItemQuantity||' by '||(tempItemQuantity - requiredQuantity)||'.');
update temp_inventory set item_quantity = item_quantity-requiredQuantity where warehouse_no = tempWareHouseNo And item_no = in_item_no;
requiredQuantity:= 0;
exit;
end if;
END LOOP;
Close fetchWareHouseQuantitiesCursor;
if(requiredQuantity != 0)
then
rollback;
Dbms_Output.Put_Line('Job Failed. Insufficient storage. Missing:'||requiredQuantity||' quantity.');
else
commit;
Dbms_Output.Put_Line('Job Completed Successfully.');
end if;
return;
End;
/
execute DEDUCT_INV(1000,400);
Others have answered this already, but just for fun I'll share the version I started anyway:
procedure upd_inventory
( p_item_no inventory.item_no%type
, p_quantity inventory.item_quantity%type )
is
l_total_stocked inventory.item_quantity%type := 0;
l_outstanding inventory.item_quantity%type := p_quantity;
begin
dbms_output.put_line('Item ' || p_item_no || ' target: ' || p_quantity);
for r in (
select i.item_no, i.warehouse_no
, i.item_quantity as warehouse_quantity
, i.item_quantity as warehouse_remaining
, sum(i.item_quantity) over () as total_quantity
from inventory i
where i.item_no = p_item_no
and i.item_quantity > 0
for update
order by i.item_quantity desc
)
loop
l_total_stocked := r.total_quantity; -- redundant after first iteration but avoids separate query
update inventory i
set i.item_quantity = greatest(r.warehouse_quantity - l_outstanding, 0)
where i.warehouse_no = r.warehouse_no
and i.item_no = p_item_no
returning i.item_quantity into r.warehouse_remaining;
dbms_output.put_line('Warehouse '||r.warehouse_no || ': reducing stock by ' || (r.warehouse_quantity - r.warehouse_remaining));
l_outstanding := l_outstanding - (r.warehouse_quantity - r.warehouse_remaining);
exit when l_outstanding = 0;
end loop;
if l_outstanding = 0 then
dbms_output.put_line('Item ' || p_item_no || ' stock reduced by ' || p_quantity);
else
raise_application_error
( -20000
, 'Insufficient stock for item ' || p_item_no ||
': stocked: ' || l_total_stocked || ', requested: ' || p_quantity ||
', short: ' || (p_quantity - l_total_stocked) );
end if;
end upd_inventory;
While the single merge approach is doubtless fastest (and avoids the locking needed to prevent lost updates in a multi-user environment), I would personally be concerned about support and maintenance. Maybe I could rewrite my cursor using a model clause or a recursive with, but the poor developer given the task of updating it one day to take into account delivery distance or something will have a much harder job.
I have not committed or rolled back within the procedure because generally it is best to leave this to the caller, but if you want to you can commit at the end (not in the middle!) Note that raising an exception implicitly rolls back the updates. If you need to roll back explicitly then declare a savepoint at the start and roll back to that.
Probably a real procedure would use a standard logger for the diagnostic messages rather than dbms_output.

Advance mysql query with order by

I use 2 tables to combine data to become like shown as below
SELECT name, price, MIN(price) AS minprice
FROM c, cp
WHERE c.id = cp.id
GROUP BY id
ORDER BY minprice = 0, minprice ASC
For Example:
id name price
1 apple 0
1 green apple 20
2 orange 10
3 strawberry 0
As the data result above the minprice of the group 1 is 0 But I don't want the min price take zero, but this is incorrenct if I give condition having minprice > 0 cause
I wanna my result become like this
2 orange 10
1 green apple 20
3 strawberry 0
Is it possible?
Here is the answer:
SELECT
(
SELECT name
FROM yourtable
WHERE price = _inner._MIN AND id = _inner.id LIMIT 1
)
AS _NAME,
_inner._MIN
FROM
(
SELECT id, IFNULL(MIN(NULLIF(price, 0)),0) AS _MIN
FROM yourtable
GROUP BY id
)
AS _inner
where yourtable is the name of your table.
MIN(NULLIF(price, 0)) allows you to calculate minimum value while not counting a zero.
IFNULL(<...>,0) here just means, that we need a real zero instead of NULL in result.
LIMIT 1 is on the case if we have an items with the same id and price but with different names. I think, you can freely remove this statement.

Oracle - additional column check with min function and group by

My Oracle table has the columns: message_id, status, status_date
I would like to return message_id where when grouping by message_id the record with the mininum value for status_date has a value of 'PC' in the status column.
In other words do not return the record if the record that is returned with the minimum value of status_date when grouped by mess_id does not have a value of 'PC' in the status column.
Thanks,
Brian
I'm guessing this is what the source data may look like:
message_id status status_date
---------- ------ -----------
1 PC 01-JAN-12
1 QC 02-JAN-12
1 RC 03-JAN-12
2 AA 04-JAN-12
2 PC 05-JAN-12
2 CC 06-JAN-12
3 PC 07-JAN-12
3 PC 08-JAN-12
3 PC 09-JAN-12
And that the expected output would be something like this:
message_id
----------
1
3
The reason that these two records are returned are because for all the records where message_id=1, the one with the minimum status_date has a value of 01-JAN-12. The record with message_id=1 and status_date of 01-JAN-12 has a status of 'PC', and similarly with message_id=3. Since the minimum status_date of the records with message_id=2 is 04-JAN-12, and it's status is NOT 'PC'.
We can build this query using inline views, but there's probably an easier way using analytic functions.
SELECT A.MESSAGE_ID
FROM MY_TABLE A,
( SELECT B.MESSAGE_ID, MIN(B.STATUS_DATE) MIN_DATE
FROM MY_TABLE B
GROUP BY B.MESSAGE_ID ) C
WHERE A.STATUS = 'PC'
AND A.STATUS_DATE = C.STATUS_DATE
AND A.MESSAGE_ID = C.MESSAGE_ID;

Resources