Shifting values in an Oracle table [duplicate] - sql

This question already has answers here:
Fill null values with last non-null amount - Oracle SQL
(4 answers)
Closed last month.
I have a table like this:
Key
values
1
null
2
value1
3
null
4
null
5
null
6
value2
7
null
8
null
I need to have a table where every value is shifted down if (and only if) the subsequent cell is null. When I found a different value I keep it and then if I found a new null cell I shift down the new value.
There is a query to do this trick? Thank you.
I want to obtain a table like this:
Key
values
1
null
2
value1
3
value1
4
value1
5
value1
6
value2
7
value2
8
value2

See LAST_VALUE() with IGNORE NULLS: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/LAST_VALUE.html
with data(Key, val) as (
select 1, null from dual union all
select 2, 'value1' from dual union all
select 3, null from dual union all
select 4, null from dual union all
select 5, null from dual union all
select 6, 'value2' from dual union all
select 7, null from dual union all
select 8, null from dual -- union all
)
select key, val, last_value(val) ignore nulls over(order by key )
from data
;
1
2 value1 value1
3 value1
4 value1
5 value1
6 value2 value2
7 value2
8 value2

Related

Compare before column in before row with next column in next row

My code is :
with x as
(
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual
)
select col col1, col col2, col col3, rownum
from x
where col2.ROWNUM > col1.ROWNUM -1
and col2.ROWNUM > col3ROWNUM +1 ;
I want to compare col2.ROWNUM > col1.ROWNUM -1 and col2.ROWNUM > col3ROWNUM + 1 but that doesn't work and I got an error
ORA-01747: invalid user.table.column, table.column, or column specification
01747. 00000 - "invalid user.table.column, table.column, or column specification"
*Cause:
*Action:
Error at Line: 10 Column: 13
Please help me
It looks you got something wrong.
Result of that CTE is a single-column table whose only column is named col. There are no other columns.
SQL> with x as (
2 select 1 col from dual union all --> in UNION, all columns are
3 select 2 col from dual union all named by column name(s) from the
4 select 8 col from dual union all first SELECT statement
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual)
8 select x.*, rownum
9 from x;
COL ROWNUM
---------- ----------
1 1
2 2
8 3
4 4
3 5
2 6
6 rows selected.
SQL>
Therefore, where clause you wrote doesn't make any sense. Perhaps you should explain what you really have, rules that should be applied to source data and result you'd like to get.
Based on text you put into the title:
compare before column in before row with next column in next row
maybe you'd be interested in lag and lead analytic functions which then let you compare values in adjacent rows (pay attention to NULL values; I didn't). For example:
SQL> with x as (
2 select 1 col from dual union all
3 select 2 col from dual union all
4 select 8 col from dual union all
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual
8 ),
9 temp as
10 (select col,
11 rownum as rn
12 from x
13 ),
14 temp2 as
15 (select
16 rn,
17 col as this_row,
18 lag(col) over (order by rn) as previous_row,
19 lead(col) over (order by rn) as next_row
20 from temp
21 )
22 select this_row,
23 previous_row,
24 next_row,
25 --
26 case when this_row < previous_row then 'This < previous'
27 when this_row < next_row then 'This < next'
28 else 'something else'
29 end as result
30 from temp2
31 order by rn;
Result:
THIS_ROW PREVIOUS_ROW NEXT_ROW RESULT
---------- ------------ ---------- ---------------
1 2 This < next
2 1 8 This < next
8 2 4 something else
4 8 3 This < previous
3 4 2 This < previous
2 3 This < previous
6 rows selected.
SQL>
Use lead or lag functions. But, please, do not use rownum for such purposes.
Rownum indicates simply the order in which a row was found in the database and cannot be used for other purposes except limiting the number of rows fetched, like where rownum<=1 to be certain you won't get a too_many_rows exception, for instance. Still, if in a query you do fetch the pseud-column rownum, give it an alias so that you may use that value later on.
Moreover, what is supposed to mean col2.ROWNUM or col1.ROWNUM? That is not clear. col1 and col2 are two columns, which do not have the attribute rownum.
Something that may help in the future for analytic queries:
https://oracle-base.com/articles/misc/lag-lead-analytic-functions
And, if you wish to get a working SQL, please explain clearly what you wish to achieve, for I haven't really understood what that code is intended to do.
A way you may use rownum without getting errors:
with x as (
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual)
,x2 as (
select col col1 ,col col2, col col3 ,rownum rn
from x
)
select *
from x2
where rn between 2 and 3 --- rownum cannot be used in such a
condition!!!
;
Or, to be certain you get only the first row from a table satisfying a given condition:
select x_col1, x_col2 into v_col1, v_col2
from x_table
where ... --- logical conditions
and rownum<=1; --- rownum <= 1 avoids too_many_rows_exception if several rows satisfy the logical conditions given before
In Oracle, results sets have a non-deterministic order (i.e. they are unordered) unless you use an ORDER BY clause. Therefore, if you have a physical table, you need another column to provide the order (rather than relying on the ROWNUM pseudo-column, which may result in unexpected behaviour):
CREATE TABLE x (order_id, col) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 8 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 3 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL;
If you want to find the rows that go up in succession, then you can use MATCH_RECOGNIZE for row-by-row pattern matching:
SELECT *
FROM x
MATCH_RECOGNIZE(
ORDER BY order_id
MEASURES
any_row.col AS col1,
FIRST(up.col) AS col2,
LAST(up.col) AS col3,
FIRST(order_id) AS start_order_id
PATTERN ( any_row up{2} )
DEFINE up AS ( col > PREV(col) )
)
or the LEAD analytic function:
SELECT *
FROM (
SELECT col AS col1,
LEAD(col, 1) OVER (ORDER BY order_id) AS col2,
LEAD(col, 2) OVER (ORDER BY order_id) AS col3,
order_id
FROM x
)
WHERE col2 > col1
AND col3 > col2;
Which both output:
COL1
COL2
COL3
START_ORDER_ID
1
2
8
1
fiddle
It looks like you want to find the rows where the value of the column is bigger than it is in both - the previous and next row. If so, you could try this:
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 3 From Dual Union All
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 3
6 2
... with a bit different sample data this will find the other row(s) that satisfy the condition ...
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 5 From Dual Union All -- value of COL changed from 3 to 5
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 5 YES
6 2
OR without ID - using ROWNUM (as in your question), - not adviseable, though...
WITH
tbl (COL) AS -- Sample data (without ID column)
(
Select 1 From Dual Union All
Select 2 From Dual Union All
Select 8 From Dual Union All
Select 4 From Dual Union All
Select 5 From Dual Union All
Select 2 From DUAL
)
Select COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ROWNUM) And COL > LEAD(COL, 1) OVER(Order By ROWNUM) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
COL BIGGER_THAN_PREV_AND_NEXT
---------- -------------------------
1
2
8 YES
4
5 YES
2
Any Order By clause added to the query could change the ROWNUM values and the result...

