Oracle SQL use previous column value to lookup next row - sql

What I currently have:
ID FROM_REF TO_REF
--- -------- ----
1 1 10
1 2 3
1 3 4
1 3 5
1 5 6
1 6 7
1 7 9
1 8 11
1 9 8
1 10 2
What's needed is the SORT column which I can use to sort according later on:
ID FROM_REF TO_REF SORT
--- -------- ---- ----
1 1 10 1
1 10 2 2
1 2 3 3
1 3 4 4
1 4 5 5
1 5 6 6
1 6 7 7
1 7 9 8
1 9 8 9
1 8 11 10
NOTE: TO_REF column indicate next FROM_REF.
How do I write SQL to achieve the SORT column as result?
Please help.

You can use a RECURSIVE function.
WITH X (ID, FROM_REF, TO_REF) AS
(
SELECT ID, FROM_REF, TO_REF
FROM tbl
WHERE FROM_REF = 1
UNION ALL
SELECT tbl.ID, tbl.FROM_REF, tbl.TO_REF
FROM tbl
JOIN X
ON tbl.ID = X.ID
AND tbl.FROM_REF = X.TO_REF
)
SELECT ID, FROM_REF, TO_REF
FROM X
ID | FROM_REF | TO_REF
-: | -------: | -----:
1 | 1 | 10
1 | 10 | 2
1 | 2 | 3
1 | 3 | 4
1 | 4 | 5
1 | 5 | 6
1 | 6 | 7
1 | 7 | 9
1 | 9 | 8
1 | 8 | 11
dbfiddle here

A simple hierarchical query, I presume.
SQL> with test (from_ref, to_ref) as
2 (select 1, 10 from dual union
3 select 2, 3 from dual union
4 select 3, 4 from dual union
5 select 4, 5 from dual union
6 select 5, 6 from dual union
7 select 6, 7 from dual union
8 select 7, 9 from dual union
9 select 8, 11 from dual union
10 select 9, 8 from dual union
11 select 10, 2 from dual
12 )
13 select from_ref, to_ref, level rn
14 from test
15 connect by from_ref = prior to_ref
16 start with from_ref = (select min(from_ref) from test);
FROM_REF TO_REF RN
---------- ---------- ----------
1 10 1
10 2 2
2 3 3
3 4 4
4 5 5
5 6 6
6 7 7
7 9 8
9 8 9
8 11 10
10 rows selected.
SQL>

Related

How to Use Group By on the Basis of Column Value?

I am trying to use Group By but unable to achieve the output
I want to Group by with Date, shift and with Mass.
Data is like :
date | shift | mass
---------+-------+------
01-05-20 | A | 5
01-05-20 | B | 3
01-05-20 | B | 3
02-05-20 | A | 11
02-05-20 | A | 5
02-05-20 | C | 12
02-05-20 | C | 12
02-05-20 | B | 5
OutPut which i want
date | shift | mass>3 | mass>10
---------+-------+--------+--------
01-05-20 | A | 1 | 0
01-05-20 | B | 2 | 0
02-05-20 | A | 1 | 1
02-05-20 | B | 1 | 0
02-05-20 | C | 0 | 2
You can use Conditional Aggregation through GROUPing BY mydate,shift :
SELECT mydate,shift,
SUM(CASE WHEN mass > 3 THEN 1 ELSE 0 END) AS "mass>3",
SUM(CASE WHEN mass >10 THEN 1 ELSE 0 END) AS "mass>10"
FROM t
GROUP BY mydate,shift
ORDER BY mydate,shift;
By the way( as you asked for it within a comment ), you can also use DECODE() function :
SELECT mydate,shift,
SUM(DECODE(SIGN(mass-3),1,1,0)) AS "mass>3",
SUM(DECODE(SIGN(mass-10),1,1,0)) AS "mass>10"
FROM t
GROUP BY mydate,shift
ORDER BY mydate,shift
Demo
SQL> with t(mydate, shift, mass) as
2 (
3 select '01-05-2020', 'A', 5 from dual union all
4 select '01-05-2020', 'B', 3 from dual union all
5 select '01-05-2020', 'B', 3 from dual union all
6 select '02-05-2020', 'A', 11 from dual union all
7 select '02-05-2020', 'A', 5 from dual union all
8 select '02-05-2020', 'C', 12 from dual union all
9 select '02-05-2020', 'C', 12 from dual union all
10 select '02-05-2020', 'B', 5 from dual
11 )
12 SELECT mydate,shift,
13 SUM(CASE WHEN mass >=3 AND
14 mass < 10 THEN 1 ELSE 0 END) AS "mass>=3",
15 SUM(CASE WHEN mass >10 THEN 1 ELSE 0 END) AS "mass>10"
16 FROM t
17 GROUP BY mydate,shift
18 ORDER BY mydate,shift;
MYDATE S mass>=3 mass>10
---------- - ---------- ----------
01-05-2020 A 1 0
01-05-2020 B 2 0
02-05-2020 A 1 1
02-05-2020 B 1 0
02-05-2020 C 0 2
SQL>
SQL>

get data from same table in sql using join

