Selecting the latest record within a table - sql

I have ant an Oracle v11 database, and whilst I do not have the schema definition of the tables, I have illustrated what I am trying to achieve below.
This is what the table looks like
I am trying to transform the data by selecting only the latest rows, the table keeps an history of changes, I am not interested in the changes only the latest value for every present issue
This is what I have so far.
select issueno,
case (when fieldname = 'name' then string_value end) name,
case (when fieldname = 'point' then string_value end) point
from issues
where issueno = 1234
The issue with the query above is that it returns 4 rows, I would like to return only a single row.

You can get the latest date by using LAST ORDER BY clause within the MAX() KEEP (..) values for transition_date(or load_date column, depending on which you mean replace within the query) such as
WITH i AS
(
SELECT CASE WHEN fieldname = 'name' THEN
MAX(string_value) KEEP (DENSE_RANK LAST ORDER BY transition_date)
OVER (PARTITION BY issue_no, fieldname)
END AS name,
CASE WHEN fieldname = 'point' THEN
MAX(string_value) KEEP (DENSE_RANK LAST ORDER BY transition_date)
OVER (PARTITION BY issue_no, fieldname)
END AS point
FROM issues
)
SELECT MAX(name) AS name, MAX(point) AS point
FROM i
But, if ties(equal values) occur for the related date values, then consider using DENSE_RANK() function in order to compute the values returning equal to 1 along with ROW_NUMBER() to be able to use with the JOIN clause in the main query such as
WITH i AS
(
SELECT i.*,
DENSE_RANK() OVER ( PARTITION BY issue_no, fieldname
ORDER BY transition_date DESC) AS dr,
ROW_NUMBER() OVER ( PARTITION BY issue_no, fieldname
ORDER BY transition_date DESC) AS rn
FROM issues i
)
SELECT i1.string_value AS name, i2.string_value AS point
FROM ( SELECT string_value, rn FROM i WHERE dr = 1 AND fieldname = 'name' ) i1
FULL JOIN ( SELECT string_value, rn FROM i WHERE dr = 1 AND fieldname = 'point' ) i2
ON i2.rn = i1.rn
Demo

Assuming that you want to have the latest record by the column load_date
select issueno,
case (when fieldname = 'name' then string_value end) name,
case (when fieldname = 'point' then string_value end) point
from issues
where issueno = 1234 and
(fieldname , load_date) in (select fieldname ,max(load_date) from issues where issueno=1234 group by fieldname)

I would use a subquery + window function to achieve what you asked for (assuming you use are basing load_date to determine the latest record)
select issueno,
case (when fieldname = 'name' then string_value end) name,
case (when fieldname = 'point' then string_value end) point
from
(
SELECT name, point, ROW_NUMBER() OVER(PARTITION BY ISSUENO, FIELDNAME ORDER BY LOAD_DATE DESC) RN
FROM issues
)
where issueno = 1234
AND RN = 1
The syntax ROW_NUMBER() OVER ([query_partition_clause] order_by_clause) is actually a window function that assign a ranking to each rows governed by how you declare the rule in [query_partition_clause] order_by_clause

See whether something like this helps; read comments within code.
SQL> with issues (issueno, fieldname, string_value,
2 transition_date, transition_id, load_date)
3 as
4 -- sample data; you have it in a table, don't type that
5 (select 1234, 'name', null , date '2021-01-01', 1, date '2021-01-02' from dual union all
6 select 1234, 'name', 'Tom', date '2021-02-11', 2, date '2021-02-12' from dual union all
7 select 1234, 'point', '0' , date '2021-02-04', 3, date '2021-02-05' from dual union all
8 select 1234, 'point', '5' , date '2021-02-10', 5, date '2021-02-11' from dual
9 ),
10 -- query you need begins here
11 temp as
12 -- rank values partitioned by ISSUENO and FIELDNAME, sorted by TRANSITION_ID
13 (select issueno, fieldname, string_value,
14 row_number() over (partition by issueno, fieldname
15 order by transition_id desc) rn
16 from issues
17 )
18 select issueno,
19 max(case when fieldname = 'name' then string_value end) name,
20 max(case when fieldname = 'point' then string_value end) point
21 from temp
22 where rn = 1
23 group by issueno;
ISSUENO NAME POINT
---------- ---------- ----------
1234 Tom 5
SQL>

Related

