Oracle select similar values [closed] - sql

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
i have a database table with a lot of values like this: 340.13 and 232.89.
Now i want to select the value with the best match with a comparison value.
Is this possible without great effort?

This will match values that are within +-10% of the search value and, if there are multiple values, will find the closest match by absolute difference.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TABLE_NAME ( VALUE ) AS
SELECT 340.13 FROM DUAL UNION ALL
SELECT 232.89 FROM DUAL UNION ALL
SELECT 224.73 FROM DUAL UNION ALL
SELECT 100.00 FROM DUAL;
Query 1:
WITH search_values ( search_value ) AS (
SELECT 330 FROM DUAL UNION ALL
SELECT 230 FROM DUAL
)
SELECT search_value,
value
FROM (
SELECT search_value,
value,
RANK() OVER ( PARTITION BY Search_value
ORDER BY ABS( value - search_value ) ) AS rnk
FROM table_name t
INNER JOIN
search_values v
ON ( t.value BETWEEN search_value * 0.9 AND search_value * 1.1 )
)
WHERE Rnk = 1
Results:
| SEARCH_VALUE | VALUE |
|--------------|--------|
| 230 | 232.89 |
| 330 | 340.13 |

This is a pretty basic and common task so here is the general approach.
First you need to decide on "best-match-criteria". Basically it as a function of value stored in row and input value. So you can implement this function and evaluate it calling something like MATCH_RATING(COLUMN, :value) for each row. Now that you have this rating for every row, you can sort rows in any way you like and filter the most fitting one (ROWNUM is great for this as are analytic functions like RANK or ROW_NUMBER).
SELECT *
FROM (
SELECT VALUE,
MATCH_RATING(VALUE, :input_value) RATING
FROM YOUR_TABLE
ORDER BY RATING DESC)
WHERE ROWNUM = 1
Then a good idea is to check whether your chosen criteria are implemented in language because if they are, using SQL features will surely be bettter performance-wise.
For example, if distance between two numbers is the only thing that concerns you, SQL will look something like this.
SELECT VALUE
FROM (
SELECT VALUE,
ABS(VALUE - :input_value) DISTANCE
FROM YOUR_TABLE
ORDER BY DISTANCE)
WHERE ROWNUM = 1
If your function assumes 0 value on some interval meaning some rows should never get into your resultset then you should also use WHERE clause filtering useless rows (WHERE MATCH_RATING(COLUMN, :value) > 0).
Back to our distance example: let's accept distance not more than 5% of input value.
SELECT VALUE
FROM (
SELECT VALUE,
ABS(VALUE - :input_value) DISTANCE
FROM YOUR_TABLE
WHERE VALUE BETWEEN 0.95 * :input_value AND 1.05 * :input_value
ORDER BY DISTANCE)
WHERE ROWNUM = 1
By the way, index on YOUR_TABLE.VALUE will surely be helpful for this example.

Related

Oracle - split single row into multiple rows based on column value [duplicate]

This question already has answers here:
How to unfold the results of an Oracle query based on the value of a column
(4 answers)
Closed 6 years ago.
I have a table like this
parent_item child_item quantity
A B 2
A C 3
B E 1
B F 2
And would like to split this in multiple lines based on the quantity
parent_item child_item quantity
A B 1
A B 1
A C 1
A C 1
A C 1
B E 1
B F 1
B F 1
The column quantity (1) is not really necessary.
I was able to generate something with the help of connect by / level, but for large tables it's very very slow.I'm not really familiar with connect by / level, but this seemed to work, although I can't really explain:
select distinct parent_item, level LEVEL_TAG, child_item, level||quantity
FROM table
CONNECT BY quantity>=level
order by 1 asc;
I found similar questions, but in most cases topicstarter want's to split a delimited column value in multiple lines (Oracle - split single row into multiple rows)
What's the most performant method to solve this?
Thanks
Use a recursive sub-query factoring clause:
WITH split ( parent_item, child_item, lvl, quantity ) AS (
SELECT parent_item, child_item, 1, quantity
FROM your_table
UNION ALL
SELECT parent_item, child_item, lvl + 1, quantity
FROM split
WHERE lvl < quantity
)
SELECT parent_item, child_item, 1 As quantity
FROM split;
Or you can use a correlated hierarchical query:
SELECT t.parent_item, t.child_item, 1 AS quantity
FROM your_table t,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= t.quantity
)
AS SYS.ODCINUMBERLIST
)
) l;
As for which is more performant - try benchmarking the different solutions as we cannot tell you what will be more performant on your system.

