How to fill in null values with previous values (Snowflake db) - datatable

I have CTEs that result in a table similar to this:
US_DATE_TIME
US_PRICE
NON_US_DATE_TIME
NON_US_PRICE
NULL
NULL
2022-06-08 14:40:13.762
NULL
2022-03-03 15:02:05.963
11
NULL
NULL
NULL
NULL
2022-06-28 21:58:43.558
14
2022-03-03 15:42:08.203
41
NULL
NULL
2022-06-08 21:57:07.909
10
2022-03-03 15:00:21.814
14
NULL
NULL
NULL
38
I would like to show the changes in price for the two columns PRICE and NON_US_PRICE. For example, in the PRICE column, the price was originally NULL, then became 11 and stayed at 11 until in changed to 41, so the price would for row 3 for PRICE would be 11 instead of NULL and similarly, it would be 10 for the last row. I would like to apply this to the NON_US_PRICE column as well, where the price was NULL until it became 14, so the 4th and 5th row would also be 14 (instead of NULL) until it became 38. Is there a way I can do this? Would it be using something like lag()? In order to result in:
US_DATE_TIME
US_PRICE
NON_US_DATE_TIME
NON_US_PRICE
NULL
NULL
2022-06-08 14:40:13.762
NULL
2022-03-03 15:02:05.963
11
NULL
NULL
NULL
11
2022-06-28 21:58:43.558
14
2022-03-03 15:42:08.203
41
NULL
14
2022-06-08 21:57:07.909
10
2022-03-03 15:00:21.814
14
NULL
10
NULL
38

So where Greg is trying to point out, is to "get the last value" you must have some details the sorts the data.
for your data, I have ignored asking you "how are you going to sort the data" and just adding those values, and you can work-out how to do this bit, thus, with this data:
WITH data(row_order, STOCK_id, US_DATE_TIME, US_PRICE, NON_US_DATE_TIME, NON_US_PRICE) as (
SELECT *
FROM VALUES
(1, 1, NULL, NULL, '2022-06-08 14:40:13.762', NULL),
(2, 1, '2022-03-03 15:02:05.963', 11, NULL, NULL),
(3, 1, NULL, NULL, '2022-06-28 21:58:43.558', 14),
(4, 1, '2022-03-03 15:42:08.203', 41, NULL, NULL),
(5, 1, '2022-06-08 21:57:07.909', 10, '2022-03-03 15:00:21.814', 14),
(6, 1, NULL, NULL, NULL, 38)
)
we can not do a per stock_id sorting by row_order, thus:
select
row_order
,US_DATE_TIME
,NVL(US_PRICE, LAG(US_PRICE) IGNORE NULLS OVER (partition by stock_id order by row_order)) as US_PRICE
,NON_US_DATE_TIME
,NVL(NON_US_PRICE, LAG(NON_US_PRICE) IGNORE NULLS OVER (partition by stock_id order by row_order)) as NON_US_PRICE
from data
order by row_order;
which gives:
ROW_ORDER
US_DATE_TIME
US_PRICE
NON_US_DATE_TIME
NON_US_PRICE
1
null
null
2022-06-08 14:40:13.762
null
2
2022-03-03 15:02:05.963
11
null
null
3
null
11
2022-06-28 21:58:43.558
14
4
2022-03-03 15:42:08.203
41
null
14
5
2022-06-08 21:57:07.909
10
2022-03-03 15:00:21.814
14
6
null
10
null
38
this uses two function NVL (which is functionally the same as COALESCE) and LAG with the IGNORE NULLS OVER clause

Related

Dynamically update null columns with the target values

Need to update columns which have null in column, with the target values
COL3 - DATE DATATYPE
COL1,2,4 - NUMBER DATATYPE
EXAMPLE
TABLE
COL1 COL2 COL3 COL4
1 2 29-02-22 NULL
1 NULL 29-02-22 4
2 4 29-02-22 8
3 NULL NULL 55
4 5 29-02-22 NULL
5 5 29-02-22 44
5 6 29-02-22 4
5 NULL NULL NULL
OUTPUT
COL1 COL2 COL3 COL4
1 2 29-02-22 30
1 3 29-02-22 4
2 4 29-02-22 8
3 5 29-02-22 55
4 5 29-02-22 33
5 5 29-02-22 44
5 6 29-02-22 4
5 1 29-02-22 2
How to compare null and replace it with the value. Tried Decode and Case in the update statement, however, Haven't got any solution. The main purpose is to update the null value with the target value. The example illustrates to show what will be the source and how output will looks like
You can use the COALESCE function. COALESCE returns the first non-null argument in the list.
UPDATE myTable
SET
Col1 = COALESCE(Col1, col1_targetValue),
Col2 = COALESCE(Col2, col2_targetValue),
Col3 = COALESCE(Col3, col3_targetValue),
Col4 = COALESCE(Col4, col4_targetValue)
This will replace the NULL values in all rows. Add an appropriate WHERE clause to update only specific rows.

