Need a query without using subquery - sql

My table is
PROPOSAL_TABLE
PID QUOTE_ID PDF1
--- -------- ----
1 123 null
2 123 null
3 123 null
4 152 null
5 888 null
I need to select Quote ID with MAX PID.
I have a query:
SELECT PROPOSAL_PDF FROM PROPOSAL_TBL WHERE QUOTE_ID = '123'
AND PID = (SELECT MAX(PID) FROM PROPOSAL_TBL WHERE QUOTE_ID = '123')
How can I get the value without using sub query?

Without using a sub-query you can use the KEEP clause:
select max(pid) as pid
, max(quote_id) keep ( dense_rank first order by pid desc ) as quote_id
, max(pdf1) keep ( dense_rank first order by pid desc ) as pdf1
from proposal_table
where quote_id = '123'
This should be highly efficient but as always test. I would recommend reading Rob van Wijk's blog post on it.
The standard alternative would be to use a sub-query but not require another scan of the table by using an analytic function, e.g. ROW_NUMBER()
select pid, quote_id, pdf1
from ( select a.*, row_number() over ( order by pid ) as rn
from proposal_table a
where quote_id = '123' )
where rn = 1

You can use analytics:
SQL> WITH DATA AS (
2 SELECT 1 pid, 123 quote_id, 'A' pdf1 FROM DUAL
3 UNION ALL SELECT 2 pid, 123 quote_id, 'B' pdf1 FROM DUAL
4 UNION ALL SELECT 3 pid, 123 quote_id, 'C' pdf1 FROM DUAL
5 UNION ALL SELECT 4 pid, 152 quote_id, 'D' pdf1 FROM DUAL
6 UNION ALL SELECT 5 pid, 888 quote_id, 'E' pdf1 FROM DUAL
7 )
8 SELECT DISTINCT first_value(pid) over (ORDER BY pid DESC) pid,
9 quote_id,
10 first_value(pdf1) over (ORDER BY pid DESC) pdf1
11 FROM DATA
12 WHERE quote_id = 123;
PID QUOTE_ID PDF1
---------- ---------- ----
3 123 C
You can also use aggregates:
SQL> WITH DATA AS (
2 SELECT 1 pid, 123 quote_id, 'A' pdf1 FROM DUAL
3 UNION ALL SELECT 2 pid, 123 quote_id, 'B' pdf1 FROM DUAL
4 UNION ALL SELECT 3 pid, 123 quote_id, 'C' pdf1 FROM DUAL
5 UNION ALL SELECT 4 pid, 152 quote_id, 'D' pdf1 FROM DUAL
6 UNION ALL SELECT 5 pid, 888 quote_id, 'E' pdf1 FROM DUAL
7 )
8 SELECT MAX(pid),
9 quote_id,
10 MAX(pdf1) KEEP (DENSE_RANK FIRST ORDER BY pid DESC) pdf1
11 FROM DATA
12 WHERE quote_id = 123
13 GROUP BY quote_id;
MAX(PID) QUOTE_ID PDF1
---------- ---------- ----
3 123 C

Try this ::
SELECT PROPOSAL_PDF
FROM PROPOSAL_TBL WHERE QUOTE_ID = '123'
ORDER BY PID desc limit 1

Sql Server
SELECT TOP 1 PROPOSAL_PDF
FROM PROPOSAL_TBL
WHERE QUOTE_ID = '123'
ORDER BY PID desc
Oracle
SELECT * from (Select * FROM PROPOSAL_TBL
WHERE QUOTE_ID = '123'
ORDER BY PID desc)
WHERE ROWNUM <=1

Related

Oracle SQL Uniquely Update Duplicate Records