Joining next Sequential Row

I am planing an SQL Statement right now and would need someone to look over my thougts.
This is my Table:
id stat period
--- ------- --------
1 10 1/1/2008
2 25 2/1/2008
3 5 3/1/2008
4 15 4/1/2008
5 30 5/1/2008
6 9 6/1/2008
7 22 7/1/2008
8 29 8/1/2008
Create Table
CREATE TABLE tbstats
(
id INT IDENTITY(1, 1) PRIMARY KEY,
stat INT NOT NULL,
period DATETIME NOT NULL
)
go
INSERT INTO tbstats
(stat,period)
SELECT 10,CONVERT(DATETIME, '20080101')
UNION ALL
SELECT 25,CONVERT(DATETIME, '20080102')
UNION ALL
SELECT 5,CONVERT(DATETIME, '20080103')
UNION ALL
SELECT 15,CONVERT(DATETIME, '20080104')
UNION ALL
SELECT 30,CONVERT(DATETIME, '20080105')
UNION ALL
SELECT 9,CONVERT(DATETIME, '20080106')
UNION ALL
SELECT 22,CONVERT(DATETIME, '20080107')
UNION ALL
SELECT 29,CONVERT(DATETIME, '20080108')
go
I want to calculate the difference between each statistic and the next, and then calculate the mean value of the 'gaps.'
Thougts:
I need to join each record with it's subsequent row. I can do that using the ever flexible joining syntax, thanks to the fact that I know the id field is an integer sequence with no gaps.
By aliasing the table I could incorporate it into the SQL query twice, then join them together in a staggered fashion by adding 1 to the id of the first aliased table. The first record in the table has an id of 1. 1 + 1 = 2 so it should join on the row with id of 2 in the second aliased table. And so on.
Now I would simply subtract one from the other.
Then I would use the ABS function to ensure that I always get positive integers as a result of the subtraction regardless of which side of the expression is the higher figure.
Is there an easier way to achieve what I want?
The lead analytic function should do the trick:
SELECT period, stat, stat - LEAD(stat) OVER (ORDER BY period) AS gap
FROM tbstats
The average value of the gaps can be done by calculating the difference between the first value and the last value and dividing by one less than the number of elements:
select sum(case when seqnum = num then stat else - stat end) / (max(num) - 1);
from (select period, row_number() over (order by period) as seqnum,
count(*) over () as num
from tbstats
) t
where seqnum = num or seqnum = 1;
Of course, you can also do the calculation using lead(), but this will also work in SQL Server 2005 and 2008.
By using Join also you achieve this
SELECT t1.period,
t1.stat,
t1.stat - t2.stat gap
FROM #tbstats t1
LEFT JOIN #tbstats t2
ON t1.id + 1 = t2.id
To calculate the difference between each statistic and the next, LEAD() and LAG() may be the simplest option. You provide an ORDER BY, and LEAD(something) returns the next something and LAG(something) returns the previous something in the given order.
select
x.id thisStatId,
LAG(x.id) OVER (ORDER BY x.id) lastStatId,
x.stat thisStatValue,
LAG(x.stat) OVER (ORDER BY x.id) lastStatValue,
x.stat - LAG(x.stat) OVER (ORDER BY x.id) diff
from tbStats x

