SQL query to find 'fruit' that no-one loves - sql

Can someone help me out with how I can find the 'Fruit' that no-one loves?
Fruit LoveIt Name
Apple Y John
Apple N Mary
Apple Y Stephen
Pear N Lois
Pear N Jo
Pear N Fiona
Thanks,

Here's a variant that doesn't rely on counting but stresses thinking in sets (relational algebra style, if you will): the fruits no one loves are all fruits but those that are loved by somebody:
SELECT DISTINCT f.Fruit
FROM fruits f
EXCEPT
SELECT f.Fruit
FROM fruits f
WHERE f.LoveIt = 'Y'
EXCEPT is SQL's set difference operator.

Using aggregation:
select fruit
from fruits
group by fruit
having count(case when LoveIt = 'Y' then 1 end) = 0;

I would try this:
select fruit, loveit, count(*)
from survey
group by 1,2
having loveit = 'N'
and count(*) = 0;

Select distinct fruit from tab x where fruit not in (select fruit from tab where love it = 'Y')

Related

Case Statement to Add a Column

I have the following table:
ID Fruit
A apple
A banana
A grapes
B orange
B apple
B grapes
C grapes
C orange
C banana
I would like to add a new column called Apple such to denote whether ID is associated with apple or not:
ID Fruit Apple
A apple yes
A banana yes
A grapes yes
B orange yes
B apple yes
B grapes yes
C grapes no
C orange no
C banana no
Since this seems like a contrived example, I'll post several options. The best one will depend on what you're really doing.
First up, this is likely to perform best, but it risks duplicating rows if you could have multiple matches for the JOINed table. It's also the only solution I'm presenting to actually use a CASE expression as requested.
SELECT a.*, case when b.ID IS NOT NULL THEN 'Yes' ELSE 'No' END AS Apple
FROM MyTable a
LEFT JOIN MyTable b on b.ID = a.ID AND b.Fruit = 'Apple'
Alternatively, this will never duplicate rows, but has to re-run the nested query for each result row. If this is not a contrived example, but something more like homework, this is probably the expected result.
SELECT *, coalesce(
(
SELECT TOP 1 'Yes'
FROM MyTable b
WHERE b.ID = a.ID AND b.Fruit = 'Apple'
), 'No') As Apple
FROM MyTable a
Finally, this also re-runs the nested query for each result row, but there is the potential a future enhancement will improve on that and it makes it possible to provide values for multiple columns from the same nested subquery.
SELECT a.*, COALESCE(c.Apple, 'No') Apple
FROM MyTable a
OUTER APPLY (
SELECT TOP 1 'Yes' As Apple
FROM MyTable b
WHERE b.ID = a.ID AND b.Fruit = 'Apple'
) c
See them work here:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=e1991e8541e7421b90f601c7e8c8906b
It could be achieved without self JOIN by using windowed COUNT_IF:
SELECT *, COUNT_IF(Fruit = 'apple') OVER(PARTITION BY ID) > 0 AS Apple
FROM tab;
Output:
In a new asking of this same question with more columns,
the form Lukas advocates performs poorly, as the COUNT/SUM is done per row,
at which point do a join between the raw, and the aggregated results should perform better.
select *
FROM MyTable as a
natural join (
SELECT c.id,
iff(count_if(c.Fruit like 'apple') > 0, 'yes', 'no') as "Apple",
--iff(count_if(c.Fruit like 'banana') > 0, 'yes', 'no') as "Banana",
--iff(count_if(c.Fruit like 'orange') > 0, 'yes', 'no') as "Orange"
FROM MyTable as c
GROUP BY 1
) as b
For the following table,
ID Fruit
A apple
A banana
A grapes
B orange
B apple
B grapes
C grapes
C orange
C banana
Adding a new column called Apple to denote whether ID is associated with apple or not, the resultset would be
ID Fruit Apple
A apple yes
A banana no
A grapes no
B orange no
B apple yes
B grapes no
C grapes no
C orange no
C banana no
If the expected resultset is as above, the below query will help to get the desired output.
select
id,
case
when fruit='apple' then 'yes'
when fruit!='apple' then 'no'
end as Apple
from Fruits;

