Oracle Select max where a certain key is matched - sql

i'm working with oracle, plSql, i need to query a table and select the max id where a key is matched, now i have this query
select t.* from (
select distinct (TO_CHAR(I.DATE, 'YYMMDD') || I.AUTH_CODE || I.AMOUNT || I.CARD_NUMBER) as kies, I.SID as ids
from transactions I) t group by kies, ids order by ids desc;
It's displaying this data
If i remove the ID from the query, it displays the distinct keys (in the query i use the alias KIES because keys was in blue, so i thought it might be a reserved word)
How can i display the max id (last one inserted) for every different key without displaying all the data like in the first image??
greetings.

Do you just want aggregation?
select thekey, max(sid)
from (select t.*,
(TO_CHAR(t.DATE, 'YYMMDD') || t.AUTH_CODE || t.AMOUNT || t.CARD_NUMBER) as thekey,
t.SID
from transactions t
) t
group by thekey
order by max(ids) desc;

Since you haven't provided data in text format, its difficult to type such long numbers and recreated the data.
However I think you can simply use the MAX analytical function to achieve your results.
with data as (
select 1111 keys,1 id from dual
union
select 2222, 1 from dual
union
select 1111, 2 from dual
union
select 2222,3 from dual
union
select 9999, 1 from dual
union
select 1111, 5 from dual
)
select distinct keys, max(id) over( partition by (keys)) from data
This query returns -
KEYS MAX(ID)OVER(PARTITIONBY(KEYS))
1111 5
9999 1
2222 3

Related

oracle count query with union

I have a query with union all functionality each giving me count(*) return from respective queries and another count query like below. I want an outer query that gives the total.
1st query
select count(*) from a
union all
select count(*) from b;
Sample result for 1st query:
COUNT
10
40
2nd query
select count(*) from xy;
Sample result for 2nd query:
COUNT
20
I want output like this in 2 rows:
TABLES
COUNT
xy
20
ab
50
something like above. How can I achieve this in oracle? please suggest the best way to do this.
I wrote a select and union all but not sure how to proceed further.
One option is to sum counts returned by the 1st query and then union it with the 2nd; also, add constants which show the source:
select 'ab' what, (select count(*) from a) + (select count(*) from b) cnt from dual
union all
select 'xy', count(*) from xy;
You can use:
SELECT 'ab' AS type,
COUNT(*) AS total
FROM ( SELECT 1 FROM a UNION ALL
SELECT 1 from b );
UNION ALL
SELECT 'xy', COUNT(*)
FROM xy;
You can sum counts from your three unioned Select statements and group the result by combination of sources:
WITH
a AS
( Select LEVEL "A_ID", 'some column a' "COL_A" From Dual Connect By LEVEL <= 30 ),
b AS
( Select LEVEL "B_ID", 'some column b' "COL_B" From Dual Connect By LEVEL <= 20 ),
xy AS
( Select LEVEL "XY_ID", 'some column xy' "COL_XY" From Dual Connect By LEVEL <= 20 )
with above sample data it is like here:
SELECT
CASE WHEN SOURCE IN('a', 'b') THEN 'ab' ELSE SOURCE END "SOURCE",
Sum(CNT) "CNT"
FROM
( Select 'a' "SOURCE", Count(*) "CNT" From a Union All
Select 'b', Count(*) From b Union All
Select 'xy', Count(*) From xy
)
GROUP BY
CASE WHEN SOURCE IN('a', 'b') THEN 'ab' ELSE SOURCE END
--
-- R e s u l t :
-- SOURCE CNT
-- ------ ----------
-- ab 50
-- xy 20
Assuming that your real queries can be a lot more complex, I take it as a given that we shall not try to change them and somehow merge or split them.
Your first query returns two rows. You want to get their sum, so you must aggregate the result and use SUM.
Below query uses CTEs (subqueries in the WITH clause) for your two queries, and then a query that gets this sum. It then uses these CTEs for the final UNION ALL query.
with query1 (cnt) as (select count(*) from a union all select count(*) from b)
, query2 (cnt) as (select count(*) from xy)
, sumquery1 (total) as (select sum(cnt) from query1)
select 'ab' as tables, total from sumquery1
union all
select 'xy' as tables, cnt from query2
order by tables desc;

SELECT rows with a new DISTINCT from a VARCHAR with CSV in it

