Forming a SQL query to compare results across rows - oracle

Problem statement:
select all stores name , their status, phone numbers , effective date
whose phone number has been changed from 2003 until present date.
Schema is
store_name,phone number , start_date , status
sample rows
abc 1234 30-DEC-2011 open
abc 3433 04-Jan-2012 close
bbb 4444 30-Jan-2010 open
bbb 4444 31-Jan-2011 open
Output
abc 1234 open 30-DEC-3011 till 3-Jan-2012
abc 3433 close 04-Jan-2012 till date
I am also fine having two rows in output with sorted start date like
abc 1234 30-DEC-2011 open
abc 3433 04-Jan-2012 close
bbb should not be reported as there was no change in phone number. We should report only those stores for which phone number was changed .
Can someone help me with this query on Oracle? I guess by using correlated queries it can be done but I am not sure how can I construct one.
Please note that my table is having around 3154953 records so I also need to make sure that correlated query doesn't lock the table for whole lot of time. Is this even possible with Oracle ?
Thanks!
APC's answer works for me just that I am seeing alot of repetitions in my result.
For input :
select store_name,phone_number,start_date, status where store_name=abc;
returns
STORE_name Phone number start_date STATUS
---------------- ---------------- ----------- ----------
abc 122 18-JAN-2011 open
abc 122 18-JAN-2011 open
abc 122 18-JAN-2011 close
running your query gives me following output.
abc 122 open from 18-JAN-2011 to 17-JAN-2011
abc 122 open from 18-JAN-2011 to 17-JAN-2011
abc 122 close from 18-JAN-2011 to date
Can you explain why and where is the miss?

