Row_number calculation in sql, oracle - sql

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

Related

How to calculate sum of rows that is last 1-7, 8-14, 15-21 etc. by group in sql as separate columns [closed]

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 2 years ago.
Improve this question
I like to calculate sum of rows that is last 1-7, 8-14, 15-21 etc. by group in sql as separate columns:
Input Data:
Expected Result:
You can use analytic functions:
SELECT t.*,
CASE COUNT(*) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN CURRENT ROW AND 6 FOLLOWING
)
WHEN 7
THEN SUM( vol ) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN CURRENT ROW AND 6 FOLLOWING
)
END AS last7,
CASE COUNT(*) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN 7 FOLLOWING AND 13 FOLLOWING
)
WHEN 7
THEN SUM( vol ) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN 7 FOLLOWING AND 13 FOLLOWING
)
END AS last8_14
FROM table_name t
Which, for your sample data:
CREATE TABLE table_name ( rw, grp, vol ) AS
SELECT 1, 'A', 1 FROM DUAL UNION ALL
SELECT 2, 'A', 2 FROM DUAL UNION ALL
SELECT 3, 'A', 3 FROM DUAL UNION ALL
SELECT 4, 'A', 4 FROM DUAL UNION ALL
SELECT 5, 'A', 2 FROM DUAL UNION ALL
SELECT 6, 'A', 3 FROM DUAL UNION ALL
SELECT 7, 'A', 4 FROM DUAL UNION ALL
SELECT 8, 'A', 5 FROM DUAL UNION ALL
SELECT 9, 'A', 5 FROM DUAL UNION ALL
SELECT 10, 'A', 6 FROM DUAL UNION ALL
SELECT 11, 'A', 7 FROM DUAL UNION ALL
SELECT 12, 'A', 3 FROM DUAL UNION ALL
SELECT 13, 'A', 4 FROM DUAL UNION ALL
SELECT 14, 'A', 5 FROM DUAL UNION ALL
SELECT 15, 'A', 4 FROM DUAL;
Outputs:
RW | GRP | VOL | LAST7 | LAST8_14
-: | :-- | --: | ----: | -------:
1 | A | 1 | 19 | 35
2 | A | 2 | 23 | 34
3 | A | 3 | 26 | null
4 | A | 4 | 29 | null
5 | A | 2 | 32 | null
6 | A | 3 | 33 | null
7 | A | 4 | 34 | null
8 | A | 5 | 35 | null
9 | A | 5 | 34 | null
10 | A | 6 | null | null
11 | A | 7 | null | null
12 | A | 3 | null | null
13 | A | 4 | null | null
14 | A | 5 | null | null
15 | A | 4 | null | null
db<>fiddle here

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>

expand oracle table fields linearly

I have the following table in oracle:
ID field_1 field_2
1 1-5 1-5
1 20-30 55-65
2 1-8 10-17
2 66-72 80-86
I need to convert this table to the following format where field_1 and field_2 must be matched linearly:
ID field_1 field_2
1 1 1
1 2 2
1 3 3
1 4 4
1 5 5
1 20 55
1 21 56
1 22 57
1 23 58
1 24 59
1 25 60
1 26 61
1 27 62
1 28 63
1 29 64
1 30 65
2 1 10
2 2 11
2 3 12
2 4 13
2 5 14
2 6 15
2 7 16
2 8 17
2 66 80
2 67 81
2 68 82
2 69 83
2 70 84
2 71 85
2 72 86
what is the easiest and fastest way to accomplish this, knowing that the original table contains thousands of records
The lateral clause, used below, is available since Oracle 12.1. For older versions, a connect by hierarchical query is still probably the fastest, but it will need to be written with a bit more care (and it will be slower than using connect by in a lateral join).
Of course, the big assumption is that the inputs are always in the form number-dash-number, and that the difference between the upper and the lower bound is the same in the two columns, for each row. I am not even attempting to check for that.
select t.id, l.field_1, l.field_2
from mytable t,
lateral (select to_number(substr(field_1, 1, instr(field_1, '-') - 1))
+ level - 1 as field_1,
to_number(substr(field_2, 1, instr(field_2, '-') - 1))
+ level - 1 as field_2
from dual
connect by level <=
to_number(substr(field_1, instr(field_1, '-') + 1))
- to_number(substr(field_1, 1, instr(field_1, '-') - 1)) + 1
) l
;
One option uses a recursive query. Starting 11gR2, Oracle supports standard recursive common table expressions, so you can do:
with cte(id, field_1, field_2, max_field_1, max_field_2) as (
select
id,
to_number(regexp_substr(field_1, '^\d+')),
to_number(regexp_substr(field_2, '^\d+')),
to_number(regexp_substr(field_1, '\d+$')),
to_number(regexp_substr(field_2, '\d+$'))
from mytable
union all
select
id,
field_1 + 1,
field_2 + 1,
max_field_1,
max_field_2
from cte
where field_1 < max_field_1
)
select id, field_1, field_2 from cte order by id, field_1
This assumes that intervals on the same row have always the same length, as showned in your sample data. If that's not the case, you would to explain how you want to handle that.
Demo on DB Fiddle:
ID | FIELD_1 | FIELD_2
-: | ------: | ------:
1 | 1 | 1
1 | 2 | 2
1 | 3 | 3
1 | 4 | 4
1 | 5 | 5
1 | 20 | 55
1 | 21 | 56
1 | 22 | 57
1 | 23 | 58
1 | 24 | 59
1 | 25 | 60
1 | 26 | 61
1 | 27 | 62
1 | 28 | 63
1 | 29 | 64
1 | 30 | 65
2 | 1 | 10
2 | 2 | 11
2 | 3 | 12
2 | 4 | 13
2 | 5 | 14
2 | 6 | 15
2 | 7 | 16
2 | 8 | 17
2 | 66 | 80
2 | 67 | 81
2 | 68 | 82
2 | 69 | 83
2 | 70 | 84
2 | 71 | 85
2 | 72 | 86
You can use the cross join with generated values as follows:
SELECT ID,
to_number(regexp_substr(field_1, '[0-9]+',1,1)) + column_value - 1 AS FIELD_1,
to_number(regexp_substr(field_2, '[0-9]+',1,1)) + column_value - 1 AS FIELD_2
FROM your_table
cross join
table(cast(multiset(select level from dual
connect by level <=
to_number(regexp_substr(field_1, '[0-9]+',1,2))
- to_number(regexp_substr(field_1, '[0-9]+',1,1))
+ 1
) as sys.odcinumberlist))
ORDER BY 1,2

