How to use LAG function in oracle pl sql until non zero or last row is reached? - sql

I want to write LAG function for table column in such a way that if column value is zero it should lag value for that id if it is again zero for previous row it should LAG again until it reaches a non zero row or last row please help.Thanks in advance

Use LAG() IGNORE NULLS ... combined with a CASE statement:
LAG(
CASE COLUMN_NAME
WHEN 0
THEN NULL
ELSE COLUMN_NAME
END
) IGNORE NULLS OVER ( ORDER BY OTHER_COLUMN )
For example:
WITH DATA ( id, value ) AS (
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 0 FROM DUAL UNION ALL
SELECT 3, 0 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 0 FROM DUAL
)
SELECT id,
value,
LAG(
CASE VALUE
WHEN 0
THEN NULL
ELSE VALUE
END
) IGNORE NULLS
OVER ( ORDER BY id )
AS prev_non_zero_value
FROM DATA;
Outputs:
ID VALUE PREV_NON_ZERO_VALUE
---------- ---------- -------------------
1 1
2 0 1
3 0 1
4 2 1
5 0 2

Wouldn't it be simpler to call the same table twice, (for example, call them A and B).
In B, filter out all the rows where the value equals zero, and then do a simple LAG(field,1).
Then just JOIN the two tables.
Voila!

Related

How can I select if row contains a specific type, not contains select existing row

I have a table below. There are 3 types(A,B,C) available. I want to create general a Sql query. If row has type B or type C, the row with type B or C should be listed. If row has just type A, the row with type A should be listed.
Table;
Number
Type
1
A
1
B
2
A
3
A
3
C
4
A
5
A
6
A
6
B
6
C
Expected result when the query run;
Number
Type
1
B
2
A
3
C
4
A
5
A
6
B
6
C
How can I create the query? Thank you in advance for your help.
I would assign each type a precedence, and only return the types of the highest precedence.
Where two types can be the same precedence (because you want to return both), RANK() (rather than ROW_NUMBER()) will ensure both are assigned the same value.
WITH
precedence AS
(
SELECT
*,
RANK()
OVER (
PARTITION BY Number
ORDER BY CASE Type WHEN 'C' THEN 2
WHEN 'B' THEN 2
WHEN 'A' THEN 1
ELSE 0
END
DESC
)
AS row_precedence
FROM
your_table
)
SELECT
*
FROM
precedence
WHERE
row_precedence = 1
One option to make it a little neater could be to use APPLY (or a join on a lookup table) to derive the integers outside of the window function's code...
WITH
precedence AS
(
SELECT
*,
RANK()
OVER (
PARTITION BY Number
ORDER BY type_precedence.value DESC
)
AS row_precedence
FROM
your_table
CROSS APPLY
(
SELECT
CASE Type WHEN 'C' THEN 2
WHEN 'B' THEN 2
WHEN 'A' THEN 1
ELSE 0
END
AS value
)
AS type_precedence
)
SELECT
*
FROM
precedence
WHERE
row_precedence = 1
Demo: https://dbfiddle.uk/2Abwpa8p

Select rows using a count condition inside a where clause

There is a table, MyTable(ID, Type, Date). Column 'Type' can have a value of 1 or 2.
Top 'x' rows, ordered by 'Date' and satisfying the following condition, have to be selected.('a' and 'b' are integer values)
The selected 'x' rows can contain only a maximum of 'a' Type 1 rows and 'b' Type 2 rows.(If a+b < x, then only a+b rows have to be selected.)
I might be completely wrong but I have an idea of doing this by having count() inside a WHERE clause. But I am not sure of how to do it.
How do I go about this problem?
UPDATE:
Example -
x = 5
Case 1:
a = 5, b = 5
Result: Rows 1,2,3,4,5
Case 2:
a = 4, b = 1
Result: Rows 1,2,4,6,8
Case 3:
a = 1, b = 5
Result: Rows 1,2,3,5,7
Case 4:
a = 2, b = 1
Result: Rows 1,2,4
You can do it by nesting a UNION inside another query:
select top #x *
from (
select top #a *
from table
where type = '1'
order by [date]
union
select top #b *
from table
where type = '2'
order by [date]
) t
order by [date]

How to write query to return value regardless of existance?