PIVOT using AVG not working correctly in my query

I have following table and finding difficult to produce expected output. I have tried following query but it doesn't produce correct 'AVG' using 'PIVOT'. The average value should show in respective columns of category as per the output. In my below query it doesn't work
SELECT *
FROM (select avg(hd.SCORE) over(partition by hd.org_id) Avg_Item
, hd.org_id,d.cateogory,hd.score FROM Category d JOIN Assign hd ON d.catid=hd.catid) first
PIVOT (
AVG(score)
FOR (cateogory)
IN ('cat1' as "cat1",'cat2' as "cat2",'cat3' as "cat3",'cat4' as "cat4")
)PIV;
Sample Tables & Expected Output
Category table
-----------------------
catid cateogory
1 cat1
2 cat2
3 cat3
4 cat4
5 cat5
Assign table
--------------------------
Aid(pk) catid(fk) pid Score org_id
1 1 1 98 1
2 1 2 99 1
3 1 3 100 1
4 2 4 12 4
5 2 8 78 5
6 5 9 98 6
org Table
-----------------------------
org_id org_name
1 ABC
2 CDE
3 FGH
4 Google
5 Yahoo
6 Facebook
Desired output
----------------------------------
org_name cat1 cat2 cat3 cat4 cat5
ABC 99
CDE 12
FGH
Google 78
Yahoo 98
Facebook
Assuming score is actually a number, not a string like 98%, then your current query seems to work, and (with cat5 added) produces:
AVG_ITEM
ORG_ID
cat1
cat2
cat3
cat4
cat5
99
1
99
null
null
null
null
12
4
null
12
null
null
null
78
5
null
78
null
null
null
98
6
null
null
null
null
98
which seems right.
But you aren't using the analytic avg_item you calculate in your desired result, so you can remove that form the subquery. And you can outer join the result of the pivot to the org table to get the organisation names:
SELECT o.org_name, p.cat1, p.cat2, p.cat3, p.cat4, p.cat5
FROM org o
LEFT JOIN (
SELECT *
FROM (
SELECT hd.org_id, d.cateogory, hd.score
FROM Category d
JOIN Assign hd ON d.catid=hd.catid
)
PIVOT (
AVG(score)
FOR (cateogory)
IN ('cat1' as cat1, 'cat2' as cat2, 'cat3' as cat3, 'cat4' as cat4, 'cat5' as cat5)
)
) p
ON p.org_id = o.org_id
ORDER BY o.org_id;
or more simply:
SELECT o.org_name, p.cat1, p.cat2, p.cat3, p.cat4, p.cat5
FROM (
SELECT hd.org_id, d.cateogory, hd.score
FROM Category d
JOIN Assign hd ON d.catid=hd.catid
)
PIVOT (
AVG(score)
FOR (cateogory)
IN ('cat1' as cat1, 'cat2' as cat2, 'cat3' as cat3, 'cat4' as cat4, 'cat5' as cat5)
) p
RIGHT JOIN org o
ON o.org_id = p.org_id
ORDER BY o.org_id;
which both get:
ORG_NAME
CAT1
CAT2
CAT3
CAT4
CAT5
ABC
99
null
null
null
null
CDE
null
null
null
null
null
FGH
null
null
null
null
null
Google
null
12
null
null
null
Yahoo
null
78
null
null
null
Facebook
null
null
null
null
98
fiddle
Your expected result seems to have the wrong values against the organisations (or the wrong IDs in the org table), but other than that, this seems to be your desired result.

Merge Columns into One Column Oracle PL/SQL

