SD0_NNcannot be evaluated without using index - sql

I know this is a frequently discussed error but I am not able to get my hands through it even after trying really hard.
I have the following query that works fine
SELECT b.BID
FROM STUDENT s,
BUILDINGS b
WHERE sdo_nn(b.LOC, s.LOC, 'sdo_num_res=1', 1) = 'TRUE'
and shows the nearest neighbor of each s. But what I want is to display the BID of the top 2 b that appears the most, so I change my query to this:
SELECT b.BID, count(b.BID)
FROM STUDENT s,
BUILDINGS b
WHERE sdo_nn(b.LOC, s.LOC, 'sdo_num_res=1', 1) = 'TRUE'
GROUP BY b.BID
and then it fails with the error SDO_NN cannot be evaluated without using index.
Can you please help with this problem or tell me an alternate way to do it.

You can try using a subquery:
SELECT BID, COUNT(*)
FROM (SELECT b.BID
FROM STUDENT s,
BUILDINGS b
WHERE sdo_nn(b.LOC, s.LOC, 'sdo_num_res=1', 1) = 'TRUE'
) b
GROUP BY BID;
I'm not sure why the subquery is needed, but if the first query works, this one should as well.
Note: I'd be inclined to write this using an explicit join (because I abhor commas in the from clause):
SELECT BID, COUNT(*)
FROM (SELECT b.BID
FROM STUDENT s JOIN
BUILDINGS b
ON sdo_nn(b.LOC, s.LOC, 'sdo_num_res=1', 1) = 'TRUE'
) b
GROUP BY BID;

Related

Use result of multiple rows to do arithmetic operation

I'm writing a query to multiply the count that I receive from subquery to fees amount, But I don't know how to do that. Any help/suggestion?
Oracle query is:
select courseid,coursename,fees*tmp
from course c join registration r on
r.courseid=c.courseid
and tmp IN (select count(*)
from course c join registration r on
r.courseid=c.courseid group by coursename);
I tried to use like a variable tmp ,But i don't think it works in oracle query. Is there an alternative way to do so?
You can't do that, because you can only select data from tables that appeared between FROM and WHERE. The IN operator is a quick way to save having to write a bunch of OR statements, it is not something that can establish a variable in the outer query.
Instead do something like:
select courseid,coursename,fees * COUNT(r.courseID) OVER(PARTITION BY c.coursename)
from course c join registration r on
r.courseid=c.courseid
Edit/update: you noted that this query produces too many rows and you only want to see distinct course names. In that case it would be better to just use the registrations table to count the number of people on the course and then multiply the fees:
SELECT
c.courseid, c.coursename, c.fees * COALESCE(r.numberOfstudents, 0) as courseWorth
FROM
course c
LEFT OUTER JOIN
(select courseid, COUNT(*) as numberofstudents FROM registration GROUP BY courseid) r
ON c.courseID = r.courseid
You can use a windowing function like Caius or you can use a join like this:
select courseid,coursename, fees * COALESCE(sub.cnt,0)
from course c
join registration r on r.courseid=c.courseid
left join (
select coursename, count(*) as cnt
from course c2
join registration r2 on r2.courseid=c2.courseid
group by coursename
) as sub;
note: I make no claim your joins are correct -- I'm basing this query off of your example not on any knowledge of your data model.

SQL Server query perfomance tuning with group by and join clause

