I have a query where based on a column value, my sort will be dynamic. So, it is something like this:
ROW_NUMBER() OVER (PARTITION BY last_action
ORDER BY CASE
WHEN last_action = 'Insert' THEN company_name
ELSE percent_change
END DESC
Issue here is that they are different data types, so it throws an error. If I convert the "percent_change" to character, then it does not sort numerically. And, to complicate things, they want the "percent_change" in DESC and the "company_name" in ASC.
So, I was thinking if there is a way to convert the actual "company_name" into some numerical value, and subtract it from 0, and then I can so the numerical sort in DESC order.
Any ideas would be helpful.......
Sounds like you're after something like this, then:
ROW_NUMBER() OVER (PARTITION BY last_action
ORDER BY CASE
WHEN last_action = 'Insert' THEN company_name
END,
CASE
WHEN last_action = 'Insert' THEN NULL
ELSE percent_change
END DESC NULLS LAST)
This works by splitting the ordering out into two expressions, but because they're conditional on the column in the partition by clause, only one of them is going to impact the ordering at any one time.
What if you take your case outside of window function -
CASE WHEN last_action = 'Insert' THEN
ROW_NUMBER() OVER(PARTITION BY last_action ORDER BY company_name)
ELSE ROW_NUMBER() OVER(PARTITION BY last_action ORDER BY percent_change DESC)
END
Have you looked at using the DECODE function?
Decode is kind of like a simplified CASE statement.
You can use it in the order by clause (or anywhere you can use an expression)
DECODE(expr, key1, value1, key2, value2, default)
For example:
ORDER BY
DECODE(tbl.companyName, 'name1', 1, 'name2', 2, 'name3', 3, 9999)
The last value is the default if the first expression doesn't match.
In your example, I think you can combine CASE and DECODE to yield the final expression for the order by clause.
Anther trick for sorting numbers as characters is to pad the string with leading zeros. You can use LPAD or concatenation and substr (RIGHT)
LPAD(expr, 9, '0');
SUBRSTR('000000000' || expr, -9);
I use something like the above to fix SSNs that have had leading 0 dropped (for example when they come through Excel)
Related
Does apex_string.split always guarantee that the order of the rows returned is the order of the characters of the string ?
Can I rely on the rownum to always correspond to 1 for the first character of the split string ?
or do I need to add a order by rownum ?
What is the method to get the rows in the same order of the characters of the string ?
My requirement is to insert the rows returned by apex_string.split in the same order as the characters of the string.
I am currently executing the below, will this maintain the character order ?
select t.column_value value, rownum seq
from table(apex_string.split('test','')) t
bulk collect into ins_arr;
for i in ins_arr.first..ins_arr.last
loop
/* execute insert statement */
insert into table (seq, value )
values (ins_arr.seq,ins_arr.value);
end loop
The insert should result in
seq
value
1
t
2
e
3
s
4
t
Thank you in advance,
I don't think it's guaranteed, becuase if it was, it would be in the documentation. But I think you can accomplish what you want by changing your routine. (Note, I have not verified this.)
insert into table (seq, value)
select t.column_value value,
row_number() over (order by t.column_value)
from table(apex_string.split('test','')) t
I think you can do the same with rownum, but I'm never 100% sure what order the rownum and the order by happen in.
i have a select(water readings, previous water reading, other columns) , a "where clause" that is based on date water reading date. however for previous water reading it must not consider the where clause. I want to get previous meter reading regardless where clause date range.
looked at union problem is that i have to use the same clause,
SELECT
WATERREADINGS.name,
WATERREADINGS.date,
LAG( WATERREADINGS.meter_reading,1,NULL) OVER(
PARTITION BY WATERREADINGS.meter_id,WATERREADINGS.register_id
ORDER BY WATERREADINGS.meter_id DESC,WATERREADINGS.register_id
DESC,WATERREADINGS.readingdate ASC,WATERREADINGS.created ASC
) AS prev_water_reading,
FROM WATERREADINGS
WHERE waterreadings.waterreadingdate BETWEEN '24-JUN-19' AND
'24-AUG-19' and isactive = 'Y'
The prev_water_reading value must not be restricted by the date BETWEEN '24-JUN-19' AND '24-AUG-19' predicate but the rest of the sql should be.
You can do this by first finding the previous meter readings for all rows and then filtering those results on the date, e.g.:
WITH meter_readings AS (SELECT waterreadings.name,
waterreadings.date dt,
lag(waterreadings.meter_reading, 1, NULL) OVER (PARTITION BY waterreadings.meter_id, waterreadings.register_id
ORDER BY waterreadings.readingdate ASC, waterreadings.created ASC)
AS prev_water_reading,
FROM waterreadings
WHERE isactive = 'Y')
-- the meter_readings subquery above gets all rows and finds their previous meter reading.
-- the main query below then applies the date restriction to the rows from the meter_readings subquery.
SELECT name,
date,
prev_water_reading,
FROM meter_readings
WHERE dt BETWEEN to_date('24/06/2019', 'dd/mm/yyyy') AND to_date('24/08/2019', 'dd/mm/yyyy');
Perform the LAG in an inner query that is not filtered by dates and then filter by the dates in the outer query:
SELECT name,
"date",
prev_water_reading
FROM (
SELECT name,
"date",
LAG( meter_reading,1,NULL) OVER(
PARTITION BY meter_id, register_id
ORDER BY meter_id DESC, register_id DESC, readingdate ASC, created ASC
) AS prev_water_reading,
waterreadingdate --
FROM WATERREADINGS
WHERE isactive = 'Y'
)
WHERE waterreadingdate BETWEEN DATE '2019-06-24' AND DATE '2019-08-24'
You should also not use strings for dates (that require an implicit cast using the NLS_DATE_FORMAT session parameter, which can be changed by any user in their own session) and use date literals DATE '2019-06-24' or an explicit cast TO_DATE( '24-JUN-19', 'DD-MON-RR' ).
You also do not need to reference the table name for every column when there is only a single table as this clutters up your code and makes it difficult to read and DATE is a keyword so you either need to wrap it in double quotes to use it as a column name (which makes the column name case sensitive) or should use a different name for your column.
I've added a subquery with previous result without filter and then joined it with the main table with filters:
SELECT
WATERREADINGS.name,
WATERREADINGS.date,
w_lag.prev_water_reading
FROM
WATERREADINGS,
(SELECT name, date, LAG( WATERREADINGS.meter_reading,1,NULL) OVER(
PARTITION BY WATERREADINGS.meter_id,WATERREADINGS.register_id
ORDER BY WATERREADINGS.meter_id DESC,WATERREADINGS.register_id
DESC,WATERREADINGS.readingdate ASC,WATERREADINGS.created ASC
) AS prev_water_reading
FROM WATERREADINGS) w_lag
WHERE waterreadings.waterreadingsdate BETWEEN '24-JUN-19' AND '24-AUG-19' and isactive = 'Y'
and WATERREADINGS.name = w_lag.name
and WATERREADINGS.date = w_lag.date
I don't often use ORACLE PL/SQL by the way but i need to understand what if anything in this function created by someone else
in the company before me is wrong as for it is not returning the latest record i've been told. I found out in some other forum issues that they
suggested to use the max(dateColumn) instead of "row_numer = 1" for example but not quite sure how to and where to incorporate that.
-- Knowing that --
We use Oracle version 12,
CustomObjectTypeA is an custom Oracle OBJECT TYPE defined by some old employee not longer in here,
V_OtherView is of Table_Mnd type beeing defined by some old employee not longer in here,
V_ABC_123 is a view created by some old employee not longer in here as well.
CREATE OR REPLACE FUNCTION F_TABLE_APPROVED (NUMBER_F_UPD number, NUMBER_F_GET VARcHAR2)
RETURN Table_Mnd
IS
V_OtherView Table_Mnd
BEGIN
SELECT CustomObjectTypeA (FromT.NUMBER_F,
FromT.OP_CODE,
FromT.CATG_CODE,
FromT.CATG_NAME,
FromT.CATG_SORT,
FromT.ORG_CODE,
FromT.ORG_NAME
FromT.DATA_ENTRY_VALID,
FromT.NUMBER_RECEIVED,
FromT.YEAR_1,
FromT.YEAR_2)
BULK COLLECT INTO V_OtherView
FROM (SELECT NUMBER_F,
OP_CODE,
CATG_CODE,
CATG_NAME,
CATG_SORT,
ORG_CODE,
ORG_NAME
DATA_ENTRY_VALID,
NUMBER_RECEIVED,
YEAR_1,
YEAR_2,
ROW_NUMBER() OVER (PARTITION BY BY ORG_CODE ORDER BY NUMBER_RECEIVED DESC, LOAD_DATE DESC) AS ROW_NUMBER
FROM V_ABC_123
WHERE NUMBER_F = NUMBER_F_UPD AND DATA_ENTRY_VALID <> 'OnGoing'
AND LOAD_DATE >= (SELECT sysdate-10 FROM dual)
AND LOAD_DATE <= (SELECT DISTINCT LOAD_DATE
FROM V_ABC_123
WHERE NUMBER_RECEIVED = NUMBER_F_GET)) FromT
WHERE FromT.ROW_NUMBER=1;
RETURN V_OtherView;
END F_TABLE_APPROVED;
The important bits of the query are:
SELECT ...
FROM (select ...,
ROW_NUMBER()
OVER (PARTITION BY ORG_CODE
ORDER BY NUMBER_RECEIVED DESC,
LOAD_DATE DESC) AS ROW_NUMBER
...) FromT
WHERE FromT.ROW_NUMBER = 1;
The "ROW_NUMBER" column is computed according to the following window clause:
PARTITION BY ORG_CODE
ORDER BY NUMBER_RECEIVED DESC, LOAD_DATE DESC
Which means that for each ORG_CODE, it will sort all the records by NUMBER_RECEVED,LOAD_DATE in descending order. Note that if the columns are Oracle DATEs, they will only be accurate to the nearest second; so if there are multiple records with date/times in the exact same 1-second interval, this sort order will not be guaranteed unique. The logic of ROW_NUMBER will therefore pick one of them arbitrarily (i.e. whichever record happens to be emitted first) and assign it the value "1", and this will be deemed the "latest". Subsequent executions of the same SQL could (in theory) return a different record.
The suspicious part is NUMBER_RECEIVED which sounds like it's a number, not a date? Sorting by this means that the records with the highest NUMBER_RECEIVED will be preferred. Was this intentional?
I'm not sure why the PARTITION is there, this would cause the query to return one "latest" record for each value of ORG_CODE that it finds. I can only assume this was intentional.
The problem is that the query can only determine the "latest record" as well as it can based on the data provided to it. In this case, it's possible the data is simply not granular enough to be able to decide which record is the actual "latest" record.
Can we use more than one column in a single WHEN clause while using CASE in oracle?
A CASE expression can use more than one column in its logic, e.g.
CASE WHEN col1 > val1 AND col2 > val2 THEN 1 ELSE 0 END
But a CASE expression must return a single scalar value.
no you cant, but you can use multi when clause statement like this:
select
case when .... end col_a,
case when .... end col_b
from
xxxx
Of course you can. For example:
select ...
from emp
where case when bonus is null then salary else salary + bonus end > 4000
Here emp is a table, and bonus and salary are two of the columns in that table. The same WHERE clause can be expressed more simply, but regardless of reformulation, it will refer to both columns. (And there will be a CASE of some sort somewhere - NVL and COALESCE are CASE expressions with a different syntax.)
I have a select statement that includes a call to the row_number() function, which technically gives me a unique id per row that is returned.
SELECT f.*, row_number() as row_id OVER(ORDER BY f.name)
FROM widgets f
It'd be kinda cool if i could somehow use this row_id to sort the table. I'd now like to try to use the row_number to sort like so:
table.sort(mytable, function(a,b) return a.row_id< b.row_id end)
I'm just trying to save myself from having to loop through the results to add a unique id and then sort it but maybe it's not possible.
I don't know how you would express this in Lua, but in Postgres you can order by a column alias. The Postgres query would be:
SELECT f.*, row_number() OVER (ORDER BY f.name) as row_id
FROM widgets f
ORDER BY row_id;
The fact that you don't want to order by f.name suggests that you have duplicates. Do note that ordering in SQL is not guaranteed to be stable. That is, duplicate names could be in different orders. If you have a way of making the ordering stable (i.e. by uniquely identifying each row), you can use those columns in the order by.