Fill nulls with lag, but ignore nulls isn't supported - sql

I have a table which looks like this:
ID
money_earned
days_since_start
1
1000
1
1
2000
2
1
null
3
1
3000
4
1
2000
5
2
1000
1
2
null
2
2
100
3
I want that rows, without a value in money_earned (which means that the money_earned column was empty that day) - will and fill the money_earned with last known value, so it to look like this:
ID
money_earned
days_since_start
1
1000
1
1
2000
2
1
2000
3
1
3000
4
1
2000
5
2
1000
1
2
1000
2
2
100
3
I have tried to look up for something like that, but postgresql doesn't support ignore nulls in lag window function :(
thank you!

The LOG() function would be suitable for this purpose, but in your
case you need to get first not null previous element. What the LOG()
function can't handle.
I suggest creating your own function that would return the first non-null value of the previous element.
CREATE OR REPLACE FUNCTION log_not_null(row_num int, dafult text = '1001') RETURNS text LANGUAGE plpgsql AS $$
DECLARE
result text;
index int;
BEGIN
if row_num > 0 then
index := row_num - 1;
else
return dafult;
end if;
result := (select money_earned from my_table offset index limit 1);
if result is null then
return log_not_null(index);
end if;
return result;
END
$$;
select id, log_not_null((row_number() over ())::int) as money_earned, days_since_start from my_table;
Note:
If the previous value is null, then the function is called recursively until it reaches the top element, and if the topmost element is also null, then the function will return the value from the dafult variable.
Demo in sqldaddy.io

You can do this (warning, untested! 😎):
UPDATE yourtable t1
SET money_earned = t3.money_earned
FROM yourtable
LEFT JOIN (SELECT
ID,
MAX(days_since_start) m
FROM yourtable
WHERE ID=t1.ID AND days_since_start<t1.ID) t2 ON t2.ID=i1.ID
INNER JOIN yourtable t3 ON t3.ID=t1.ID and t3.days_since_start = t2.days_since_start
WHERE t1.money_earned is null

Related

How to sum non-null values from multiple columns (workaround for CASE WHEN limitation) Postgresql

So I essentially want to work around the fact that CASE WHEN stops executing when it finds its first TRUE return.
I'd like to sum every instance of a non-null value between multiple columns, and group these based on my ID. Example table:
id
input1
input2
input3
1
a
null
k
2
null
null
b
3
null
null
null
4
q
null
r
5
x
p
j
6
null
y
q
I would like the output of my function to be:
id
total_inputs
1
2
2
1
3
0
4
2
5
3
6
2
Any work arounds? Is a custom function in order to create a count of unique or non-null entries across multiple columns, grouped by row?
I know I can create a CTE and assign 1's to each non-null column but that seems tedious (my data set has 39 inputs) - and I'd like to have a reusable function I could use again in the future.
You could use a simple aggregation as the following:
Select id,
Count(input1) + Count(input2) + Count(input3) As total_inputs
From table_name
Group By id
Order By id
Noting that Count(inputX) = 0, where inputX is null.
See a demo.
We can simply use:
select ID,
case when input1 is not null then 1 else 0 end
+ case when input2 is not null then 1 else 0 end
+ ...
+ case when input39 is not null then 1 else 0 end as total_inputs
from ...
No need to group by if you want every row (or count, we are not aggregating rows - that is what COUNT()..GROUP BY is for), or CTE.
Also, for some PostgreSQL versions, there is a num_nulls function to count null parameters:
select
, 32-num_nulls(input1, input2, input3, ..., input32)

Consecutively update values starting from a number in the table SQL