We have been experiencing performance concerns over job and I could fortunately find the query causing the slowness..
select name from Student a, Student_Temp b
where a.id = b.id and
a.name in (select name from Student
group by name having count(*) = #sno)
group by a.name having count(*) = #sno
OPTION (MERGE JOIN, LOOP JOIN)
This particular query is iteratively called many times slowing down the performance..
Student table has 8 Million records and Student_temp receives 5-20 records in the iteration process each time.
Student table has composite primary key on ( id and name)
and sno = No of records in Student_Temp.
My questions are below,
1) why does this query show performance issues.
2) could you guys give a more efficient way of writing this piece ?
Thanks in Advance !
It's repeating the same logic unnecessarily. You really just want:
Of the Student(s) who also exist in Student_temp
what names exist #sno times?
Try this:
SELECT
name
FROM
Student a JOIN
Student_Temp b ON a.id = b.id
GROUP BY
name
HAVING
count(*) = #sno
Your query returns the following result: Give me all names that are #sno times in the table Student and exactly once in Student_temp.
You can rewrite the query like this:
SELECT a.name
FROM Student a
INNER JOIN Student_temp b
ON a.id = b.id
GROUP BY a.name
HAVING COUNT(*) = #sno
You should omit the query hint unless you are absolutely sure that the query optimizer screws up.
EDIT: There is of course a difference between the queries: if for instance #sno=2 then a name that shows up once in Student but twice in Student_temp would be included in my query but not in the original. I depends on what you really want to achieve whether that needs to be adressed or not.
Here you go
select name
from Student a
inner join Student_Temp b
on a.id = b.id
group by a.name
HAVING COUNT(*) = #sno

SQL SELECT and SUM from three

