VARCHAR column with numeric and non-numeric data to be filtered by numeric comparasion in WHERE clause - sql

DB: Oracle (But I'm looking for generic solution in ANSI SQL)
SELECT *
FROM TABLE1 A INNER JOIN TABLE2 B ON A.FIELD_KEY = B.FIELD_KEY
WHERE TO_NUMBER (FIELD_VALUE) < 10
I've a table (TABLE1) which stores 'Value' and 'Frequency'. Now data in the FIELD_VALUE column can be numeric as well as non-numeric. The datatype of this column is VARCHAR2. I want to filter this table where Value < 10 (say).
I understand that 'Where TO_NUMBER(Value) < 10' won't work because Value column contains non numeric data as well.
But, I'm joining table TABLE1 with table TABLE2 Such that post join only numeric values are returned in the resultset in 'Value' column and then I'm applying 'Where TO_NUMBER(Value) < 10'on this already filtered resultset. I'm expecting that since the resultset is already filtered and contains only numeric data in the 'Value' column I should be able to filter the resultset further with 'Where TO_NUMBER(Value) < 10' caluse but that ain't happening due to the fact that Oracle optimizer changes the order of my where clause and join condition thus I'm getting 'ORA-01722: invalid number' error.
Solution that works for me is:
WITH BASE_QUERY
AS (SELECT *
FROM TABLE1 A INNER JOIN TABLE2 B ON A.FIELD_KEY = B.FIELD_KEY)
SELECT *
FROM BASE_QUERY A
INNER JOIN
BASE_QUERY B
ON A.VALUE = B.VALUE AND TO_NUMBER (FIELD_VALUE) < 10
But I've to do a self-join here which is costly and unnecessary. Plus I'm not sure if this solution will work always. I mean, if Oracle changes the execution plan and executes TO_NUMBER (FIELD_VALUE) < 10 before joining the tables then the query might again fail with same `ORA-01722: invalid number ‘error.
Questions:
Is my solution guaranteed to work always?
Is my solution reasonable performance wise?
Is there a better way to do this?

But I've to do a self-join here which is costly and unnecessary.
So don't do the join at all. All you need is the rows which have the FIELD_VALUE as only DIGIT. You want to ignore the ALPHANUMERIC values. So, filter out only the digits.
For example,
TRANSLATE and REPLACE
SQL> WITH DATA AS(
2 SELECT 'mix-'||LEVEL str FROM dual CONNECT BY LEVEL <=10
3 UNION ALL
4 SELECT to_char(LEVEL) FROM dual CONNECT BY LEVEL <=10
5 )
6 SELECT str FROM DATA
7 WHERE REPLACE(translate(str, '0123456789',' '), ' ') IS NULL
8 /
STR
--------------------------------------------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
SQL>
REGEXP_LIKE
SQL> WITH DATA AS(
2 SELECT 'mix-'||LEVEL str FROM dual CONNECT BY LEVEL <=10
3 UNION ALL
4 SELECT to_char(LEVEL) FROM dual CONNECT BY LEVEL <=10
5 )
6 SELECT str FROM DATA
7 WHERE REGEXP_LIKE(str, '^[[:digit:]]+')
8 /
STR
--------------------------------------------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
SQL>
Personally, I would go with TRANSLATE and REPLACE, since REGEXP is still quite resource consuming.

If you can guarantee that content in field_value starting with a digit will be strictly numeric, you can employ a subquery that filters your table to leave only records that have numeric content in the field_value column:
select *
from (
select t1.*
from table t1
where t1.field_value >= '0'
and t1.field_value <= chr(ascii('9')+1) -- ':'; invariant to charset and encoding
) t
where to_number(t.field_value) < 10
;
I'd rather advise you to use one of Lalit Kumar B's solution which appear to be more robust ( think of a relaxation of the 'infer numeric from initial digit' policy in the future ).

Related

Why does this sql snippet return 8 or 1 always?

What is the result of:
WITH Tbl AS (SELECT 5 AS A UNION SELECT 6 AS A)
SELECT COUNT(*) AS Tbl FROM Tbl AS A, Tbl AS B, Tbl AS C;
I know the result is supposed to be 8 but I don't know why. Also when I change both values (the 5 or 6) to the same thing it returns a table with the value 1 instead of 8 but all other instances it returns 8 no matter what numbers if they are different. I tested it out with an online sql executor.
Here is what the query does:
the common table expression (the subquery within the with clause) generates a derived table made of two rows
then, in the outer query, the from clause generates a cartesian product of this resultset twice: that's a total of 8 rows (2 * 2 * 2)
the select clause counts the number of rows - that's 8
The content of the rows in the with clause does not matter: this 5 and 6 could very well be foo and bar, or null and null, the result would be the same.
What makes a difference is the number of rows that the with clause generates. If it was generating just one row, you would get 1 as a result (1 * 1 * 1). If it was generating 3 rows, you would get 27 - and so on.
This expression:
WITH Tbl AS (SELECT 5 AS A UNION SELECT 6 AS A)
creates a (derived) table with two rows.
This expression:
WITH Tbl AS (SELECT 5 AS A UNION SELECT 5 AS A)
creates a (derived) table with one row, because UNION removes duplicates.
The rest of the query just counts the number of rows in the 3-way Cartesian product, which is either 111 or 222.

How to return the rows between 20th and 30th in Oracle Sql [duplicate]

This question already has answers here:
Oracle SQL: Filtering by ROWNUM not returning results when it should
(2 answers)
Closed 4 years ago.
so I have a large table that I'd like to output, however, I only want to see the rows between 20 and 30.
I tried
select col1, col2
from table
where rownum<= 30 and rownum>= 20;
but sql gave an error
I also tried --where rownum between 20 and 30
it also did not work.
so whats the best way to do this?
SELECT *
FROM T
ORDER BY I
OFFSET 20 ROWS --skips 20 rows
FETCH NEXT 10 ROWS ONLY --takes 10 rows
This shows only rows 21 to 30. Take care here that you need to sort the data, otherwise you may get different results every time.
See also here in the documentation.
Addendum: As in the possible duplicate link shown, your problem here is that there can't be a row with number 20 if there is no row with number 19. That's why the rownum-approach works to take only the first x records, but when you need to skip records you need a workaround by selecting the rownum in a subquery or using offset ... fetch
Example for a approach with using rownum (for lower oracle versions or whatever):
with testtab as (
select 'a' as "COL1" from dual
union all select 'b' from dual
union all select 'c' from dual
union all select 'd' from dual
union all select 'e' from dual
)
select * from
(select rownum as "ROWNR", testtab.* from testtab) tabWithRownum
where tabWithRownum.ROWNR > 2 and tabWithRownum.ROWNR < 4;
--returns only rownr 3, col1 'c'
Whenever you use rownum, it counts the rows that your query returns. SO if you are trying to filter by selecting all records between rownum 20 and 30, that is only 10 rows, so 20 and 30 dont exist. You can however, use WITH (whatever you want to name it) as and then wrap your query and rename your rownum column. This way you are selecting from your select. Example.
with T as (
select requestor, request_id, program, rownum as "ROW_NUM"
from fnd_conc_req_summary_v where recalc_parameters='N')
select * from T where row_num between 20 and 30;

Multiplying records in SQL based on combinations of two columns

I know that title's a little bit vague.
Here's what I'm trying to accomplish:
I have a table with two particular rows, some_id and some_string.
some_id some_string
4 'a'
7 'j'
12 'ee'
I would like to create entries for all combinations of these two columns, so that I end up with:
some_id some_string
4 'a'
4 'j'
4 'ee'
7 'a'
7 'j'
7 'ee'
12 'a'
12 'j'
12 'ee'
How would I approach this? I happen to be using Postgres, but I can understand general instructions.
Just select from both of them:
SELECT t1.some_id, t2.some_string
FROM table1 t1, table2 t2
This will combine every value from t1 with every value from t2 resulting in a total amount of records equal to multiplying the total amount of records in both tables. You should add an ORDER BY clause if you want to order by t1.some_id as it is in your example
Here's one way to do it:
select distinct s.some_id, s2.some_string
from yourtable s, yourtable s2
SQL Fiddle Demo

How to check any missing number from a series of numbers?

I am doing a project creating an admission system for a college; the technologies are Java and Oracle.
In one of the tables, pre-generated serial numbers are stored. Later, against those serial numbers, the applicant's form data will be entered. My requirement is that when the entry process is completed I will have to generate a Lot wise report. If during feeding pre-generated serial numbers any sequence numbers went missing.
For example, say in a table, the sequence numbers are 7001, 7002, 7004, 7005, 7006, 7010.
From the above series it is clear that from 7001 to 7010 the numbers missing are 7003, 7007, 7008 and 7009
Is there any DBMS function available in Oracle to find out these numbers or if any stored procedure may fulfill my purpose then please suggest an algorithm.
I can find some techniques in Java but for speed I want to find the solution in Oracle.
A solution without hardcoding the 9:
select min_a - 1 + level
from ( select min(a) min_a
, max(a) max_a
from test1
)
connect by level <= max_a - min_a + 1
minus
select a
from test1
Results:
MIN_A-1+LEVEL
-------------
7003
7007
7008
7009
4 rows selected.
Try this:
SELECT t1.SequenceNumber + 1 AS "From",
MIN(t2.SequenceNumber) - 1 AS "To"
FROM MyTable t1
JOIN MyTable t2 ON t1.SequenceNumber < t2.SequenceNumber
GROUP BY t1.SequenceNumber
HAVING t1.SequenceNumber + 1 < MIN(t2.SequenceNumber)
Here is the result for the sequence 7001, 7002, 7004, 7005, 7006, 7010:
From To
7003 7003
7007 7009
This worked but selects the first sequence (start value) since it doesn't have predecessor. Tested in SQL Server but should work in Oracle
SELECT
s.sequence FROM seqs s
WHERE
s.sequence - (SELECT sequence FROM seqs WHERE sequence = s.sequence-1) IS NULL
Here is a test result
Table
-------------
7000
7001
7004
7005
7007
7008
Result
----------
7000
7004
7007
To get unassigned sequence, just do value[i] - 1 where i is greater first row e.g. (7004 - 1 = 7003 and 7007 - 1 = 7006) which are available sequences
I think you can improve on this simple query
This works on postgres >= 8.4. With some slight modifications to the CTE-syntax it could be made to work for oracle and microsoft, too.
-- EXPLAIN ANALYZE
WITH missing AS (
WITH RECURSIVE fullhouse AS (
SELECT MIN(num)+1 as num
FROM numbers n0
UNION ALL SELECT 1+ fh0.num AS num
FROM fullhouse fh0
WHERE EXISTS (
SELECT * FROM numbers ex
WHERE ex.num > fh0.num
)
)
SELECT * FROM fullhouse fh1
EXCEPT ( SELECT num FROM numbers nx)
)
SELECT * FROM missing;
Here's a solution that:
Relies on Oracle's LAG function
Does not require knowledge of the complete sequence (but thus doesn't detect if very first or last numbers in sequence were missed)
Lists the values surrounding the missing lists of numbers
Lists the missing lists of numbers as contiguous groups (perhaps convenient for reporting)
Tragically fails for very large lists of missing numbers, due to listagg limitations
SQL:
WITH MentionedValues /*this would just be your actual table, only defined here to provide data for this example */
AS (SELECT *
FROM ( SELECT LEVEL + 7000 seqnum
FROM DUAL
CONNECT BY LEVEL <= 10000)
WHERE seqnum NOT IN (7003,7007,7008,7009)--omit those four per example
),
Ranges /*identifies all ranges between adjacent rows*/
AS (SELECT seqnum AS seqnum_curr,
LAG (seqnum, 1) OVER (ORDER BY seqnum) AS seqnum_prev,
seqnum - (LAG (seqnum, 1) OVER (ORDER BY seqnum)) AS diff
FROM MentionedValues)
SELECT Ranges.*,
( SELECT LISTAGG (Ranges.seqnum_prev + LEVEL, ',') WITHIN GROUP (ORDER BY 1)
FROM DUAL
CONNECT BY LEVEL < Ranges.diff) "MissingValues" /*count from lower seqnum+1 up to lower_seqnum+(diff-1)*/
FROM Ranges
WHERE diff != 1 /*ignore when diff=1 because that means the numers are sequential without skipping any*/
;
Output:
SEQNUM_CURR SEQNUM_PREV DIFF MissingValues
7004 7002 2 "7003"
7010 7006 4 "7007,7008,7009"
One simple way to get your answer for your scenario is this:
create table test1 ( a number(9,0));
insert into test1 values (7001);
insert into test1 values (7002);
insert into test1 values (7004);
insert into test1 values (7005);
insert into test1 values (7006);
insert into test1 values (7010);
commit;
select n.n from (select ROWNUM + 7001 as n from dual connect by level <= 9) n
left join test1 t on n.n = t.a where t.a is null;
The select will give you the answer from your example. This only makes sense, if you know in advance in which range your numbers are and the range should not too big. The first number must be the offset in the ROWNUM part and the length of the sequence is the limit to the level in the connect by part.
I would have suggested connect by level as Stefan has done, however, you can't use a sub-query in this statement, which means that it isn't really suitable for you as you need to know what the maximum and minimum values of your sequence are.
I would suggest a pipe-lined table function might be the best way to generate the numbers you need to do the join. In order for this to work you'd need an object in your database to return the values to:
create or replace type t_num_array as table of number;
Then the function:
create or replace function generate_serial_nos return t_num_array pipelined is
l_first number;
l_last number;
begin
select min(serial_no), max_serial_no)
into l_first, l_last
from my_table
;
for i in l_first .. l_last loop
pipe row(i);
end loop;
return;
end generate_serial_nos;
/
Using this function the following would return a list of serial numbers, between the minimum and maximum.
select * from table(generate_serial_nos);
Which means that your query to find out which serial numbers are missing becomes:
select serial_no
from ( select *
from table(generate_serial_nos)
) generator
left outer join my_table actual
on generator.column_value = actual.serial_no
where actual.serial_no is null
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
MINUS
SELECT a FROM test1 ;
Improved query is:
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
MINUS
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL < (SELECT Min(a) FROM test1)
MINUS
SELECT a FROM test1;
Note: a is column in which we find missing value.
Try with a subquery:
SELECT A.EMPNO + 1 AS MissingEmpNo
FROM tblEmpMaster AS A
WHERE A.EMPNO + 1 NOT IN (SELECT EMPNO FROM tblEmpMaster)
select A.ID + 1 As ID
From [Missing] As A
Where A.ID + 1 Not IN (Select ID from [Missing])
And A.ID < n
Data: ID
1
2
5
7
Result: ID
3
4
6

generate_series() equivalent in DB2

I'm trying to search the DB2 equivalent of generate_series() (the PostgreSQL-way of generating rows). I obviously don't want to hard-code the rows with a VALUES statement.
select * from generate_series(2,4);
generate_series
-----------------
2
3
4
(3 rows)
The where clause needs to be a bit more explicit about the bounds of the recursion in order for DB2 to suppress the warning. Here's a slightly adjusted version that does not trigger the warning:
with dummy(id) as (
select 2 from SYSIBM.SYSDUMMY1
union all
select id + 1 from dummy where id < 4
)
select id from dummy
I managed to write a recursive query that fits :
with dummy(id) as (
select 2 from SYSIBM.SYSDUMMY1
union all
select id + 1 from dummy where id + 1 between 2 and 4
)
select id from dummy
The query can be adapted to whatever for(;;) you can dream of.