SELECT query with combined conditions - sql

SQL noobie here, tried my luck googling around, but came up empty or didn't know the proper keyword. Still, feeling quite awkward between all those advanced questions, however still hopeful to get a solution and learn.
Let's suppose we have a table representing participants for different teams for a children sports tournament.
Participant table:
Our goal is to select out participants that have chosen a WRONG team. Let's suppose that the conditions for the teams are as such:
team Yellow = boys with age 12;
team Red = girls with age 13;
team Blue = boys with age 11;
That would mean that the incorrect registrants are Sarah (incorrect gender, correct age), Jack (incorrect gender and age) and Mary who all should therefore be included in the result of the query.
However I'm struggling with creating a SQL query that would consider conditions from multiple fields (comparing team towards gender and age at the same time) + having more than one set of comparison done at the same time (looking for incorrect participants from 3 teams at the same time).
Help is much appreciated!

You didn't state your DBMS so this is ANSI SQL:
Just select all rows that do not comply with any of the rules:
select *
from participants
where (team, gender, age) not in ( ('Yellow', 'M', 12),
('Red', 'F', 13),
('Blue', 'M', 11) );
Online example: http://rextester.com/ZTEON26060

Main thing here is you have to convert your team rules into some kind of proper data structure. You can put it into the table, or use derived table, like this:
select *
from participants as p
where
not exists (
select *
from (values
('Yellow', 'M', 12),
('Red', 'F', 13),
('Blue', 'M', 11)
) as t(Team, Gender, Age)
where
t.Team = p.Team and
t.Gender = p.Gender and
t.Age = p.Age
)
Or you can check for correct team and then compare with current team:
select
p.*, t.Team as Correct_Team
from participants as p
left join (values
('Yellow', 'M', 12),
('Red', 'F', 13),
('Blue', 'M', 11)
) as t(Team, Gender, Age) on
t.Gender = p.Gender and
t.Age = p.Age
sql fiddle demo

You can try this :
Select
Name,
Gender,
Age,
Team AS Chosen_team,
Case when Gender='M' and Age=12 Then 'Yellow'
when Gender='F' and Age=13 then 'Red'
when gender='M' and Age=11 then 'Blue'
End as Ideal_team,
Case when Chosen_team <> Ideal_team then 'FALSE' ELSE 'TRUE'
from your_table;
Now select the records with value false. You will get your list.

You can use a combination of or and and to do this.
select * from yourtable
where (team ='Yellow' and not (gender = 'M' and age = 12))
or (team ='Red' and not (gender = 'F' and age = 13))
or (team ='Blue' and not (gender = 'M' and age = 11))