I have a table [dbo].[UserImages] where user uploads their photos after every 6 day, total 18 records for user 3. 9 records of day 1 and 9 records of day 6. There are 4 columns In this table
[Id, UserId, Image, Day]
Id UserId Image Day
1 3 3_20200408_1.png 1
2 3 3_20200408_2.png 1
3 3 3_20200408_3.png 1
4 3 3_20200408_4.png 1
5 3 3_20200408_5.png 1
6 3 3_20200408_6.png 1
7 3 3_20200408_7.png 1
8 3 3_20200408_8.png 1
9 3 3_20200408_9.png 1
10 3 3_20200410_9.png 6
11 3 3_20200410_2.png 6
12 3 3_20200410_3.png 6
13 3 3_20200410_4.png 6
14 3 3_20200410_5.png 6
15 3 3_20200410_6.png 6
16 3 3_20200410_7.png 6
17 3 3_20200410_8.png 6
18 3 3_20200410_9.png 6
I need something like that
ImgCount UserId ImageDay1 ImageDay6
1 3 3_20200408_1.png 3_20200408_1.png
2 3 3_20200408_2.png 3_20200408_2.png
3 3 3_20200408_3.png 3_20200408_3.png
4 3 3_20200408_4.png 3_20200408_4.png
5 3 3_20200408_5.png 3_20200408_5.png
6 3 3_20200408_6.png 3_20200408_6.png
7 3 3_20200408_7.png 3_20200408_7.png
8 3 3_20200408_8.png 3_20200408_8.png
9 3 3_20200408_9.png 3_20200408_9.png
What should I do for this
You can use row_number() and aggregation:
select
imgCount,
userId,
max(case when day = 1 then image end) ImageDay1,
max(case when day = 6 then image end) ImageDay6
from (
select t.*, row_number() over(partition by userId, day order by image) imgCount
from mytable t
where day in (1, 6)
) t
group by userId, imgCount
order by ImgCount
Demo on DB Fiddle:
ImgCount | userId | ImageDay1 | ImageDay6
:------- | -----: | :--------------- | :---------------
1 | 3 | 3_20200408_1.png | 3_20200410_1.png
2 | 3 | 3_20200408_2.png | 3_20200410_2.png
3 | 3 | 3_20200408_3.png | 3_20200410_3.png
4 | 3 | 3_20200408_4.png | 3_20200410_4.png
5 | 3 | 3_20200408_5.png | 3_20200410_5.png
6 | 3 | 3_20200408_6.png | 3_20200410_6.png
7 | 3 | 3_20200408_7.png | 3_20200410_7.png
8 | 3 | 3_20200408_8.png | 3_20200410_8.png
9 | 3 | 3_20200408_9.png | 3_20200410_9.png

Row_number calculation in sql, oracle