I have a STUDENT table and need to update the STUDENT_ID values by prefixing with the letter SS followed by STUDENT_ID value. For any duplicate STUDENT_ID records, I should prefix the duplicate records as SS1 SS2. Below is an example
Before Update:
NUM
STUDENT_ID
1
9234
2
9234
3
9234
4
3456
5
3456
6
789
7
956
After Update:
NUM
STUDENT_ID
1
SS9234
2
SS19234
3
SS29234
4
SS3456
5
SS13456
6
SS789
7
SS956
Below is the query for updating the STUDENT_ID for unique records.
update student set student_id = 'SS'||student_id ;
commit;
Need suggestion for updating the STUDENT_ID for duplicate records. There are around 1 million duplicate records in the table and total volume is around 40 million. Appreciate for any inputs for performance enhancement.
You can use a MERGE statement correlated on the ROWID pseudo-column and using the ROW_NUMBER() analytic function:
MERGE INTO table_name dst
USING (
SELECT ROWID as rid,
ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY num) AS rn
FROM table_name
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE
SET student_id = 'SS' || CASE WHEN rn > 1 THEN rn - 1 END || dst.student_id;
Which, for the sample data:
CREATE TABLE table_name (NUM, STUDENT_ID) AS
SELECT 1, CAST('9234' AS VARCHAR2(20)) FROM DUAL UNION ALL
SELECT 2, '9234' FROM DUAL UNION ALL
SELECT 3, '9234' FROM DUAL UNION ALL
SELECT 4, '3456' FROM DUAL UNION ALL
SELECT 5, '3456' FROM DUAL UNION ALL
SELECT 6, '789' FROM DUAL UNION ALL
SELECT 7, '956' FROM DUAL;
Then after the MERGE the table contains:
NUM
STUDENT_ID
1
SS9234
2
SS19234
3
SS29234
4
SS3456
5
SS13456
6
SS789
7
SS956
fiddle
I'm sure there must be a better way, but this query can get the job done:
update t
set student_id = (
select new_student_id
from (
select x.*, 'SS' || case when rn = 1 then '' else '' || rn end
|| student_id as new_student_id
from (
select t.*, row_number() over(partition by student_id order by num) as rn
from t
) x
) y
where t.num = y.num
)
Result:
NUM STUDENT_ID
---- ----------
1 SS9234
2 SS29234
3 SS39234
4 SS3456
5 SS23456
6 SS789
7 SS956
See running example at db<>fiddle.
Maybe you could do it without updating!?
I would probably try to :
CREATE NEW_TABLE AS
SELECT [do the "update" here] FROM OLD_TABLE;
- add indexes on new table
- add constraints on new table
- add anything else you need on new table (foreign keys, grants...)
and then
DROP TABLE OLD_TABLE;
-- and
RENAME NEW_TABLE To OLD_TABLE;
SELECT with your sample data:
WITH
tbl as
(
Select 1 "NUM", 9234 "STUDENT_ID" From Dual Union All
Select 2 "NUM", 9234 "STUDENT_ID" From Dual Union All
Select 3 "NUM", 9234 "STUDENT_ID" From Dual Union All
Select 4 "NUM", 3456 "STUDENT_ID" From Dual Union All
Select 5 "NUM", 3456 "STUDENT_ID" From Dual Union All
Select 6 "NUM", 789 "STUDENT_ID" From Dual Union All
Select 7 "NUM", 956 "STUDENT_ID" From Dual
)
Select
NUM,
CASE WHEN Count(NUM) Over(Partition By STUDENT_ID) = 1 THEN 'SS' || STUDENT_ID
ELSE 'SS' || Replace(Sum(1) Over(Partition By STUDENT_ID Order By NUM) - 1, 0, '') || STUDENT_ID
END "STUDENT_ID"
From
tbl
Order By NUM
Result:
NUM
STUDENT_ID
1
SS9234
2
SS19234
3
SS29234
4
SS3456
5
SS13456
6
SS789
7
SS956

Select Duplicate records in Oracle

I have a table below in Oracle
Table1
State | Product |other fields
CA | P1 | xxxx
OR | P1 | xxxx
OR | P1 | xxxx
OR | P1 | xxxx
WA | P1 | xxxx
VA | P2 | xxxx
My Output should be only select if State has been occurred more than once.
State | Product |other fields
OR | P1 | xxxx
If you just want to count duplicate states then:
SELECT DISTINCT
State,
Product,
Other_Fields
FROM (
SELECT t.*,
COUNT(1) OVER ( PARTITION BY State ) AS cnt
FROM Table1 t
)
WHERE cnt > 1;
If you want to consider duplicate rows (considering all fields) then:
SELECT *
FROM Table1
GROUP BY State, Product, Other_Fields
HAVING COUNT(1) > 1;
select state, product, column_3, column_4
from (
select state, product, column_3, column_4,
count(*) over (partition by state) as cnt
from the_table
) t
where cnt > 1;
You could use ROW_NUMBER analytic function.
For example,
SQL> WITH sample_data AS(
2 SELECT 'CA' State, 'P1' product FROM dual UNION ALL
3 SELECT 'OR', 'P1' product from dual union all
4 SELECT 'OR', 'P1' product FROM dual UNION ALL
5 SELECT 'OR', 'P1' product FROM dual UNION ALL
6 SELECT 'WA', 'P1' product FROM dual UNION ALL
7 SELECT 'VA', 'P2' product from dual
8 )
9 -- end of sample_data mimicking real table
10 SELECT distinct state,
11 product
12 FROM
13 (SELECT state,
14 product,
15 row_number() OVER(PARTITION BY state ORDER BY product) rn
16 FROM sample_data
17 )
18 WHERE rn >1;
ST PR
-- --
OR P1
SQL>

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.

