Update with count and group by expressions - oracle

First, I know there is a common issue in Stack Overflow, but the following solutions are not working well here. So I still need some help.
Oracle - Update COUNT of rows with specific value
Oracle - Update rows with a min value in the group of a column from another table
Oracle update statement with group function
Oracle - Update COUNT of rows with specific value
The problem is: I have a +700k lines table:
REVIEWS (PRODUCT_ID, REVIEW, REVIEW_DATE, RELEASE_DATE, ..., REVIEW_COUNT)
I'm trying to update REVIEW_COUNT by counting the lines with the same PRODUCT_ID (I want just reviews before product release). So the code below works very well for my purpose:
SELECT COUNT(PRODUCT_ID) FROM REVIEWS
WHERE REVIEW_DATE < RELEASE_DATE
GROUP BY PRODUCT_ID
But I'm having a hard time to do the update. First I tried this:
UPDATE REVIEWS R
SET R.REVIEWS_COUNT =
(SELECT COUNT(RR.PRODUCT_ID) FROM REVIEWS RR
WHERE RR.DATA < RR.REL_DATE
GROUP BY RR.PRODUCT_ID)
The error is "more than one row", which is not surprising, but since I'm using the group by statement, it shouldn't occur. So I tried a self-join:
UPDATE REVIEWS R
SET R.REVIEWS_COUNT =
(SELECT COUNT(RR.PRODUCT_ID) FROM REVIEWS RR
WHERE RR.PRODUCT_ID = R.PRODUCT_ID AND RR.DATA < RR.REL_DATE)
But the query is taking forever and I don't think that should take so long, the simple select is pretty normal-fast.
I've also tested some more fancy and more simple stuff, but the outcome remains the same: long time waiting and it seems just wrong.
Please, what I'm missing in such easy update?

Maybe instead of updating you could define view:
select product_id, review_date, release_date,
count(case when review_date < release_date then 1 end)
over (partition by product_id) review_count
from reviews;
You could also try merge instead update:
merge into reviews a
using (select product_id, count(product_id) cnt from reviews
where review_date < release_date
group by product_id ) b
on (a.product_id = b.product_id)
when matched then update set reviews_count = b.cnt
dbfiddle

I think your second update is correct:
UPDATE REVIEWS R
SET R.REVIEWS_COUNT =
(SELECT COUNT(RR.PRODUCT_ID) FROM REVIEWS RR
WHERE RR.PRODUCT_ID = R.PRODUCT_ID AND RR.DATA < RR.REL_DATE)
;
This will update every record in the reviews table. Is that what you wanted?
An index on product_id will make the inner query run faster, but it will still update all 700K or so records.

Related

Oracle Table Variables

