I'm looking for a better way of searching through numeric ranges in Oracle Text. I have a DB app that does a lot of GIS-type things, but we now want to add street range searching to it.
So I'd like to store the min and max values in a column, and search for a number within those values. I'm happy to go explore options, but I'd like some pointers on where to head. Does anyone have any suggestions for me?
EDIT: we're just trying to make address lookups easier. Text on the address parts has been a huge success, but we want to store street ranges instead of every individual house number.
So, if I searched for "11 high street", I'd expect a match if high street had a range of 1 to 1000. I'd also like some options that I can use if I searched for "flat 1 11 high street" too though. I expect that I will have to do some jiggery with the input in these cases, I just want to know what kind of tools there are that I could try working with.
My suggestion is to make standard length string field for storing building numbers, create index on this field and then use between for search.
Something like this format:
NNNNNNCCCCBBBB
where:
NNNNNN - left-padded house number;
CCCC - left-padded character (like 'A' in '11A');
BBBB - left-padded building number
Under 'left-padded' I mean "filled with some symbol to standard length at left side", see for example result of select lpad('11',5,'X') from dual; query.
E.g. suppose, you have "11A high street building 5" address and choose '%' as filling symbol. When converted to proposed format it looks like '%%%11%%%A%%%' and 'high street' stored at separated field(s).
Next is query example for selecting all houses between 1 and 1000:
with address_list as (
select '%%%11%%%A%%%%' bnum from dual union all
select '%1001%%%A%%%%' bnum from dual union all
select '%%%%1%%%A%%%%' bnum from dual union all
select '%%%%1%%%%%%%%' bnum from dual union all
select '%%321%%%A%%%%' bnum from dual union all
select '%1000%%%A%%%%' bnum from dual union all
select '%1000%%QQ%%12' bnum from dual
)
select * from address_list
where
-- from '1 high street'
bnum >= '%%%%1%%%%%%%%'
and
-- less then '1001 high street'
bnum < '%1001%%%%%%%%'
order by
bnum
In real case is better to use chr(1) or any other unprintable symbol as symbol for padding.
Another thing is to build only function-based index for search without real field storage.
Anything wrong with
WHERE <number> BETWEEN minColumn AND maxColumn
Related
How can I find if the fifth position is a letter and thus not a number using Oracle ?
My last try was using the following statement:
REGEXP_LIKE (table_column, '([abcdefghijklmnopqrstuvxyz])');
Perhaps you'd rather check whether 5th position contains a number (which means that it is not something else), i.e. do the opposite of what you're doing now.
Why? Because a "letter" isn't only ASCII; have a look at the 4th row in my example - it contains Croatian characters and these aren't between [a-z] (nor [A-Z]).
SQL> with test (col) as
2 (select 'abc_3def' from dual union all
3 select 'A435D887' from dual union all
4 select '!#$%&/()' from dual union all
5 select 'ASDĐŠŽĆČ' from dual
6 )
7 select col,
8 case when regexp_like(substr(col, 5, 1), '\d+') then 'number'
9 else 'not a number'
10 end result
11 from test;
COL RESULT
------------- ------------
abc_3def number
A435D887 not a number
!#$%&/() not a number
ASDĐŠŽĆČ not a number
SQL>
Anchor to the start of the string else you may get unexpected results. This works, but remove the caret (start of string anchor) and it returns 'TRUE'! Note it uses the case-insensitive flag of 'i'.
select 'TRUE'
from dual
where regexp_like('abcd4fg', '^.{4}[A-Z]', 'i');
Yet another way to do it:
regexp_like(table_column, '^....[[:alpha:]]')
Using the character class [[:alpha:]] will pick up all letters upper case, lower case, accented and etc. but will ignore numbers, punctuation and white space characters.
If what you care about is that the character is not a number, then use
not regexp_like(table_column, '^....[[:digit:]]')
or
not regexp_like(table_column, '^....\d')
Try:
REGEXP_LIKE (table_column, '^....[a-z]')
Or:
SUBSTR (table_column, 5, 1 ) BETWEEN 'a' AND 'z'
I'm new to Oracle and recently ran into the following query. I'm trying to understand what it's doing and hopefully rewrite it to optimize it. In this example, :NameList would be a comma separated list (like: "Bob,Bill,Fred") and then :N_NameList would be the number of tokens (in above example, 3)
SELECT ... FROM
(
SELECT
REGEXP_SUBSTR(:NameList,'[^,]+',1,LEVEL, 'i') Name
FROM DUAL CONNECT BY LEVEL <= :N_NameList
) x
INNER JOIN PEOPLE ppl
ON ppl.Name LIKE x.Name
...
From what I can tell, it expands out the delimited list into unique rows and then joins it with the following tables for each name, but I'm not sure if that's all it's doing. If that is the case, is there a better way to accomplish this?
You could try this instead:
select ...
from people ppl
where instr (','||:NameList||',',
','||ppl.name||',') > 0;
is there a better way to accomplish this?
Well, you could get rid of N_NameList because you can easily count number of tokens. This doesn't mean that it is a better way, it's just a different option. To be honest, it is probably slower option than yours as I have to calculate something that you entered as a parameter.
As this example is based on SQLPlus, I've used & instead of : for substitution variables. && means that it'll "remember" previously entered value (otherwise, I should type NameList twice.
Your current query:
SQL> select regexp_substr('&namelist', '[^,]+', 1, level, 'i') name
2 from dual
3 connect by level <= &n_namelist;
Enter value for namelist: Bob,Bill,Fred
Enter value for n_namelist: 3
Bob
Bill
Fred
Calculated N_NameList (using REGEXP_COUNT):
SQL> select regexp_substr('&&namelist', '[^,]+', 1, level, 'i') name
2 from dual
3 connect by level <= regexp_count('&&namelist', ',') + 1;
Enter value for namelist: Bob,Bill,Fred
Bob
Bill
Fred
For Sample purpose lets create a table with below schema and fill some sample values
CREATE TABLE games(ID INT ,Name VARCHAR(20));
INSERT INTO games(ID,Name) VALUES (2008,'Beijing');
INSERT INTO games(ID,Name) VALUES (2012,'London');
INSERT INTO games(ID,Name) VALUES (2012,12);
INSERT INTO games(ID,Name) VALUES (2012,654);
Output:
ID NAME
2008 Beijing
2012 London
2012 12
2012 654
In the above table we have both number and string data in the name column lets write a query that uses the REGX to filter only the numerical rows
SELECT TO_NUMBER(Name)as Trimmed FROM games where REGEXP_LIKE(Name, '(?<=\s|^)\d+(?=\s|$)', '')
Output:
TRIMMED
12
654
Now here is the problem if write a where clause of getting values greater than 12 from the above result it throws invalid number.
Select * from (SELECT TO_NUMBER(Name)as Trimmed FROM games where REGEXP_LIKE(Name, '(?<=\s|^)\d+(?=\s|$)', '')) T1 where T1.Trimmed >12 ;
I found this is how the oracle query planning works but is there any other way i can achieve this
This will work:
Select * from
(SELECT Name as Trimmed
FROM games where REGEXP_LIKE(Name, '(?<=\s|^)\d+(?=\s|$)', '')) T1
where to_number(T1.Trimmed) >12 ;
Unfortunately you need a subquery. It can't be done with one where.
This can be done in a single query:
with
inputs as (
select 2008 as id, 'Beijing' as name from dual union all
select 2012 , 'London' from dual union all
select 2012 , '12' from dual union all
select 2012 , '654' from dual
)
select id, name
from inputs
where translate(name, 'a0123456789', 'a') is null
and to_number(regexp_replace(name, '[^[:digit:]]', '')) > 12
;
ID NAME
---------- -------
2012 654
1 row selected.
regexp_replace removes all the characters except digits, so the test can be done regardless of what the name is. If there are no digits in the name, the result is NULL, which can be converted to number (it is still null).
The translate solution for testing for "all-digits" is more efficient than using regexp_like. The odd-looking 'a' in translate is needed due to an oddity in the translate function itself (see the documentation). This test is not needed if all the names are either "all letters" or "all digits" (if "all letters", the second test with regexp_replace would suffice); but the first test is needed if names like "Sydney 2000" are possible in the name column.
Using LPAD requirement, all of my expressions are characters.
Why am I getting an ORA-01722: invalid number message?
I want to left pad my varchar2 value with leading zeroes.
All my airpor_codes are up to three bytes and I want to left pad with leading zeros, if necessary.
In this example, I want 0777 and 0LAX.
/* This works */
with numeric_airport_code
as (select '777' as airport from dual)
select airport,
to_char(airport,'0000') as to_char_padding,
lpad(airport,4,'000') as lpadding
from numeric_airport_code;
/* This gives invalid number error */
with alpha_airport_code
as (select 'LAX' as airport from dual)
select airport,
to_char(airport,'000') as to_char_padding,
lpad(airport,4,'000') as lpadding
from alpha_airport_code;
The lpad isn't the problem, it's the to_char(). In the second query you're doing:
to_char('LAX', '000')
which is trying to to an implicit conversion to number first:
to_char(to_number('LAX'), '000')
... and to_number('LAX') of course gets ORA-01722. It doesn't make sense to call to_char() for something that is already a string. At best you'll get unnecessary conversions, but often - as here - those are implicit and/or will fail.
If you only use lpad it does what you said you want:
with airport_code as (
select '777' as airport from dual
union all select 'LAX' as airport from dual
)
select airport,
lpad(airport, 4, '0') as lpadding
from airport_code;
AIR LPAD
--- ----
777 0777
LAX 0LAX
Also notice that you only need a single zero in the third argument.
Firstly, I greatly appreciate any feedback that anyone can offer. I am using Oracle SQL Developer, Version 4.0.2.15, Build 15.21.
I know and understand that many, many similar questions have been asked, as I've searched around on stackoverflow as well as the rest of the internet. However, the corresponding answers are either too vague or too extravagant, and attempt to do things that are way over my head and not what I am trying to accomplish. I am extremely new to SQL and haven't seriously done any coding since I did Java about 12 years ago. So please understand that something simple to you, is not so simple and obvious to me.
My bare-bones endstate that I am shooting for is taking a pre-existing Oracle Table Column, which is called 'service_level', that has parameters of 1-3, and making them A-C (where A=1, B=2, C=3). The reason for this is that I have an ArcGIS gdB featureclass that has a corresponding column, called 'MaintServi', with the parameters of A-C. I am going to join them using ArcToolbox once I have converted/replaced the 1-3 to A-C, and have exported them from Oracle into an Arc gdB as another table. The reason being is that the featureclass (obviously) has geometry, but this particular Oracle table does not.
From what I have gathered I know (or think) I will need to use something like:
chr(ord('a') + 3)
^ Where I will need to use/call upon the chr/ord functions. However, due to my inexperience, I cannot think of how to properly call this without getting an error. Below is what I have for my query thus far (but without chr/ord). I just need to figure out how to correctly insert it into my query to achieve the desired results.
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 );
Thanks again and hopefully I have complied with the posting rules of stackoverflow.
# Mark J. Bobak -
When implementing his ideas I get either this (Like I said, i'm not sure how to insert it properly without receiving an error)
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 )
UNION ALL
WITH service_level as (select 1 service_level from dual
union all
select 2 service_level from dual union all
select 3 service_level from dual)
select decode(service_level,1,'A',2,'B',3,'C') from service_level;
I receive the following error:
*ORA-32034: unsupported use of WITH clause
32034. 00000 - "unsupported use of WITH clause"
*Cause: Inproper use of WITH clause because one of the following two reasons
1. nesting of WITH clause within WITH clause not supported yet
2. For a set query, WITH clause can't be specified for a branch.
3. WITH clause can't sepecified within parentheses.
Action: correct query and retry
Error at Line: 14 Column: 25
Or I receive an output of only 3 rows (A, B, C) if I run the query separately - sorry I don't have enough reputation to post the image yet.
You can use the DECODE() function. Something like this should work:
with list_of_digits as (select 1 col_a from dual
union all
select 2 col_a from dual
union all
select 3 col_a from dual
union all
select 4 col_a from dual)
select decode(col_a,1,'A',2,'B',3,'C','Other') from list_of_digits;
Using your query, try this:
WITH service_level as (select 1 service_level from dual
union all
select 2 service_level from dual union all
select 3 service_level from dual)
select decode(service_level,1,'A',2,'B',3,'C') from service_level
union all
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 );
ord isn't an Oracle function. The equivalent Oracle function is ASCII. However, even substituting in the correct function, I don't see how that gets you what you want.
It seems most likely that you just want to add a column (I'd use case to translate the values):
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID,
case service_level
when '1' then 'a'
when '2' then 'b'
when '3' then 'c'
end as service_level_alpha
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 );
If you want to return this column as service_level, then you'll need to return the full list of columns instead of using the asterisk.
Since this is a straight-forward character swap, you could use translate to really streamline the operation: translate(service_level,'123','abc'). However, I vastly prefer case over either decode or translate for readability