Now i am facing one small issue in sql code.
I have no idea about this logic.
with res as (
select
85,
2,
45,
34,
60,
2,
6,
4,
78,
6,
23,
45,
80,
80
from dual
)
select * from res
I have mention 3 header .1.input 2.Expected 3.Actual.
And my question is set one column based on input days we have to display rn column. IF value is days > 60 them we have to display as zero and next row value will be start as 1 and to printed as 2, 3, 4... till days > 60.then we have to set as 0 for days > 60 and agin start from 1, 2,3 ... still days > 60. its loop till end of value.
Input Expected Actual
DAYS DAYS RN DAYS RN
85 85 0 85 1
4 4 1 4 2
32 32 2 32 3
7 7 3 7 4
5 5 4 5 5
66 66 0 66 6
14 14 1 14 7
25 25 2 25 8
2 2 3 2 9
9 9 4 9 10
70 70 0 70 11
80 80 0 80 12
6 6 1 6 13
3 3 2 3 14
1 1 3 1 15
78 78 0 78 16
OK, first of all... for meaningful results, you need a way to order the input data to get a row number for each record. You cannot just rely on each record's position in a list, because that's undefined in Oracle if these records are coming from a table.
So, assuming we add a rn column to your res table, all you need to do is this:
select res.rn,
res.days,
res.rn - nvl(last_value(case
when res.days < 60 then null
else res.rn end) ignore nulls over (order by rn),0) lrn
from res;
That nvl(last_value... expression finds the rn (row number) of the most recent input record having days >= 60. So, if a record's rn is 15 and the most recent record with days >= 60 was 13, then that record gets renumbered as "2".
For this to work, your rn values in the input data must start at 1 and have no gaps. If that is not the case with your real data, then you will need to add another with clause before this to do a dense_rank or something to get them that way.
Here is a complete example, using your test data (from your expected result):
with res (rn, days) as (
select 1, 85 from dual union all
select 2, 4 from dual union all
select 3, 32 from dual union all
select 4, 7 from dual union all
select 5, 5 from dual union all
select 6, 66 from dual union all
select 7, 14 from dual union all
select 8, 25 from dual union all
select 9, 2 from dual union all
select 10, 9 from dual union all
select 11, 70 from dual union all
select 12, 80 from dual union all
select 13, 6 from dual union all
select 14, 3 from dual union all
select 15, 1 from dual union all
select 16, 78 from dual
)
-- Above is just test data.. solution starts here
/* with ... */
select res.rn, res.days, res.rn - nvl(last_value(
case when res.days < 60 then null else res.rn end)
ignore nulls over (order by rn),0) lrn
from res
+----+------+-----+
| RN | DAYS | LRN |
+----+------+-----+
| 1 | 85 | 0 |
| 2 | 4 | 1 |
| 3 | 32 | 2 |
| 4 | 7 | 3 |
| 5 | 5 | 4 |
| 6 | 66 | 0 |
| 7 | 14 | 1 |
| 8 | 25 | 2 |
| 9 | 2 | 3 |
| 10 | 9 | 4 |
| 11 | 70 | 0 |
| 12 | 80 | 0 |
| 13 | 6 | 1 |
| 14 | 3 | 2 |
| 15 | 1 | 3 |
| 16 | 78 | 0 |
+----+------+-----+
LRN is the "expected" RN from your desired results. For simplicity, I omitted the "actual" columns, which just seemed to be duplicates of the input data.
You don't include your actual select statement.
I'm guessing it is something like this:
select res.*, row_number() OVER () AS RN from res
You want to use modulus like this:
select res.*, (row_number() OVER ()) % 5 AS RN from res

Select pair where value of other column is equal

How do I select a pair (number, number) where tabid is equal for two numbers from the following table (i.e: number 7 and 11 have the same tabid):
tabid | number
---------+--------
1 | 6
1 | 6
2 | 7
3 | 8
4 | 8
5 | 10
5 | 11
6 | 12
6 | 11
5 | 6
4 | 7
3 | 8
2 | 11
The result of this should be:
number | number
---------+--------
7 | 11
7 | 8
10 | 11
11 | 12
6 | 10
6 | 11
Is this what you're looking for:
select
t1.number, t2.number
from t t1, t t2
where t1.tabid = t2.tabid
and t1.number < t2.number;
produces:
NUMBER NUMBER
---------- ----------
6 10
6 11
7 8
7 11
10 11
11 12
Use array_agg to concatenate the tabid's into an array. Thereafter self join this cte to check if one array is an overlap of the other using the array operator &&.
with concatenated as (
select array_agg(tabid) as arr_tab, num
from t
group by num
)
select c1.num,c2.num
from concatenated c1
join concatenated c2 on c1.num < c2.num
where c2.arr_tab && c1.arr_tab
order by 1,2
Sample Demo

determining histogram bin size

I'm looking to create a histogram in SQL (which in itself isn't too tricky), but what I'm looking for is a way of splitting the bins so that each bin / band has the same proportion of the data included within.
For example if I have the sample data (the value column) and I want to divide it into 5 bins, I know that I can work out the number of bins by doing something like
(MAX(Value) - MIN(Value)) / numberofsteps
Will give the groups we see in the band 1 column.
However what I want is for the bands to be calculated so that each band accounts for (100 / n) % of the total where n is the number of bands (so in this case each of the 5 bands would represent 20% of the total data) - which is what is shown in the band 2 column
Value band 1 band 2
1 | 1 to 2 | 0 to 1
1 | 1 to 2 | 0 to 1
1 | 1 to 2 | 0 to 1
1 | 1 to 2 | 0 to 1
2 | 1 to 2 | 2 to 3
2 | 1 to 2 | 2 to 3
3 | 1 to 2 | 2 to 3
3 | 1 to 2 | 2 to 3
4 | 3 to 4 | 4 to 6
4 | 3 to 4 | 4 to 6
5 | 5 to 6 | 4 to 6
6 | 5 to 6 | 4 to 6
7 | 7 to 8 | 7 to 8
8 | 7 to 8 | 7 to 8
8 | 7 to 8 | 7 to 8
8 | 7 to 8 | 7 to 8
9 | 9 to 10 | 9 to 10
10 | 9 to 10 | 9 to 10
10 | 9 to 10 | 9 to 10
10 | 9 to 10 | 9 to 10
Is there a way to do this in SQL (i'm using SQL server 2005 if that helps), possibly without creating a UDF and having it so that I can easily alter the number of bins would be great (if that's not asking the impossible!)
Thanks
To divide into bins you can use the ntile function.
with Vals AS
(
SELECT 1 AS value UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 8 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 10 UNION ALL SELECT 10
), TiledVals AS
(
SELECT value, NTILE(5) OVER (ORDER BY value) AS BinNumber
FROM Vals
)
SELECT value, BinNumber,
Min(value) OVER (PARTITION BY BinNumber) As StartBin,
MAX(value) OVER (PARTITION BY BinNumber) As EndBin
FROM TiledVals
Gives
value BinNumber StartBin EndBin
----------- -------------------- ----------- -----------
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
2 2 2 3
2 2 2 3
3 2 2 3
3 2 2 3
4 3 4 6
4 3 4 6
5 3 4 6
6 3 4 6
7 4 7 8
8 4 7 8
8 4 7 8
8 4 7 8
9 5 9 10
10 5 9 10
10 5 9 10
10 5 9 10