I have an Oracle database table with a field called Classification which is VARCHAR. The VARCHAR is a CSV(using semi colons). Example:
;CHR;
;OTR;CHR;ROW;
;CHA;ROW;
;OTR;ROW;
I want to pull all the rows with ONLY a different value in the CSV from the others. It is ok if a row has a previously found value as long as it has a new different value.
For instance from the above dataset it would be:
;CHR;
;OTR;CHR;ROW;
;CHA;ROW;
If I do just:
Select DISTINCT Classification from Table1
I get rows that overlap distinct values due to the overall VARCHAR being Distinct.
I can get all the distinct values using:
select LISTAGG(val,',') WITHIN GROUP ( ORDER BY val ) as final
FROM
(
select distinct trim(regexp_substr("Classification",'[^;]+', 1, level) ) as val
from Table1
connect by regexp_substr("Classification", '[^,]+', 1, level) is not null
ORDER BY val
)
which give me
FINAL
CHA,CHR,OTR,ROW
but am unable to make the link to pull out one record per unique value
Is this possible with SQL?
EDIT: This is a database created by a large corporation and mine purchased the product. Now I am tasked with data mining the backend database for BI and have absolutely no control of the database structure.
No offence but I see many answers in the questions I have researched stating 'Do better database design/normalization' and while I agree MOST I have read have no control over the database and are asking for SO assistance with a problem because of this, not ridicule on bad database design.
I apologize if I offend anyone
There is no parent/child relationship. I cannot see the object layer but I assume these values are changed in the object layer before propagating to the client as there is no link to them in the actual database
Clarification:
I see 2 ways to solve this:
1: One select statement that pulls out 1 row based on a new unique value within the VARCHAR CSV(Classification)
2: Use my select statement to loop through and pull one row containing that value in the VARCHAR CSV(Classification)
Thanks all for the input. I upvoted the ones that worked for me. In the end I will be using the one I developed just because I can easily manipulate the output(to a csv) for what the analyst wishes.
Here's one way to approach it:
Assign row numbers to the original CSV data
Split the CSV -> rows
Now assign the split CSV values row numbers, sorted by the CSV ordering from the first step
Return any rows where the row number for the previous step = 1
Return the distinct list of CSVs
For example:
with tab as (
select ';CHR;' str from dual union all
select ';OTR;CHR;ROW;' str from dual union all
select ';CHA;ROW;' str from dual union all
select ';OTR;ROW;' str from dual
), ranks as (
select row_number() over ( order by str ) rn, tab.* from tab
), rws as (
select trim ( regexp_substr(str,'[^;]+', 1, level ) ) as val, rn, str
from ranks
connect by regexp_substr ( str, '[^;]+', 1, level ) is not null
and prior rn = rn
and prior sys_guid () is not null
), rns as (
select row_number () over (
partition by val
order by rn
) val_rn, r.*
from rws r
)
select distinct str
from rns
where val_rn = 1;
STR
;CHA;ROW;
;OTR;CHR;ROW;
;CHR;
This is an ad Hoc solution proposal if the generic answer yields a suboptimal performance and some restrictions are fullfiled:
all the keys have a fixed length
the maximal number of the keys is known
Than to parse the CSV string you may use this query (add further UNION ALL for longer strings)
with tab as (
select ';CHR;' str from dual union all
select ';OTR;CHR;ROW;' str from dual union all
select ';CHA;ROW;' str from dual union all
select ';OTR;ROW;' str from dual
), tab2 as (
select str, substr(str,2,3) val from tab union all
select str, substr(str,6,3) val from tab where substr(str,6,3) is not null union all
select str, substr(str,10,3) val from tab where substr(str,10,3) is not null)
select * from tab2;
which results in
STR VAL
------------- ------------
;CHR; CHR
;OTR;CHR;ROW; OTR
;CHA;ROW; CHA
;OTR;ROW; OTR
;OTR;CHR;ROW; CHR
;CHA;ROW; ROW
;OTR;ROW; ROW
;OTR;CHR;ROW; ROW
Now you need only to find the first occurence of each key and get all distinct strings with this first occurence.
I'm reusing the approach from the solution of Chris Saxon
with tab as (
select ';CHR;' str from dual union all
select ';OTR;CHR;ROW;' str from dual union all
select ';CHA;ROW;' str from dual union all
select ';OTR;ROW;' str from dual
), tab2 as (
select str, substr(str,2,3) val from tab union all
select str, substr(str,6,3) val from tab where substr(str,6,3) is not null union all
select str, substr(str,10,3) val from tab where substr(str,10,3) is not null),
tab3 as (
select STR, VAL,
row_number() over (partition by val order by str) rn
from tab2)
select distinct str
from tab3
where rn = 1
You were very close since you had already gotten the list of distinct values. Instead of combining them with LISTAGG, you can use that list to find a row that contains that unique value. Below are two separate queries that will return a Classification for each unique value. You can try them both and see which performs better based on the data you have in the table.
Query Option 1
WITH
table1 (classification)
AS
(SELECT ';CHR;' FROM DUAL
UNION ALL
SELECT ';OTR;CHR;ROW;' FROM DUAL
UNION ALL
SELECT ';CHA;ROW;' FROM DUAL
UNION ALL
SELECT ';OTR;ROW;' FROM DUAL),
dist_vals (val)
AS
( SELECT DISTINCT TRIM (REGEXP_SUBSTR (classification,
'[^;]+',
1,
LEVEL)) AS val
FROM Table1
CONNECT BY LEVEL < REGEXP_COUNT (classification, ';'))
SELECT val, classification
FROM (SELECT dv.val,
t.classification,
ROW_NUMBER () OVER (PARTITION BY dv.val ORDER BY t.classification) AS occurence
FROM dist_vals dv, table1 t
WHERE t.classification LIKE '%;' || dv.val || ';%')
WHERE occurence = 1;
Query Option 2
WITH
table1 (classification)
AS
(SELECT ';CHR;' FROM DUAL
UNION ALL
SELECT ';OTR;CHR;ROW;' FROM DUAL
UNION ALL
SELECT ';CHA;ROW;' FROM DUAL
UNION ALL
SELECT ';OTR;ROW;' FROM DUAL),
dist_vals (val)
AS
( SELECT DISTINCT TRIM (REGEXP_SUBSTR (classification,
'[^;]+',
1,
LEVEL)) AS val
FROM Table1
CONNECT BY LEVEL < REGEXP_COUNT (classification, ';'))
SELECT dv.val,
(SELECT classification
FROM table1
WHERE classification LIKE '%;' || dv.val || ';%' AND ROWNUM = 1)
FROM dist_vals dv;
I figured it out this way and it runs fast(even once all my joins to other tables are added). Will test other answers as I can and decide best one(others look better than mine if they work as I would rather not use dbms_output)
DECLARE
v_search_string varchar2(4000);
v_classification varchar2(4000);
BEGIN
select LISTAGG(val,',') WITHIN GROUP ( ORDER BY val ) as final
INTO v_search_string
FROM
(
select distinct trim(regexp_substr("Classification",'[^;]+', 1, level) ) as val
from mytable
connect by regexp_substr("Classification", '[^,]+', 1, level) is not null
ORDER BY val
);
FOR i IN
(SELECT trim(regexp_substr(v_search_string, '[^,]+', 1, LEVEL)) l
FROM dual
CONNECT BY LEVEL <= regexp_count(v_search_string, ',')+1
)
LOOP
SELECT "Classification"
INTO v_classification
FROM mytable
WHERE "Classification" LIKE '%' || i.l || '%'
FETCH NEXT 1 ROWS ONLY;
dbms_output.put_line(v_classification);
END LOOP;
END;

