Right way to shift bits in PL/SQL - oracle

I have a 64-bits integer id, and I want to display the left most 32-bits. This could be done using Shift operators in PL/SQL.
Indeed, these two lines gives the same result:
-- 1- not using a hex mask
select to_char(id / power(2, 32), 'XXXXXXXXXXXXXXXX', 'XXXXXXXX') from dual;
-- 2- using a hex mask
select to_char( bitand(id, to_number('ffffffff00000000', 'XXXXXXXXXXXXXXXX') / power(2, 32), 'XXXXXXXXXXXXXXXX', 'XXXXXXXX') from dual;
However, I dont understand why the first line works, since id is not converted to hex or binary.
It seems the bitmask is not necessary, but I don't understand why.
Without it, doesn't it means that the decimal number will be divided by 2^32 ? Shouldn't this give a floating number in certain cases?
Thanks

I don't really understand why the hex mask is unecessary.
Hmm, I really don't know how to explain this, I will try using an example.
The first command just takes a number and divides it by power(2, 32).
This is equivalent to shifting a number by 32 bits to the right.
If you want to shift a number by 1 bit to right, just divide it by 2
To shift a number by 2 bits to the right, divide it by 2 * 2 (power(2,2)).
To shift a number by 3 bits to the right, divide it by 2 * 2 * 2 (power(2,3)).
......
To shift a number by 32 bits to the right, divide it by 2 * .... * 2 (power(2,32)).
Some simple examples - let say we have a number 12345 (decimal).
12345 (decimal) = 10011010010 (binary)
If we shift this number to the right by 1,2,3,4 bits, we should get:
+-----+------------------+------------------+
| bit | result binary | result decimal |
+-----+------------------+------------------+
| 0 | 11000000111001 | 12345 |
| 1 | 1100000011100 | 6172 |
| 2 | 110000001110 | 3086 |
| 3 | 11000000111 | 1543 |
| 4 | 1100000011 | 771 |
+-----+------------------+------------------+
If we run this query:
select 12345 / power(2, 0) x0,
12345 / power(2, 1) x1,
12345 / power(2, 2) x2,
12345 / power(2, 3) x3,
12345 / power(2, 4) x4
FROM dual;
we get these results (as decimal numbers):
X0 X1 X2 X3 X4
---------- ---------- ---------- ---------- ----------
12345 6172.5 3086.25 1543.125 771.5625
If you take an integer part (before the dot) of these results and convert them to the binary notation, you will get shifted numbers from the above table.
----------- EDIT ------------------
why select to_char(13 / 2, 'XXXXXXXX') from dual give 7 and not 6.5?
Because to_char function rounds the result due to a format XXXXX.
Refer to the documentation: https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34510
XXXX
Returns the hexadecimal value of the specified number of digits. If
the specified number is not an integer, then Oracle Database rounds it
to an integer.
The below command gives 6.5
select 13 / 2 from dual;
The below command gives 7, 7, 6 because to_char function rounds numbers to integers due to XXXXX format used:
select to_char( 13 / 2, 'XXXXX' ),
to_char( 6.5, 'XXXXX' ),
to_char( 6.4, 'XXXXX' )
from dual;
TO_CHAR(13/2,'XXXXX') TO_CHAR(6.5,'XXXXX') TO_CHAR(6.4,'XXXXX')
--------------------- -------------------- --------------------
7 7 6

Related

What is the syntax in Oracle to round any number to the greatest/highest place value of that number?

I have a wide variety of numbers
In the ten thousands, thousands, hundreds, etc
I would like to compute the rounding to the highest place value ex:
Starting #: 2555.5
Correctly Rounded : 3000
——
More examples ( in the same report )
Given: 255
Rounded: 300
Given: 25555
Rounded: 30000
Given: 2444
Rounded: 2000
But with the Round() or Ceil() functions I get the following
Given: 2555.5
Did not want : 2556
Any ideas ??? Thank you in advance
You can combine numeric functions like this
SELECT
col,
ROUND(col / POWER(10,TRUNC(LOG(10, col)))) * POWER(10,TRUNC(LOG(10,col)))
FROM Data
See fiddle
Explanation:
LOG(10, number) gets the power you need to raise 10 to in order get the number. E.g., LOG(10, 255) = 2.40654 and 10^2.40654 = 255
TRUNC(LOG(10, col)) the number of digit without the leading digit (2).
POWER(10,TRUNC(LOG(10, col))) converts, e.g., 255 to 100.
Then we divide the number by this rounded number. E.g. for 255 we get 255 / 100 = 2.55.
Then we round. ROUND(2.55) = 3
Finally we multiply this rounded result again by the previous divisor: 3 * 100 = 300.
By using the Oracle ROUND function with a second parameter specifying the number of digits with a negative number of digits, we can simplify the select command (see fiddle)
SELECT
col,
ROUND(col, -TRUNC(LOG(10, col))) AS rounded
FROM Data
You can also use this to round by other fractions like quarters of the main number:
ROUND(4 * col, -TRUNC(LOG(10, col))) / 4 AS quarters
see fiddle
Similar to what Olivier had built, you can use a combination of functions to round the numbers as you need. I had built a similar method except instead of using LOG, I used LENGTH to get the number of non-decimal digits.
WITH
nums (num)
AS
(SELECT 2555.5 FROM DUAL
UNION ALL
SELECT 255 FROM DUAL
UNION ALL
SELECT 25555 FROM DUAL
UNION ALL
SELECT 2444 FROM DUAL)
SELECT num,
ROUND (num, (LENGTH (TRUNC (num)) - 1) * -1) as rounded
FROM nums;
NUM ROUNDED
_________ __________
2555.5 3000
255 300
25555 30000
2444 2000

Recursion in DAX

I don't know if this is even possible, but I'd like to be able to create a calculated column where each row is dependent on the rows above it.
A classic example of this is the Fibonacci sequence, where the sequence is defined by the recurrence relationship F(n) = F(n-1) + F(n-2) and seeds F(1) = F(2) = 1.
In table form,
Index Fibonacci
----------------
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
... ...
I want to be able to construct the Fibonacci column as a calculated column.
Now, I know that the Fibonacci sequence has a nice closed form where I can define
Fibonacci = (((1 + SQRT(5))/2)^[Index] - ((1 - SQRT(5))/2)^[Index])/SQRT(5)
or using the shallow diagonals of Pascal's triangle form:
Fibonacci =
SUMX (
ADDCOLUMNS (
SELECTCOLUMNS (
GENERATESERIES ( 0, FLOOR ( ( [Index] - 1 ) / 2, 1 ) ),
"ID", [Value]
),
"BinomCoeff", IF (
[ID] = 0,
1,
PRODUCTX (
GENERATESERIES ( 1, [ID] ),
DIVIDE ( [Index] - [ID] - [Value], [Value] )
)
)
),
[BinomCoeff]
)
but this is not the case for recursively defined functions in general (or for the purposes I'm actually interested in using this for).
In Excel, this is easy to do. You would write a formula like this
A3 = A2 + A1
or in R1C1 notation,
= R[-1]C + R[-2]C
but I just can't figure out if this is even possible in DAX.
Everything I've tried either doesn't work or gives a circular dependency error. For example,
Fibonacci =
VAR n = [Index]
RETURN
IF(Table1[Index] <= 2,
1,
SUMX(
FILTER(Table1,
Table1[Index] IN {n - 1, n - 2}),
Table1[Fibonacci]
)
)
gives the error message
A circular dependency was detected: Table1[Fibonacci].
Edit:
In the book Tabular Modeling in Microsoft SQL Server Analysis Services by Marco Russo and Alberto Ferrari, DAX is described and includes this paragraph:
As a pure functional language, DAX does not have imperative statements, but it leverages special functions called iterators that execute a certain expression for each row of a given table expression. These arguments are close to the lambda expression in functional languages. However, there are limitations in the way you can combine them, so we cannot say they correspond to a generic lambda expression definition. Despite its functional nature, DAX does not allow you to define new functions and does not provide recursion.
It appears there is no straightforward way to do recursion. I do still wonder if there is a way to still do it indirectly somehow using Parent-Child functions, which appear to be recursive in nature.
Edit 2:
While general recursion doesn't seem feasible, don't forget that recursive formulas may have a nice closed form that can be fairly easily derived.
Here are a couple of examples where I use this workaround to sidestep recursive formulas:
How to perform sum of previous cells of same column in PowerBI
DAX - formula referencing itself
Based on your first sample dataset, it looks to me like a "sort of" Cummulative Total, which can probably calculated easily in SQL using WINDOW function-- I tried a couple things but nothing panned out just yet. I don't work with DAX enough to say if it can be done.
Edit: In reviewing a little closer the Fibonacci sequence, it turns out that my SQL code doing cumulative comparison is not correct. You can read the SO Post How to generate Fibonacci Series, and it has a few good SQL Fibonacci answers that I tested; in particular the post by N J - answered Feb 13 '14. I'm not sure of a DAX Fibonacci recursion function capability.
SQL Code (not quite correct):
DECLARE #myTable as table (Indx int)
INSERT INTO #myTable VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
SELECT
Indx
,SUM(myTable.Indx) OVER(ORDER BY myTable.Indx ASC ROWS BETWEEN UNBOUNDED PRECEDING and CURRENT ROW) -- + myTable.Indx
AS [Cummulative]
,SUM(myTable.Indx) OVER(ORDER BY myTable.Indx ASC ROWS BETWEEN UNBOUNDED PRECEDING and 2 PRECEDING)
+ SUM(myTable.Indx) OVER(ORDER BY myTable.Indx ASC ROWS BETWEEN UNBOUNDED PRECEDING and 1 PRECEDING)
AS [Fibonacci]
from #myTable myTable
Result Set:
+------+-------------+-----------+
| Indx | Cummulative | Fibonacci |
+------+-------------+-----------+
| 1 | 1 | NULL |
+------+-------------+-----------+
| 2 | 3 | NULL |
+------+-------------+-----------+
| 3 | 6 | 4 |
+------+-------------+-----------+
| 4 | 10 | 9 |
+------+-------------+-----------+
| 5 | 15 | 16 |
+------+-------------+-----------+
| 6 | 21 | 25 |
+------+-------------+-----------+
| 7 | 28 | 36 |
+------+-------------+-----------+
| 8 | 36 | 49 |
+------+-------------+-----------+
| 9 | 45 | 64 |
+------+-------------+-----------+
| 10 | 55 | 81 |
+------+-------------+-----------+
DAX Cummulative:
A link that could help calculate cumulative totals with DAX-- https://www.daxpatterns.com/cumulative-total/. And here is some sample code from the article.
Cumulative Quantity :=
CALCULATE (
SUM ( Transactions[Quantity] ),
FILTER (
ALL ( 'Date'[Date] ),
'Date'[Date] <= MAX ( 'Date'[Date] )
)
)
DAX language doesn't support recursion.
It's also been written in a sqlbi's article about calculation groups
DAX is not recursive, so Calculation Groups do not allow recursion. This is a good idea for controlling performance, but it requires a different approach compared to certain techniques that are possible in MDX Script by leveraging recursion.
https://www.sqlbi.com/blog/marco/2019/03/01/calculation-groups-in-dax-first-impressions/

custom round logic off in SQL

Iwant to round of the value upto 2 decimal point when third decimal digit is greater then 5:
39.956 should be round off to 39.96,
35.665 should be round off to 35.66 ,
39.997 should be round off to 40.00 ,
56.684 should be round off to 56.68.
I am trying to do below
SELECT CAST(FLOOR(VALUE) AS VARCHAR2(30))
+ CASE
WHEN CAST(SUBSTR(SUBSTR(VALUE, INSTR(VALUE, '.')), 4) AS INT) > 5
THEN
CONCAT(
'.',
( SUBSTR(
SUBSTR(VALUE, INSTR(VALUE, '.')),
2,
2
)
+ 1)
)
ELSE
CONCAT(
'.',
SUBSTR(
SUBSTR(VALUE, INSTR(VALUE, '.')),
2,
2
)
)
END
FROM DUAL;
but for the border cases, for example 39.897 and 39.997 it is not working.
Maybe you simply need this:
SQL> with test(num) as (
2 select 39.956 from dual union all
3 select 35.665 from dual union all
4 select 39.997 from dual union all
5 select 56.684 from dual
6 )
7 select num, round(num -0.001, 2)
8 from test;
NUM ROUND(NUM-0.001,2)
---------- ------------------
39,956 39,96
35,665 35,66
39,997 40
56,684 56,68
Aleksej's solution will work fine and is probably the most efficient if it is known beforehand that the input numbers have at most three decimal places.
The problem can be generalized though, like so: round 38.445 down to 38.44; however, round 38.44503 to 38.45. (That is, if there are non-zero digits after the "5" in the third decimal position, then round up.)
Something like the query below can be used in the general case. The only time the result is different from "usual" rounding is when the input number has exactly three non-zero decimal places, and the third decimal place is 5. This is exactly how the solution reads.
with inp (n) as (select 38.445 from dual union all select 38.44503 from dual)
select n,
round(n,2) - case when n = round(n, 3) and mod(1000*n, 10) = 5
then 0.01
else 0 end as custom_rounded
from inp;
N CUSTOM_ROUNDED
---------- --------------
38.445 38.44
38.44503 38.45

how to find even position letters and odd position lettes in a record in ORACLE

Please find below the rule of calculating the check digit. The customer number will be 8 digit number. The first 7 digits will be a series and the 8th digit will be a check digit as demonstrated below:
Add all the odd position digits of the running number
Add all the even position digits of the running number
Multiply the sum of above two
Take the modulo 10 of the sum
So as per the above rule the first account number will be:
First part : 0030001
Sum of odd position digits : 0+3+0+1=4
Sum of even position digits : 0+0+0=0
Product of above two sums : 4x0 = 0
Modulo 10 of above product = 0
The complete account number = 00300010
Second account number:
First part : 0030002
Sum of odd position digits : 0+3+0+2=4
Sum of even position digits : 0+0+0=0
Product of above two sums : 4x0 = 0
Modulo 10 of above product = 0
The complete account number = 00300020
Also the following thing to be maintained in the account number format
Length each account has to be of 8 digits (seven digit running number and one check digit, the last digit)
Leading zeros has to be there
The running number must start from 30001
Taking the oracle tag as a hint (and not yet allowed to comment)…
WITH
Input (str, note) AS (
SELECT '00300010', 'OK' FROM DUAL UNION ALL
SELECT '00300020', 'OK' FROM DUAL UNION ALL
SELECT '00300025', 'check digit wrong' FROM DUAL UNION ALL
SELECT '00200010', '< 30001' FROM DUAL UNION ALL
SELECT '0300010', 'too short' FROM DUAL UNION ALL
SELECT '000200010', 'too long' FROM DUAL UNION ALL
SELECT 'a0300025', 'not a number' FROM DUAL
)
SELECT
str
, note
, CASE
WHEN REGEXP_REPLACE(str, '\d{8}', '', 1, 1, '') IS NULL AND
TO_NUMBER(SUBSTR(str, 1, 7)) > 30000 AND
MOD((TO_NUMBER(SUBSTR(str, 1, 1)) +
TO_NUMBER(SUBSTR(str, 3, 1)) +
TO_NUMBER(SUBSTR(str, 5, 1)) +
TO_NUMBER(SUBSTR(str, 7, 1)))
*
(TO_NUMBER(SUBSTR(str, 2, 1)) +
TO_NUMBER(SUBSTR(str, 4, 1)) +
TO_NUMBER(SUBSTR(str, 6, 1))),
10)
= TO_NUMBER(SUBSTR(str, 8, 1))
THEN 'TRUE'
ELSE 'FALSE'
END valid
FROM Input
;
returns
| STR | NOTE | VALID |
|-----------|-------------------|-------|
| 00300010 | OK | TRUE |
| 00300020 | OK | TRUE |
| 00300025 | check digit wrong | FALSE |
| 00200010 | < 30001 | FALSE |
| 0300010 | too short | FALSE |
| 000200010 | too long | FALSE |
| a0300025 | not a number | FALSE |
SQL Fiddle
Which uses SUBSTR(str, 1, 1) to get the respective figures…

Is there an algorithm that can divide a number into three parts and have their totals match the original number?

For example if you take the following example into consideration.
100.00 - Original Number
33.33 - 1st divided by 3
33.33 - 2nd divided by 3
33.33 - 3rd divided by 3
99.99 - Is the sum of the 3 division outcomes
But i want it to match the original 100.00
One way that i saw it could be done was by taking the original number minus the first two divisions and the result would be my third number. Now if i take those 3 numbers i get my original number.
100.00 - Original Number
33.33 - 1st divided by 3
33.33 - 2nd divided by 3
33.34 - 3rd number
100.00 - Which gives me my original number correctly. (33.33+33.33+33.34 = 100.00)
Is there a formula for this either in Oracle PL/SQL or a function or something that could be implemented?
Thanks in advance!
This version takes precision as a parameter as well:
with q as (select 100 as val, 3 as parts, 2 as prec from dual)
select rownum as no
,case when rownum = parts
then val - round(val / parts, prec) * (parts - 1)
else round(val / parts, prec)
end v
from q
connect by level <= parts
no v
=== =====
1 33.33
2 33.33
3 33.34
For example, if you want to split the value among the number of days in the current month, you can do this:
with q as (select 100 as val
,extract(day from last_day(sysdate)) as parts
,2 as prec from dual)
select rownum as no
,case when rownum = parts
then val - round(val / parts, prec) * (parts - 1)
else round(val / parts, prec)
end v
from q
connect by level <= parts;
1 3.33
2 3.33
3 3.33
4 3.33
...
27 3.33
28 3.33
29 3.33
30 3.43
To apportion the value amongst each month, weighted by the number of days in each month, you could do this instead (change the level <= 3 to change the number of months it is calculated for):
with q as (
select add_months(date '2013-07-01', rownum-1) the_month
,extract(day from last_day(add_months(date '2013-07-01', rownum-1)))
as days_in_month
,100 as val
,2 as prec
from dual
connect by level <= 3)
,q2 as (
select the_month, val, prec
,round(val * days_in_month
/ sum(days_in_month) over (), prec)
as apportioned
,row_number() over (order by the_month desc)
as reverse_rn
from q)
select the_month
,case when reverse_rn = 1
then val - sum(apportioned) over (order by the_month
rows between unbounded preceding and 1 preceding)
else apportioned
end as portion
from q2;
01/JUL/13 33.7
01/AUG/13 33.7
01/SEP/13 32.6
Use rational numbers. You could store the numbers as fractions rather than simple values. That's the only way to assure that the quantity is truly split in 3, and that it adds up to the original number. Sure you can do something hacky with rounding and remainders, as long as you don't care that the portions are not exactly split in 3.
The "algorithm" is simply that
100/3 + 100/3 + 100/3 == 300/3 == 100
Store both the numerator and the denominator in separate fields, then add the numerators. You can always convert to floating point when you display the values.
The Oracle docs even have a nice example of how to implement it:
CREATE TYPE rational_type AS OBJECT
( numerator INTEGER,
denominator INTEGER,
MAP MEMBER FUNCTION rat_to_real RETURN REAL,
MEMBER PROCEDURE normalize,
MEMBER FUNCTION plus (x rational_type)
RETURN rational_type);
Here is a parameterized SQL version
SELECT COUNT (*), grp
FROM (WITH input AS (SELECT 100 p_number, 3 p_buckets FROM DUAL),
data
AS ( SELECT LEVEL id, (p_number / p_buckets) group_size
FROM input
CONNECT BY LEVEL <= p_number)
SELECT id, CEIL (ROW_NUMBER () OVER (ORDER BY id) / group_size) grp
FROM data)
GROUP BY grp
output:
COUNT(*) GRP
33 1
33 2
34 3
If you edit the input parameters (p_number and p_buckets) the SQL essentially distributes p_number as evenly as possible among the # of buckets requested (p_buckets).
I've solved this problem yesterday by subtracting 2 of 3 parts from the starting number, e.g. 100 - 33.33 - 33.33 = 33.34 and the result of summing it up is still 100.

Resources