I'm presuming that this is for Oracle rather than MySQL, as my solution uses a couple of magic tricks which I'm pretty certain are not available in MySQL. The first is the Common Table Expression to get a result set which we can use more than once. The second is the use of the LEAD() analytic function to "predict" values in the next row.
So, here's the query:
with a as ( select store_name
, phone_number
, status
, start_date
, lead (start_date, 1, trunc(sysdate)) over (partition by store_name
order by start_date) as next_date
, lead (phone_number, 1, null) over (partition by store_name
order by start_date) as next_number
from your_table
where start_date >= date '2003-01-01' )
select a.store_name
, a.phone_number
, case when a.next_date != trunc(sysdate) then
a.status||' from '|| a.start_date ||' to '||to_char(a.next_date - 1)
else a.status||' from '||a.start_date ||' to date'
end as status_text
from a
where a.store_name in (
select store_name
from a
where phone_number != next_number)
order by a.store_name, a.start_date
/
And here's its output:
SQL> r
1 with a as ( select store_name
...
22 order by a.store_name, a.start_date
23 /
STORE_NAME PHONE_NUMBER STATUS_TEXT
-------------------- ------------ --------------------------------
abc 1234 open from 30-DEC-11 to 03-JAN-12
abc 3433 close from 04-JAN-12 to date
2 rows selected.
SQL>
As for this remark:
"so I also need to make sure that correlated query doesn't lock the
table for whole lot of time"
Doesn't matter in Oracle, because reads don't block other reads. Nor writes come to that.

It will be something along the lines of
select * from table0 as q0 join
(
select min(date) from table0 as q1 where q1.store_name = q0.store_name
) as q2 on q2.store_name = q0.store_name
left join
(
select max(date) from table0 as q1 where q1.store_name = q0.store_name
) as q3 on q3.store_name = q0.store_name
That's not quite right as I don't have MySQL in front of me but its something along these lines.

Related

It is possible insert dummy (dash) into first row data use select statement in oracle SQL?

table_A
col_color col_name col_qty
- - - <----- dummy dash
RED APPLE 2
YEL BANANA 1
GRN GREEN_APPLE 3
Hi, it is posible to insert first row of dummy dash for viewing not store into database
use oracle sql plus ?
Anyone help is much apprecited.
One option is to UNION two data sets; one contains dummy dashes, while another contains "real" data. Note that dashes are considered to be strings, which means that you'll have to cast other datatypes to character datatype (see to_char(deptno) in my example):
SQL> with temp as
2 (select 1 rn, '-' deptno , '-' dname, '-' loc from dual
3 union all
4 select 2 rn, to_char(deptno), dname , loc from dept
5 )
6 select deptno, dname, loc
7 from temp
8 order by rn, deptno;
DEPTNO DNAME LOC
---------- -------------- -------------
- - -
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
The rn column is used to correctly sort the output (dashes first, the rest of data next).
If you don't want to use 'with', then how about this?
(
SELECT '-' COL_COLOR
, '-' COL_NAME
, '-' COL_QTY
FROM DUAL
)
UNION ALL
(
SELECT *
FROM table_A
)
I think this way is the best way not using 'with'.

Oracle Delete/Update in one query

I want to Delete the Duplicates from the table update the unique identifier and merge it with the already existing record.
I have a table which can contain following records -
ID Name Req_qty
1001 ABC-02/01+Time 10
1001 ABC-03/01+Time 20
1001 ABC 30
1002 XYZ 40
1003 DEF-02/01+Time 10
1003 DEF-02/01+Time 20
And I am expecting the records after the operation as follows:
ID Name Req_Qty
1001 ABC 60
1002 XYZ 40
1003 DEF 30
Any assistance would be really helpful. Thanks!
It is possible to do this in a single SQL statement:
merge into (select rowid as rid, x.* from test_table x ) o
using ( select id
, regexp_substr(name, '^[[:alpha:]]+') as name
, sum(reg_qty) as reg_qty
, min(rowid) as rid
from test_table
group by id
, regexp_substr(name, '^[[:alpha:]]+')
) n
on (o.id = n.id)
when matched then
update
set o.name = n.name
, o.reg_qty = n.reg_qty
delete where o.rid > n.rid;
Working example
This uses a couple of tricks:
the delete clause of a merge statement will only operate on data that has been updated, and so there's no restriction on what gets updated.
you can't select rowid from a "view" and so it's faked as rid before updating
by selecting the minimum rowid from per ID we make a random choice about which row we're going to keep. We can then delete all the rows that have a "greater" rowid. If you have a primary key or any other column you'd prefer to use as a discriminator just substitute that column for rowid (and ensure it's indexed if your table has any volume!)
Note that the regular expression differs from the other answer; it uses caret (^) to anchor the search for characters to the beginning of the string before looking for all alpha characters thereafter. This isn't required as the default start position for REGEXP_SUBSTR() is the first (1-indexed) but it makes it clearer what the intention is.
In your case, you will need to update the records first and then delete the records which are not required as following (Update):
UPDATE TABLE1 T
SET T.REQ_QTY = (
SELECT
SUM(TIN.REQ_QTY) AS REQ_QTY
FROM
TABLE1 TIN
WHERE TIN.ID = T.ID
)
WHERE (T.ROWID,1) IN
(SELECT TIN1.ROWID, ROW_NUMBER() OVER (PARTITION BY TIN1.ID)
FROM TABLE1 TIN1); --TAKING RANDOM RECORD FOR EACH ID
DELETE FROM TABLE1 T
WHERE NOT EXISTS (SELECT 1 FROM TABLE1 TIN
WHERE TIN.ID = T.ID AND TIN.REQ_QTY > T.REQ_QTY);
UPDATE TABLE1 SET NAME = regexp_substr(NAME,'[[:alpha:]]+');
--Update--
The following merge should work for you
MERGE INTO
(select rowid as rid, T.* from MY_TABLE1 T ) MT
USING
(
SELECT * FROM
(SELECT ID,
regexp_substr(NAME,'^[[:alpha:]]+') AS NAME_UPDATED,
SUM(Req_qty) OVER (PARTITION BY ID) AS Req_qty_SUM,
ROWID AS RID
FROM MY_TABLE1) MT1
WHERE RN = 1
) mt1
ON (MT.ID = MT1.ID)
WHEN MATCHED THEN
UPDATE SET MT.NAME = MT1.NAME_UPDATED, MT.Req_qty = MT1.Req_qty_SUM
delete where (MT.RID <> MT1.RID);
Cheers!!

Okay so I am trying to a rownum into a variable but I need it to give me only one value, so 2 if it's the second number in the row

select rownum into v_rownum
from waitlist
where p_callnum=callnum
order by sysdate;
tried doing this but gives too many values.
and if I do p_snum=snum, it will keep returning 1. I need it to return 2 if it's #2 on the waitlist.
select rn into v_rownum
from (select callnum,
row_number() over (order by sysdate) rn
from waitlist)
where p_snum=snum;
Almost got it to work. Running into issues in the first select. I believe I might have to use v_count instead. Also Ordering by Sysdate even if a second apart will order it correctly.
SNU CALLNUM TIME
--- ---------- ---------
101 10125 11-DEC-18
103 10125 11-DEC-18
BTW time is = date which I entered people into waitlist using sysdate. So I suppose ordering by time could work.
create table waitlist(
snum varchar2(3),
callnum number(8),
time date,
constraint fk_waitlist_snum foreign key(snum) references students(snum),
constraint fk_waitlist_callnum foreign key(callnum) references schclasses(callnum),
primary key(snum,callnum)
);
is the waitlist table.
I used Scott's DEPT table to create your WAITLIST; department numbers represent CALLNUM column:
SQL> select * From waitlist;
CALLNUM WAITER
---------- --------------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
How to fetch data you need?
using analytic function (ROW_NUMBER) which orders values by CALLNUMs, you'll know the order
that query will be used as an inline view for the main query that returns number in the waitlist for any CALLNUM
Here's how:
SQL> select rn
2 from (select callnum,
3 row_number() over (order by callnum) rn
4 from waitlist
5 )
6 where callnum = 30;
RN
----------
3
SQL>
rownum in oracle is a generated column, it does not refer to any specific row, it is just the nth row in a set.
With a select into it can only return one row (hence the two many rows error) so rownum will always be 1.
Without more details about your table structure, and how you are uniquely identifying records it is hard to give assist you further with a solution.

Oracle sql Select the first and last name of the customer with most orders in 2017

I have the following tables
f_orders
ORDER_NUMBER NUMBER(5,0)
ORDER_DATE DATE
ORDER_TOTAL NUMBER(8,2)
CUST_ID NUMBER(5,0)
STAFF_ID NUMBER(5,0)
with the following data
ORDER_NUMBER ORDER_DATE ORDER_TOTAL CUST_ID STAFF_ID
5678 10-Dec-2017 103.02 123 12
9999 10-Dec-2017 10 456 19
9997 09-Dec-2017 3 123 19
9989 10-Dec-2016 3 123 19
and
f_customers
ID NUMBER(5,0)
FIRST_NAME VARCHAR2(25)
LAST_NAME VARCHAR2(35)
ADDRESS VARCHAR2(50)
with the following data
ID FIRST_NAME LAST_NAME ADDRESS
123 Cole Bee 123 Main Street
456 Zoe Twee 1009 Oliver Avenue
I'm supposed to display the name of the customer wthi the most orders placed in the year 2017.
My query looks like this
SELECT f_customers.first_name,
f_customers.last_name,
count(order_total)
FROM f_orders JOIN f_customers
ON f_customers.id = f_orders.CUST_ID
WHERE TO_CHAR(order_date, 'DD-Mon-YYYY') LIKE '%2017'
GROUP BY f_customers.first_name, f_customers.last_name
HAVING count(order_total) = (SELECT max(count(cust_id))
FROM f_orders
GROUP BY cust_id)
The problem is that whenever I insert the where statement it returns no data found, even though it should return the name Cole Bee with 2 orders
If I remove the where statement it will show that Cole Bee has placed 3 orders
I can't figure out why I get the no data found result. Any ideas?
Your main query is filtering on the year; the subquery on the right hand side of the having clause is not. The max(count()) is 3 if you run that subquery on its own, and you’re comparing that with the filtered list which (as you expect) only finds 2 rows for that customer.
Run the whole query with just the having part removed (rather than the where clause), and run just the subquery; and compare the results.
The simple answer is to repeat the filter:
SELECT f_customers.first_name,
f_customers.last_name,
count(order_total)
FROM f_orders JOIN f_customers
ON f_customers.id = f_orders.CUST_ID
WHERE TO_CHAR(order_date, 'DD-Mon-YYYY') LIKE '%2017'
GROUP BY f_customers.first_name, f_customers.last_name
HAVING count(order_total) = (SELECT max(count(cust_id))
FROM f_orders
WHERE TO_CHAR(order_date, 'DD-Mon-YYYY') LIKE '%2017'
GROUP BY cust_id)
Both filters could be written more simply as:
WHERE TO_CHAR(order_date, 'YYYY') = '2017'
or even:
WHERE EXTRACT(YEAR FROM order_date) = 2017
You can avoid hitting the table twice using analytic queries and other tricks but as this seems to be an assignment that may be getting beyond what you’ve been taught and are expected to know/use.

oracle sqlplus report compute columns

new to this site, so I may not be formatting things right
I have a SQLPlus report that has a very unique requirement that I can't seem to figure out. What I need to do is based on the total value of a break column display a piece of text that says 'Available' or 'Full'.
Here is what the report looks like:
date column 1 column 2 count
===== ======== ======== =====
1/5/14 ABC ABC 10
DEF DEF 20
****** -----
total 30 - this would be a normal compute when break on date
What I need to do is compare that total count field (30 in this case) and if it is > a hard-coded value (say 20) print 'Full' otherwise 'Available'. I'm open to any suggestions, I don't have to print the 30 if there is a way to substitute the text in its place or print on another line or next to it somewhere (Or even if I can make the label say text I want). Issue I have is applying that compare logic in the sqlplus report itself.
Any ideas would be greatly appreciated.
Many thanks.
Mark
SQLPlus has only basic reporting features. In particular, you can't really customize most of its features like BREAK and COMPUTE SUM.
However, you can use all of SQL features so you can probably customize your SQL to get the result you want. Something like this:
SQL> WITH DATA AS (
2 SELECT DATE '2014-05-01' d, 'ABC' c1, 'ABC' c2, 10 cnt FROM dual UNION ALL
3 SELECT DATE '2014-05-01' d, 'DEF' c1, 'DEF' c2, 20 cnt FROM dual UNION ALL
4 SELECT DATE '2014-06-01' d, 'GHI' c1, 'GHI' c2, 10 cnt FROM dual UNION ALL
5 SELECT DATE '2014-06-01' d, 'JKL' c1, 'JKL' c2, 5 cnt FROM dual
6 )
7 SELECT CASE
8 WHEN GROUPING(c1) = 1 THEN '**Total**'
9 ELSE to_char(d, 'dd/mm/yyyy')
10 END dt,
11 c1, c2, SUM(cnt),
12 CASE
13 WHEN GROUPING(c1) = 1 AND SUM(cnt) > 20 THEN 'Full'
14 WHEN GROUPING(c1) = 1 AND SUM(cnt) <= 20 THEN 'Available'
15 END AVAILABILITY
16 FROM DATA
17 GROUP BY d, ROLLUP ((c1, c2));
DT C1 C2 SUM(CNT) AVAILABILITY
---------- --- --- ---------- ------------
01/05/2014 ABC ABC 10
01/05/2014 DEF DEF 20
**Total** 30 Full
01/06/2014 GHI GHI 10
01/06/2014 JKL JKL 5
**Total** 15 Available
6 rows selected
For further reading: an excellent article about GROUP BY, ROLLUP and CUBE by Rob van Wijk.

Resources