Get distinct rows based on priority?

I have a table as below.i am using oracle 10g.
TableA
------
id status
---------------
1 R
1 S
1 W
2 R
i need to get distinct ids along with their status. if i query for distinct ids and their status i get all 4 rows.
but i should get only 2. one per id.
here id 1 has 3 distinct statuses. here i should get only one row based on priority.
first priority is to 'S' , second priority to 'W' and third priority to 'R'.
in my case i should get two records as below.
id status
--------------
1 S
2 R
How can i do that? Please help me.
Thanks!
select
id,
max(status) keep (dense_rank first order by instr('SWR', status)) as status
from TableA
group by id
order by 1
fiddle
select id , status from (
select TableA.*, ROW_NUMBER()
OVER (PARTITION BY TableA.id ORDER BY DECODE(
TableA.status,
'S',1,
'W',2,
'R',3,
4)) AS row_no
FROM TableA)
where row_no = 1
This is first thing i would do, but there may be a better way.
Select id, case when status=1 then 'S'
when status=2 then 'W'
when status=3 then 'R' end as status
from(
select id, max(case when status='S' then 3
when status='W' then 2
when status='R' then 1
end) status
from tableA
group by id
);
To get it done you can write a similar query:
-- sample of data from your question
SQL> with t1(id , status) as (
2 select 1, 'R' from dual union all
3 select 1, 'S' from dual union all
4 select 1, 'W' from dual union all
5 select 2, 'R' from dual
6 )
7 select id -- actual query
8 , status
9 from ( select id
10 , status
11 , row_number() over(partition by id
12 order by case
13 when upper(status) = 'S'
14 then 1
15 when upper(status) = 'W'
16 then 2
17 when upper(status) = 'R'
18 then 3
19 end
20 ) as rn
21 from t1
22 ) q
23 where q.rn = 1
24 ;
ID STATUS
---------- ------
1 S
2 R
select id,status from
(select id,status,decode(status,'S',1,'W',2,'R',3) st from table) where (id,st) in
(select id,min(st) from (select id,status,decode(status,'S',1,'W',2,'R',3) st from table))
Something like this???
SQL> with xx as(
2 select 1 id, 'R' status from dual UNION ALL
3 select 1, 'S' from dual UNION ALL
4 select 1, 'W' from dual UNION ALL
5 select 2, 'R' from dual
6 )
7 select
8 id,
9 DECODE(
10 MIN(
11 DECODE(status,'S',1,'W',2,'R',3)
12 ),
13 1,'S',2,'W',3,'R') "status"
14 from xx
15 group by id;
ID s
---------- -
1 S
2 R
Here, logic is quite simple.
Do a DECODE for setting the 'Priority', then find the MIN (i.e. one with Higher Priority) value and again DECODE it back to get its 'Status'
Using MOD() example with added values:
SELECT id, val, distinct_val
FROM
(
SELECT id, val
, ROW_NUMBER() OVER (ORDER BY id) row_seq
, MOD(ROW_NUMBER() OVER (ORDER BY id), 2) even_row
, (CASE WHEN id = MOD(ROW_NUMBER() OVER (ORDER BY id), 2) THEN NULL ELSE val END) distinct_val
FROM
(
SELECT 1 id, 'R' val FROM dual
UNION
SELECT 1 id, 'S' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
UNION
SELECT 2 id, 'R' val FROM dual
UNION -- comment below for orig data
SELECT 3 id, 'K' val FROM dual
UNION
SELECT 4 id, 'G' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
))
WHERE distinct_val IS NOT NULL
/
ID VAL DISTINCT_VAL
--------------------------
1 S S
2 R R
3 K K
4 G G

SQL - display a row if the parameter exists , else display another parameter if exists

