Sum and condition in oracle on update - oracle

am working on oracle database system , and i have to do the following:
All employees got a salary increase depending on their hiring dates as follows: (use one SQL statement)
10% salary increase if the employee was hired after 2001.
7% salary increase if the employee was hired between 1997 and 2000.
4% salary increase if the employee was hired before 1996.
and i used the following code
update employee set salary = case
when hire_date>'1-1-2001' then salary=salary+salary*0.1
when hire_date between ('1-1-1997','1-1-2000') then salary=salary+salary*0.07
when hire_date < '1-1-1996' then salary=salary+salary*0.04
end
what is wrong with that ???

Try:
update stackoverflow_employee set salary = case
when hire_date>'1-Jan-2001' then salary+salary*0.1
when hire_date between ('1-Jan-1997') AND ('1-Jan-2000') then salary+salary*0.07
when hire_date < '1-Jan-1996' then salary+salary*0.04
end
Points to remember:
Correct use of between operator Between X AND X
Date Format is 1-Jan-2012
While you are using SET salary = 'YOUR case statements', don't use salary= salary+salary*0.04 wihin the case.
Hope now your query is working :)

I would write this as:
update employee
set salary = (salary * (case when hire_date >= '2001-01-01' then 1.10
when hire_date >= '1997-01-01' then 1.07
else 1.04
end)
);
This fixes the obvious problems with the salary = and funky between clause. It also moves into the case statement the logic that only needs to be there -- the amount of the increase. Tt removes the between by taking advantage of the fact that case statements return the first match. Finally, it uses ISO standard date formats.

Related

How to use SUM and MAX in select statement more than one table

I have 2 Tables
table a mempur
memberno = member number
purdt = purchase date
amount = purchase amount
table b meminfo
memberno = member number
fname = first name
age = age
select a.memberno,b.fname,sum(a.amount),a.purdt,b.age from mempur a,(select max(purdt) as maxdate,memberno from mempur group by memberno) maxresult,meminfo b
where a.memberno=b.memberno
and a.purdt between '01-JAN-22' and '28-FEB-22'
and a.memberno=maxresult.memberno
and a.purdt=maxresult.maxdate
group by a.memberno,b.fname,a.purdt,b.age
order by a.memberno;
How to get my result with total purchase amount and highest date purchase from table mempur?
I use this query able to show the result but the total amount incorrect between the range.
Anyone help is much appreciated.
my sample data
MEMBERNO PURDT AMOUNT
--------------- --------------- ---------
BBMY0004580 12-AUG-21 823.65
BBMY0004580 12-AUG-21 1709.1
BBMY0004580 26-AUG-21 1015.1
BBMY0004580 28-AUG-21 1105.1
my result only show total amount 1105.1
You can aggregate in mempur and then join to meminfo:
SELECT i.*, p.total_amount, p.maxdate
FROM meminfo i
INNER JOIN (
SELECT memberno, SUM(amount) total_amount, MAX(purdt) maxdate
FROM mempur
WHERE purdt BETWEEN '01-JAN-22' AND '28-FEB-22'
GROUP BY memberno
) p ON p.memberno = i.memberno;
You may use a LEFT join if there are members with no purchases which you want in the results.
Your query gets the maximum purdt and adds up the amount for this date. It also checks whether the maximum purdt is in January or February 2022. If it is, the result gets show, if it is not, you don't show the result. This is not the query you want.
Apart from that, the query looks rather ugly. You are using an ancient join syntax that is hard to read and prone to errors. We used that in the 1980s, but in 1992 explicit joins made it into the SQL standard. You should no longer use this old comma syntax. It is strange to see it still being used. Feels like going to a museum. Then, you are using table aliases. The idea of these is to get a query more readable, but yours even lessen readability, because your alias names a and b are arbitrary. Use mnemonic names instead, e.g. mp for mempur and mi for meminfo. Then, you are comparing the date (I do hope purdt is a date!) with strings. Don't. Use date literals instead.
As to your tables: Are you really storing the age? You will have to update it daily to keep it up-to-date. Better store the date of birth and calculate the age from it in your queries.
Here is a query that gets you the maximum date and the total amount for the given date range:
select memberno, m.name, p.sum_amount, p.max_purdt, m.age
from meminfo m
left outer join
(
select memberno, sum(amount) as sum_amount, max(purdt) as max_purdt
from mempur
where purdt >= date '2022-01-01' and purdt < date '2022-03-01'
group by memberno
) p using (memberno)
order by memberno;
And here is a query that gets you the maximum overall date along with the total amount for the given date range:
select memberno, m.name, p.sum_amount, p.max_purdt, m.age
from meminfo m
left outer join
(
select
memberno,
sum(case when where purdt >= date '2022-01-01' and purdt < date '2022-03-01'
then amount
end) as sum_amount,
max(purdt) as max_purdt
from mempur
group by memberno
) p using (memberno)
order by memberno;

