select first N distinct rows without inner select in oracle - sql

I have something like the following structure: Table1 -> Table2 relationship is 1:m
I need to perform queries similar to the next one:
select Table1.id from Table1 left outer join Table2 on (Table1.id1 = Table2.id2) where Table2.name like '%a%' and rownum < 11
i.e. I want first 10 ids from Table 1 which fulfils conditions in Table2. The problem is that I've to use distinct, but the distinct clause applies after 'rownum < 11', so the result could be e.g. 5 records even if their number is more than 10.
The apparent solution is to use the following:
select id from ( select Table1.id from Table1 left outer join Table2 on (Table1.id1 = Table2.id2) where Table2.name like '%a%' ) where rownum < 11
But I'm afraid of performance of such a query. If Table1 contains about 300k records, and Table2 contains about 700k records, wouldn't such a query be really slow?
Is there another query, but without inner select? Unluckily, I want to avoid using inner selects.

Unluckily, I want to avoid using inner
selects
With having the WHERE clause on TABLE2, you are filtering the select to an INNER JOIN (ie. since Table2.name IS null <> Table2.name like '%a%' you will only get results where the join is INNER to one another. Also, the %a% without a function based index will result in a full table scan on each iteration.
but #lweller is completely correct, to do the query correctly you will need to use a subquery. keep in mind, without an ORDER BY you have no guarantee of the order of your top X records (it may always 'appear' that the values conform to the primary key or whatnot, but there is no guarantee.
WITH TABLE1 AS(SELECT 1 ID FROM DUAL
UNION ALL
SELECT 2 ID FROM DUAL
UNION ALL
SELECT 3 ID FROM DUAL
UNION ALL
SELECT 4 ID FROM DUAL
UNION ALL
SELECT 5 ID FROM DUAL) ,
TABLE2 AS(SELECT 1 ID, 'AAA' NAME FROM DUAL
UNION ALL
SELECT 2 ID, 'ABB' NAME FROM DUAL
UNION ALL
SELECT 3 ID, 'ACC' NAME FROM DUAL
UNION ALL
SELECT 4 ID, 'ADD' NAME FROM DUAL
UNION ALL
SELECT 1 ID, 'BBB' NAME FROM DUAL
) ,
sortable as( --here is the subquery
SELECT
Table1.ID ,
ROW_NUMBER( ) OVER (ORDER BY Table2.NAME NULLS LAST) ROWOverName , --this wil handle the sort
table2.name
from
Table1
LEFT OUTER JOIN --this left join it moot, pull the WHERE table2.name into the join to have it LEFT join as expected
Table2
on
(
Table1.id = Table2.id
)
WHERE
Table2.NAME LIKE '%A%')
SELECT *
FROM sortable
WHERE ROWOverName <= 2;
-- you can drop the ROW_NUMBER( ) analytic function and replace the final query as such (as you initially indicated)
SELECT *
FROM sortable
WHERE
ROWNUM <= 2
ORDER BY sortable.NAME --make sure to put in an order by!
;

You don't need DISTINCT here at all, and there is nothing bad in subqueries as such.
SELECT id
FROM Table1
WHERE id IN
(
SELECT id
FROM Table2
WHERE name LIKE '%a%'
)
AND rownum < 11
Note that the order is not guaranteed. To guarantee order, you have to use a nested query:
SELECT id
FROM (
SELECT id
FROM Table1
WHERE id IN
(
SELECT id
FROM Table2
WHERE name LIKE '%a%'
)
ORDER BY
id -- or whatever else
)
WHERE rownum < 11
There is no way to do it without nested queries (or the CTE).

For me there is no reason to be afraid of performance. I think the sub select ist the best way to solve your problem. And if you want don't trust me, take a look at explain plan of your query and you will see that it behave not so bad as you might think.

Related

Join table on Count

I have two tables in Access, one containing IDs (not unique) and some Name and one containing IDs (not unique) and Location. I would like to return a third table that contains only the IDs of the elements that appear more than 1 time in either Names or Location.
Table 1
ID Name
1 Max
1 Bob
2 Jack
Table 2
ID Location
1 A
2 B
Basically in this setup it should return only ID 1 because 1 appears twice in Table 1 :
ID
1
I have tried to do a JOIN on the tables and then apply a COUNT but nothing came out.
Thanks in advance!
Here is one method that I think will work in MS Access:
(select id
from table1
group by id
having count(*) > 1
) union -- note: NOT union all
(select id
from table2
group by id
having count(*) > 1
);
MS Access does not allow union/union all in the from clause. Nor does it support full outer join. Note that the union will remove duplicates.
Simple Group By and Having clause should help you
select ID
From Table1
Group by ID
having count(1)>1
union
select ID
From Table2
Group by ID
having count(1)>1
Based on your description, you do not need to join tables to find duplicate records, if your table is what you gave above, simply use:
With A
as
(
select ID,count(*) as Times From table group by ID
)
select * From A where A.Times>1
Not sure I understand what query you already tried, but this should work:
select table1.ID
from table1 inner join table2 on table1.id = table2.id
group by table1.ID
having count(*) > 1
Or if you have ID's in one table but not the other
select table1.ID
from table1 full outer join table2 on table1.id = table2.id
group by table1.ID
having count(*) > 1

Determine which values are not in sql table

I have this query in oracle:
select * from table where col2 in (1,2,3,4);
lets say I got this result
col1 | col2
-----------
a 1
b 2
My 'in (1,2,3,4)' part has like 20 or more options, how can I determinate which values I don't found in my table? in my example 3 and 4 doesn't exist in the table
You can't in the way you want.
You need to insert the values you want to find into a table and than select all the values which don't exist in the desired table.
Lets say the data you want to find is in A and you want to know which doesn't exist in B.
SELECT *
FROM table_a A
WHERE NOT EXISTS (SELECT *
FROM table_b B
WHERE B.col1 = A.col1);
IN lists are stupid, or at least not very useful. Use a SQL Type collection to store your values instead because we can turn them into tables.
In this example I'm using the obscure SYS.KU$_OBJNUMSET type, which is the only nested table of Number I know of on 10g. (There's lots more in 11g).
So
select t.column_value
from table ( SYS.KU$_OBJNUMSET (1,2,3,4) ) t
left join your_table
on col2 = t.column_value
where col2 is null;
Here would be a way to do it if you're just using integers for your specific example:
SELECT *
FROM (
Select Rownum r
From dual
Connect By Rownum IN (1,2,3,4)
) T
LEFT JOIN YourTable T2 ON T.r = T2.Col2
WHERE T2.Col2 IS NULL
And the Fiddle.
This creates a table out of your where criteria 1,2,3,4 and uses that to LEFT JOIN on.
--EDIT
Because values aren't ints, here is another "ugly" option:
SELECT *
FROM (
Select 'a' r From dual UNION
Select 'b' r From dual UNION
Select 'c' r From dual UNION
Select 'd' r From dual
) T
LEFT JOIN YourTable T2 ON T.r = T2.Col2
WHERE T2.Col2 IS NULL
http://www.sqlfiddle.com/#!4/5e769/2
Good luck.