I have been cracking my head for hours on what I thought to be simple SQL SELECT command. I searched every where and read all questions related to mine. I tried an SQL Command Builder, and even read and applied complete series of SQL tutorials and manuals to try to build it from scratch understanding it (which is very important for me, regarding next commands I'll eventually have to build...).
But now I'm just stuck with the results I want, but on separates SELECT commands which I seem to be unable to get together !
Here is my case : 3 tables, first linked to the second with a common id, second linked to the third with another common id, but no common id from the first to the third. Let's say :
Table A : id, name
Table B : id, idA, amount
Table C : id, idB, amount
Several names in Table A. Several amounts in Table B. Several amounts in Table C. Result wanted : each A.id and A.name, with the corresponding SUM of B.amount, and with the corresponding SUM of C.amount. Let's say :
A.id
A.name
SUM(B.amount) WHERE B.idA = A.id
SUM(C.amount) WHERE C.idB = B.id for each B which B.idA = A.id
It's okay for "the first three columns", and "the first two columns and the fourth", both with a WHERE clause and/or a LEFT JOIN. But I can't achieve cumulating all fourth columns together without messing everything !
One could say "it's easy, just put an idA column in Table C" ! Should be easier, sure. But is it really necessary ? I don't think so, but I could be wrong ! So, I just please anyone (who I will give an eternal "SQL God" decoration) with SQL skills to answer laughing "That's so simple ! Just do that and you are gone ! Stupid little newbies..." ;)
Running VB 2010 and MS SQL Server
Thanks for reading !
Try this:
SELECT A.Id, A.Name, ISNULL(SUM(B.amount), 0) as bSum, ISNULL(SUM(C2.Amount), 0) as cSum
FROM A
LEFT OUTER JOIN B ON A.Id = B.idA
LEFT OUTER JOIN (SELECT C.idB, SUM(C.AMOUNT) AS Amount FROM C GROUP BY C.idB) AS C2 ON C2.idB = B.Id
GROUP BY A.Id, A.Name
Try this:
SELECT
a.id,
a.name,
sum(x.amount) as amountb,
sum(x.amountc) as amountc
from a
left join (
select
b.id,
b.ida,
b.amount,
SUM(c.amount) as amountc
from b
left join c
on b.id = c.idb
group by
b.id,
b.amount,
b.ida
) x
on a.id = x.ida
group by
a.id,
a.name
This should give you the result set you're looking for. It sums all C.Amount's for each B.id, then adds it all together into a single result set. I tested it with a bit of sample data in MSSQL, and it works as expected.
Select a.id, a.name, sum(b.amount), sum(c.amount)
from a inner join b on a.id = b.idA
inner join c on b.id = c.idB
group by a.id, a.name
You need to add them separately:
select a.id, a.name, (coalesce(b.amount, 0.0) + coalesce(c.amount, 0.0))
from a left outer join
(select b.ida, sum(amount) as amount
from b
group by b.ida
) b
on a.id = b.ida left outer join
(select b.ida, sum(amount) as amount
from c join
b
on c.idb = b.id
group by b.ida
) c
on a.id = c.ida
The outer joins are to take into account when b and c records don't both exist for a given id.

comparison query taking ages

My query is quite simple:
select a.ID, a.adres, a.place, a.postalcode
from COMPANIES a, COMPANIES b
where a.Postcode = b.Postcode
and a.Adres = b.Adres
and (
select COUNT(COMPANYID)
from USERS
where COMPANYID=a.ID
)>(
select COUNT(COMPANYID)
from USERS
where COMPANYID=b.ID
)
Database: sql server 2008 r2
What I'm trying to do:
The table of COMPANIES contains double entries. I want to know the ones that are connected to the most amount of users. So I only have to change the foreign keys of those with the least. ( I already know the id's of the doubles)
Right now it's taking a lot of time to complete. I was wondering if if could be done faster
Try this version. It should be only a little faster. The COUNT is quite slow. I've added a.ID <> b.ID to avoid few cases earlier.
select a.ID, a.adres, a.place, a.postalcode
from COMPANIES a INNER JOIN COMPANIES b
ON
a.ID <> b.ID
and a.Postcode = b.Postcode
and a.Adres = b.Adres
and (
select COUNT(COMPANYID)
from USERS
where COMPANYID=a.ID
)>(
select COUNT(COMPANYID)
from USERS
where COMPANYID=b.ID
)
The FROM ... INNER JOIN ... ON ... is a preferred SQL construct to join tables. It may be faster too.
One approach would be to pre-calculate the COMPANYID count before doing the join since you'll be repeatedly calculating it in the main query. i.e. something like:
insert into #CompanyCount (ID, IDCount)
select COMPANYID, COUNT(COMPANYID)
from USERS
group by COMPANYID
Then your main query:
select a.ID, a.adres, a.place, a.postalcode
from COMPANIES a
inner join #CompanyCount aCount on aCount.ID = a.ID
inner join COMPANIES b on b.Postcode = a.Postcode and b.Adres = a.Adres
inner join #CompanyCount bCount on bCount.ID = b.ID and aCount.IDCount > bCount.IDCount
If you want all instances of a even though there is no corresponding b then you'd need to have left outer joins to b and bCount.
However you need to look at the query plan - which indexes are you using - you probably want to have them on the IDs and the Postcode and Adres fields as a minimum since you're joining on them.
Build an index on postcode and adres
The database probably executes the subselects for every row. (Just guessing here, veryfy it in the explain plan. If this is the case you can rewrite the query to join with the inline views (note this is how it would look in oracle hop it works in sql server as well):
select distinct a.ID, a.adres, a.place, a.postalcode
from
COMPANIES a,
COMPANIES b,
(
select COUNT(COMPANYID) cnt, companyid
from USERS
group by companyid) cntA,
(
select COUNT(COMPANYID) cnt, companyid
from USERS
group by companyid) cntb
where a.Postcode = b.Postcode
and a.Adres = b.Adres
and a.ID<>b.ID
and cnta.cnt>cntb.cnt

Oracle SQL: Joining at most one associated entity

I have tables Building and Address, where each Building is associated with 0..n Addresses.
I'd like to list Buildings with an associated Address. If a Building has several entrances, and thus several Addresses, I don't care which one is displayed. If a Building has no known addresses, the address fields should be null.
This is, I want something like a left join that joins each row at most once.
How can I express this in Oracle SQL?
PS: My query will include rather involved restrictions on both tables. Therefore, I'd like to avoid repeating those restrictions in the query text.
I would consider querying the address in the SELECT clause, e.g.:
SELECT b.*
,(SELECT a.text
FROM addresses a
WHERE a.buildingid = b.id
AND ROWNUM=1) as atext
FROM building b;
The ROWNUM=1 means "just get one if there are any, don't care which".
The advantage of this approach is that it will probably perform better than most alternatives, as long as a suitable index on addresses.buildingid exists. It will stop looking for more addresses as soon as it finds one for each building queried.
The downside to this approach is if you want multiple columns from the address table, you can't - although you can concatenate them together into one string.
Because you don't care which of many addresses is displayed:
Oracle 9i+:
WITH summary AS (
SELECT b.*,
a.*,
ROW_NUMBER() OVER (PARTITION BY b.building_id) rn
FROM BUILDINGS b
LEFT JOIN ADDRESSES a ON a.building_id = b.building_id)
SELECT s.*
FROM summary s
WHERE s.rn = 1
Non-Subquery Factoring Equivalent:
SELECT s.*
FROM (SELECT b.*,
a.*,
ROW_NUMBER() OVER (PARTITION BY b.building_id) rn
FROM BUILDINGS b
LEFT JOIN ADDRESSES a ON a.building_id = b.building_id) s
WHERE s.rn = 1
what you could do is an restriction on the addresses dat you join.
For instance by requiring that there is no address with a lower id:
select *
from building b
left join addresses a on (a.buildingid = b.id)
where not exists (select 1 from addresses a2
where a2.buildingid = b.id and a2.id < a.id)
in this case you will get at most 1 address per building.
select b.*, max(a.id) as aid
from building b
left outer join addresses a on (a.buildingid = b.id)
group by a.buildingid
or
select b.*, maxid
from building b
left outer join
(
select buildingid, max(id) as maxid
from addresses
group by buildingid
) a on (a.buildingid = b.id)
Meriton,
This approach uses nested inline views. I've proven this approach on large data sets, it performs very well.
The best way to understand the query is to start from the inner-most "M" inline view. I added the count for the sake of debugging and clarity. This identifies the maximum (ie. most recent???) address id for each building:
select maxa.b_id, max(maxa.a_id) a_id, count(*) c
from address maxa
group by maxa.b_id;
The next "A" inline view uses the above "M" inline view to decide which address to get, then joins to that address id to return a set of address fields:
select ma.b_id, ma.a_id, ma.addr1, ma.addr2, ma.addr3, m.c
from address ma,
( select maxa.b_id, max(maxa.a_id) a_id, count(*) c
from address maxa
group by maxa.b_id ) m
where ma.a_id = m.a_id;
The above "A" inline view delivers a transformed set of addresses to the final query. Whereas the relationship between BUILDING and ADDRESS is 1 to 0..n, the relationship between BUILDING and "A" is 1 to 0..1, a basic outer-join:
select b.b_id, b.b_code, b.b_name, a.*
from building b,
( select ma.b_id, ma.a_id, ma.addr1, ma.addr2, ma.addr3, m.c
from address ma,
( select maxa.b_id, max(maxa.a_id) a_id, count(*) c
from address maxa
group by maxa.b_id ) m
where ma.a_id = m.a_id ) a
where b.b_id = a.b_id (+);
The key advantages with this approach are:
Delivers any number of address columns.
Deterministic, returns exactly the same results each time it is run.
Does not place undue complexities on your final query, which will surely be more complex than this one.
The "A" inline view can be easily encapsulated within a database view, perhaps call it the LATEST_ADDRESS view:
create view latest_address (b_id, a_id, addr1, addr2, addr3, c) as
select ma.b_id, ma.a_id, ma.addr1, ma.addr2, ma.addr3, m.c
from address ma,
( select maxa.b_id, max(maxa.a_id) a_id, count(*) c
from address maxa
group by maxa.b_id ) m
where ma.a_id = m.a_id;
select b.b_id, b.b_code, b.b_name, a.*
from building b, latest_address a
where b.b_id = a.b_id (+);
Enjoy!
Matthew