Can you use multiple columns for a not in query? - sql

I recently saw someone post this as part of an answer to an SO query question:
SELECT DISTINCT a, b, c
FROM t1
WHERE (a,b,c) NOT IN
( SELECT DISTINCT a,b,c FROM t2 )
I'm a bit confused, as I always thought that you can't use multiple columns for "NOT IN" ("where(a,b,c)", etc.). Is this correct SQL syntax? And how about MySQL?

Googling it suggests that it will work on some databases but not others. You can use this instead:
SELECT DISTINCT a, b, c
FROM t1
WHERE NOT EXISTS
(SELECT 1 FROM t2
WHERE t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c)

It's a SQL extension. Oracle, PostgreSQL and MySQL have it. SQL Server 2005 does not have it. I'm not sure about others.

It certainly does work in Oracle. Quick contrived example:
SQL> select ename, job, deptno from emp
2 where (ename, deptno) in
3 ( select ename, deptno from emp
4 where job = 'MANAGER'
5 );
ENAME JOB DEPTNO
---------- --------- ----------
JONES MANAGER 20
CLARK MANAGER 10
PARAG MANAGER 30
This also works:
SQL> select ename, job, deptno from emp
2 where (ename, deptno) in (('JONES',20),('CLARK',10));
ENAME JOB DEPTNO
---------- --------- ----------
JONES MANAGER 20
CLARK MANAGER 10
NOT IN too:
SQL> select ename, job, deptno from emp
2 where (ename, deptno) not in
3 ( select ename, deptno from emp
4 where job = 'MANAGER'
5 );
ENAME JOB DEPTNO
---------- --------- ----------
SMITH CLEANER 99
SCOTT ANALYST 20
KING PRESIDENT 10
FORD ANALYST 20
MILLER CLERK 10

Not that I'm aware of, but if thy're character type (or can be converted to char types), you can fake it:
SELECT DISTINCT a, b, c
FROM t1
WHERE a+b+c NOT IN
( SELECT DISTINCT a+b+c FROM t2 )

Try this
SELECT DISTINCT a, b, c
FROM t1,
(SELECT DISTINCT a,b,c FROM t2) as tt
WHERE t1.a NOT IN tt.a
AND t1.b NOT IN tt.b
AND t1.c NOT IN tt.c
Note: This has not been tested, it hasn't even been proven correct.

Others have already answered the question, but as a performance suggestion, if you're dealing with data of any significant size always use the EXISTS statement rather than IN. It will be faster in almost every case.
http://decipherinfosys.wordpress.com/2007/01/21/32/

Related

using set operator query to find the result of the given questions

Using set operator display the DEPTNO,SUM(SAL) for each dept, JOB,SUM(SAL) for each Job and Total Salary.
Using Set Operator display the JOB and Deptno in employees working in deptno 20,10,30 in that order.
for first question my query is this:
select e.deptno,to_char(null),e.sum(sal),
from emp e
UNION
select d.deptno,d.job,d.sum(sal)
from emp d
group by deptno,job;
I have no idea how to do the second one.
SET opetator can be union,intersection,minus...
The phrasing of the first question is not 100% clear to me, but the query should be:
select deptno, '', sum(sal) from emp group by deptno
union
select deptno, job, sum(sal) from emp group by deptno, job
For the second question you can use UNION again. For example:
select job, deptno
from (
select job, deptno, 2 as o from emp where deptno = 10
union
select job, deptno, 1 from emp where deptno = 20
union
select job, deptno, 3 from emp where deptno = 30
) x
order by o

Get Unique column value along with other. Columns when using inner join