Multiple count based on dynamic criteria

I have two database for which I want to compare the amount of times a case appears.
TAB1:
ID Sequence
A2D 1
A2D 2
A2D 3
A3D 1
TAB2:
ID Sequence
A2D 1
A2D 2
A3D 1
A3D 2
Now, for this example, I am trying to get this result:
ID Table1 Table2
A2D 3 2
A3D 1 2
I have tried these code without any success:
SELECT R1.ID as ID, COUNT(R1.ID) as Table1,
COUNT(R2.ID) as Table2
FROM TAB1 AS R1, TAB2 AS R2
WHERE R1.ID = R2.ID
GROUP BY R1.ID
This one gave me wrong count values...
Also, this one simply crash:
select
(
select count(*) as Table1
from TAB1
where ID = R1.ID
),(
select count(*) as Table2
from TAB2
where ID= R1.ID
)
FROM TAB1 AS R1
As you can see though, I am trying to have my criteria dynamic. Most examples I found were including basic hard-coded criteria. But for my case, I want the query to look at my first table ID, count the amount of time it appears, do it for the 2nd table with the same ID, then move on to the next ID.
If my question lacks information or is confusing just ask me, I'll do my best to be more precise.
Thanks in advance !
Here I am using a UNION ALL as a subquery
SELECT ID, SUM(T1) AS Table1, SUM(T2) AS Table2
FROM
(SELECT ID, COUNT(ID) AS T1, 0 AS T2 FROM TAB1 GROUP BY ID
UNION ALL
SELECT ID, 0 AS T1, COUNT(ID) AS T2 FROM TAB2 GROUP BY ID)
GROUP BY ID
HAVING SUM(T1)>0 AND SUM(T2)>0
I used a different approach, but unfortunately I have to use two queries, i still don't know if they can be combined together. The first one is just for making sums of both tables, and combining the results:
SELECT "Tab1" AS [Table], Tab1.ID, Count(*) AS Total
FROM Tab1
GROUP BY "Tab1", Tab1.ID
UNION SELECT "Tab2" AS [Table], Tab2.ID, Count(*) AS Total
FROM Tab2
GROUP BY "Tab2", Tab2.ID
and, since Access supports Pivot queries, you can use this:
TRANSFORM Sum(qrySums.[Total]) AS Total
SELECT qrySums.[ID]
FROM qrySums
GROUP BY qrySums.[ID]
PIVOT qrySums.[Table];
Not sure if I understand your question, but you could try something like this:
SELECT DISTINCT t.ID,
(SELECT COUNT(ID) FROM R1 WHERE ID = t.ID) AS table1,
(SELECT COUNT(ID) FROM R2 WHERE ID = t.ID) AS table2
FROM table1 t
To get the desired results, I broke it down into two sub-queries (R1SQ and R2SQ) and a main UNION query - R1R2 that uses inner, left and right joins to include all row entries including those rows that do not appear in both tables:
R1SQ
SELECT R1.Builder, Count(R1.Builder) AS Table1
FROM R1
GROUP BY R1.Builder;
R2SQ
SELECT R2.Builder_E, Count(R2.Builder_E) AS Table2
FROM R2
GROUP BY R2.Builder_E;
R1R2
SELECT R1SQ.Builder, R1SQ.Table1, R2SQ.Table2
FROM R1SQ INNER JOIN R2SQ ON R1SQ.Builder = R2SQ.Builder_E
UNION
SELECT R1SQ.Builder, R1SQ.Table1, 0 AS Table2
FROM R1SQ LEFT JOIN R2SQ ON R1SQ.Builder = R2SQ.Builder_E
WHERE (((R2SQ.Builder_E) Is Null))
UNION
SELECT R2SQ.Builder_E, 0 AS Table1, R2SQ.Table2
FROM R1SQ RIGHT JOIN R2SQ ON R1SQ.Builder = R2SQ.Builder_E
WHERE (((R1SQ.Builder) Is Null))
ORDER BY R1SQ.Builder;

