how to find even position letters and odd position lettes in a record in ORACLE - 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…

Related

Oracle Hierarchical query with multiple nodes

I have a View and a Table that has the following details:
V_Mgmt
DMId PMId TLId
1 1 1
1 1 2
1 2 3
2 3 4
2 3 5
2 4 6
T_ProjLevels
TLId DevId ParentDevId
1 1 0
1 2 1
1 3 1
2 4 0
2 5 4
2 6 4
2 7 6
3 8 0
3 9 0
4 10 0
4 11 0
4 12 11
Ideally, my tree structure would be as per the left image. But, I am required to create a tree structure as per the right image by skipping the TL's.
So far, I was successfully able to create my tree with only DevId's by using the below query. Need some help in creating this new tree structure.
SELECT DevId,ParentDevId from T_ProjLevels
START WITH ParentDevId=0
Connect By Nocycle Prior "DevId" = "ParentDevId"
ORDER SIBLINGS BY ParentDevId
Look at your data structure. You have one row representing DMID 1, PMID 1, which you want to show as two separate rows in your tree. Somehow, you need to create extra rows in your result set to represent these intermediate nodes. A good way to do that is using GROUPING SETS.
So, what we will do is join the two tables and GROUP BY GROUPING SETS(...) in such a way as to get a merged hierarchy of all the nodes, whether they are DM, PM, or DEV nodes.
Once we have that, we will just do a simple CONNECT BY query on the merged hierarchy.
I am pasting in a working implementation of that plan below, but first let me just say that if you need to do stuff like this often, seriously consider that maybe your data model is not properly designed.
Here is the query, with comments. This version will give you the tree on the right in your post: that is, the one that omits the TL levels. A simple modification would give the tree on the left instead... it was unclear which one you were really after (sorry).
-- First provide data to simulate your V_Mgmt table...
With V_Mgmt ( DMid, PMId, TLId ) AS (
SELECT 1, 1, 1 FROM DUAL UNION ALL
SELECT 1, 1, 2 FROM DUAL UNION ALL
SELECT 1, 2, 3 FROM DUAL UNION ALL
SELECT 2, 3, 4 FROM DUAL UNION ALL
SELECT 2, 3, 5 FROM DUAL UNION ALL
SELECT 2, 4, 6 FROM DUAL ),
-- ... and your T_ProjLevels table
T_ProjLevels (TLId, DevId, ParentDevId) AS (
SELECT 1, 1, 0 FROM DUAL UNION ALL
SELECT 1, 2, 1 FROM DUAL UNION ALL
SELECT 1, 3, 1 FROM DUAL UNION ALL
SELECT 2, 4, 0 FROM DUAL UNION ALL
SELECT 2, 5, 4 FROM DUAL UNION ALL
SELECT 2, 6, 4 FROM DUAL UNION ALL
SELECT 2, 7, 6 FROM DUAL UNION ALL
SELECT 3, 8, 0 FROM DUAL UNION ALL
SELECT 3, 9, 0 FROM DUAL UNION ALL
SELECT 4, 10, 0 FROM DUAL UNION ALL
SELECT 4, 11, 0 FROM DUAL UNION ALL
SELECT 4, 12, 11 FROM DUAL ),
-- Next, merge them together
-- (A) using GROUPING_SETS to create extra rows for the DM, PM, but not the TL-level nodes
-- (B) combining DM, PM, (but not TL), and DEV ids into a common set of "node", "parent_node", and "node_name" columns
merged_hierarchy ( node, parent_node, node_name ) AS (
SELECT rtrim(m.dmid || '.' || m.pmid || '.' || pl.devid,'.') node,
rtrim(
case
when grouping(m.pmid) = 1 then NULL
when grouping(m.tlid) = 1 then to_char(m.dmid)
when grouping(pl.devid) = 1 then m.dmid || '.' || m.pmid
else
m.dmid || '.' || m.pmid || '.' || nullif(pl.parentdevid,0) end,'.') parent_node,
case when grouping(pl.devid) = 0 THEN 'DEV' || pl.devid
when grouping(m.tlid) = 0 THEN 'TL' || m.tlid
when grouping(m.pmid) = 0 THEN 'PM' || m.pmid
when grouping(m.dmid) = 0 THEN 'DM' || m.dmid
end node_name
from v_mgmt m
left join t_projlevels pl on pl.tlid = m.tlid
group by grouping sets ( ( m.dmid ), ( m.dmid, m.pmid), (m.dmid, m.pmid, m.tlid, pl.devid, pl.parentdevid ) )
)
-- Finally, query the merged hierarchy as a straight-forward CONNECT BY query
SELECT lpad(' ',5*(level-1),' ') || node_name output
FROM merged_hierarchy
START WITH parent_node IS NULL
CONNECT BY parent_node = prior node
-- Exclude the outer-joined rows from T_ProjLevels...
AND node_name != 'DEV';
+--------------------------+
| OUTPUT |
+--------------------------+
| DM1 |
| PM1 |
| DEV1 |
| DEV2 |
| DEV3 |
| DEV4 |
| DEV5 |
| DEV6 |
| DEV7 |
| PM2 |
| DEV8 |
| DEV9 |
| DM2 |
| PM3 |
| DEV10 |
| DEV11 |
| DEV12 |
| PM4 |
+--------------------------+