Need a SQL select statement to return rows that have the same id in one column and distinct value in another column

I have a table that contains a group number column and a data column:
GROUP
DataColumn
1
NULL
1
NULL
1
"hello"
1
NULL
2
"bye"
2
"sorry"
3
NULL
3
NULL
3
NULL
I want to return the string in the DataColunm as long as all rows in that group contain a string (no row is null).
If any row in the group is NULL then I'd like to return all rows in that group with NULL in the DataColumn.
My desired output would be:
GROUP
DataColumn
1
NULL
1
NULL
1
NULL (swap "hello" to null since the other values for group 1 are null)
1
NULL
2
"bye"
2
"sorry"
3
NULL
3
NULL
3
NULL
Use COUNT() window function to count all the rows of each GROUP and compare the result to the number of the rows with non-null values:
SELECT "GROUP",
CASE
WHEN COUNT(*) OVER (PARTITION BY "GROUP") =
COUNT("DataColumn") OVER (PARTITION BY "GROUP")
THEN "DataColumn"
END "DataColumn"
FROM tablename;
See the demo.
Here's one option: check whether number of null and not null values per each group is a positive number; if so, return null for that group.
Sample data:
SQL> set null NULL
SQL> with test (cgroup, datacolumn) as
2 (select 1, null from dual union all
3 select 1, null from dual union all
4 select 1, 'hello' from dual union all
5 select 1, null from dual union all
6 select 2, 'bye' from dual union all
7 select 2, 'sorry' from dual union all
8 select 3, null from dual union all
9 select 3, null from dual union all
10 select 3, null from dual
11 ),
Query begins here:
12 temp as
13 (select cgroup, datacolumn,
14 sum(case when datacolumn is null then 1 else 0 end) over (partition by cgroup) cnt_null,
15 sum(case when datacolumn is null then 0 else 1 end) over (partition by cgroup) cnt_not_null
16 from test
17 )
18 select cgroup,
19 case when cnt_null > 0 and cnt_not_null > 0 then null
20 else datacolumn
21 end as datacolumn
22 from temp;
CGROUP DATACOLUMN
---------- ---------------
1 NULL
1 NULL
1 NULL
1 NULL
2 bye
2 sorry
3 NULL
3 NULL
3 NULL
9 rows selected.
SQL>