I have the following script and tables where upon running the script produces the output for LOG_ID, YEAR, WA.SUB_DIVISION, AI.SUB_DIVISION, EA.SUB_DIVISION, FI.SUB_DIVISION
Is it possible to Merge four columns into one column
WA.SUB_DIVISION, AI.SUB_DIVISION, EA.SUB_DIVISION, FI.SUB_DIVISION into SUB_DIVISION a single column
Not sure how to proceed.
I have created a sample sql fiddle
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=3c4abb924462dcf5e5f8b0f91019b6b6
select distinct L.LOG_ID,
FC.LOG_YR as YEAR,
WA.SUB_DIVISION,
AI.SUB_DIVISION AS SUB_DIV,
EA.SUB_DIVISION AS SUB_DIV3,
FI.SUB_DIVISION AS SUB_DIV4
FROM FINAL_CALENDAR FC
JOIN LOG L
ON TO_DATE ( TO_CHAR (L.LOG_DATE, 'MM/DD/YYYY'), 'MM/DD/YYYY') = FC.CAL_DATE
LEFT OUTER JOIN LOG_WATER WA
ON WA.LOG_ID = L.LOG_ID
LEFT OUTER JOIN LOG_AIR AI
ON AI.LOG_ID = L.LOG_ID
LEFT OUTER JOIN LOG_EARTH EA
ON EA.LOG_ID = L.LOG_ID
LEFT OUTER JOIN LOG_FIRE FI
ON FI.LOG_ID = L.LOG_ID
Actual Output / ISSUE / Existing Output
LOG_ID YEAR SUB_DIVISION SUB_DIV SUB_DIV3 SUB_DIV4
990741 2020 NULL NULL NULL NULL
990742 2020 NULL NULL NULL NULL
991122 2020 NULL NULL NULL NULL
991123 2020 NULL NULL NULL NULL
994461 2020 NULL 4 NULL NULL
994468 2020 NULL 2 NULL NULL
994466 2020 NULL 2 NULL NULL
994480 2020 8 NULL NULL NULL
994479 2020 8 NULL NULL NULL
994476 2020 6 NULL NULL NULL
994478 2020 6 NULL NULL NULL
994440 2020 NULL NULL NULL NULL
994432 2020 NULL NULL NULL NULL
994450 2020 NULL NULL NULL NULL
994154 2020 NULL NULL NULL NULL
Required / Desired Output
LOG_ID YEAR SUB_DIVISION DISPLAY_NAME
990741 2020 NULL NULL
990742 2020 NULL NULL
991122 2020 NULL NULL
991123 2020 NULL NULL
994461 2020 4 Triangle
994468 2020 2 Circle
994466 2020 2 Circle
994480 2020 8 Rhombus
994479 2020 8 Rhombus
994476 2020 6 Dot
994478 2020 6 Dot
994440 2020 NULL NULL
994432 2020 NULL NULL
994450 2020 NULL NULL
994154 2020 NULL NULL
Table LOG;
LOG_ID, LOG_DATE,
990741, to_date('21-JAN-20','DD-MON-RR')
990742 21-JAN-20
991122 24-JAN-20
991123 25-JAN-20
994461 25-JAN-20
994468 25-JAN-20
994466 25-JAN-20
994480 25-JAN-20
994479 25-JAN-20
994476 25-JAN-20
994478 25-JAN-20
994440 25-JAN-20
994432 25-JAN-20
994450 25-JAN-20
994154 25-JAN-20
TABLE FINAL_CALENDAR;
CAL_DATE CAL_MONTH LOG_YR
21-JAN-20 1 2020
21-JAN-20 1 2020
24-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
25-JAN-20 1 2020
TABLE LOG_AIR;
ID LOG_ID SUB_DIVISION
134 994468 2
132 994461 4
133 994466 2
TABLE LOG_WATER;
ID LOG_ID SUB_DIVISION
9345 994480 8
9344 994479 8
9342 994476 6
9343 994478 6
TABLE LOG_EARTH;
ID LOG_ID SUB_DIVISION
0118 994440 null
0117 994432 null
TABLE LOG_FIRE;
ID LOG_ID SUB_DIVISION
706 994450 null
705 994154 null
TABLE Z_SUB_DIVISION_TYPE;
SUB_DIVISION DISPLAY_NAME
1 Parallelogram
2 Circle
3 Square
4 Triangle
5 Tangent
6 Dot
7 Line
8 Rhombus
9 Trapezium
If that's the final result you can merge last four columns assuming one column has value and rest are null(sub division columns)
SELECT log_id,
year,
CASE
WHEN sub_division IS NULL AND SUB_DIV3 IS NULL AND SUB_DIV4 IS NULL THEN sub_div
WHEN sub_division IS NULL AND SUB_DIV IS NULL AND SUB_DIV4 IS NULL THEN sub_div3
WHEN sub_division IS NULL AND SUB_DIV IS NULL AND SUB_DIV3 IS NULL THEN sub_div4
ELSE
sub_division
END as sub_division,
display_name
FROM (SELECT DISTINCT L.log_id,
FC.log_yr AS YEAR,
WA.sub_division,
AI.sub_division AS SUB_DIV,
EA.sub_division
AS SUB_DIV3,
FI.sub_division AS SUB_DIV4,
(SELECT display_name
FROM z_sub_division_type a
WHERE a.sub_division = WA.sub_division
OR a.sub_division = AI.sub_division
OR a.sub_division = EA.sub_division
OR a.sub_division = FI.sub_division
) AS DISPLAY_NAME
FROM final_calendar FC
join log L
ON To_date (To_char (L.log_date, 'MM/DD/YYYY'), 'MM/DD/YYYY') =
FC.cal_date
left outer join log_water WA
ON WA.log_id = L.log_id
left outer join log_air AI
ON AI.log_id = L.log_id
left outer join log_earth EA
ON EA.log_id = L.log_id
left outer join log_fire FI
ON FI.log_id = L.log_id)
Edit 1:-This sql works assuming atleast one column has value and rest are null
Edit 2:- You can replace case clause with coalesce
coalesce(sub_division,sub_div,sub_div3,sub_div4) as sub_division