I have a question regarding the usage of UPDATE statement. Is there a way for me to code the following scenario with only SQL instead of writing a stored procedure?
I tried to simplify the case.
myTable has 4 columns and its values are as follows:
COMP1 COMP2 NO ACTIVE
0 0 4 N
4 1 Y
2 2 21 Y
3 1 1 Y
1 3 43 Y
2 1 Y
3 1 12 Y
2 2 0 Y
3 2 Y
1 1 5 N
I want to select the values WHERE ACTIVE = 'Y' and start updating
their values in NO column by ordering them by COMP1, COMP2 ASC.
The numbering has to start from the MAX(NO) value in the table WHERE ACTIVE = 'N'
The rows that have same COMP1 and COMP2 will have same NO values.
(Update after comment) NO column is not always NULL. There are erroneous values in this column which are need to be corrected by the update query.
So that, after the UPDATE statement is executed, myTable will be as follows (if we order it)
COMP1 COMP2 NO ACTIVE
0 0 4 N
1 1 5 N
1 3 6 Y
2 1 7 Y
2 2 8 Y
2 2 8 Y
3 1 9 Y
3 1 9 Y
3 2 10 Y
4 1 11 Y
I'm wondering if I would be able to do it without the need to select the whole list and looping in a CURSOR in a stored procedure.
(Update: I've written the SP. A simplified version is as follows. It may have problems since I changed all the names and deleted many conditions.)
PROCEDURE updateAllNo
IS
CURSOR c1
IS
SELECT *
FROM MyTable SE
WHERE SE.ACTIVE = 'Y'
ORDER BY SE.COMP1, SE.COMP2;
v_last_no NUMBER := 0;
v_last_comp2 DATE := SYSDATE + 100;
v_last_comp1 DATE := SYSDATE + 100;
v_now_comp2 DATE := SYSDATE;
v_now_comp1 DATE := SYSDATE;
BEGIN
SELECT MAX (SE.NO)
INTO v_last_no
FROM MyTable SE
WHERE SE.ACTIVE = 'N';
SELECT MAX (SE.COMP1), MAX (SE.COMP2)
INTO v_last_comp1, v_last_comp2
FROM MyTable SE
WHERE SE.ISLEMBASARILI = 'E'
AND SE.NO = v_last_no;
FOR r1 IN c1
LOOP
BEGIN
v_now_comp2 := r1.COMP2;
v_now_comp1 := r1.COMP1;
IF v_now_comp2 != v_last_comp2 OR v_now_comp1 != v_last_comp1
THEN
v_last_no := v_last_no + 1;
END IF;
UPDATE MyTable SE
SET SE.NO = v_last_no
WHERE SEQ_ID = r1.seq_id;
v_last_comp2 := v_now_comp2;
v_last_comp1 := v_now_comp1;
END;
END LOOP;
COMMIT;
END;
I'm using Oracle 11g.
The new number can be retrieved using a window function:
The following will retrieve the active rows and calculate the new value for no
select comp1,
comp2,
row_number() over (order by comp1, comp2) + (select max(no) from mytable where active = 'N') as rn
from mytable
where active = 'Y'
This can now be used in a MERGE statement to run an update against the table. As the table apparently has no PK, I will use the ROWID to match the rows:
merge into mytable tg
using (
select rowid as rid,
comp1,
comp2,
row_number() over (order by comp1, comp2) + (select max(no) from mytable where active = 'N') as rn
from mytable
where active = 'Y'
) t on (t.rid = tg.rowid)
when matched then update
set no = t.rn;
This will most definitely be faster than a loop with single row updates - especially for larger tables.
I am assuming that the field NO is always null when ACTIVE='Y'. If this is the case, then MAX(NO) will return the same value whether or not the WHERE ACTIVE = 'N' condition is specified. With this assumption, this UPDATE statement should do the trick.
UPDATE myTable
SET NO = (SELECT MAX(NO) FROM myTable) + 1
WHERE ROWID IN
(SELECT ROWID FROM myTable
WHERE ACTIVE = 'Y'
ORDER BY COMP1, COMP2);

Select rows until condition met

I would like to write an Oracle query which returns a specific set of information. Using the table below, if given an id, it will return the id and value of B. Also, if B=T, it will return the next row as well. If that next row has a B=T, it will return that, and so on until a F is encountered.
So, given 3 it would just return one row: (3,F). Given 4 it would return 3 rows: ((4,T),(5,T),(6,F))
id B
1 F
2 F
3 F
4 T
5 T
6 F
7 T
8 F
Thank you in advance!
Use a sub-query to find out at what point you should stop, then return all row from your starting point to the calculated stop point.
SELECT
*
FROM
yourTable
WHERE
id >= 4
AND id <= (SELECT MIN(id) FROM yourTable WHERE b = 'F' AND id >= 4)
Note, this assumes that the last record is always an 'F'. You can deal with the last record being a 'T' using a COALESCE.
SELECT
*
FROM
yourTable
WHERE
id >= 4
AND id <= COALESCE(
(SELECT MIN(id) FROM yourTable WHERE b = 'F' AND id >= 4),
(SELECT MAX(id) FROM yourTable )
)

If null, return multiple values

I had posted a similar question earlier - a slightly different requirement here.
I have a textBox which returns the user-selected 'Number' value.(eg. : 100,200,300)
What needs to be done is basically check a table MyTable if a record/records exist for the particular Number value selected by user. If it returns NULL, then I need to return the records for the default Number value of 999.
MyTable:
id Number MyVal
1 100 55
2 200 66
3 400 22
4 400 12
5 999 23
6 999 24
Here's what I have so far :(Assuming textBoxInput(Number) = 300)
SELECT Myval
from MyTable
where id in (
SELECT ISNULL(
SELECT id
from MyTable
where Number=300,
select id
from MyTable
where Number = 999
)
)
So here, since Number=300 does not exist in the table, return the records for Number=999.
But when I run this query, I'm getting an error 'Subquery returned more than 1 value...'
Any suggestions/ideas?
This should work:
SELECT Myval
from MyTable
where Number = #Number
OR (NOT EXISTS(SELECT * FROM MyTable WHERE Number = #Number) AND Number = 999)
SELECT id, Myval
FROM MyTable
WHERE id = #id
UNION
SELECT id, 999 AS Myval
FROM MyTable
WHERE id = 999
AND #id IS NULL;

Check that all rows match a given criterion

requestId Consultantid statusid
1 2 10
2 2 10
3 2 10
I want to check if every row has a statusid of 10.
if (every row has a statusid of 10) then
-----do this
endif;
I'm a bit rusty on PL-SQL, but something like this would work in T-SQL:
if not exists (select * from your_table where statusid <> 10) then
-- whatever
end
Edit:
Ok, apparantly in PL-SQL, you need to do something like this:
DECLARE
notallten INTEGER;
BEGIN
SELECT COUNT(*) INTO notallten
FROM your_table
WHERE statusid <> 10
AND ROWNUM = 1;
IF notallten = 0 THEN
-- Do something
END IF;
END;
I don't have an Oracle server to test on though.
declare
v_exists_status_10 number(1);
...
begin
...
-- v_exists_status_10 = 0 if no such row exist, 1 if at least one does
select count(*)
into v_exists_status_10
from dual
where exists
(select * from your_table where statusid <> 10);
if v_exists_status_10 > 0 then
...
Note that you could also do a dumb COUNT() into a variable, but it could be massively inefficient compared to EXISTS. With a COUNT() you'd have to scan all the records, whereas with an EXISTS as soon as it hits a statusid = 10 it can stop scanning.
Simpler solution, that accounts for NULLs in statusid:
for r in (
select 1 dummy
from your_table
where (statusid != 10 or statusid is null)
and rownum = 1) loop
-----do this
end loop;