I have following query and I need unique value for column
Select unique(t.id), log.*
from tableA log
inner join tableT t on t.id1=log.id1
where log.time>=(somedate)
and log.time<(somedate)
and ref-id=20
and ref-id=30
and t.id not in (select unique t.id
from tableA log
inner join tableT t on t.id1=log.id1
where log.time>=(somedate)
and log.time<(somedate)
and ref-id=20
and ref-id=30);
I am not getting unique values for t.id.Can anyone please help ? I am using oracle data base
Remove LOG.* from the SELECT; UNIQUE (as well as DISTINCT) is applied to all columns you select, not only to the one you put into brackets.
[EDIT]
Scott's EMP table contains employees that work in different departments. List of distinct departments is:
SQL> select distinct deptno from emp;
DEPTNO
----------
30
20
10
SQL>
If you include additional columns, such as JOB, the list is changed to
SQL> select distinct deptno, job from emp order by deptno, job;
DEPTNO JOB
---------- ---------
10 CLERK
10 MANAGER
10 PRESIDENT
20 ANALYST
20 CLERK
20 MANAGER
30 CLERK
30 MANAGER
30 SALESMAN
9 rows selected.
SQL>
It is now distinct per DEPTNO and per JOB; you can't get only distinct DEPTNO in such a query.
[EDIT #2: aggregation]
If "distinct" means distinct DEPTNO and the first job for that DEPTNO, then you might need this:
SQL> select deptno, min(job) first_job from emp
2 group by deptno
3 order by deptno;
DEPTNO FIRST_JOB
---------- ---------
10 CLERK
20 ANALYST
30 CLERK
SQL>
[EDIT #3, example based on your query]
Select t.id,
min(log.id) log_id,
min(log.time) log_time,
<the rest of LOG table columns goes here, all of them with MIN>
from tableA log
inner join table ...
<the rest of your query goes here>
group by t.id;
How about something like this?
Select t.id, log.*
from tableA log inner join
(select t.*, row_number() over (partition by id1 order by id) as seqnum
from tableT t
) t
on t.id1 = log.id1
where log.time >= (somedate) and
log.time < (somedate) and
t.seqnum = 1
ref_id = 20 and ref_id = 30 -- this is ALWAYS false, but I'm ignoring that
Notes:
I have no idea what the subquery is supposed to be doing.
The conditions on ref_id will always evaluate to FALSE, so that logic is almost certainly not what you intend.
If ref_id is in the transaction table, then the conditions should be moved to the subquery.

ORACLE SQL retrieve n rows without subqueries or derived tables

I'm doing my SQL exercises but I got stuck in one. I need to retrieve the employees with the two highest salaries, but I can't use any type of subquery or derived table. I do it with a subquery like this:
SELECT *
FROM (SELECT * FROM emp ORDER BY sal DESC) new_emp
WHERE ROWNUM < 3;
I also know that this can be achieved using the WITH clause, but I'm wondering if there is any alternative to this.
PS: I'm using Oracle 11.
If you are Oracle version 12.1 or above you can use a row limiting clause. In your case you would just use the subquery plus the row limiting clause like so:
SELECT * FROM emp
ORDER BY sal DESC
FETCH FIRST 5 ROWS ONLY;
Source: https://oracle-base.com/articles/12c/row-limiting-clause-for-top-n-queries-12cr1#top-n
This is actually a pathetic method, in my opinion, but you can use a join:
select e.col1, e.col2, . . .
from emp e left join
emp e2
on e2.salary >= e.salary
group by e.col1, e.col2, . . .
having count(distinct e2.salary) <= 2;
Note: this is really equivalent to a dense_rank(), so if there are ties, you'll get more than two rows. It is easy enough to fix this (assuming you have a unique identifier for each row), but the fix complicates the logic and hides the basic idea.
A good exercise should help to prepare to solve practical problems. So the important thing in this one is not the usage of subquery but to realize that the two highes salaries can have hunderts of employees.
While using the #MT0 view workaround this is the query
CREATE VIEW sal_ordered_emps AS
SELECT e.*,
row_number() over (order by sal desc) as RN
FROM SCOTT.emp e
ORDER BY sal DESC;
select e.* from scott.emp e join
sal_ordered_emps soe on e.sal = soe.sal and rn <= 2
;
result as explained can be more than 2 records
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 19.04.1987 00:00:00 3000 20
7839 KING PRESIDENT 17.11.1981 00:00:00 5000 10
7902 FORD ANALYST 7566 03.12.1981 00:00:00 3000 20
This is cheating but... instead of using a subquery you can define a view:
CREATE VIEW sal_ordered_emps AS
SELECT *
FROM emp
ORDER BY sal DESC;
Then you can do:
SELECT * FROM sal_ordered_emps WHERE ROWNUM < 3;
Alternatively, you can do it with a pipelined function...
CREATE OR REPLACE PACKAGE emp_pkg
AS
TYPE emp_table IS TABLE OF EMP%ROWTYPE;
FUNCTION get_max_sals(
n INT
) RETURN emp_table PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY emp_pkg
AS
FUNCTION get_max_sals(
n INT
) RETURN emp_table PIPELINED
AS
cur SYS_REFCURSOR;
in_rec EMP%ROWTYPE;
i INT := 0;
BEGIN
OPEN cur FOR SELECT * FROM EMP ORDER BY SAL DESC;
LOOP
i := i + 1;
FETCH cur INTO in_rec;
EXIT WHEN cur%NOTFOUND OR i > n;
PIPE ROW(in_rec);
END LOOP;
CLOSE cur;
RETURN;
END;
END;
/
SELECT *
FROM TABLE( emp_pkg.get_max_sals( 2 ) );
This solution use no subquery, but requires three steps:
-- Q1
select max(sal) from scott.emp;
-- Q2
select max(sal) from scott.emp where sal < {result of Q1};
select * from scott.emp where sal in ({result of Q1},{result of Q2});
General you'll need N+1 queries to get top-N salaries emps.

Oracle SQL - Update a database field with the content of the next record

I am trying to anonymize the 'name' field of a customer table. I want to replace the 'name' of every record with the name from the customer in the next record. (I know: That's not really anonymous but 'name' and 'customerId' won't match after that. That's enough for my purposes)
I tried this, but I get an ORA-01747 error.
UPDATE Customer A
SET NAME =
(SELECT NAME
FROM Customer
WHERE ROWNUM = A.ROWNUM + 1)
What is wrong? How can I update every 'name'-field with the content of the next 'name'-field in the table?
ROWNUM is a pseudocolumn, it's not stored with the data, its part of your result set. Additionally, typically relational databases have no concept of row order.
We can probably work out a kludgy way to do it, but instead, couldn't you just instead do something like:
UPDATE CUSTOMER SET NAME = DBMS_RANDOM.STRING('a', 10);
In Oracle, this updates every customer with a random string of 10 alphanumeric digits.
Mix'em all!!!
merge into Customer dest
using (
select r, name from
(select name, row_number() over (order by dbms_random.value) n from Customer)
join (select rowid r, rownum n from Customer) using(n)
) src
on (dest.rowid = src.r)
when matched then update set
dest.name = src.name;
You need to use LEAD().
Corrected as per 'a_horse_with_no_name' comments: LEAD(sal, 1, sal)
UPDATE emp_test a
SET sal =
(
SELECT LEAD(sal, 1, sal) OVER (ORDER BY sal) AS sal_next
FROM scott.emp b
WHERE a.empno = b.empno
)
/
Same with ename:
SELECT empno, ename, job, sal,
LEAD(ename, 1, ename) OVER (ORDER BY ename) AS name_next
FROM scott.emp
/
EMPNO ENAME JOB SAL NAME_NEXT
--------------------------------------------
7876 ADAMS CLERK 1100 ALLEN
7499 ALLEN SALESMAN 1600 BLAKE
7698 BLAKE MANAGER 2850 CLARK
7782 CLARK MANAGER 2450 FORD
....
7844 TURNER SALESMAN 1500 WARD
7521 WARD SALESMAN 1250 WARD
This will not work:
SELECT * FROM scott.emp
WHERE ROWNUM = 5
/
But this will:
SELECT * FROM scott.emp
WHERE ROWNUM <= 5
/
This might work but is untested.
UPDATE Customer A
SET NAME =
(SELECT NAME
FROM Customer
WHERE ROWNUM = (SELECT (A.ROWNUM + 1))

Selecting based on condition having multiple options

I'm totally confused about using aggregate functions after where clause or anywhere after mentioning the table_name
EMP Table as posted on http://viditkothari.co.in/post/27045365558/sql-commands-1
Query Info:
Display all the emp who have sal equal to any of the emp of deptno 30
Suggested query:
select *
from employee_4521
where sal having (select sal
from employee_4521
where deptno = 30);
Returns following error:
ERROR at line 1:
ORA-00920: invalid relational operator
with an asterik marked under 'h' of having clause
There doesn't appear to be any reason to use an aggregate function here. Just use an IN or an EXISTS
select *
from employee_4521
where sal in (select sal
from employee_4521
where deptno=30);
or
select *
from employee_4521 a
where exists( select 1
from employee_4521 b
where b.deptno = 30
and a.sal = b.sal );