Unable to fetch modify data in select query [duplicate]

This question already has answers here:
Increased amount of each row in sql
(4 answers)
Closed 8 years ago.
I did not get a good answer for my previous question. In this question I provide more thing but still not get proper result.. anyone help me...
1st row amount=1200,
2nd row amount=1320(1200+120),
3rd row amount=1452(1320+132)
Logic is 10% add with previous amount
;WITH nums AS
(SELECT 1 AS RowNum, 1200 AS value
)
SELECT RowNum, Value
FROM nums
Results:
Sno - Name- Amount
1 - A - 1200
Now I want result like this..
Sno - Name- Amount
1 - A - 1200
2 - A - 1320
3 - A - 1452
Can anybody help me I'm not find any logic for that in same query no external table create or implement
I come from Oracle background; but I hope this helps as it works for me in Oracle:
WITH nums(rn, value) AS
(
SELECT ROWNUM rn, 1200 AS value FROM DUAL
UNION ALL
SELECT rn + 1 rn, round(value * 1.1) FROM nums
where rn < 100
)
SELECT RowNum, Value
FROM nums
It generates up to 100 rows.
You need to use Recursive CTE like this :
WITH nums(rn, value) AS
(
SELECT 1 rn, 1200 AS value
UNION ALL
SELECT rn + 1 rn, value + round(value/10,2) as value
FROM nums
where rn < 10
)
SELECT rn, Value
FROM nums
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

SQL Query with ORDER BY Part 2

This is a followup question to:
SQL Query with ORDER BY
But I think the SQL logic is going to be quite different, so I am posting it as separate question.
I am trying to extend my sql SELECT query it and having some trouble:
I have the table:
id type radius
-------------------------
1 type1 0.25
2 type2 0.59
3 type1 0.26
4 type1 0.78
5 type3 0.12
6 type2 0.45
7 type3 0.22
8 type3 0.98
and I am trying to learn how to SELECT the second smallest radius for each given type. So the returned recordset should look like:
id type radius
-------------------------
3 type1 0.26
2 type2 0.59
7 type3 0.22
(Note: in the referenced question, I was looking for the lowest radius, not the second lowest radius).
I am assuming I have to use LIMIT and OFFSET, but if I use the MIN() won't that return a distinct record containing the minimum radius?
Does anyone have any thoughts on how to attack this?
Many thanks,
Brett
You didn't mention your DBMS, so I'll post a solution that works with DBMS that support the standard windowing functions:
SELECT *
FROM (
SELECT id,
type,
radius,
dense_rank() OVER (PARTITION BY type ORDER BY radius ASC) as radius_rank
FROM radius_table
) t
WHERE radius_rank = 2
You can easily pick the 3rd lowest or 14th lowest as well by adjusting the WHERE condition
This solution will also work if you have more than one row that qualifies for 2nd lowest (the LIMIT solutions would only show one of them)
This query gives you the 2nd position of a given type
SELECT *
FROM `test`.`rads`
WHERE type = 'type wanted'
ORDER BY `radius` ASC
LIMIT 1, 1
You can mix this in a subquery to fetche a whole list, like this query
SELECT id, type, radius
FROM `test`.`rads` t
WHERE id = (
SELECT id
FROM `test`.`rads` ti
WHERE ti.type = t.type
ORDER BY `radius` ASC
LIMIT 1, 1)
ORDER BY radius ASC, id DESC
With this query you can vary the position by changing the LIMIT first parameter
I would use the SQL query from your previous answer and add a WHERE instrution in it removing all records containing the 'id' of the matching '1st lowest radius'.
SELECT t1.id,t1.type,t1.radius FROM table t1
WHERE radius = (
SELECT MIN(radius) FROM table
WHERE radius = t1.radius
AND id not IN (
SELECT t2.id FROM table t2
WHERE radius = (
SELECT MIN(radius) FROM table
WHERE radius = t2.radius
)
)
)