Logic for change in one column value in pl/sql

I have an assignmentes table asg_tab with effective start date and effective end date columns which track on which dates which change was made.
asg_tab
eff_start_date eff_End_date PER_ASG_ATTRIBUTE2 job name pos name
01-Jan-2015 03-feb-2015 Ck Bonus Retail Mgr
04-Feb-2015 20-Feb-2015 UK Bonus Sales Mgr
21-Feb-2015 28-Nov-2015 UK Bonus Sales Snr. Mgr
Now I have to calculate the number of days for which PER_ASG_ATTRIBUTE2 is UK Bonus. For example in the above case it will be days between 04-Feb-2015 to 28-Nov-2015.
I have written the logic below, which is fetching values from cursor.
cursor cur_asg
is
select
eff_start_date,
eff_End_date,
PER_ASG_ATTRIBUTE2,
job_name,
pos_name
from
asg_tab
Logic I have built :
START_DT ='01-Jan-2015'
END_DT ='31-Dec-2015'
IF PER_ASG_ATTRIBUTE2 LIKE '%UK Bonus%' THEN
(
l_new_ATTR = PER_ASG_ATTRIBUTE2
l_effective_date = i.eff_start_date
IF (l_new_ATTR <> l_old_ATTR) AND (l_effective_date >= START_DT ) AND (l_effective_date =< END_DT)
THEN
(
l_days=eff_end_date -eff_start_date
)
l_old_ATTR = l_new_ATTR
)
The issue which is coming up is that from this condition: IF (l_new_ATTR <> l_old_ATTR) AND (l_effective_date >= START_DT ) AND (l_effective_date =< END_DT)
This condition will pick the 2nd row where the PER_ASG_ATTRIBUTE2 changed from Ck Bonus to UK Bonus but when the pos name changes the 3rd row is generated.
Even though the PER_ASG_ATTRIBUTE2 is still UK Bonus this will not be filtered in the if condition.
What more can I add to this condition ?

How to generate each day of backlog for a ticket