There are a few things you haven't mentioned about your team restrictions:
Can you have different combinations of Age and Gender for the same teams?
Can any of the same Age and Gender combinations match multiple teams?
Would the valid teams cover all permutations of of participant ages and genders?
Since that's not stated, I'm going to provide a generic solution that makes it easy to extend the valid teams. And the query will return all participants that don't match at least one valid team. (NOTE: It may be that there is no valid team for a particular participant.)
Approach:
Put valid combinations in a temporary (or even persistent) table of some sorts (I'll use a CTE).
Select all participants where you cannot find a matching Age, Gender and Team in the "Valid Teams" table.
The CTE for ValidTeams below could easy be replaced with a table if your RDBMS doesn't support CTE's. NOTE: If there are many permutations of valid gender/age/team a separate table will be better.
;WITH ValidTeams AS (
SELECT 'Yellow' AS Team, 'M' AS Gender, 12 AS Age
UNION ALL SELECT 'Red', 'F', 13
UNION ALL SELECT 'Blue', 'M', 11
)
SELECT Name, Gender, Age, Team AS InvalidTeam
FROM Participants p
WHERE NOT EXISTS (
SELECT *
FROM ValidTeams v
WHERE v.Gender = p.Gender
AND v.Age = p.Age
AND v.Team = p.Team
)

Related

SQL get avg of column for id 1, grouped by values in the same column for id 2

The same column in my table needs to be used for Averaging and Grouping By. The catch is that the id is different when implementing each function.
My assignment questions are "What is your age" and "What is your gender".
I found the top 7 genders, these are: 'Male', 'Female', 'male', 'female', '-1', 'Nonbinary', 'non-binary'.
AnswerText contains the answers for each question. I want to get the Average age for each gender category in the list. I have done this:
SELECT AVG(AnswerText), Gender
FROM
(
SELECT AnswerText as Gender
FROM Answer
WHERE Answer.QuestionID = 2
AND Gender IN ('Male', 'Female', 'male', 'female', '-1', 'Nonbinary', 'non-binary')
)
WHERE Answer.QuestionID = 1
GROUP BY Gender
This is throwing error no such column: AnswerText.
How do I achieve this? The solution can either be in MySQL or SQLite.
I want to get the Average age for each gender category in the list.
That's a self-join:
select a2.answertext as gender, avg(a1.answertext * 1.0) as avg_age
from answer a1
inner join answer a2 on a2.userid = a1.userid
where a1.questionid = 1 and a2.questionid = 2
group by a2.answertext

SQL for selecting values in a single column by 'AND' condition

I have a table data like bellow
PersonId
Eat
111
Carrot
111
Apple
111
Orange
222
Carrot
222
Apple
333
Carrot
444
Orange
555
Apple
I need an sql query which return the total number of PersonId's who eat both Carrot and Apple.
In the above example the result is, Result : 2. (PersonId's 111 and 222)
An ms-sql query like 'select count(distinct PersonId) from Person where Eat = 'Carrot' and Eat = 'Apple''
You can actually get the count without using a subquery to determine the persons who eat both. Assuming that the rows are unique:
select ( count(distinct case when eat = 'carrot' then personid end) +
count(distinct case when eat = 'apple' then personid end) -
count(distinct personid)
) as num_both
from t
where eat in ('carrot', 'apple')
SELECT PersonID FROM Person WHERE Eat = 'Carrot'
INTERSECT
SELECT PersonID FROM Person WHERE Eat = 'Apple'
You can use conditional aggregation of a sort:
select
personid
from <yourtable>
group by
personid
having
count (case when eat = 'carrot' then 1 else null end) >= 1
and count (case when eat = 'apple' then 1 else null end) >= 1
At this example, I use STRING_AGG to make easy the count and transform 'Apple' and 'Carrot' to one string comparison:
create table #EatTemp
(
PersonId int,
Eat Varchar(50)
)
INSERT INTO #EatTemp VALUES
(111, 'Carrot')
,(111, 'Apple')
,(111, 'Orange')
,(222, 'Carrot')
,(222, 'Apple')
,(333, 'Carrot')
,(444, 'Orange')
,(555, 'Apple')
SELECT Count(PersonId) WhoEatCarrotAndApple FROM
(
SELECT PersonId,
STRING_AGG(Eat, ';')
WITHIN GROUP (ORDER BY Eat) Eat
FROM #EatTemp
WHERE Eat IN ('Apple', 'Carrot')
GROUP BY PersonId
) EatAgg
WHERE Eat = 'Apple;Carrot'
You can use EXISTS statements to achieve your goal. Below is a full set of code you can use to test the results. In this case, this returns a count of 2 since PersonId 111 and 222 match the criteria you specified in your post.
CREATE TABLE Person
( PersonId INT
, Eat VARCHAR(10));
INSERT INTO Person
VALUES
(111, 'Carrot'), (111, 'Apple'), (111, 'Orange'),
(222, 'Carrot'), (222, 'Apple'), (333, 'Carrot'),
(444, 'Orange'), (555, 'Apple');
SELECT COUNT(DISTINCT PersonId)
FROM Person AS p
WHERE EXISTS
(SELECT 1
FROM Person e1
WHERE e1.Eat = 'Apple'
AND p.PersonId = e1.PersonId)
AND EXISTS
(SELECT 1
FROM Person e1
WHERE e1.Eat = 'Carrot'
AND p.PersonId = e1.PersonId);
EXISTS statements have a few advantages:
No chance of changing the granularity of your data since you aren't joining in your FROM clause.
Easy to add additional conditions as needed. Just add more EXISTS statements in your WHERE clause.
The condition is cleanly encapsulated in the EXISTS, so code intent is clear.
If you ever need complex conditions like existence of a value in another table based on specific filter conditions, then you can easily add this without introducing table joins in your main query.
Some alternative solutions such as PersonId IN (SUBQUERY) can introduce unexpected behavior in certain conditions, particularly when the subquery returns a NULL value.
select
count(PersonID)
from Person
where eat = 'Carrot'
and PersonID in (select PersonID
from Person
where eat = 'Apple');
Only selecting those persons who eat apples, and from that result select all those that eat carrots too.
SELECT COUNT (A.personID) FROM
(SELECT distinct PersonID FROM Person WHERE Eat = 'Carrot'
INTERSECT
SELECT distinct PersonID FROM Person WHERE Eat = 'Apple') as A

select different value depend on the other columns' tag/value

I have a table that has 4 columns (customer, gender, division, product)
gender has unique value linking to each customer: male, female or null.
The division has unique value linking to each product: men or women
for a male customer may buy the women products, vice versa, for female bought men's product
I want to create a table, only select the records/rows having the men's product to male and only women product for female (if the gender is null select women products records)
is there any simple way to do that
I did a complicated process. first, use customer info to separate male and other customers, (created 2 tables, cust_male, and cust_other)
then use join if the customer in the cust_male table, return the men division products rows (where division='men'); if the customer in the cust_other table, return women division products rows (where division='women') then 'union all' two parts.
Hope I could have a much simple way or code to solve this.
we can use the code to create the tep table
create table tep (id, gender, division, product) as
(
select 1, ‘male’, ‘men’, ‘aaa’ from dual
union all select 2, ‘female’, ‘women’, ’bbb’ from dual
union all select 2, ‘female’, ‘men’, ‘ccc’ from dual
union all select 1, ‘male’, ‘women’, ‘ddd’ from dual
union all select 3, ‘female’, ‘women’, ’ddd’ from dual
union all select 4, ‘null’, ‘women’, ’eee’ from dual
union all select 4, ‘null, ‘men, ’ccc’ from dual
);
my method was
create table cust_male as
select id from tep where gender='male';
create table cust_other as
select id from tep where (gender ='female') or (gender='null');
select * from tep t
inner join cust_male m
on m.id=t.id
where division ='men'
union all
select * from tep t
inner join cust_other f
on f.id=t.id
where division ='women'
hope I do not need to create those two more tables and having simple we to realize choosing only men's division product rows for male, and choose women's division product rows for female or null customers
I want to create a table, only select the records/rows having the men's product to male and only women product for female (if the gender is null select women products records) is there any simple way to do that
You want to filter the data, so this sounds like a where clause:
select tep.*
from tep
where (gender = 'male' and division = 'men') or
((gender = 'female' or gender is null) and division = 'women')

Postgresql check for double entries

I searched for nearly one hour to solve my problem but i cant find anything.
So:
I created a table named s (Suppliers) where some Suppliers for Parts are listed, it looks like this:
insert into S(sno, sname, status, city)
values ('S1', 'Smith', 20, 'London'),
('S2', 'Jones', 10, 'Paris'),
('S3', 'Blake', 30, 'Paris'),
('S4', 'Clark', 20, 'London'),
('S5', 'Adams', 30, 'Athens');
Now i want to check this table for double entries in the column "city", so this would be London and Paris and i want to sort it by the sno and print it out.
I know that it's a bit harder in Postgres than in mySQL and i tried it like this:
SELECT sno, COUNT(city) AS NumOccurencies FROM s GROUP BY sno HAVING ( COUNT (city) > 1 );
But all i get is an empty table :(. I tried different ways but it's always the same, i don't know what to do to be honest. I hope some of you could help me out here :).
Greetings Max
You're thinking about it a little backwards. By grouping by the sno you're finding all of those rows with the same sno, not the same city. Try this instead:
SELECT
city
FROM
S
GROUP BY
city
HAVING
COUNT(*) > 1
You can then use that as a subquery to find the rows that you want:
SELECT
sno, sname, status, city
FROM
S
WHERE
city IN
(
SELECT
city
FROM
S
GROUP BY
city
HAVING
COUNT(*) > 1
)

Who to Insert data into ODD/EVEN rows only in SQL

I have one table with gender as one of the columns.
In gender column only M or F are allowed.
Now i want to sort the table so that while displaying the table in gender field M and F will come alternetivly.
I have Tried....
I have tried to create one(new) table with the same structure as my existing table.
Now using high leval insert i want to insert M to odd rows and F to even rows.
After that i want to join those two statements using union operator.
I am able to insert to ( new ) the table only male or female but not to the even or odd rows...
Can any body help me regarding this....
Thanks in Advance....
Don't consider a table to be "sorted". The SQL server may return the rows in any order depending on execution plan, index, joins etc. If you want a strict order you need to have an ordered column, like an identity column. Usually it is better to apply the desired sorting when selecting data.
However the interleaving of M and F is a little bit tricky, you need to use the ROW_NUMBER function.
Valid SQL Server code:
CREATE TABLE #GenderTable(
[Name] [nchar](10) NOT NULL,
[Gender] [char](1) NOT NULL
)
-- Create sample data
insert into #GenderTable (Name, Gender) values
('Adam', 'M'),
('Ben', 'M'),
('Casesar', 'M'),
('Alice', 'F'),
('Beatrice', 'F'),
('Cecilia', 'F')
SELECT * FROM #GenderTable
SELECT * FROM #GenderTable
order by ROW_NUMBER() over (partition by gender order by name), Gender
DROP TABLE #GenderTable
This gives the output
Name Gender
Adam M
Ben M
Casesar M
Alice F
Beatrice F
Cecilia F
and
Name Gender
Alice F
Adam M
Beatrice F
Ben M
Cecilia F
Casesar M
If you use another DBMS the syntax may differ.
I think the best way to do it would be to have two queries (one for M, one for F) and then join them together. The catch would be you would have to calculate the "rank" of each query and then sort accordingly.
Something like the following should do what you need:
select * from
(select
#rownum:=#rownum+1 rank,
t.*
from people_table t,
(SELECT #rownum:=0) r
where t.gender = 'M'
union
select
#rownum:=#rownum+1 rank,
t.*
from people_table t,
(SELECT #rownum:=0) r
where t.gender = 'F') joined
order by joined.rank, joined.gender;
If you are using SQL Server, you can seed your two tables with an IDENTITY column as follows. Make one odd and one even and then union and sort by this column.
Note that you can only truly alternate if there are the same number of male and female records. If there are more of one than the other, you will end up with non-alternating rows at the end.
CREATE TABLE MaleTable(Id INT IDENTITY(1,2) NOT NULL, Gender CHAR(1) NOT NULL)
INSERT INTO MaleTable(Gender) SELECT 'M'
INSERT INTO MaleTable(Gender) SELECT 'M'
INSERT INTO MaleTable(Gender) SELECT 'M'
CREATE TABLE FemaleTable(Id INT IDENTITY(2,2) NOT NULL, Gender CHAR(1) NOT NULL)
INSERT INTO FemaleTable(Gender) SELECT 'F'
INSERT INTO FemaleTable(Gender) SELECT 'F'
INSERT INTO FemaleTable(Gender) SELECT 'F'
SELECT u.Id
,u.Gender
FROM (
SELECT Id, Gender
FROM FemaleTable
UNION
SELECT Id, Gender
FROM MaleTable
) u
ORDER BY u.Id ASC
See here for a working example