update sql to update lowest hierarchical data for each row in a table

I have a table as shown below
id
previous_id
latest_id
1
null
null
2
1
null
3
2
null
4
null
null
5
4
null
6
6
null
I want to update the table by setting the latest_id column value to lowest hierarchical value, which will look like this:
id
previous_id
latest_id
1
null
3
2
1
3
3
2
3
4
null
6
5
4
6
6
5
6
I have tried to use connect by, but the query is getting too complicated as start with cannot have a static value assigned, this update is for the entire table.
Below is what I could write for a single record based on it's id, how can I generalize it for all records in the table?
UPDATE TABLENAME1
SET LATEST_ID = (SELECT MAX(ID)
FROM TABLENAME1
START WITH ID = 3
CONNECT BY PREVIOUS_ID = PRIOR ID );
You can use a correlated hierarchical query and filter to get the leaf rows:
UPDATE table_name t
SET latest_id = (SELECT id
FROM table_name h
WHERE CONNECT_BY_ISLEAF = 1
START WITH h.id = t.id
CONNECT BY previous_id = PRIOR id);
Which, for the sample data:
CREATE TABLE table_name (id, previous_id, latest_id) AS
SELECT 1, null, CAST(null AS NUMBER) FROM DUAL UNION ALL
SELECT 2, 1, null FROM DUAL UNION ALL
SELECT 3, 2, null FROM DUAL UNION ALL
SELECT 4, null, null FROM DUAL UNION ALL
SELECT 5, 4, null FROM DUAL UNION ALL
SELECT 6, 5, null FROM DUAL;
Updates the table to:
ID
PREVIOUS_ID
LATEST_ID
1
null
3
2
1
3
3
2
3
4
null
6
5
4
6
6
5
6
db<>fiddle here
To the accepted answer, I will add this alterative which might perform better for large datasets by eliminating the correlated subquery.
MERGE INTO table_name t
USING (
SELECT CONNECT_BY_ROOT(id) root_id, id latest_id
FROM table_name
WHERE connect_by_isleaf = 1
CONNECT BY previous_id = prior id ) u
ON ( t.id = u.root_id )
WHEN MATCHED THEN UPDATE SET t.latest_id = u.latest_id;

Merge columns with empty values in oracle

I have two columns of data name1 and name2. I need to merge these columns but they are arranged like this:
name1 | name2
1 empty
empty 2
3 empty
empty 4
Here empty means a blank space in the table.
I need the output to be like this
1
2
3
4
This can be achieved with a simple NVL
WITH
sample_data (name1, name2)
AS
(SELECT 1, NULL FROM DUAL
UNION ALL
SELECT NULL, 2 FROM DUAL
UNION ALL
SELECT 3, NULL FROM DUAL
UNION ALL
SELECT NULL, 4 FROM DUAL)
SELECT nvl(name1, name2) as names
FROM sample_data;

How can I back fill null values in bigquery?

I'm trying to perform a null backfill, similar to Panda's dataframe bfill, in BigQuery. Reading the docs, the last_value function seems to be a good choice. However, this leaves some null spots until it finds the first value (quite reasonable, given the name of the function). How can I backfill those null? Or I just have to drop them?
This is a sample query:
select table_path.*, last_value(sn_6 ignore nulls) over (order by time)
from (select 1 as time, null as sn_6 union all
select 2, 1 union all
select 3, null union all
select 4, null union all
select 5, null union all
select 6, 0 union all
select 7, null union all
select 8, null
) table_path;
Actual output:
time sn_6 f0_
1 null null
2 1 1
3 null 1
4 null 1
5 null 1
6 0 0
7 null 0
8 null 0
Desired output:
time sn_6 f0_
1 null 1 <---Back fill all the gaps!
2 1 1
3 null 1
4 null 1
5 null 1
6 0 0
7 null 0
8 null 0
The real data has a timestamp column followed by 6 float columns and there are null values everywhere.
If the intention is to make the missing "backfill" to be a "forward-fill", you can use first_value function to look forward to locate the first non-null value, as:
select table_path.*,
coalesce(
last_value(sn_6 ignore nulls) over (order by time),
first_value(sn_6 ignore nulls) over (order by time RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
)
from (select 1 as time, null as sn_6 union all
select 2, 1 union all
select 3, null union all
select 4, null union all
select 5, null union all
select 6, 0 union all
select 7, null union all
select 8, null
) table_path;