How to return a redundant row for duplicate ID in single row in Oracle

I have a Audit kind of table where we store some information based on some triggers in other tables.
ID, Changed_Column, OldValue, NewValue will be available.
Now there is a possibility for same Id there will be 3-4 duplicates since the changed column will have different values I wants to merge them into single row and take data
For exmaple,
ID ChangedColumn OldValue NewValue
1 Name Bob Roy
1 Age 26 28
1 Section B C
When we select now it will display all the rows into separte but I wants to self join and retrieve only one record by merging based on ID value
Expected result is like,
ID Name Age Section ChangedColumns
1 was :Bob now : Roy was:26 now:28 Was:B now:C Name, Age, Section
to Group the column names you can use listagg function.
to convert rows to columns use Pivot function.
with tab as(
select 1 as id, 'Name' as col, 'Bob' as OldValue , 'Roy' as NewValue from dual union all
select 1 as id, 'Age', '26', '28' as NewValue from dual union all
select 1 as id, 'Section', 'B', 'C' as NewValue from dual
)
select *
from (
select id
,t.col as col
,max('was: '|| t.OldValue || ' now: ' || t.NewValue) as val
,listagg(t.col,',') within group(order by t.id) OVER (PARTITION BY null) as ChangedColumn
from tab t
group by id,t.col
)
pivot ( max(val) for col in('Name','Age','Section'));
db<>fiddle here
This seems pretty simple to do using conditional aggregation:
select id,
max(case when col = 'Name' then str end) as name,
max(case when col = 'Age' then str end) as age,
max(case when col = 'Section' then str end) as section
from (select t.*, ('was: ' || OldValue || ' now: ' || NewValue) as str
from t
) t
group by id;
Here is a db<>fiddle.

How to return two values from PostgreSQL subquery?

I have a problem where I need to get the last item across various tables in PostgreSQL.
The following code works and returns me the type of the latest update and when it was last updated.
The problem is, this query needs to be used as a subquery, so I want to select both the type and the last updated value from this query and PostgreSQL does not seem to like this... (Subquery must return only one column)
Any suggestions?
SELECT last.type, last.max FROM (
SELECT MAX(a.updated_at), 'a' AS type FROM table_a a WHERE a.ref = 5 UNION
SELECT MAX(b.updated_at), 'b' AS type FROM table_b b WHERE b.ref = 5
) AS last ORDER BY max LIMIT 1
Query is used like this inside of a CTE;
WITH sql_query as (
SELECT id, name, address, (...other columns),
last.type, last.max FROM (
SELECT MAX(a.updated_at), 'a' AS type FROM table_a a WHERE a.ref = 5 UNION
SELECT MAX(b.updated_at), 'b' AS type FROM table_b b WHERE b.ref = 5
) AS last ORDER BY max LIMIT 1
FROM table_c
WHERE table_c.fk_id = 1
)
The inherent problem is that SQL (all SQL not just Postgres) requires that a subquery used within a select clause can only return a single value. If you think about that restriction for a while it does makes sense. The select clause is returning rows and a certain number of columns, each row.column location is a single position within a grid. You can bend that rule a bit by putting concatenations into a single position (or a single "complex type" like a JSON value) but it remains a single position in that grid regardless.
Here however you do want 2 separate columns AND you need to return both columns from the same row, so instead of LIMIT 1 I suggest using ROW_NUMBER() instead to facilitate this:
WITH LastVals as (
SELECT type
, max_date
, row_number() over(order by max_date DESC) as rn
FROM (
SELECT MAX(a.updated_at) AS max_date, 'a' AS type FROM table_a a WHERE a.ref = 5
UNION ALL
SELECT MAX(b.updated_at) AS max_date, 'b' AS type FROM table_b b WHERE b.ref = 5
)
)
, sql_query as (
SELECT id
, name, address, (...other columns)
, (select type from lastVals where rn = 1) as last_type
, (select max_date from lastVals where rn = 1) as last_date
FROM table_c
WHERE table_c.fk_id = 1
)
----
By the way in your subquery you should use UNION ALL with type being a constant like 'a' or 'b' then even if MAX(a.updated_at) was identical for 2 or more tables, the rows would still be unique because of the difference in type. UNION will attempt to remove duplicate rows but here it just isn't going to help, so avoid that wasted effort by using UNION ALL.
----
For another way to skin this cat, consider using a LEFT JOIN instead
SELECT id
, name, address, (...other columns)
, lastVals.type
, LastVals.last_date
FROM table_c
WHERE table_c.fk_id = 1
LEFT JOIN (
SELECT type
, last_date
, row_number() over(order by last_date DESC) as rn
FROM (
SELECT MAX(a.updated_at) AS last_date, 'a' AS type FROM table_a a WHERE a.ref = 5
UNION ALL
SELECT MAX(b.updated_at) AS last_date, 'b' AS type FROM table_b b WHERE b.ref = 5
)
) LastVals ON LastVals.rn = 1