Hi I'm trying to create a procedure for calculating the backlog for each day.
For example: I have a ticket with ticket_submitdate on 12-sep-2015 and resolved_date on 15-sep-2015 in one table. This ticket should come as a backlog in the backlog_table because it was not resolved on the same day as the ticket_submitdate.
I have another column date_col in the backlog_table where the date on which the ticket was a backlog is displayed,i.e, it should be there in the ticket_backlog table for dates 13-sep-2015 and 14-sep-2015 and the date_col column should have this ticket for both these dates.
Please help.
Thanks in advance.
Here is some test data:
create table backlog (ticket_no number, submit_date date, resolved_date date);
insert into backlog values (100, date '2015-09-12', date '2015-09-15');
insert into backlog values (200, date '2015-09-12', date '2015-09-14');
insert into backlog values (300, date '2015-09-13', date '2015-09-15');
insert into backlog values (400, date '2015-09-13', date '2015-09-16');
insert into backlog values (500, date '2015-09-13', date '2015-09-13');
This query generates a list of dates which spans the range of BACKLOG records, and joins them to the BACKLOG.
with dt as ( select min(submit_date) as st_dt
, greatest(max(resolved_date), max(submit_date)) as end_dt
from backlog)
, dt_range as ( select st_dt + (level-1) as date_col
from dt
connect by level <= ( end_dt - st_dt ))
select b.ticket_no
, d.date_col
from backlog b
cross join dt_range d
where d.date_col between b.submit_date and b.resolved_date
and b.submit_date != b.resolved_date
order by b.ticket_no
, d.date_col
/
Therefore it produces a list of TICKET_NOs with all the dates when they are live:
TICKET_NO DATE_COL
---------- ---------
100 12-SEP-15
100 13-SEP-15
100 14-SEP-15
100 15-SEP-15
200 12-SEP-15
200 13-SEP-15
200 14-SEP-15
300 13-SEP-15
300 14-SEP-15
300 15-SEP-15
400 13-SEP-15
400 14-SEP-15
400 15-SEP-15
14 rows selected.
SQL>
The result set does not include ticket #500 because it was resolved on the day of submission. You will probably need to tweak the filters to fit your actual business rules.
I m not sure I understood your question, if you are looking for all dates between two date range then you can use below query -
select trunc(date_col2+lv) from
(select level lv from dual connect by level < (date_col1-date_col2-1) )
order by 1

Showing Different aggregate for different products

I want a code which is used to generate aggregate product by product. The product aggregate can be any like from Year to Date(YTD), Months to Date(MTD) and Quarter to Date(QTD). The user will pass the parameter on that basis the code should decide what kind of output the user wants.
If the Year is passing in the parameter than the code should generate the aggregate from the starting of the year to the sysdate.
If the Quarter No is passing in the parameter than the code should generate the aggregate from the starting of the quarter to the sysdate.
If the Month is passing in the parameter than the code should generate the aggregate from the starting of the month to the sysdate.
It means that on the basis of the parameter it should be able to decide which kind of user want from those 3. My input data is like this-
Product Table
Product_ID Product_name Price
1 Mobile 200
2 T.V. 400
3 Mixer 300
and
Sales Table-
Product_ID Sales_Date Quantity
1 01-01-2015 30
2 03-01-2015 40
3 06-02-2015 10
1 22-03-2015 30
2 09-04-2015 10
3 21-05-2015 40
1 04-06-2015 40
2 29-07-2015 30
1 31-08-2015 30
3 14-09-2015 30
And my ouput column contains 3 columns that are- Product_id, Product_Name and Total. The column Total_Amount(quantity*price) have to calculate sale on the basis of input given by user and is be something like this-
For example ,
If pro_test is the procedure then
call pro_test('YTD') -- Should Return the ProductWise YTD,
call pro_test('QTD') -- Should Return the ProductWise QTD and so on..
You are looking for a WHERE clause :-) List your conditions with OR and you are done.
select
p.product_id,
p.product_name,
coalesce(sum(s.quantity * p.price), 0) as total
from product p
left join sales s on s.product_id = p.product_id
where
(:aggregate = 'YTD' and to_char(s.sales_date, 'yyyy') = to_char(sysdate, 'yyyy'))
or
(:aggregate = 'MTD' and to_char(s.sales_date, 'yyyymm') = to_char(sysdate, 'yyyymm'))
or
(:aggregate = 'QTD' and to_char(s.sales_date, 'yyyyq') = to_char(sysdate, 'yyyyq'))
group by p.product_id, p.product_name;
EDIT: Here is how the corresponding PL/SQL function would look like:
create or replace function matches_date_aggregate(in_sales_date date, in_aggregate char)
return integer as
begin
if (in_aggregate = 'YTD' and to_char(in_sales_date, 'yyyy') = to_char(sysdate, 'yyyy'))
or (in_aggregate = 'MTD' and to_char(in_sales_date, 'yyyymm') = to_char(sysdate, 'yyyymm'))
or (in_aggregate = 'QTD' and to_char(in_sales_date, 'yyyyq') = to_char(sysdate, 'yyyyq')) then
return 1;
else
return 0;
end if;
end matches_date_aggregate;
Your query's WHERE clause would become:
where matches_date_aggregate(s.sales_date, :aggregate) = 1
The function cannot return BOOLEAN unfortunately, for even though Oracle's PL/SQL knows the BOOLEAN data type, Oracle SQL doesn't.