I have a table like the following :
ID |INFO | DATE_DT
-------------------------
1091|info5 |10/10/2010
1239|old.info |14/09/2010
1340|old.info |07/10/2010
3481|info |16/10/2010
4134|info3 |21/01/2011
i would like to display just one row with the following conditions :
- if i have in my table one row with INFO = 'info' --> display just this row
- if i dont have one row with INFO = 'info' so i --> display the row with INFO = 'old.info' and DATE_DT = MAX(DATE_DT)
so, in my example if my table is :
ID |INFO | DATE_DT
-------------------------
1091|info5 |10/10/2010
1239|old.info |14/09/2010
1340|old.info |07/10/2010
3481|info |16/10/2010 ===> display this row
4134|info3 |21/01/2011
or if my table doesnt containt INFO = 'info'
ID |INFO | DATE_DT
-------------------------
1091|info5 |10/10/2010
1239|old.info |14/09/2010
1340|old.info |07/10/2010 ===> display this row
4134|info3 |21/01/2011
any suggestions?
Thanks.
You could select both row and take by priority the one that satisfies the first condition:
SQL> WITH my_table AS (
2 SELECT 1091 id, 'info5' info, to_date('10/10/2010') date_dt FROM DUAL
3 UNION ALL SELECT 1239, 'old.info' , to_date('14/09/2010') FROM DUAL
4 UNION ALL SELECT 1340, 'old.info' , to_date('07/10/2010') FROM DUAL
5 UNION ALL SELECT 3481, 'info' , to_date('16/10/2010') FROM DUAL
6 UNION ALL SELECT 4134, 'info3' , to_date('21/01/2011') FROM DUAL)
7 SELECT * FROM (
8 SELECT 1 ord, t.*
9 FROM my_table t
10 WHERE info = 'info'
11 UNION ALL
12 SELECT 2 ord, t.*
13 FROM my_table t
14 WHERE date_dt = (SELECT MAX(date_dt) FROM my_table)
15 ORDER BY ord)
16 WHERE ROWNUM = 1;
ORD ID INFO DATE_DT
---------- ---------- -------- -----------
1 3481 info 16/10/2010
If you remove the row 'info', the row where DATE_DT = MAX(DATE_DT) will be chosen:
SQL> WITH my_table AS (
2 SELECT 1091 id, 'info5' info, to_date('10/10/2010') date_dt FROM DUAL
3 UNION ALL SELECT 1239, 'old.info' , to_date('14/09/2010') FROM DUAL
4 UNION ALL SELECT 1340, 'old.info' , to_date('07/10/2010') FROM DUAL
5 /*UNION ALL SELECT 3481, 'info' , to_date('16/10/2010') FROM DUAL*/
6 UNION ALL SELECT 4134, 'info3' , to_date('21/01/2011') FROM DUAL)
7 SELECT * FROM (
8 SELECT 1 ord, t.*
9 FROM my_table t
10 WHERE info = 'info'
11 UNION ALL
12 SELECT 2 ord, t.*
13 FROM my_table t
14 WHERE date_dt = (SELECT MAX(date_dt) FROM my_table)
15 ORDER BY ord)
16 WHERE ROWNUM = 1;
ORD ID INFO DATE_DT
---------- ---------- -------- -----------
2 4134 info3 21/01/2011
You can also do this with analytic functions, to only require one pass over the data:
with my_tab as (
select 1091 as id, 'info5' as info, to_date('10/10/2010',' DD/MM/YYYY') as date_dt from dual
union all select 1239, 'old.info', to_date('14/09/2010', 'DD/MM/YYYY') from dual
union all select 1340, 'old.info', to_date('07/10/2010', 'DD/MM/YYYY') from dual
union all select 3481, 'info', to_date('16/10/2010', 'DD/MM/YYYY') from dual
union all select 4134, 'info3', to_date('21/01/2011', 'DD/MM/YYYY') from dual
)
select id, info, to_char(date_dt, 'DD/MM/YYYY')
from (
select id, info, date_dt, rank() over (order by ord, date_dt desc) as rnk
from (
select id, info, date_dt,
case info
when 'info' then 1
when 'old.info' then 2
when 'info3' then 3
else null
end as ord
from my_tab
)
)
where rnk = 1;
ID INFO DATE_DT
---------- -------- ----------
3481 info 16/10/2010
Knocking out the 'info' row gives:
ID INFO DATE_DT
---------- -------- ----------
1340 old.info 07/10/2010
Probably no better than #Vincent's for this trivial case, but with more data and more values to choose between this might scale better - just need more ord values in the case, though with any real data I'd have thought you'd look up the precedence from another table...