How to do sorting and then numbering on an Oracle database

As an example I have a database with the following information
Name Number
Boris
Trevor
Arthur
bessie
big Dave
BOB
I want to be able to sort that data in the below order and then add a number to the number column in that specific order
Name Number
Arthur 1
BOB 2
Boris 3
big Dave 4
bessie 5
Trevor 6
I can select using the order I have specified using
select DB.TABLE.NAME , case
when row_number() over(partition by lower(DB.TABLE.NAME )
order by DB.TABLE.NAME ) = 1
then 1
else 0
end as result
from DB.TABLE;
but I then have no idea how to apply the numbers to the numbers column.
If I try a different method of sorting, I can use a sequence to apply the numbers but the order is not what I want. It seems to be the row_number() function that is causing me problems.
Any help would be appreciated.
I think what you're after is something like:
with sample_data as (select 'Boris' name from dual union all
select 'Trevor' name from dual union all
select 'BO Derek' name from dual union all
select 'Arthur' name from dual union all
select 'big dave' name from dual union all
select 'big Dave' name from dual union all
select 'BOB' name from dual union all
select 'BORAT' name from dual union all
select 'Brian' name from dual union all
select 'Big Bad Dom' name from dual)
-- end of creating a subquery "sample_data" to mimic a table with data in it.
-- see SQL below:
select name,
row_number() over (order by upper(substr(name, 1, 1)),
name) row_num
from sample_data
order by upper(substr(name, 1, 1)),
name;
NAME ROW_NUM
----------- ----------
Arthur 1
BO Derek 2
BOB 3
BORAT 4
Big Bad Dom 5
Boris 6
Brian 7
big Dave 8
big dave 9
Trevor 10
To update a table, you'd do something like (assuming name is a unique column):
merge into some_table tgt
using (select name,
row_number() over (order by upper(substr(name, 1, 1)),
name) row_num
from some_table) src
on (tgt.name = src.name)
when matched then
update set tgt.number = src.row_num;
Use a MERGE statement:
merge into the_table t
using (
select rowid as rid,
row_number() over(order by lower(name)) as result
from the_table
) nr on (nr.rid = t.rowid)
when matched then update
set "number" = nr.result;
I am not sure what the CASE should do. It only returns 1 or 0 but the expected result shows you want numbers from 1 to 6, so I removed the CASE
If you have a proper primary key on the table, it's better to use that instead of rowid
Try this.
select DB.TABLE.NAME ,
row_number() over(ORDER by DB.TABLE.NAME ) as Number
from DB.TABLE
order by DB.TABLE.NAME;
Maybe you are looking to update db.table in that case:
update DB.TABLE
set number = (select row_number() over(ORDER by DB.TABLE.NAME ) as Number
from DB.TABLE t1 where t1.name = DB.TABLE.NAME );
Thanks all for your suggestions.
I went with this hacky approach to the answer by #a_horse_with_no_name
CREATE SEQUENCE NEWSEQ
START WITH 1
MAXVALUE 999999999999999999999999999
MINVALUE 1;
merge into DB.TABLE t
using (
select rowid as rid, DB.TABLE.NAME, case
when row_number() over(partition by lower(DB.TABLE.NAME )
order by DB.TABLE.NAME ) = 1
then 1
else 0
end as result
from DB.TABLE
) nr on (nr.rid = t.rowid)
when matched then update
set NUMBER = NEWSEQ.NEXTVAL;
drop sequence NEWSEQ;
It may not be the most efficient way to do it, but it works

How to get the first not null value from a column of values in Big Query?