Min(), Max() within date subset

Not sure if the title fits, but here's my problem:
I have the following table:
create table OpenTrades(
AccountNumber number,
SnapshotTime date,
Ticket number,
OpenTime date,
TradeType varchar2(4),
TradeSize number,
TradeItem char(6),
OpenPrice number,
CurrentAsk number,
CurrentBid number,
TradeSL number,
TradeTP number,
TradeSwap number,
TradeProfit number
);
alter table OpenTrades add constraint OpenTrades_PK Primary Key (AccountNumber, SnapshotTime, Ticket) using index tablespace MyNNIdx;
For every (SnapshotTime, account), I want to select min(OpenPrice), max(OpenPrice) in such a way that the resultimg min and max are relative to the past only, with respect to SnapshotTime.
For instance, for any possible (account, tradeitem) pair, I may have 10 records with, say, Snapshottime=10-jun and openprice between 0.9 and 2.0, as well as 10 more records with SnapshotTime=11-jun and openprice between 1.0 and 2.1, as well as 10 more records with SnapshotTime=12-jun and openprice between 0.7 and 1.9.
In such scenario, the sought query should return something like this:
AccountNumber SnapshotTime MyMin MyMax
------------- ------------ ----- -----
1234567 10-jun 0.9 2.0
1234567 11-jun 0.9 2.1
1234567 12-jun 0.7 2.1
I've already tried this, but it only returns min() and max() within the same snapshottime:
select accountnumber, snapshottime, tradeitem, min(openprice), max(openprice)
from opentrades
group by accountnumber, snapshottime, tradeitem
Any help would be appreciated.
You can use the analytic versions of min() and max() for this, along with windowing clauses:
select distinct accountnumber, snapshottime, tradeitem,
min(openprice) over (partition by accountnumber, tradeitem
order by snapshottime, openprice
rows between unbounded preceding and current row) as min_openprice,
max(openprice) over (partition by accountnumber, tradeitem
order by snapshottime, openprice desc
rows between unbounded preceding and current row) as max_openprice
from opentrades
order by accountnumber, snapshottime, tradeitem;
ACCOUNTNUMBER SNAPSHOTTIME TRADEITEM MIN_OPENPRICE MAX_OPENPRICE
------------- ------------ --------- ------------- -------------
1234567 10-JUN-14 X .9 2
1234567 11-JUN-14 X .9 2.1
1234567 12-JUN-14 X .7 2.1
SQL Fiddle.
The partition by calculates the value for the current accountnumber and tradeitem, within the subset of rows based on the rows between clause; the order by means that it only looks at rows in any previous snapshot and up to the lowest (for min) or highest (for max, because of the desc) in the current snapshot, when calculating the appropriate min/max for each row.
The analytic result is calculated for every row. If you run it without the distinct then you see all your base data plus the same min/max for each snapshot (Fiddle). As you don't want any of the varying data you can suppress the duplication with distinct, or by making it a query with a row_number() that you then filter on, etc.
Does this answer your problem ?
select ot1.accountnumber, ot1.snapshottime, ot1.tradeitem,
min(ot2.openprice), max(ot2.openprice)
from opentrades ot1, opentrades ot2
where ot2.accountnumber = ot1.accountnumber
and ot2.tradeitem = ot1.tradeitem
and ot2.snapshottime <= ot1.snapshottime
group by ot1.accountnumber, ot1.snapshottime, ot1.tradeitem

Resources