Using Oracle PL/SQL is there a simple equivalent of the following set of T-SQL statements? It seems that everything I am finding is either hopelessly outdated or populates a table data type with no explanation on how to use the result other than writing values to stdout.
declare #tempSites table (siteid int)
insert into #tempSites select siteid from site where state = 'TX'
if 10 > (select COUNT(*) from #tempSites)
begin
insert into #tempSites select siteid from site where state = 'OK'
end
select * from #tempSites ts inner join site on site.siteId = ts.siteId
As #AlexPoole points out in his comment, this is a fairly contrived example.
What I am attempting to do is get all sites that meet a certain set of criteria, and if there are not enough matches, then I am looking to use a different set of criteria.
Oracle doesn't have local temporary tables, and global temporary tables don't look appropriate here.
You could use a common table expression (subquery factoring):
with tempSites (siteId) as (
select siteid
from site
where state = 'TX'
union all
select siteid
from site
where state = 'OK'
and (select count(*) from site where state = 'TX') < 10
)
select s.*
from tempSites ts
join site s on s.siteid = ts.siteid;
That isn't quite the same thing, but gets all the TX IDs, and only includes the OK ones if the count of TX ones - which has to be repeated - is less than 10. The CTE is then joined back to the original table, which all seems a bit wasteful; you're hitting the same table three times.
You could use a subquery directly in a filter instead:
select *
from site
where state = 'TX'
or (state = 'OK'
and (select count(*) from site where state = 'TX') < 10);
but again the TX sites have to be retrieved (or at least counted) a second time.
You can do this with a single hit of the table using an inline view (or CTE if you prefer) with an analytic count - which add the count of TX rows to the columns in the actual table, so you'd probably want to exclude that dummy column from the final result set (but using * is bad practice anyway):
select * -- but list columns, excluding tx_count
from (
select s.*,
count(case when state = 'TX' then state end) over (partition by null) as tx_count
from site s
where s.state in ('TX', 'OK')
)
where state = 'TX'
or (state = 'OK' and tx_count < 10);
From your description of your research it sounds like what you've been looking at involved PL/SQL code populating a collection, which you could still do, but it's probably overkill unless your real situation is much more complicated.

oracle - UPDATE SQL: how to update several times the same line

I need to Update quantity of several books, before delete them.
the deleting happen with this code (oracle has a hidden column called ROWNUM)
DELETE FROM project.cart WHERE isbn = ? and ROWNUM=1;
to delete ISBN one at time (there are equal isbn in the table). but the update only works for one isbn. it should update several times the same line for all isbn found.
String sql3= "UPDATE PROject.book SET quantity=quantity +1 WHERE project.book.isbn in "
+ "(SELECT project.cart.isbn FROM project.cart) ";
// this code work perfectly, but for one time.
I hope you can help me. thanks
You can try this:
String sql3= "UPDATE PROject.book SET quantity=quantity + "
+ "(select count(*) FROM project.cart where project.cart.isbn = project.book.isbn) "
+ " WHERE project.book.isbn in (SELECT project.cart.isbn FROM project.cart)";
Do you mean that if a book is in the cart three times it should have 3 added its quantity column?
If so, you need to add the count. When a row is included by the WHERE logic, it's included only once, even if it matched more than one of the criteria. Try a correlated subquery instead:
UPDATE book
SET quantity = quantity + (SELECT COUNT(*) FROM cart WHERE book.isbn = cart.isbn)
This query will run very slowly if there are a lot of book rows, so you may want to add your original WHERE clause to the end to limit the number of book rows:
UPDATE book
SET quantity = quantity + (SELECT COUNT(*) FROM cart WHERE book.isbn = cart.isbn)
WHERE isbn IN (SELECT isbn FROM cart)

Oracle bulk update table from where clause

I have a master (MD_TS_MAST) which contains
MAST_ID
ENG_ID
MS_DATE
Detail (MD_TS_DETAIL) table
ID
MD_ID (rel with MAST_ID)
JOB_FOR
JOB_TYPE_ID
ACCOUNT_ID
Below is SQL where it locates the "MD_TS_DETAIL IDs" by eng_id and date range.
this gets me the details of.
102038
102134
101970
102244
Then I just need to update the MD_TS_DETAIL table.
UPDATE MD_TS_DETAIL
SET JOB_FOR ='25',
JOB_TYPE_ID ='344',
ACCOUNT_ID ='8'
WHERE MD_TS_DETAIL.ID IN (
SELECT D.ID
FROM MD_TS_MAST M
LEFT JOIN MD_TS_DETAIL D ON M.MAST_ID = D.MD_ID WHERE ENG_ID = '621'
AND MS_DATE BETWEEN '02-OCT-14' AND '05-OCT-14';
)
This would be converted into a form. But my mind has gone blank on how to update this.
Looks like ive had too much coffee.
I added a semi_colon just before the closing bracket (oops). removed and worked fine

Need to select column from subquery into main query

I have a query like below - table names etc. changed for keeping the actual data private
SELECT inv.*,TRUNC(sysdate)
FROM Invoice inv
WHERE (inv.carrier,inv.pro,inv.ndate) IN
(
SELECT carrier,pro,n_dt FROM Order where TRUNC(Order.cr_dt) = TRUNC(sysdate)
)
I am selecting records from Invoice based on Order. i.e. all records from Invoice which are common with order records for today, based on those 3 columns...
Now I want to select Order_Num from Order in my select query as well.. so that I can use the whole thing to insert it into totally seperate table, let's say orderedInvoices.
insert into orderedInvoices(seq_no,..same columns as Inv...,Cr_dt)
(
SELECT **Order.Order_Num**, inv.*,TRUNC(sysdate)
FROM Invoice inv
WHERE (inv.carrier,inv.pro,inv.ndate) IN
(
SELECT carrier,pro,n_dt FROM Order where TRUNC(Order.cr_dt) = TRUNC(sysdate)
)
)
?? - how to do I select that Order_Num in main query for each records of that sub query?
p.s. I understand that trunc(cr_dt) will not use index on cr_dt (if a index is there..) but I couldn't select records unless I omit the time part of it..:(
If the table ORDER1 is unique on CARRIER, PRO and N_DT you can use a JOIN instead of IN to restrict your records, it'll also enable you to select whatever data you want from either table:
select order.order_num, inv.*, trunc(sysdate)
from Invoice inv
join order ord
on inv.carrier = ord.carrier
and inv.pro = ord.pro
and inv.ndate = ord.n_dt
where trunc(order.cr_dt) = trunc(sysdate)
If it's not unique then you have to use DISTINCT to deduplicate your record set.
Though using TRUNC() on CR_DT will not use an index on that column you can use a functional index on this if you do need an index.
create index i_order_trunc_cr_dt on order (trunc(cr_dt));
1. This is a really bad name for a table as it's a keyword, consider using ORDERS instead.

Distinct by order date and products SQL Server 2008

This is my first post here so if I've been mistaken for my post and English I say sorry :)
I already do research in google and unfortunately until now I can't solve this.
This my problem, show the total orders by date on each product and I want to distinct by products
Here's my query sample
SELECT Orders.OrderDate
,SUM(OrderDetails.OrderDetailQuantity) AS totalOrdered
,Products.ProductId
FROM Orders
INNER JOIN OrderDetails ON Orders.OrderId = OrderDetails.OrderId
INNER JOIN Products ON OrderDetails.ProductId = Products.ProductId
GROUP BY Orders.OrderId
,Orders.OrderDate
,Products.ProductId
HAVING (CONVERT(VARCHAR, Orders.OrderDate, 23) BETWEEN #from AND #to)
Now, I want to distinct by product according to the between OrderDate, I know the big problem here is the DATE but I don't have any idea on how to distinct the date by this query.
Please help me. Thank you.
P.S.: if do you want to solve this in linq expression it would be highly accepted :)
Sample data and desired results would help. When you say "distinct by" I assume you mean group by. Note, in the WHERE clause you dont need to cast Order.OrderDate if you ensure that the time component of your #from & #to params are set correctly (to include each entire day). Its never a good idea to apply a cast operation to the left side of a comparison.
SELECT --cast(Orders.OrderDate as date),
Products.ProductId
SUM(OrderDetails.OrderDetailQuantity) AS totalOrdered,
FROM Orders
INNER JOIN OrderDetails ON Orders.OrderId = OrderDetails.OrderId
INNER JOIN Products ON OrderDetails.ProductId = Products.ProductId
where Orders.OrderDate between cast(#from as date) AND cast(#to as date)
GROUP
BY --cast(Orders.OrderDate as date),
Products.ProductId
-- to illustrate:
declare #From datetime = '1/1/2000 10:30:22',
#To datetime = '1/3/2000 9:11:31'
declare #orders table (i int, dt datetime)
insert into #orders
values(1, '1/1/2000 8:00'),(2, '1/2/2000 10:00'), (3, '1/4/2000 3:00')
-- only returns i=2
select *
from #orders
where dt between #From and #To
-- returns i=1 & i=2
select *
from #orders
where dt between cast(#From as date) and cast(#To as date)

Resources