Oracle hierarchy data representation ambiguity - oracle10g

I am having a table in following structure.
_________________________________
|| ExpObjkey Exp1 Exp2 operator||
________________________________
1 2 3 +
2 4 5 +
3 6 7 -
I want to have records in the following order:
for expObjKey=1, we will have
ExpObjKey Expression
1 (4+5)+(6-7)
explanation:
for ExpObjKey 1 we will have 2 +3
then 2 will have 4+5
and 3 will have 6+7.
Its more like a hierarchy.
I have tried lots of possible ways but no near the solution.
SELECT expObjkey, SYS_CONNECT_BY_PATH(exp1||' ' ||operator|| exp2||')', ' ( ') "Path"
FROM bpmn_expression
CONNECT BY PRIOR
exp1=expObjkey or exp2=expObjkey
start with expObjkey=1

I don't think you can do this with a hierarchical query, but I'd be interested to be proven wrong. You'd need to start at the bottom of the tree and work your way up to allow the substitutions to happen; but then there doesn't seem to be a way to combine the partial expressions that produces. It might be possible with recursive subquery factoring, but that isn't available until 11gR2.
On 10g you could use your own recursive function to generate what you need:
create or replace function get_expression(p_key bpmn_expression.expobjkey%type)
return varchar2 is
row bpmn_expression%rowtype;
begin
select * into row from bpmn_expression where expobjkey = p_key;
return '(' || get_expression(row.exp1)
|| row.operator || get_expression(row.exp2) || ')';
exception
when no_data_found then
return to_char(p_key);
end;
/
select get_expression(1) as expression from dual;
EXPRESSION
------------------------------
((4+5)+(6-7))
SQL Fiddle.
You can strip the outer parentheses with trim or regexp_replace if you want to, but they may be acceptable.
If you add another layer, say a record with values 7, 8, 9, '*', this would give:
EXPRESSION
------------------------------
((4+5)+(6-(8*9)))
SQL Fiddle.
But this isn't going to be very efficient against a large data set, since it will do a lot of single-row look-ups.

Related

Converting function instr from Oracle to PostgreSQL (sql only)