Oracle add group function over result rows

I'm trying to add an aggregate function column to an existing result set. I've tried variations of OVER(), UNION, but cannot find a solution.
Example current result set:
ID ATTR VALUE
1 score 5
1 score 7
1 score 9
Example desired result set:
ID ATTR VALUE STDDEV (score)
1 score 5 2
1 score 7 2
1 score 9 2
Thank you
Seems like you're after:
stddev(value) over (partition by attr)
stddev(value) over (partition by id, attr)
It just depend on what you need to partition by. Based on sample data the attr should be enough; but I could see possibly the ID and attr.
Example:
With CTE (ID, Attr, Value) as (
SELECT 1, 'score', 5 from dual union all
SELECT 1, 'score', 7 from dual union all
SELECT 1, 'score', 9 from dual union all
SELECT 1, 'Z', 1 from dual union all
SELECT 1, 'Z', 5 from dual union all
SELECT 1, 'Z', 8 from dual)
SELECT A.*, stddev(value) over (partition by attr)
FROM cte A
ORDER BY attr, value
DOCS show that by adding an order by to the analytic, one can acquire the cumulative standard deviation per record.
Giving us:
+----+-------+-------+------------------------------------------+
| ID | attr | value | stdev |
+----+-------+-------+------------------------------------------+
| 1 | Z | 1 | 3.51188458428424628280046822063322249225 |
| 1 | Z | 5 | 3.51188458428424628280046822063322249225 |
| 1 | Z | 8 | 3.51188458428424628280046822063322249225 |
| 1 | score | 5 | 2 |
| 1 | score | 7 | 2 |
| 1 | score | 9 | 2 |
+----+-------+-------+------------------------------------------+

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

Right way to shift bits in PL/SQL

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

Sample time serie by time interval with Hive QL and calculate jumps

I have time series data in a table. Basically each row has a timestamp and a value.
The frequency of the data is absolutely random.
I'd like to sample it with a given frequency and for each frequency extract relevant information about it: min, max, last, change (relative previous), return (change / previous) and maybe more (count...)
So here's my input:
08:00:10, 1
08:01:20, 2
08:01:21, 3
08:01:24, 5
08:02:24, 2
And I'd like to get the following result for 1 minute sampling (ts, min, max, last, change, return):
ts m M L Chg Return
08:01:00, 1, 1, 1, NULL, NULL
08:02:00, 2, 5, 5, 4, 4
08:03:00, 2, 2, 2, -3, -0.25
You could do it with something like this (comments inline):
SELECT
min
, mn
, mx
, l
, l - LAG(l, 1) OVER (ORDER BY min) c
-- This might not be the right calculation. Unsure how -0.25 was derived in question.
, (l - LAG(l, 1) OVER (ORDER BY min)) / (LAG(l, 1) OVER (ORDER BY min)) r
FROM
(
SELECT
min
, MIN(val) mn
, MAX(val) mx
-- We can take MAX here because all l's (last values) for the minute are the same.
, MAX(l) l
FROM
(
SELECT
min
, val
-- The last value of the minute, ordered by the timestamp, using all rows.
, LAST_VALUE(val) OVER (PARTITION BY min ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) l
FROM
(
SELECT
ts
-- Drop the seconds and go back one minute by converting to seconds,
-- subtracting 60, and then going back to a shorter string format.
-- 2000-01-01 is a dummy date just to enable the conversion.
, CONCAT(FROM_UNIXTIME(UNIX_TIMESTAMP(CONCAT("2000-01-01 ", ts), "yyyy-MM-dd HH:mm:ss") + 60, "HH:mm"), ":00") min
, val
FROM
-- As from the question.
21908430_input a
) val_by_min
) val_by_min_with_l
GROUP BY min
) min_with_l_m_M
ORDER BY min
;
Result:
+----------+----+----+---+------+------+
| min | mn | mx | l | c | r |
+----------+----+----+---+------+------+
| 08:01:00 | 1 | 1 | 1 | NULL | NULL |
| 08:02:00 | 2 | 5 | 5 | 4 | 4 |
| 08:03:00 | 2 | 2 | 2 | -3 | -0.6 |
+----------+----+----+---+------+------+

Resources