Is it possible to compare tuples in oracle-compatible sql? - sql

I'm not 100% if tuples is the term for what I'm talking about but I'm looking at something like this:
Table grades
user grade
------------
Jim B
Bill C
Tim A
Jim B+
I know I can do:
SELECT COUNT(*)
FROM grades
WHERE (
(user = 'Jim' AND grade = 'B')
OR (user = 'Tim' AND grade = 'C')
);
But is there a way to do something more like this?
SELECT COUNT(*)
FROM grades
WHERE (user, grade) IN (('Jim','B'), ('Tim','C'));
EDIT: As a side note, I'd only tested with:
(user, grade) = ('Tim','C')
And that fails, so I assumed IN would fail as well, but I was wrong (thankfully!).

The query you posted should be valid syntax
SQL> ed
Wrote file afiedt.buf
1 with grades as (
2 select 'Jim' usr, 'B' grade from dual
3 union all
4 select 'Bill', 'C' from dual
5 union all
6 select 'Tim', 'A' from dual
7 union all
8 select 'Jim', 'B+' from dual
9 )
10 select *
11 from grades
12 where (usr,grade) in (('Jim','B'),
13 ('Tim','C'),
14* ('Tim','A'))
SQL> /
USR GR
---- --
Jim B
Tim A

You could use a subquery to treat a list of tuples like a table:
SELECT COUNT(*)
FROM grades
JOIN (
SELECT 'Jim' as user, 'B' as grade from dual
UNION ALL
SELECT 'Tim', 'C' from dual
UNION ALL
SELECT 'Pim', 'D' from dual
) as SearchTarget
ON SearchTarget.user = grades.user
and SearchTarget.grade = grades.grade

Related

I want to count city column's values which are repeated

id city
1 London
2 Rome
3 London
4 Rome
Expected output like this:
London Rome
2 2
Using case expression...
How can I solve this query?
This is a standard aggregation + group by + having problem.
Sample data:
SQL> with test (id, city) as
2 (select 1, 'London' from dual union all
3 select 2, 'Rome' from dual union all
4 select 3, 'London' from dual union all
5 select 4, 'Rome' from dual union all
6 select 5, 'Zagreb' from dual
7 )
Query:
8 select city,
9 count(*) cnt
10 from test
11 group by city
12 having count(*) > 1;
CITY CNT
------ ----------
London 2
Rome 2
SQL>
What does case expression have to do with it?
You can get your output using conditional aggregation:
SELECT COUNT(CASE city WHEN 'London' THEN 1 END) AS London,
COUNT(CASE city WHEN 'Rome' THEN 1 END) AS Rome
FROM table_name
WHERE city IN ('London', 'Rome')
Which, for the sample data:
CREATE TABLE table_name (id, city) AS
SELECT 1, 'London' FROM DUAL UNION ALL
SELECT 2, 'Rome' FROM DUAL UNION ALL
SELECT 3, 'London' FROM DUAL UNION ALL
SELECT 4, 'Rome' FROM DUAL;
Outputs:
LONDON
ROME
2
2
However, in this case, it is unclear how to handle your requirement for repeated values if the values were not repeated.
fiddle

Printing data from sub-queries SQL