Can you define values in a SQL statement that you can join/union, but are not stored in a table outside of the statement?

I'm trying to create a query and need to join against something that I can define values in without creating a table.
I'll attempt to describe what I'm trying to do:
table1
-------
fieldA
is joined on fieldA with
table2
-------
(titles for FK in table 1)
Table1 has values outside of what exists in table2
I want to add an additional 'table' to be unioned with table2 and then joined with table 1
Thanks
Sure, you can use a UNION ALL inside a subselect and join with the result of that. Something like this might do the trick:
SELECT *
FROM table1 T1
JOIN (
SELECT titles, stuff
FROM table2
UNION ALL
SELECT 'foo' AS titles, 'foostuff' AS stuff
UNION ALL
SELECT 'bar' AS titles, 'barstuff' AS stuff
) T2
ON T1.id = T2.titles
Note that the columns in the UNION ALL must be of the same type and in the same order. The column names don't have to match though.
Looks like you want to add arbitrary results to your query?
select
id,
titles
from
table1 t1
inner join table2 t2
on t2.titles = t1.titles
union (
(select 100, 'Dogs' from dual)
union
(select 200, 'Pigs' from dual)
union
(select 300, 'Sheep' from dual)
)
That's an oracle flavour, for other RDBMS' there will be an equivalent to dual
If you're using a modern Oracle version, there is an even neater solution
WITH arbitrary_data AS (
SELECT 100 id, 'Dogs' titles FROM DUAL
UNION ALL SELECT 200, 'Pigs' FROM DUAL
UNION ALL SELECT 300, 'Sheep' FROM DUAL
)
SELECT
id,
titles
FROM
table1 t1
inner join table2 t2
on t2.titles = t1.titles
inner join arbitrary_data ad
on ad.titles = t1.titles

mysql - union tables by unique field

I have two tables with the same structure:
id name
1 Merry
2 Mike
and
id name
1 Mike
2 Alis
I need to union second table to first with keeping unique names, so that result is:
id name
1 Merry
2 Mike
3 Alis
Is it possible to do this with MySQL query, without using php script?
This is not a join (set multiplication), this is a union (set addition).
SELECT #r := #r + 1 AS id, name
FROM (
SELECT #r := 0
) vars,
(
SELECT name
FROM table1
UNION
SELECT name
FROM table2
) q
This will select all names from table1 and combine those with all the names from table2 which are not in table1.
(
select *
from table1
)
union
(
select *
from table2 t2
left join table1 t1 on t2.name = t1.name
where t1.id is null
)
Use:
SELECT a.id,
a.name
FROM TABLE_A a
UNION
SELECT b.id,
b.name
FROM TABLE_B b
UNION will remove duplicates.
As commented, it all depends on what your 'id' means, cause in the example, it means nothing.
SELECT DISTINCT(name) FROM t1 JOIN t2 ON something
if you only want the names
SELECT SUM(something), name FROM t1 JOIN t2 ON something GROUP BY name
if you want to do some group by
SELECT DISTINCT(name) FROM t1 JOIN t2 ON t1.id = t2.id
if the id's are the same
SELECT DISTINCT COALESCE(t1.name,t2.name) FROM
mytable t1 LEFT JOIN mytable t2 ON (t1.name=t2.name);
will get you a list of unique names from the 2 tables. If you want them to get new ids (like Alis does in your desired results), that's something else and requires the answers to a couple of questions:
do any of the names need to maintain their previous id. And if they do, which table's id should be preferred?
why do you have 2 tables with the same structure? ie what are you trying to accomplish when you generate the unique name list?