I am trying to extract the first not null value from a column of values based on timestamp. Can somebody share your thoughts on this. Thank you.
What have i tried so far?
FIRST_VALUE( column ) OVER ( PARTITION BY id ORDER BY timestamp)
Input :-
id,column,timestamp
1,NULL,10:30 am
1,NULL,10:31 am
1,'xyz',10:32 am
1,'def',10:33 am
2,NULL,11:30 am
2,'abc',11:31 am
Output(expected) :-
1,'xyz',10:30 am
1,'xyz',10:31 am
1,'xyz',10:32 am
1,'xyz',10:33 am
2,'abc',11:30 am
2,'abc',11:31 am
You can modify your sql like this to get the data you want.
FIRST_VALUE( column )
OVER (
PARTITION BY id
ORDER BY
CASE WHEN column IS NULL then 0 ELSE 1 END DESC,
timestamp
)
Try this old trick of string manipulation:
Select
ID,
Column,
ttimestamp,
LTRIM(Right(CColumn,20)) as CColumn,
FROM
(SELECT
ID,
Column,
ttimestamp,
MIN(Concat(RPAD(IF(Column is null, '9999999999999999',STRING(ttimestamp)),20,'0'),LPAD(Column,20,' '))) OVER (Partition by ID) CColumn
FROM (
SELECT
*
FROM (Select 1 as ID, STRING(NULL) as Column, 0.4375 as ttimestamp),
(Select 1 as ID, STRING(NULL) as Column, 0.438194444444444 as ttimestamp),
(Select 1 as ID, 'xyz' as Column, 0.438888888888889 as ttimestamp),
(Select 1 as ID, 'def' as Column, 0.439583333333333 as ttimestamp),
(Select 2 as ID, STRING(NULL) as Column, 0.479166666666667 as ttimestamp),
(Select 2 as ID, 'abc' as Column, 0.479861111111111 as ttimestamp)
))
As far as I know, Big Query has no options like 'IGNORE NULLS' or 'NULLS LAST'. Given that, this is the simplest solution I could come up with. I would like to see even simpler solutions.
Assuming the input data is in table "original_data",
select w2.id, w1.column, w2.timestamp
from
(select id,column,timestamp
from
(select id,column,timestamp, row_number()
over (partition BY id ORDER BY timestamp) position
FROM original_data
where column is not null
)
where position=1
) w1
right outer join
original_data as w2
on w1.id = w2.id
SELECT id,
(SELECT top(1) column FROM test1 where id=1 and column is not null order by autoID desc) as name
,timestamp
FROM yourTable
Output :-
1,'xyz',10:30 am
1,'xyz',10:31 am
1,'xyz',10:32 am
1,'xyz',10:33 am
2,'abc',11:30 am
2,'abc',11:31 am

sql order by hardcoded values

I have the following query:
select 'junior' as type, value
from mytable
union
select 'intermediate' as type, value
from mytable
union
select 'senior' as type, value
from mytable
Which returns the following data:
type value
Intermediate 10
Junior 5
Senior 1
I just need to reorder it so it looks like this
Junior 5
Intermediate 10
Senior 1
I can't figure out which order by clause to use to achieve ordering by custom specific values, how would I achieve this?
You can either sort by adding a sort key column or add a simple case statement based on the values.
-- Sort with Case statement
with sourceData as
(
select 'junior' type, 5 value
union all
select 'intermediate' type, 10 value
union all
select 'senior' type, 1 value
)
select *
from sourceData
order by
case type
when 'junior' then 0
when 'intermediate' then 1
when 'senior' then 2
else null
end
SQL Fiddle for testing.
You need to add a third column, called e.g. sortorder. Then, assign the proper integer value to it,
select 'junior' as type, value, 1 as sortorder
from mytable
union
select 'intermediate' as type, value, 2 as sortorder
from mytable
union
select 'senior' as type, value, 3 as sortorder
from mytable
order by 3
I don't know if understood you well but I would extract one more column e.g. "sequence" with integer values. It will provide to the ability of applying custom order.
As an example:
select results.type, results.value, results.sequence
from (
select 'junior' as type, value, 0 as sequence
from mytable
union
select 'intermediate' as type, value, 1 as sequence
from mytable
union
select 'senior' as type, value, 2 as sequence
from mytable
) as results order by results.sequence
Try this one.
select TYPE, value from
(
select type,value , row_number() over ( order by type ) as rowno
from #mytable
union
select type, value, row_number() over ( order by type ) as rowno
from #mytable
union
select type,value, row_number() over ( order by type ) as rowno
from #mytable
)a order by 2 desc