Convert delimited data into rows in Oracle

Here I have a source table
Source Table :
ID | Log
____________________
1 | Status : New
| Assignment : 1
| Priority : Low
_____________________
2 | Status : In Progress
Target Table :
ID | Key | Value
____________________
1 | Status | New
1 | Assignment| 1
1 | Priority | Low
2 | Status | In Progress
Please suggest the approach.
Thanks in advance.
Something like this should work. You seem to have some spaces around the actual tokens that must be removed, so I use the TRIM() function for that.
The WITH clause is there just for testing (not part of the SQL solution to your question - remove it before testing it against your actual table and columns).
with
source_table ( id, log ) as (
select 1, 'Status : New
Assignment : 1
Priority : Low' from dual union all
select 2, 'Status : In Progress' from dual
)
select id,
trim(regexp_substr(log, '(' || chr(10) || '|^)([^:]*):', 1, level, null, 2)) key,
trim(regexp_substr(log, ':([^:]*)(' || chr(10) || '|$)', 1, level, null, 1)) value
from source_table
connect by level <= regexp_count(log, ':')
and prior id = id
and prior sys_guid() is not null
;
ID KEY VALUE
-- ------------ --------------
1 Status New
1 Assignment 1
1 Priority Low
2 Status In Progress
Here's yet another option; TEST represents your sample data. INTER splits rows (separated by CHR(10)), while the final SELECT utilizes trivial SUBSTR + INSTR combination.
SQL> WITH test (id, LOG)
2 AS (SELECT 1,
3 'status: new'
4 || CHR (10)
5 || 'assignment: 1'
6 || CHR (10)
7 || 'priority: low'
8 FROM DUAL
9 UNION
10 SELECT 2, 'status: in progress' FROM DUAL),
11 inter
12 AS (SELECT id,
13 REGEXP_SUBSTR (REPLACE (LOG, CHR (10), ';'),
14 '[^;]+',
15 1,
16 COLUMN_VALUE)
17 LOG
18 FROM test,
19 TABLE (
20 CAST (
21 MULTISET (
22 SELECT LEVEL
23 FROM DUAL
24 CONNECT BY LEVEL <= REGEXP_COUNT (LOG, ':') + 1) AS SYS.odcinumberlist)))
25 SELECT id,
26 TRIM (SUBSTR (LOG, 1, INSTR (LOG, ':') - 1)) key,
27 TRIM (SUBSTR (LOG, INSTR (LOG, ':') + 1, LENGTH (LOG))) VALUE
28 FROM inter
29 WHERE LOG IS NOT NULL
30 ORDER BY id;
ID KEY VALUE
---- --------------- ---------------
1 status new
1 assignment 1
1 priority low
2 status in progress
SQL>

Oracle: Combine Two Tables with Different Columns

This is table 1:
col_1 col_2 date_1
----- ----- ------
1 3 2016
2 4 2015
And this is table 2:
col_3 col_4 date_2
----- ----- ------
5 8 2014
6 9 2012
I want a result like this:
col_1 col_2 col_3 col_4 date_1 date_2
----- ----- ----- ----- ------ ------
1 3 NULL NULL 2016 NULL
2 4 NULL NULL 2015 NULL
NULL NULL 5 8 NULL 2014
NULL NULL 6 9 NULL 2012
Any solutions?
Using Union All and Null as a different column:
SELECT col_1, col_2, NULL as col_3, NULL as col_4,
date_1, NULL as date_2
FROM table_1
Union All
SELECT NULL, NULL, col_3, col_4, NULL, date_2
FROM table_2
Use union all:
select col_1, col_2, NULL as col_3, NULL as col_4, date_1, NULL as date_2
from table1
union all
select NULL, NULL, col_3, col_4, NULL, date_2
from table2;
Using Join:
select t1.col_1,t1.col_2,t2.col_3,t2.col_4,t1.date_1,t2.date_2
from t1
full join t2
on t1.col_1=t2.col_3
order by t1.col_1;

Resources