replacement of Offset Limit in SQL Server

We have DataTemp table which has the records in desc order.
select * from (
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
sno - Auto Id.
id - code of the Doors*.
N_th_Record - for denoting the n the record.
At a time, only three* records per Door are need to store on this table. For example Door 'a' has new entry(means 4th record) then first of 'a' Door need to delete.
4th record:
select * from (
select 10,'a',4 union --- new entry
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union -- need to delete
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
I do following query. But I need easiest way for deleting the row. Because, we are try to reduce the time consumption of over all project.
delete from door where sno = (
select sno from (
select 10,'a',4 union
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
where id = 'a'
order by sno desc -- For 'DataTemp' *order by* is no needed.
offset 3 rows fetch next 1 rows only
)
Note:
Three rows and three Door are given for example. Actually we work with 144 rows per 12 Doors.
Before this delete, we check lot of Business rules.
Version: SQL Server 2012
You could use ROW_NUMBER:
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY id ORDER BY sno DESC) rn FROM t)
DELETE FROM cte WHERE rn > 3;
db<>fiddle demo

SQL assigning incremental ID to subgroups

As the title says, I'm trying to add an extra column to a table which autoincrements everytime a different string in another column changes.
I would like to do this in a query.
Example:
MyCol GroupID
Cable 1
Cable 1
Foo 2
Foo 2
Foo 2
Fuzz 3
Fizz 4
Tv 5
Tv 5
The GroupID column is what I want to accomplish.
We can be sure that MyCol's strings will be the same in each subgroup (Foo will always be Foo, etc).
Thanks in advance
If I understand correctly, you can use dense_rank():
select t.*, dense_rank() over (order by col1) as groupid
from t;
You could make a temporal table with the distinct value of the MyCol and get the groupId throught the RowNumber of the temp table, and join the rownumbered result with your table.
This is a raw example in oracle:
WITH data AS
(SELECT 'Cable' MyCol FROM dual
UNION ALL
SELECT 'Cable' FROM dual
UNION ALL
SELECT 'Foo' FROM dual
UNION ALL
SELECT 'Foo' FROM dual
UNION ALL
SELECT 'Foo' FROM dual
UNION ALL
SELECT 'Fuzz' FROM dual
UNION ALL
SELECT 'Fizz' FROM dual
UNION ALL
SELECT 'Tv' FROM dual
UNION ALL
SELECT 'Tv' FROM dual
),
tablename AS
(SELECT * FROM data
),
temp AS
( SELECT DISTINCT mycol FROM tablename
),
temp2 AS
( SELECT mycol, rownum AS groupid from temp
)
SELECT tablename.mycol, temp2.groupid FROM temp2 JOIN tablename ON temp2.mycol = tablename.mycol
You could also check for a way to implement the tabibitosan method knowing that your column condition is string.

Oracle_ ROW_NUMBER for paging hierarchy query

This time I face a problem that is paging query for a hierarchy table.
table_name(id varchar2(50), id_parent varchar2(50) );
So for sample data I have:
WITH table_name AS
(
SELECT '3' id, '' id_parent FROM DUAL UNION ALL
SELECT '5' id, '3' id_parent FROM DUAL UNION ALL
SELECT 's' id, '3' id_parent FROM DUAL UNION ALL
SELECT '4' id, 'as' id_parent FROM DUAL UNION ALL
SELECT 'aa' id, 'as' id_parent FROM DUAL UNION ALL
SELECT 'as' id, '3' id_parent FROM DUAL UNION ALL
SELECT 'ad' id, '3' id_parent FROM DUAL UNION ALL
SELECT '2' id, '' id_parent FROM DUAL
)
SELECT LPAD('-', 2 * (level - 1), '-') || id AS id1,
id_parent,
rownum --Seem not legit here
FROM table_name
START WITH id_parent IS NULL
CONNECT BY PRIOR id = id_parent
--ORDER SIBLINGS BY id
;
This is expected output:
id id_parent rownum
2 1
3 2
--5 3 3
--ad 3 4
--as 3 5
----4 as 6
----aa as 7
--s 3 8
For now I count on rownum for row_number to make paging. As Oracle hierarchy query's doc, this make sure I can display the tree as pre-order Traveral that's what we want, but as SQL in general, IMO it don't guarantee that the same-level-nodes will be sorted (or not?).
So I need something like ROW_NUMBER() OVER (ORDER SIBLINGS BY id). But I have got no luck searching for something like that.
Is there some way to work with this?
Order first and then generate the ROWNUM afterwards using a sub-query:
SELECT t.*,
ROWNUM
FROM (
SELECT LPAD('-', 2 * (level - 1), '-') || id AS id1,
id_parent
FROM table_name
START WITH id_parent IS NULL
CONNECT BY PRIOR id = id_parent
ORDER SIBLINGS BY id
) t;
You need the outer query as the order in which execution happens in a query is:
The rows are selected;
Then the WHERE clause filters are applied (and the ROWNUM pseudo-column is
generated for each row that matches all the WHERE clause filters - in this case there is no WHERE clause so all rows will be numbered);
Then the ORDER BY clause is applied.
Applying this in a single query will get the rows found in the database then give them a row number and finally order those rows (not what you want). Using an inner query you can force the ORDER BY clause to be applied first and then the generation of the row numbers will occur, subsequently, in the execution of the outer query.