Oracle SQL use previous column value to lookup next row

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>

Count Of Distinct Records In SQL Where a Distinct Record does not exist [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I have data that looks like this:
HOUR COUNT
0 3
1 3
2 6
4 2
5 1
7 4
8 3
9 6
10 11
...
23 3
I need the data to look like this:
HOUR COUNT
0 3
1 3
2 6
3 0
4 2
5 1
6 0
7 4
8 3
9 6
10 11
...
23 3
When there is no distinct record of anything happening at a certain hour (ie hour 2 or 6) I want SQL to insert that hour with a count of 0.
Here is what my code looks like:
-- Columns selected
SELECT DISTINCT col1 AS 'HOUR', COUNT(col1) AS 'COUNT'
-- Database Used: DB
FROM DB
-- Filters
WHERE date_col BETWEEN '1/1/13' AND '1/2/13'
GROUP BY col1
ORDER BY col1
--*********************************************************************************
-- End Report
If you don't have a table of hours, then you can use the query below. Depending on your platform, you may be able to use a function in place of the CASE.
SELECT a.hour,
CASE
WHEN b.count IS NULL THEN 0
ELSE b.count
END AS 'count'
FROM (SELECT 1 AS 'hour'
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
UNION ALL
SELECT 10
UNION ALL
SELECT 11
UNION ALL
SELECT 12
UNION ALL
SELECT 13
UNION ALL
SELECT 14
UNION ALL
SELECT 15
UNION ALL
SELECT 16
UNION ALL
SELECT 17
UNION ALL
SELECT 18
UNION ALL
SELECT 19
UNION ALL
SELECT 20
UNION ALL
SELECT 21
UNION ALL
SELECT 22
UNION ALL
SELECT 23) a
LEFT JOIN tbl b
ON b.hour = a.hour
Result
| HOUR | COUNT |
----------------
| 1 | 3 |
| 2 | 6 |
| 3 | 0 |
| 4 | 2 |
| 5 | 1 |
| 6 | 0 |
| 7 | 4 |
| 8 | 3 |
| 9 | 6 |
| 10 | 11 |
| 11 | 10 |
| 12 | 11 |
| 13 | 5 |
| 14 | 7 |
| 15 | 10 |
| 16 | 7 |
| 17 | 6 |
| 18 | 8 |
| 19 | 2 |
| 20 | 7 |
| 21 | 5 |
| 22 | 6 |
| 23 | 3 |
You can use a common table expression to create all the hours:
WITH [Hours]([Hour])
AS
(
SELECT 0 AS [Hour]
UNION ALL
SELECT [Hours].[Hour] + 1 AS [Hour]
FROM [Hours]
WHERE [Hours].[Hour] < 23
)
SELECT * FROM [Hours]
Then you can use these hours to join on your query:
WITH [Hours]([Hour])
AS
(
SELECT 0 AS [Hour] --Don't forget 0, else you miss one hour on your day!
UNION ALL
SELECT [Hours].[Hour] + 1 AS [Hour]
FROM [Hours]
WHERE [Hours].[Hour] < 23
)
SELECT
[Hours].[Hour],
COUNT([table].[col1]) AS [Count]
FROM [Hours]
LEFT JOIN [table] ON [Hours].[Hour] = [table].[col1]
WHERE [table].[date_col] BETWEEN '1/1/13' AND '1/2/13'
ORDER BY [Hour] ASC
You need a case statement:
case count(col1) as 'Count'
when null
THEN 0
ELSE 'Count'
Simpler query to get 24 hours - this is only to build hours table if needed. Use of Sysdate is optional:
SELECT to_char(trunc(SYSDATE), 'hh24')-1 + LEVEL "24_hrs"
FROM dual
CONNECT BY LEVEL <= 24;