I have 2 tables: SalesPeople and Customers that have snum and cnum as primary key respectively; both tables have city column as well.
Without using joins, we have to tell the names of customers and salespeople that belong to same city.
I have used nested queries to print the salespeople that belong to the city of customers, but cant figure out how to print customer names with this .
SELECT S.*
FROM SalesPeople S
WHERE City IN(
SELECT City
FROM Customers CX
);
How about this? (Disregard the fact that the WITH factoring clause doesn't exist in Oracle 9i (at least, I think so); you already have those tables).
Sample data:
SQL> with
2 salespeople (snum, city) as
3 (select 1, 'London' from dual union all
4 select 2, 'Paris' from dual union all
5 select 3, 'Rome' from dual
6 ),
7 customers (cnum, city) as
8 (select 100, 'Zagreb' from dual union all
9 select 101, 'Rome' from dual union all
10 select 102, 'Rome' from dual union all
11 select 103, 'Paris' from dual
12 )
Query:
13 select person_num
14 from (select snum as person_num, city from salespeople
15 union
16 select cnum, city from customers
17 )
18 where city = 'Rome';
PERSON_NUM
----------
3
101
102
SQL>

How to filter records based on another grouped by column in a same table

I have a sample table as below:
Target output:
I have tried with below script but only number of count produced as output:
select ID, count(COUNTRY) from test
group by COUNTRY
having count(COUNTRY)=3;
Could anyone here please help how can I get the targeted output? Thanks.
Here's one option - a subquery:
Sample data:
SQL> with test (id, name, country) as
2 (select 1, 'Mike' , 'Australia' from dual union all
3 select 2, 'Jason', 'Australia' from dual union all
4 select 3, 'Lee' , 'China' from dual union all
5 select 4, 'Simon', 'India' from dual union all
6 select 5, 'Alex' , 'Malaysia' from dual union all
7 select 6, 'John' , 'Australia' from dual
8 )
Query:
9 select id, country
10 from test
11 where country in (select country
12 from test
13 group by country
14 having count(*) = 3
15 )
16 order by id;
ID COUNTRY
---------- ---------
1 Australia
2 Australia
6 Australia
SQL>
You can use analytic functions and only scan the table once:
SELECT id, name, country
FROM (
SELECT id, name, country,
COUNT(*) OVER (PARTITION BY country) AS people_per_country
FROM table_name
)
WHERE people_per_country = 3;
Which, for the sample data:
CREATE TABLE table_name (id, name, country) AS
SELECT 1, 'Mike' , 'Australia' FROM DUAL UNION ALL
SELECT 2, 'Jason', 'Australia' FROM DUAL UNION ALL
SELECT 3, 'Lee' , 'China' FROM DUAL UNION ALL
SELECT 4, 'Simon', 'India' FROM DUAL UNION ALL
SELECT 5, 'Alex' , 'Malaysia' FROM DUAL UNION ALL
SELECT 6, 'John' , 'Australia' FROM DUAL;
Outputs:
ID
NAME
COUNTRY
1
Mike
Australia
2
Jason
Australia
6
John
Australia
db<>fiddle here

How to get distinct employees that do not have a particular skillset

I have a table that has two columns. Employee_id (which is unique per employee) and next column for employee skillset. One employee can have multiple skillset. How do I retrieve the list of distinct employees who don't have skillset 'c' if A,B,C,D,E are the five types of skillset that employees can have.
employee_id skillset
1 A
1 C
2 E
3 A
3 B
3 C
4 D
4 C
5 B
I have tried self join and other methods but it is not working.
select distinct employee_id from employee_skillset where skillset not like 'C'
When I run my query, it is still giving me employee_ids that have skillset of "c"
You can group by employee_id and set a condition in the HAVING clause:
select employee_id
from employee_skillset
group by employee_id
having sum(case when skillset = 'C' then 1 else 0 end) = 0
Or with NOT EXISTS:
select distinct s.employee_id
from employee_skillset s
where not exists (
select 1 from employee_skillset
where employee_id = s.employee_id and skillset = 'C'
)
What are your expected results from your data set? 2 and 5?
Why not something like below
SELECT DISTINCT employee_id
FROM Table1
WHERE skillset <> 'C';
MINUS set operator is one option:
SQL> with employee_skillset (employee_id, skillset) as
2 (select 1, 'a' from dual union all
3 select 1, 'c' from dual union all
4 select 2, 'e' from dual union all
5 select 3, 'a' from dual union all
6 select 3, 'b' from dual union all
7 select 3, 'c' from dual union all
8 select 4, 'd' from dual union all
9 select 4, 'c' from dual union all
10 select 5, 'b' from dual
11 )
12 select employee_id from employee_skillset
13 minus
14 select employee_id from employee_skillset where skillset = 'c';
EMPLOYEE_ID
-----------
2
5
SQL>
Yet another option:
<snip>
12 select employee_id
13 from (select employee_id,
14 case when skillset = 'c' then 1 else 0 end flag
15 from employee_skillset
16 )
17 group by employee_id
18 having sum(flag) = 0;
EMPLOYEE_ID
-----------
2
5
SQL>
Or:
<snip>
12 select employee_id
13 from (select employee_id,
14 listagg(skillset, ',') within group (order by null) lagg
15 from employee_skillset
16 group by employee_id
17 )
18 where instr(lagg, 'c') = 0;
EMPLOYEE_ID
-----------
2
5
SQL>

Oracle/SQL - Using the rank function

What I'm trying to do is a list of persons from a table and in the event where a person exists more than once then return back their record that contains the highest ranked 'code'
Code ranking (high to low): T, E, F
So for the given dataset
Person Code
----------------
Tom F
Paul E
Mark F
Paul T
Mark E
Chris T
Chris E
I would get the following back from my query
Person Code
----------------
Tom F
Paul T
Mark E
Chris T
I'm assuming this is going to use the rank/analytic functions, but I'm just not familiar enough with them.
Thanks!
You can use the RANK function to rank the data
SQL> ed
Wrote file afiedt.buf
1 with data as (
2 select 'Tom' person, 'F' code from dual union all
3 select 'Paul', 'E' from dual union all
4 select 'Paul', 'T' from dual union all
5 select 'Mark', 'F' from dual union all
6 select 'Mark', 'E' from dual
7 )
8 select *
9 from (select person,
10 code,
11 rank() over (partition by person
12 order by (case when code='T' then 1
13 when code='E' then 2
14 when code='F' then 3
15 else null
16 end)) rnk
17* from data)
SQL> /
PERS C RNK
---- - ----------
Mark E 1
Mark F 2
Paul T 1
Paul E 2
Tom F 1
Elapsed: 00:00:00.00
Then, you just need to select the rows with a RNK of 1
SQL> ed
Wrote file afiedt.buf
1 with data as (
2 select 'Tom' person, 'F' code from dual union all
3 select 'Paul', 'E' from dual union all
4 select 'Paul', 'T' from dual union all
5 select 'Mark', 'F' from dual union all
6 select 'Mark', 'E' from dual
7 )
8 select *
9 from (select person,
10 code,
11 rank() over (partition by person
12 order by (case when code='T' then 1
13 when code='E' then 2
14 when code='F' then 3
15 else null
16 end)) rnk
17 from data)
18* where rnk = 1
SQL> /
PERS C RNK
---- - ----------
Mark E 1
Paul T 1
Tom F 1
Elapsed: 00:00:00.00
The shortest and most performant and Oracle specific solution:
SQL> create table mytable(person,code)
2 as
3 select 'Tom', 'F' from dual union all
4 select 'Paul', 'E' from dual union all
5 select 'Mark', 'F' from dual union all
6 select 'Paul', 'T' from dual union all
7 select 'Mark', 'E' from dual union all
8 select 'Chris', 'T' from dual union all
9 select 'Chris', 'E' from dual
10 /
Table created.
SQL> select person
2 , max(code) keep (dense_rank first order by decode(code,'T',1,'E',2,'F',3,4)) code
3 from mytable
4 group by person
5 /
PERSO C
----- -
Chris T
Mark E
Paul T
Tom F
4 rows selected.
Regards,
Rob.
i don't think RANK is what you need...
basically, your delete will look like this: (pseudo-query)
delete the rows from person
where that row is not in ( select the rows from person with the highest code )
edit:
this trick might help you too:
select person, code, decode( code, 'T', 1, 'E', 2, 'F', 3, 0 ) from mytable
Hum... Alternative suggestion with standard SQL.
Have a CODE_WEIGHT table such as:
CODE WEIGHT
T 3
E 2
F 1
Then group your query by Person (if this is the grouping criterion) and select the distinct code containing max(weight).
I'll post the query in a couple of minutes.
UPDATE
Ok, sorry for the delay.
Here is a solution using the previous stated table and #Randy trick:
SELECT
pp.person, decode(max(c.weight), 3, 'T', 2, 'E', 1, 'F', '') code
FROM
person pp INNER JOIN code_weight c on (pp.code = c.code)
GROUP BY
pp.person
ORDER BY
person DESC;
I'm pretty sure there is a way to dump Oracle proprietary function and get things done in pure SQL... Anyway, since you've asked for a Oracle solution, here it is.
UPDATE 2
And as promised, here's the best standard SQL version that I was able to come up with:
SELECT
p.person, c.code
FROM
(
SELECT
pp.person, MAX(cc.weight) weight
FROM
person pp INNER JOIN code_weight cc ON (pp.code = cc.code)
GROUP BY
pp.person
) p INNER JOIN code_WEIGHT c ON (p.weight = c.weight)
ORDER BY
p.person DESC;
Kinda ugly with the two joins... But it does the job without proprietary extensions. Any SQL guru knows how to optimize it?
Cheers,