Given this:
with data_row as (select 1 as col_1 from dual)
select 'Y' as row_exists from dual where exists
(select null
from data_row
where col_1 in (2,1))
How can I get this?
Col_1 Row_exists
--------------------
1 Y
2 N
In order to get a row of output, you need a row of input. You want to get the second row with a "2", but there is no table with that value.
The approach is to generate a table that has the values that you want, and then use left outer join to find which match:
with data_row as (
select 1 as col_1
from dual
),
what_i_care_about as (
select 1 as col from dual union all
select 2 from dual
)
select wica.col,
(case when dr.col_1 is NULL then 'N' else 'Y' end) as row_exists
from what_i_care_about wica left outer join
data_row dr
on wica.col = dr.col_1;
You cannot do directly what you want -- which is to create a row for each missing value in the in list. If you have a lot of values and they are consecutive numeric, then you can use connect by or a recursive CTE to generate the values.

SUM in Oracle: return NULL only when all fields are NULL

I have a rather complicated select query that contains two types of SUM expressions. The Simple SUM:
SUM(X)
And the more complicated SUM/CASE:
SUM(CASE WHEN TYPE IN (
'ORANGES',
'LEMONS',
'LIMES')
THEN X
ELSE 0 END) AS SUM_CITRUS
Now, here's the problem: I need both of these SUMS to evaluate to NULL only if all of the values summed are null. If any one of the values summed is a value then the sum needs to be returned as if all null values were substituted for 0.
For the cases shown, how might this be achieved? My concerns are efficiency and simplicity, in that order.
I need both of these SUMS to evaluate to NULL only if all of the
values summed are null.
It will by default. Just replace 0 with null in your ELSE condition.
09:43:30 SYSTEM#dwal> ed
Wrote file S:\spool\dwal\BUFFER_SYSTEM_65.sql
1 with t (x, y) as (
2 select null, 1 from dual union all
3 select null, 1 from dual union all
4 select null, 2 from dual
5 )
6* select sum(x), sum(case y when 1 then x else null end ) from t
09:43:40 SYSTEM#dwal> /
SUM(X) SUM(CASEYWHEN1THENXELSENULLEND)
---------- -------------------------------
Elapsed: 00:00:00.00
09:43:41 SYSTEM#dwal> 2
2* select null, 1 from dual union all
09:43:59 SYSTEM#dwal> c/null/1
2* select 1, 1 from dual union all
09:44:03 SYSTEM#dwal> /
SUM(X) SUM(CASEYWHEN1THENXELSENULLEND)
---------- -------------------------------
1 1
Elapsed: 00:00:00.01
Oracle itself is doing what you want: for sum(), if all elements are null, sum() will return null
Therefore, you should be able to do something like:
SELECT FOO,
SUM(CASE
WHEN TYPE IN (
'ORANGES',
'LEMONS',
'LIMES') THEN X
WHEN TYPE IS NULL THEN NULL
ELSE 0
END) AS SUM_CITRUS
FROM YOUR_TABLE
GROUP BY FOO
(Here I assume there are lots of different other type, and you want orange/lemon/limes to count as X, other type count as 0, null count as null)

Can I get the minimum of 2 columns which is greater than a given value using only one scan of a table

This is my example data (there are no indexes and I do not want to create any):
CREATE TABLE tblTest ( a INT , b INT );
INSERT INTO tblTest ( a, b ) VALUES
( 1 , 2 ),
( 5 , 1 ),
( 1 , 4 ),
( 3 , 2 )
I want the minimum value in of both column a and column b which is greater then a given value. E.g. if the given value is 3 then I want 4 to be returned.
This is my current solution:
SELECT MIN (subMin) FROM
(
SELECT MIN (a) as subMin FROM tblTest
WHERE a > 3 -- Returns 5
UNION
SELECT MIN (b) as subMin FROM tblTest
WHERE b > 3 -- Returns 4
)
This searches the table twice - once to get min(a) once to get min(b).
I believe it should be faster to do this with just one pass. Is this possible?
You want to use conditional aggregatino for this:
select min(case when a > 3 then a end) as minA,
min(case when b > 3 then b end) as minB
from tblTest;
To get the minimum of both values, you can use a SQLite extension, which handles multiple values for min():
select min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
The only issue is that the min will return NULL if either argument is NULL. You can fix this by doing:
select coalesce(min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
),
min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
This version will return the minimum value, subject to your conditions. If one of the conditions has no rows, it will still return the minimum of the other value.
From the top of my head, you could modify the table and add a min value column to store the minimum value of the two columns. then query that column.
Or you can do this:
select min(val)
from
(
select min(col1, col2) as val
from table1
)
where
val > 3
The outer SELECT, queries the memory, not the table itself.
Check SQL Fiddle