I am working on converting something from Oracle to PostgreSQL. In the Oracle file there is a function:
instr(string,substring,starting point,nth location)
Example:
instr(text, '$', 1, 3)
In PostgreSQL this does not exist, so I looked up an equivalent function (4 parameter is important).
I found:
The function strpos(str, sub) in Postgres is equivalent of instr(str, sub) in Oracle. Tried options via split_part (it didn't work out).
I need the same result only with standard functions Postgres (not own function).
Maybe someone will offer options, even redundant in code.
This may be done in pure SQL using string_to_array.
with tab(val) as (
select 'qwe$rty$123$456$78'
union all
select 'qwe$rty$123$'
union all
select '123$456$'
union all
select '123$456'
)
select
val
/*Oracle's signature: instr(string , substring [, position [, occurrence ] ])*/
, case
when
array_length(
string_to_array(substr(val /*string*/, 1 /*position*/), '$' /*substring*/),
1
) <= 3 /*occurrence*/
then 0
else
length(array_to_string((
string_to_array(substr(val /*string*/, 1 /*position*/), '$' /*substring*/)
)[:3/*occurrence*/],
'$'/*substring*/)
) + 1
end as instr
from tab
val
instr
qwe$rty$123$456$78
12
qwe$rty$123$
12
123$456$
0
123$456
0
Postgres: fiddle
Oracle: fiddle

DB2: Converting varchar to money

I have 2 varchar(64) values that are decimals in this case (say COLUMN1 and COLUMN2, both varchars, both decimal numbers(money)). I need to create a where clause where I say this:
COLUMN1 < COLUMN2
I believe I have to convert these 2 varchar columns to a different data types to compare them like that, but I'm not sure how to go about that. I tried a straight forward CAST:
CAST(COLUMN1 AS DECIMAL(9,2)) < CAST(COLUMN2 AS DECIMAL(9,2))
But I had to know that would be too easy. Any help is appreciated. Thanks!
You can create a UDF like this to check which values can't be cast to DECIMAL
CREATE OR REPLACE FUNCTION IS_DECIMAL(i VARCHAR(64)) RETURNS INTEGER
CONTAINS SQL
--ALLOW PARALLEL -- can use this on Db2 11.5 or above
NO EXTERNAL ACTION
DETERMINISTIC
BEGIN
DECLARE NOT_VALID CONDITION FOR SQLSTATE '22018';
DECLARE EXIT HANDLER FOR NOT_VALID RETURN 0;
RETURN CASE WHEN CAST(i AS DECIMAL(31,8)) IS NOT NULL THEN 1 END;
END
For example
CREATE TABLE S ( C VARCHAR(32) );
INSERT INTO S VALUES ( ' 123.45 '),('-00.12'),('£546'),('12,456.88');
SELECT C FROM S WHERE IS_DECIMAL(c) = 0;
would return
C
---------
£546
12,456.88
It really is that easy...this works fine...
select cast('10.15' as decimal(9,2)) - 1
from sysibm.sysdummy1;
You've got something besides a valid numerical character in your data..
And it's something besides leading or trailing whitespace...
Try the following...
select *
from table
where translate(column1, ' ','0123456789.')
<> ' '
or translate(column2, ' ','0123456789.')
<> ' '
That will show you the rows with alpha characters...
If the above does't return anything, then you've probably got a string with double decimal points or something...
You could use a regex to find those.
There is a built-in ability to do this without UDFs.
The xmlcast function below does "safe" casting between (var)char and decfloat (you may use as double or as decimal(X, Y) instead, if you want). It returns NULL if it's impossible to cast.
You may use such an expression twice in the WHERE clause.
SELECT
S
, xmlcast(xmlquery('if ($v castable as xs:decimal) then xs:decimal($v) else ()' passing S as "v") as decfloat) D
FROM (VALUES ( ' 123.45 '),('-00.12'),('£546'),('12,456.88')) T (S);
|S |D |
|---------|------------------------------------------|
| 123.45 |123.45 |
|-00.12 |-0.12 |
|£546 | |
|12,456.88| |

Postgres SQL - different results from LIKE query using OR vs ||

I have a table with an integer column. It has 12 records numbered 1000 to 1012. Remember, these are ints.
This query returns, as expected, 12 results:
select count(*) from proposals where qd_number::text like '%10%'
as does this:
SELECT COUNT(*) FROM "proposals" WHERE (lower(first_name) LIKE '%10%' OR qd_number::text LIKE '%10%' )
but this query returns 2 records:
SELECT COUNT(*) FROM "proposals" WHERE (lower(first_name) || ' ' || qd_number::text LIKE '%10%' )
which implies using || in concatenated where expressions is not equivalent to using OR. Is that correct or am I missing something else here?
You probably have nulls in first_name. For these records (lower(first_name) || ' ' || qd_number::text results in null, so you don't find the numbers any longer.
using || in concatenated where expressions is not equivalent to using ORIs that correct or am I missing something else here?
That is correct.
|| is the string concatenation operator in SQL, not the OR operator.

SQL Between Alphanumeric value

I have an Alphanumeric column in my db table. For my filter, I was using between to get the result filter value. Everything is okay. But, In some cases it misses some of the data's from filtering. Here are my samples,
Sample data
ACQPO14
002421
ACQPO8
ACQPO14
ACQPO19
DUMMY0001
Sql Query
SELECT po.No,
po.PoS
FROM PoDetails pod
INNER JOIN Pors po ON po.Id=PoD.PoId
WHERE po.No BETWEEN 'ACQPO1' AND 'ACQPO20'
For the above sample. the query returns only ACQPO14 and ACQPO19 NOT ACQPO8.
Any help to this issue will be appreciated.
Thanks
It makes sense as it is just text.
1 comes before 8 so, ordering in text (left to right) the db will disregard the last digit of ACQPO14 to compare it against ACQPO8. So ACQPO1 (4 removed) comes before ACQPO8 and ACQPO2 (0 removed) comes before ACQPO8 as well. So it gets filtered out by the between.
The only way for you to fix this is to parse the column by splitting it. EG: If ACQPO is a fixed-length prefix you can use some DBMS function (you haven't specified any) to trim that part and turn into a numeric format the rest. Then compare/filter by that numeric remainder.
This is how you would do this in Oracle
SELECT po.No, po.PoS
FROM PoDetails pod
INNER JOIN Pors po ON po.Id=PoD.PoId
WHERE SUBSTR(po.No, 1, 5) = 'ACPQO'
AND TO_NUMBER(SUBSTR(po.No, 6, 2)) >= 1
AND TO_NUMBER(SUBSTR(po.No, 6, 2)) <= 20;
First SUBSTR() is used to match the textual part of the value. Then the numeric part of the values is parsed into a number using TO_NUMBER() and made available for numeric comparison between 1 and 20. (Other databases would also have similar functions to do the same.)
If ACQPO is fixed then try below in SQL Server
SELECT po.No, po.PoS
FROM PoDetails pod
INNER JOIN Pors po ON po.Id=PoD.PoId
WHERE left(po.No,5) and
cast(substring(po.No,6,len(po.No)) as int)
BETWEEN cast(substring('ACQPO1',6,len(po.No)) as int) AND cast(substring('ACQPO20',6,len(po.No)) as int)
SELECT substring(data,6,len(data)),* FROM #Temp Where
left(data,5) ='ACQPO' And
cast(substring(data,6,len(data)) as int)
BETWEEN cast(substring('ACQPO1',6,len(data)) as int) AND cast(substring('ACQPO20',6,len(data)) as int)
FOR MYSQL:-
SELECT * FROM my_table WHERE
SUBSTR(seq_num, 1,3) = 'ABC' /* verifying prefix code */
AND
REPLACE(seq_num, 'ABC', '') >= 61440
AND
REPLACE(seq_num, 'ABC', '') <= 61807
OR
SELECT * FROM my_table WHERE
SUBSTR(seq_num, 1,3) = 'ABC' /* verifying prefix code */
AND
substr(seq_num, 4, length(seq_num)) >= 61440
AND
SUBSTR(seq_num, 4, LENGTH(seq_num)) <= 61807
works for (like) :
ABC61447,
ABC61448,
ABC61545,
...,
...,
ABC61807
Just use range 10 to 20 and it works!
SELECT po.No,
po.PoS
FROM PoDetails pod
INNER JOIN Pors po ON po.Id=PoD.PoId
WHERE po.No BETWEEN 'ACQPO10' AND 'ACQPO20'

how does one extract octets from an inet value in postgres sql?

I would like to convert an inet formatted IPv4 address into the integer components.
For example, turn '101.255.30.40' into oct1=101, oct2=255, oct3=30, and oct4=40.
There are regex expressions that should do this if I cast the inet as a varchar, but that seems inelegant. Is there a 1-line function for returning the nth octet of an inet?
select inet_to_octet('101.255.30.40', 4) as temp; -- returns temp=40?
I finally got an excellent answer from a co-worker...
For some flavors of sql, use "split_part" along with host(inet) to get the text field.
select split_part(host('101.255.30.40'::inet), '.', 1);
select split_part(host('101.255.30.40'::inet), '.', 2);
select split_part(host('101.255.30.40'::inet), '.', 3);
select split_part(host('101.255.30.40'::inet), '.', 4);
results in
101
255
30
40
If you want to get trickier and handle IPv6, use a mask to speed up the operation along with case statements to get the IP version:
select
(case
when family('101.255.30.40'::inet) = 4 then split_part(host(broadcast(set_masklen('101.255.30.40'::inet, 32))), '.', 4)::varchar
when family('101.255.30.40'::inet) = 6 then split_part(host(broadcast(set_masklen('101.255.30.40'::inet, 64))), ':', 4)::varchar
else null end)::varchar as octet4;
select
(case
when family('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet) = 4 then split_part(host(broadcast(set_masklen('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet, 32))), '.', 4)::varchar
when family('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet) = 6 then split_part(host(broadcast(set_masklen('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet, 64))), ':', 4)::varchar
else null end)::varchar as octet4;
results in
40
80b0
you can then add in a hex-to-int conversion into the case statement if you want to cast the IPv6 as a number instead.
Unless you want to try to contribute the function to the inet datatype, you'll need to rely on a string based version. Maybe put something like this (but with some error checking) into an SQL function for easy access?:
CREATE OR REPLACE FUNCTION extract_octet(inet, integer) RETURNS integer AS $$
SELECT ((regexp_split_to_array(host($1), E'\\.'))[$2])::int;
$$ LANGUAGE SQL;
select extract_octet(inet '192.26.22.2', 2)
Output: 26
Here are a couple of one-liners for the separate octets of an IPv4 address:
SELECT substring(host('1.2.3.4'::inet) FROM '^([0-9]+)\.[0-9]+\.[0-9]+\.[0-9]+$');
will return 1
SELECT substring(host('1.2.3.4'::inet) FROM '^[0-9]+\.([0-9]+)\.[0-9]+\.[0-9]+$');
will return 2
SELECT substring(host('1.2.3.4'::inet) FROM '^[0-9]+\.[0-9]+\.([0-9]+)\.[0-9]+$');
will return 3
SELECT substring(host('1.2.3.4'::inet) FROM '^[0-9]+\.[0-9]+\.[0-9]+\.([0-9]+)$');
will return 4