I have the below query
SELECT decode(detl_cd,'AAA',trunc(amt * to_number(pct) / 100,2),
'BBB',trunc(amt * to_number(pct) / 100,2),
'CCC',(amt-trunc(amt * to_number(pct) / 100,2))
INTO trans_amount
FROM dual;
I get the value detl_cd from a cursor and amt from an input file.
Select tb1.id, tb2.detl_cd,tb2.pct
from tb1
join tb2 on tb1.agent_code=tb2.agent_code
where tb1.id='1';
Each id has 3 detl cd's and each detl code has different calculation. How to avoid hardcode in decode. Creating a table is not an option.
Input file
ID Amount
1 1000
2 2500
3 350
Id 1 & 2 belong to a group that is assigned 3 different detl cd's and different precentage(pct).
output file
ID Detl_cd Amount
1 AAA1 250
1 BBB1 250
1 CCC1 750
2 AAA3 625
2 BBB3 625
2 CCC3 1875
3 350
Each ID has 3 different detl_cd's but the calculation for AAA1 and AAA2 are the same so is BBB & CCC.
Creating a table is not an option.
You want a solution which stores a set of business rules without specifying the business rules in the code. But also without creating a table to store those rules.
That only leaves a user-defined function.
create or replace function calc_amount
( p_detl_cd in varchar2
,p_amt in number
,p_pct in number )
return number
as
begin
case substr(p_detl_cd, 1, 3)
when 'AAA' then return trunc(p_amt * to_number(p_pct) / 100,2);
when 'BBB' then return trunc(p_amt * to_number(p_pct) / 100,2);
when 'CCC' then return (p_amt-trunc(p_amt * to_number(p_pct) / 100,2);
end case;
end calc_amount;
You would call this function in SQL or in PL/SQL. You are a bit vague regarding tables and files, so I'm not really clear where the data comes from, but it might look something like this in PL/SQL:
trans_amount := calc_amount(detl_cd, amt, pct);
I am looking to avoid hardcoding of 'AAA' as these codes may change/replaced in future and i do not want a rework
Or the codes may change the same and the calculations change. Doesn't matter. The hard truth is, you have to hard code the codes and their associated rules somewhere. It is impossible to have an infinitely flexible, soft coded system.
A table is the easiest thing to maintain, and that offers you the most flexibility. But you need to use dynamic SQL or a function to apply the calculation; a function would be my preference. The worst solution would be to have the codes and calculations in an external configuration file which you load at the same time as the input file.
Alternatively, try to put a value on "may change". How likely is it that the codes (or calculations) will change? How often? Do the maths and maybe you'll discover that change is unlikely or very infrequent, and the simplest option is to stick with that decode and take the hit of rework should the occasion arise.
Incidentally, are you sure you mean trunc() and not round()?
I guess you may want
WITH DATA AS
(Select tb1.id , tb2.detl_cd,tb2.pct
from tb1
join tb2 on tb1.agent_code=tb2.agent_code
where tb1.id='1')
SELECT decode(detl_cd,'AAA',trunc(amt * to_number(pct) /
100,2),
'BBB',trunc(amt * to_number(pct) / 100,2),
'CCC',(amt-trunc(amt * to_number(pct) / 100,2))
as trans_amount
FROM Data;
Related
I have data as following
STORE_NO STORE_ADDRESS STORE_TYPE STORE_OWNER STORE_HOURS
1 123 Drive Thru Harpo 24hrs
1 123 Curbside Harpo 24hrs
1 123 Counter Harpo 24hrs
2 456 Drive Thru Groucho 9 to 9
2 456 Counter Groucho 9 to 9
And I want to pivot it as following.
STORE_NO STORE_ADDRESS Drive Thru Curbside Counter STORE_OWNER STORE_HOURS
1 123 TRUE TRUE TRUE Harpo 24hrs
2 456 TRUE FALSE TRUE Groucho 9 to 9
Here is what I have
select *
from stores
pivot(count(STORE_TYPE) for STORE_TYPE in ('Drive Thru', 'Curbside', 'Counter'))
as store_flattened;
But this returns a 1 or a 0. How do I convert to TRUE / FALSE without making this a CTE?
If you are ok with putting column names rather then select *, then following can be used -
select STORE_NO,STORE_ADDRESS,STORE_OWNER,STORE_HOURS,
"'Drive Thru'"=1 as drivethru,
"'Curbside'"=1 as curbside,
"'Counter'"=1 as counter
from stores
pivot(count(STORE_TYPE) for STORE_TYPE in ('Drive Thru', 'Curbside', 'Counter'))
as store_flattened;
I honestly think you should leave it as is. Any attempt at a workaround will result in either complicating the pivot logic or having to manually specify the columns names in multiple places; especially with pivoted columns appearing before the rest. Having said that, if you must find a way to do this, here is an attempt.
I know you wanted to avoid a CTE, but I am using it for a purpose different than what you might had in mind. General idea in steps--
In a CTE, sub-select some of the columns you want to appear before
the pivoted columns. Create a flag based on whether store_type
(b.value) from the lateral flatten matches existing store_type
for a given row. You'll notice the values passed to input=> can be easily copy-pasted to the pivot clause
Pivot using max(flag) which will turn (false,true)->true and
(false,false)->false. You can run the CTE portion to see why that
matters and how it solves the main issue
Finally, use a natural join with the main table to append the rest of
the columns (this is the first time I found a natural join useful enough to keep it. I actively avoid them otherwise)
with cte (store_no, store_address, store_type, flag) as
(select store_no, store_address, b.value::string, b.value::string = store_type
from t, lateral flatten(input=>['Drive Thru', 'Curbside', 'Counter']) b)
select *
from cte pivot(max(flag) for store_type in ('Drive Thru', 'Curbside', 'Counter'))
natural join (select distinct store_no, store_owner, store_hours from t)
Outputs:
I'm having trouble trying to explain my necessity, so I'll describe the scenario.
Scenario:
Product A has a maximum production of 125KG at a time.
The operator received a production order of 1027,5KG of product A.
The operator have to calculate how many rounds he'll have to
manufacture and adjust the components quantity for each round.
We want to create a report where this calculations are already done and what we believe would be the first step, based on the values of this scenario, is to return something like this:
ROUND QUANTITY(KG)
1 125
2 125
3 125
4 125
5 125
6 125
7 125
8 125
9 27,5
After that, the recalculation of the components could be done with simple operations.
The problem is that we couldn't think of a way to get the desired return and we also couldn't think of a different way of achieving the said report.
All we could do is get the integer part of the division
SELECT FLOOR(1027.5/125) AS "TEST" FROM DUMMY
and the remainder
SELECT MOD(1027.5,125) AS "TEST" FROM DUMMY
We are using:
SAP HANA SQL
Crystal Reports
SAP B1
Any help would be appreciated
Thanks in advance!
There are several ways to achieve want you described.
One way is to translate the requirement into a function that takes the two input parameter values and returns the table of production rounds.
This can look like this:
create or replace function production_rounds(
IN max_production_volume_per_round decimal (10, 2)
, IN production_order_volume decimal (10, 2)
)
returns table (
production_round integer
, production_volume decimal (10, 2))
as
begin
declare rounds_to_produce integer;
declare remainder_production_volume decimal (10, 2);
rounds_to_produce := floor( :production_order_volume / :max_production_volume_per_round);
remainder_production_volume := mod(:production_order_volume, :max_production_volume_per_round);
return
select /* generate rows for all "max" rounds */
s.element_number as production_round
, :max_production_volume_per_round as production_volume
from
series_generate_integer
(1, 1, :rounds_to_produce + 1) s
UNION ALL
select /* generate a row for the final row with the remainder */
:rounds_to_produce + 1 as production_round
, :remainder_production_volume as production_volume
from
dummy
where
:remainder_production_volume > 0.0;
end;
You can use this function just like any table - but with parameters:
select * from production_rounds (125 , 1027.5) ;
PRODUCTION_ROUND PRODUCTION_VOLUME
1 125
2 125
3 125
4 125
5 125
6 125
7 125
8 125
9 27.5
The bit that probably needs explanation is SERIES_GENERATE_INTEGER. This is a HANA-specific built-in function that returns a number of records from a "series". Series here is a sequence of periods within a min and max limit and with a certain step-size between two adjacend periods.
More on how this works can be found in the HANA reference documentation, but for now just say, this is the fastest way to generate a result set with X rows.
This series-generator is used to create all "full" production rounds.
For the second part of the UNION ALL then creates just a single row by selecting from the built-in table DUMMY (DUAL in Oracle) which is guaranteed to only have a single record.
Finally, this second part needs to be "disabled" if there actually is no remainder, which is done by the WHERE clause.
I have a Postgres 9.0 query returning results in a way similar to this:
item;qty
AAAA;2
EEEE;3
What I would like is to transform that into:
AAAA
AAAA
EEEE
EEEE
EEEE
Is there any way I can do that on simple, i.e., without stored procedures, functions, etc?
There's a function 'generate_series' which can be used to generate a table of values. These can be used to repeat a column via joining:
select item
from data,generate_series(0,1000)
where generate_series<qty order by item;
Consider the following demo:
CREATE TEMP TABLE x(item text, qty int);
INSERT INTO x VALUES
('AAAA',2)
,('EEEE',3)
,('IIII',4);
SELECT regexp_split_to_table(rtrim(repeat(item||'~#~',qty),'~#~'),'~#~') AS item
FROM x;
Produces exactly the requested result.
In my tests it performs faster by an order of magnitude than the solution with generate_series().
Additional bonus: works with any number of qty.
Weakness: you need a delimiter-string not contained in any item.
SELECT
myTable.item
FROM
myTable
INNER JOIN
(SELECT 1 AS counter UNION ALL SELECT 2 UNION ALL SELECT 3) AS multiplier
ON multiplier.counter <= myTable.qty
Increase the number of UNIONS based on your Maximum value in qty
But I'd also follow #djacobson's advice : explain why you want to do this, as the may be a completely different approach altogether. Doing this feels, ummm, odd...
I have the following query, which works great. The problem I have is that in both tables (and the aggregate unioned table), there is a field called MTGUID. I need to multiply MTGUID by a number (let's say 1.35, for ease of use) and have it return that number in the MTGUID field. I have tried a dozen ways to do this and can't get anything to play ball. I can create a new column for each calculated price, like (BKRETAIL.MTGUID * 1.35) AS MTG1, but we've got tens of thousands of lines of code that specifically use MTGUID. Any ideas?
I'm using Firebird SQL.
SELECT * FROM (
SELECT BKRETAIL.* FROM BKRETAIL WHERE BKRETAIL.MKEY='SOMEKEY'
UNION SELECT BKWHOLESALE.* FROM BKWHOLESALE WHERE MKEY='SOMEKEY')
ORDER BY
case STATUS
WHEN 'RT' then 1
WHEN 'WH' then 2
WHEN 'OL' then 3
WHEN 'OD' then 4
WHEN NULL then 5
else 6
end;
How about this:
SELECT MTGUID * 1.35 as calculatedMTGUID, SUBSEL.* FROM (
SELECT BKRETAIL.* FROM BKRETAIL WHERE BKRETAIL.MKEY='SOMEKEY'
UNION SELECT BKWHOLESALE.* FROM BKWHOLESALE WHERE MKEY='SOMEKEY') SUBSEL
ORDER BY
case STATUS
WHEN 'RT' then 1
WHEN 'WH' then 2
WHEN 'OL' then 3
WHEN 'OD' then 4
WHEN NULL then 5
else 6
end;
try this
SELECT MTGUID * 1.35 AS MTGUID,<list rest OF COLUMNS here>
FROM (
SELECT BKRETAIL.* FROM BKRETAIL WHERE BKRETAIL.MKEY='SOMEKEY'
UNION SELECT BKWHOLESALE.* FROM BKWHOLESALE WHERE MKEY='SOMEKEY')
ORDER BY
case STATUS
WHEN 'RT' then 1
WHEN 'WH' then 2
WHEN 'OL' then 3
WHEN 'OD' then 4
WHEN NULL then 5
else 6
end;
One option would be to replace the original MTGUID column with computed one, ie
rename the original MTGUID column in table(s);
add new MTGUID column with desired expression using COMPUTED BY (expr);
Advantage of this is that you don't have to alter your SQL statements, disadvantage is that you have to maintain the expression in many places (all the tables which have the column). Of course, the queries which need the original MTGUID value must be updated to use the renamed column, but if the number of such statements is significantly lower it could be worth the trouble.
I think a better solution would be to "hide" all this stuff behind a view but this requires alerting your SQL queries...
I need to show more than one result from each field in a table. I need to do this with only one SQL sentence, I donĀ“t want to use a Cursor.
This seems silly, but the number of rows may vary for each item. I need this to print afterwards this information as a Crystal Report detail.
Suppose I have this table:
idItem Cantidad <more fields>
-------- -----------
1000 3
2000 2
3000 5
4000 1
I need this result, using one only SQL Sentence:
1000
1000
1000
2000
2000
3000
3000
3000
3000
3000
4000
where each idItem has Cantidad rows.
Any ideas?
It seems like something that should be handled in the UI (or the report). I don't know Crystal Reports well enough to make a suggestion there. If you really, truly need to do it in SQL, then you can use a Numbers table (or something similar):
SELECT
idItem
FROM
Some_Table ST
INNER JOIN Numbers N ON
N.number > 0 AND
N.number <= ST.cantidad
You can replace the Numbers table with a subquery or function or whatever other method you want to generate a result set of numbers that is at least large enough to cover your largest cantidad.
Check out UNPIVOT (MSDN)
Another example
If you use a "numbers" table that is useful for this and many similar purposes, you can use the following SQL:
select t.idItem
from myTable t
join numbers n on n.num between 1 and t.Cantidad
order by t.idTtem
The numbers table should just contain all integer numbers from 0 or 1 up to a number big enough so that Cantidad never exceeds it.
As others have said, you need a Numbers or Tally table which is just a sequential list of integers. However, if you knew that Cantidad was never going to be larger than five for example, you can do something like:
Select idItem
From Table
Join (
Select 1 As Value
Union All Select 2
Union All Select 3
Union All Select 4
Union All Select 5
) As Numbers
On Numbers.Value <= Table.Cantidad
If you are using SQL Server 2005, you can use a CTE to do:
With Numbers As
(
Select 1 As Value
Union All
Select N.Value + 1
From Numbers As N
)
Select idItem
From Table
Join Numbers As N
On N.Value <= Table.Cantidad
Option (MaxRecursion 0);