subset a table to include rows associated with only one occurence of a set of entries

I have the following table for 4 individuals with their favorite fruit:
tbl:
ID FRUIT
personA banana
personB apple
personC orange
personD grapefruit
personA avocado
personB banana
personC melon
personD pear
personA banana
I would like to extract all the entries for the IDs that are associated with only one of the following: banana, apple, orange.
This means that I am only hoping to extract the rows for person A and person C. person B has both ("apple" AND "banana"). person D has none of the three desired fruits.
I currently have:
select *
from tbl t1
where exists (select 1
from tbl t2
where t1.ID=t2.ID and
(t2.fruit='banana' or
t2.fruit='apple' or
t2.fruit='orange'))
join (select ID, count(*) over (partition by t3.fruit)
from (select distinct ID, fruit
from tbl
where fruit='banana' or
fruit='apple' or
fruit='orange')) t3
on t3.ID=t.ID and
t3.cnt=1
Is there a better/simpler way to execute this? I would like to optimize it to reduce run time given the tables are quite large.
desired table:
ID FRUIT
personA banana
personC orange
personA avocado
personC melon
personA banana
The subquery determines all IDs which have those three fruits and count how many individual fruits they have.
You know can join the ID to the table and only take those IDs which have only 1 fruit, if you also want three fruits you and add this in the join condition
SELECT
t1.ID, FRUIT
FROM
tbl t1
JOIN
(SELECT
ID, COUNT(DISTINCT FRUIT) countf
FROM
tbl
WHERE
FRUIT IN ('banana', 'apple', 'orange')
GROUP BY 1) t2 ON t1.ID = t2.ID AND t2.countf = 1;
We can use GROUP BY and HAVING COUNT(DISTINCT) to find the persons we want.
SELECT
a.ID,
b.FRUITID
FROM tb1 a
JOIN tb1 b ON a.ID = b.ID
WHERE a.FRUITID IN ('banana', 'apple', 'orange')
GROUP BY
a.ID,
b.FRUITID
HAVING COUNT(DISTINCT a.FRUITID) = 1;
GO
ID | FRUITID
:------ | :------
personA | avocado
personA | banana
personC | melon
personC | orange
db<>fiddle here
Alternative approach is to use COUNT_IF:
SELECT *
FROM tbl t
QUALIFY COUNT_IF(t.fruit = 'banana') OVER(PARTITION BY ID) = 1
AND COUNT_IF(t.fruit = 'apple') OVER(PARTITION BY ID) = 1
AND COUNT_IF(t.fruit = 'orange') OVER(PARTITION BY ID) = 1;
Answer:
This can be done with:
select *
from data
qualify count( distinct iff(fruit in ('apple','banana','orange'), fruit, null))
over(partition by id) = 1
which gives:
ID
FRUIT
personA
avocado
personA
banana
personA
banana
personC
melon
personC
orange
How that works:
this can be shown how it works by showing the intermediate state of the IFF and the COUNT DISTINCT like so:
select *
,iff(fruit in ('apple','banana','orange'), fruit, null) as v
,count( distinct v) over(partition by id) as c
from data
gives:
ID
FRUIT
V
C
personA
avocado
1
personA
banana
banana
1
personA
banana
banana
1
personB
apple
apple
2
personB
banana
banana
2
personC
melon
1
personC
orange
orange
1
personD
grapefruit
0
personD
pear
0
Thus personD is eliminated for have no magic fruit, and personB is eliminated for have too much.
If you are deeply caring about performance (which I would test) I assume nbk's solution would perform the fastest.

SQL: Select a partition group which dies not have a specified value

I have a Table as follows
IDNumber
Name
123
Apple
123
Mango
123
Banana
126
Apple
126
Mango
126
Orange
128
Apple
128
Mango
128
Banana
151
Apple
151
Mango
151
Banana
I have used the partition by clause to partition the group based on ID Number.
with part as (Select IDNumber,count(IDNumber) over(Partition by IDNumber) cnt from myTable)
Select IDNumber from myTable where do not exists ???"Orange in partitionGroup"????
Problem:I am not really sure how this can be achieved.For a given ID, i would need a partition group to NOT select if a given value is present.
This gives me a partition where every group has three Names, But i do not want the IDNumber to be sselected if it has Orange as Name.
Please let me know in case any other details are required. Any help is highly appreciated.
You may use aggregation here:
SELECT IDNumber
FROM yourTable
GROUP BY IDNumber
HAVING COUNT(CASE WHEN Name = 'Orange' THEN 1 END) = 0;
Another way:
SELECT IDNumber
FROM yourTable
WHERE IDNumber NOT IN (
SELECT IDNumber FROM yourTable WHERE Name = 'Orange'
);
But i do not want the IDNumber to be selected if it has Orange as Name.
You can use not exists for this:
select t.*
from myTable t
where not exists (select 1
from myTable t2
where t2.id = t.id and t2.name = 'Orange'
);
You can also phrase this using window functions, but that seems unnecessary.

SQL - Deleting duplicate rows without leaving original

How to delete duplicate rows based on select columns without leaving original? In this example, deleting based on Name and Animal.
ID Name Animal Fruit
1 Bob Dog Orange
2 Adam Dog Orange
3 Bob Dog Apple
4 Adam Cat Orange
5 Bob Cat Apple
6 Bob Hamster Apple
7 Adam Cat Apple
So the expected result would be:
ID Name Animal Fruit
2 Adam Dog Orange
5 Bob Cat Apple
6 Bob Hamster Apple
You can use a delete with join the subquery grouped by name and animal having count > 1
delete m
from my_table m
inner join (
select name, animal
from my_table
group by name, animal
having count(*) > 1
) t on t.name = m.name
and t.animal = m.animal
try this:
first, select the duplicates in a subquery.
then, delete all results
delete from mytable T
left join
(select count(*) cnt, Name, Animal
from mytable
group by Name, Animal) X
on t.Name = X.Name
and t.Animal = X.Animal
where cnt>1
I would do this using exists:
delete from t
where exists (select 1
from t t2
where t2.name = t.name and t2.animal = t.animal and t2.id <> t.id
);

Count from several columns Oracle Sql

I have a table that contain the following:
Fruit_id Fruit1 Fruit2
-------------------------------------
1 Apple Orange
2 Orange Orange
3 Orange Orange
4 Banana Banana
5 Apple Orange
I would like to count the total number for each fruit so that the output is something like
Fruit Frequency
---------------------------
Apple 2
Banana 2
Orange 6
I have tried
select distinct Fruit1, count(Fruit1 Fruit2) from Fruits group by Fruit1 order by count(Fruit1 Fruit2);
I also tried:
select distinct Fruit1, count(Fruit1 || Fruit2) from Fruits group by Fruit1 order by count(Fruit1 Fruit2);
I'm new to oracle sql so please understand my ignorance
You could get the count for each distinct fruit value in the Fruit1 and Fruit2 columns, and then add those together with a SUM aggregate.
The "trick" is to use an inline view that concatenates the two results together with a UNION ALL set operator.
SELECT f.fruit, SUM(f.cnt) AS cnt
FROM ( SELECT d.Fruit1 AS fruit, COUNT(1) AS cnt FROM Fruits d GROUP BY d.Fruit1
UNION ALL
SELECT e.Fruit2 AS fruit, COUNT(1) AS cnt FROM Fruits e GROUP BY e.Fruit2
) f
GROUP BY f.fruit
ORDER BY f.fruit
You could use the following:
select fruits, count(*) as freq
from (select fruit1 as fruits
from tbl
union all
select fruit2 as fruits
from tbl)
group by fruits