SQL resulting table satisfies two conditions - sql

I hope I can explain this well enough.
Say I have this table:
Owner
+--------+--------+
| Name | Type |
+--------+--------+
| Bob | Cat |
| Bob | Dog |
| Bob | Cow |
| Tim | Dog |
| Tim | Cat |
| Ted | Cat |
| Joe | Dog |
| Joe | Cat |
| Joe | Sheep |
+--------+--------+
I am trying to find everyone who has all the animals tim has (so a cat and a dog). This means Joe and Bob would satisfy this, but not Ted as he only has one type of animal Tim has
How would I go about getting this result?
So I have a table with all the types tim owns:
SELECT Type FROM Owner WHERE Name= 'Tim';
How do I get it so that only those who have both Types tim has get selected from the list of owners?
Any guidance would be appreciated, thanks in advance.

select name
from owner
where type in (select distinct type from owner where name = 'tim')
group by name
having count(distinct type) = (select count(distinct type) from owner where name = 'tim')

I think of this as a join and group by problem. Join Tim's records to all the other owners, but on the type field. Then do an aggregation and keep only the records where all the types match:
select o.name
from owner otim left join
owner o
on o.type = tim.type and o.name <> 'Tim' and otim.name = 'Tim'
group by o.name
having min(case when o.type is null then 0 else 1 end) = 0;
Note that this works even when duplicate type values are allowed in the table.

If you are just trying to get all the persons who have more animals than Tim has then you can do it like
select Name from owners
group by Name
having count(distinct [Type]) > (select count(distinct type) from owners
where Name='tim')

Related

Merging results present in all sub queries

I have the a postgre table with a similar representation to the data below.
Caters Table:
-----------------------
| Name | Option |
-----------------------
| jane | social |
| jane | vegan |
| jane | gmo-free |
| jane | italian |
| jack | social |
| jack | corporate |
| jack | gmo-free |
| jack | greek |
| rodz | social |
| rodz | wedding |
| rodz | gmo-free |
| rodz | vegan |
| rodz | french |
This is the "pseudo" query I'm trying to run
SELECT * FROM caters
WHERE option is either ['italian', 'french']
AND WHERE option is both ['wedding', 'social']
This pseudo query should return rodz. Because it either has italian or french and it has both wedding and social.
This is the query I tried to write to accomplish my sudo query
SELECT c.name FROM caters c
WHERE c.option in ('italian', 'french')
GROUP BY c.name
HAVING array_agg(c.option) #> array['wedding', 'social']
How ever this returns no results. Running the query individually
SELECT c.name FROM caters c
WHERE c.option in ('italian', 'french')
GROUP BY c.name
Result:
-----------
| Name |
-----------
| jane | // has italian
| rodz | // has french
The other query
SELECT c.name FROM caters c
GROUP BY c.name
HAVING array_agg(c.option) #> array['wedding', 'social']
Result:
-----------
| Name |
-----------
| rodz | // has wedding and social
So I can see individually the queries are correct. This made me think well if I have 2 queries giving me the correct results just need to filter out results that are in both queries why don't I JOIN them.
So I tried
SELECT c.name FROM caters c
JOIN caters c1
ON c1.name = c.name and c1.option = c.option
WHERE c1.option in ('italian', 'french')
GROUP BY c.name
HAVING array_agg(c.option) #> array['wedding', 'social']
But this also yielding no results. Any idea how I can go about this?
NOTE: The query is dynamic each time its ran the values being used could be different sometimes maybes it 5 languages sometimes its 2 languages like in this example ('italian', 'french'). To give an example what I mean by dynamic query another query could be
SELECT * FROM caters
WHERE option is either ['italian']
AND WHERE option is both ['corporate', 'social']
// returns none
----------------------------------------------------------
SELECT * FROM caters
WHERE option is either ['french', 'greek']
AND WHERE option is either ['gmo-free', 'vegan']
AND WHERE option is both ['corporate', 'social']
// returns jack
----------------------------------------------------------
SELECT * FROM caters WHERE option is ['social']
// returns jack, and rodz
You can try using a correlated subquery
DEMO
select distinct name from tablename a
where option in ('italian', 'french') and exists
(
select 1 from tablename b where a.name=b.name and option in ('wedding', 'social')
group by b.name having count(distinct option)=2
)
OUTPUT:
name
rodz
Here is one method:
SELECT c.name
FROM caters c
WHERE c.option in ('italian', 'french', 'wedding', 'social')
GROUP BY c.name
HAVING COUNT(*) FILTER (WHERE c.option IN ('italian', 'french')) >= 1 AND
COUNT(*) FILTER (WHERE c.option IN ('wedding', 'social')) = 2;

JOIN, aggregate and convert in postgres between two tables

Here are the two tables i have: [all columns in both tables are of type "text"], Table name and the column names are in bold fonts.
Names
--------------------------------
Name | DoB | Team |
--------------------------------
Harry | 3/12/85 | England
Kevin | 8/07/86 | England
James | 5/05/89 | England
Scores
------------------------
ScoreName | Score
------------------------
James-1 | 120
Harry-1 | 30
Harry-2 | 40
James-2 | 56
End result i need is a table that has the following
NameScores
---------------------------------------------
Name | DoB | Team | ScoreData
---------------------------------------------
Harry | 3/12/85 | England | "{"ScoreName":"Harry-1", "Score":"30"}, {"ScoreName":"Harry-2", "Score":"40"}"
Kevin | 8/07/86 | England | null
James | 5/05/89 | England | "{"ScoreName":"James-1", "Score":"120"}, {"ScoreName":"James-2", "Score":"56"}"
I need to do this using a single SQL command which i will use to create a materialized view.
I have gotten as far as realising that it will involve a combination of string_agg, JOIN and JSON, but haven't been able to crack it fully. Please help :)
I don't think the join is tricky. The complication is building the JSON object:
select n.name, n.dob, n.team,
json_agg(json_build_object('ScoreName', s.name,
'Score', s.score)) as ScoreData
from names n left join
scores s
ons.name like concat(s.name, '-', '%')
group by n.name, n.dob, n.team;
Note: json_build_object() was introduced in Postgres 9.4.
EDIT:
I think you can add a case statement to get the simple NULL:
(case when s.name is null then NULL
else json_agg(json_build_object('ScoreName', s.name,
'Score', s.score))
end) as ScoreData
Use json_agg() with row_to_json() to aggregate scores data into a json value:
select n.*, json_agg(row_to_json(s)) "ScoreData"
from "Names" n
left join "Scores" s
on n."Name" = regexp_replace(s."ScoreName", '(.*)-.*', '\1')
group by 1, 2, 3;
Name | DoB | Team | ScoreData
-------+---------+---------+---------------------------------------------------------------------------
Harry | 3/12/85 | England | [{"ScoreName":"Harry-1","Score":30}, {"ScoreName":"Harry-2","Score":40}]
James | 5/05/89 | England | [{"ScoreName":"James-1","Score":120}, {"ScoreName":"James-2","Score":56}]
Kevin | 8/07/86 | England | [null]
(3 rows)

SQL Find value in different tables from generic table

I have 3 tables
Cats:
| Id | Name |
|----|-------|
| 1| Cat1|
| 2| Cat2|
| 3| Cat3|
Dogs:
| Id | Name |
|----|-------|
| 1 | Dog1|
| 2 | Dog2|
| 3 | Dog3|
Owner:
| Id | Name |TableName |EntityId|
|----|-------|--------------------
| 1 | John| Dog | 1|
| 2 | Pete| Cat | 1|
| 3 |Jessica| Cat | 2|
I have no control over the animal tables, I mean, may be N animal tables, cats and dogs was just an example.
There is a way to get the names of cats and dogs in one select query?
The result must be
| OwnerId | Name |TableName |EntityId|AnimalName|
|---------|-------|-------------------------------
| 1 | John| Dog | 1| Dog1 |
| 2 | Pete| Cat | 1| Cat1 |
| 3 |Jessica| Cat | 2| Cat2 |
You can use a case statement for this:
select o.id, o.name,
case when o.tablename = 'dogs' then d.name else c.name end name
from owner o
left join cats c on o.entityid = c.id and o.tablename = 'cats'
left join dogs d on o.entityid = d.id and o.tablename = 'dogs'
SQL Fiddle Demo
As others have mentioned, if you have the opportunity to update your data model, then this could be a lot easier. Have a Pet table, an Owner table and an OwnerPet table (x-ref table). Then it's a simple query with inner join.
If I were you, I would modify your table schema to something like:
PetType
PetTypeId
PetTypeName
Pet
PetId
PetTypeId
PetName
Owner
OwnerId
OwnerName
OwnerPet
OwnerId
PetId
Then you could do something like this:
select *
from Owner
join OwnerPet on OwnerPet.OwnerId = Owner.OwnerId
join Pet on OwnerPet.PetId = Pet.PetId
join PetType on Pet.PetTypeId = PetType.PetTypeId
SQL Fiddle Example
You can use either left join or union to accomplish this task, both methods are mentioned below
SELECT
OW.ID,OW.Name,OW.TableName,OW.EntityId,
CASE WHEN OW.TableName='Cat' THEN CA.Name ELSE DO.Name END AnimalName
FROM Owner OW
LEFT JOIN Cats CA ON OW.EntityId=CA.ID AND OW.TableName='Cat'
LEFT JOIN Dogs DO ON OW.EntityId=DO.ID AND OW.TableName='Dog'
SELECT
OW.ID,OW.Name,OW.TableName,OW.EntityId,CA.Name AnimalName
FROM Owner OW
JOIN Cats CA ON OW.EntityId=CA.ID AND OW.TableName='Cat'
UNION
SELECT
OW.ID,OW.Name,OW.TableName,OW.EntityId,DO.Name AnimalName
FROM Owner OW
JOIN Dogs DO ON OW.EntityId=DO.ID AND OW.TableName='Dog'

Self Referencing SQL query when condition is met

I'm trying to create a SQL query to return the column values from a table that meet certain criteria.
Currently I have used the CONCAT function to join the first and last names into a single column in the query result for employees that have the role of 'Programmer'.
SELECT
person.id, CONCAT(person.firstname,' ', person.lastname) AS FULLNAME
FROM
person, role
WHERE
person.role_id = role.id AND role.name = 'Programmer'
This successfully runs and returns all programmers from the tables. Notice in my table structure I have an actingas_id column. This is the key to another person.id for people who are working on behalf of another people whilst they're on leave from work.
Thus, we arrive at my Question: How do I modify the SQL query such that when a person is acting that the query retrieves the first and last name of this person as well as the person who's 'shoes are being filled'?
My table structure is as follows:
person:
id | firstname | lastname | role_id | actingas_id |
role:
id | name |
+----+-----------+----------+---------+-------------+
| id | firstname | lastname | role_id | actingas_id |
+----+-----------+----------+---------+-------------+
| 1 | John | Smith | 1 | 0 |
| 2 | Kevin | Tull | 2 | 1 |
| 3 | Michael | Woods | 1 | 0 |
+----+-----------+----------+---------+-------------+
Here Kevin is Acting for for John, and Michael is also a Programmer, so the result of my query should be:
+----+-------------------------+
| id | NAME |
+----+-------------------------+
| 1 | John Smith - Kevin Tull |
| 3 | Michael Woods |
| x | Other Programmers.. |
+----+-------------------------+
This untested query should give you the result you whant:
SELECT person1.id, CASE WHEN person1.actingas_id =0 then CONCAT(person1.firstname,' ', person1.lastname) else CONCAT(person1.firstname,' ', person1.lastname,' - ', person2.firstname,' ', person2.lastname) AS FULLNAME
FROM person person1 left join person person2 on person1.actingas_id=person2.id
join role on person1.role_id=role.id
WHERE role.name = 'Programmer'
Use UNION ALL to add those additional records:
SELECT person.id,
CONCAT(person.firstname,' ', person.lastname) AS FULLNAME
FROM person INNER JOIN role
ON person.role_id = role.id
WHERE role.name = 'Programmer'
AND NOT EXISTS(SELECT 1 FROM person p WHERE p.actingas_id = person.id)
UNION ALL
SELECT a.id,
CONCAT(a.firstname,' ', a.lastname, ' - ', person.firstname,' ', person.lastname) AS FULLNAME
FROM person INNER JOIN person a
ON a.acting_as = person.id
INNER JOIN role
ON person.role_id = role.id
WHERE role.name = 'Programmer' AND a.actingas_id <> 0
Also, avoid using old style comma-separated JOINs. Use INNER JOINs

Conditional join based on lookup

Apologies if a similar problem is posted earlier, I couldn't find the same.
Problem: I need to join two tables based a conditional look up in the second table.
Tables: Below are the two tables which have a subset of the total fields.
+-------------------------------------------------------+
| Persons |
+----------+------------+---------------+---------------+
| PersonID | PersonName | HomeAddressID | WorkAddressID |
+----------+------------+---------------+---------------+
| P1 | Doe, John | HA1 | WA1 |
+----------+------------+---------------+---------------+
| P2 | Doe, Jane | HA2 | WA2 |
+----------+------------+---------------+---------------+
| P3 | Doe, Jane | | WA3 |
+----------+------------+---------------+---------------+
+-----------------------------------+
| Addresses |
+-----------+--------+------+-------+
| AddressID | Street | City | State |
+-----------+--------+------+-------+
| HA1 | 123 | A | B |
+-----------+--------+------+-------+
| WA1 | 456 | C | D |
+-----------+--------+------+-------+
| HA2 | 111 | | |
+-----------+--------+------+-------+
| WA2 | 101 | G | H |
+-----------+--------+------+-------+
| WA3 | 333 | I | J |
+-----------+--------+------+-------+
Current Scenario: The SELECT query in a view fetches PersonName from first table and work address fields from second table. (Join is on WorkAddressID)
Expected Result: The SELECT query should fetch PersonName field from first table and address fields from second table conditions being:
If state for home address is available then display Street, City and State for home address.
If state for home address is NULL/blank then display Street, City and State for work address.
Notes:
Many rows in Persons table do not have HomeAddressID but all do have WorkAddressID.
Many rows in Addresses table do not have City and State information for Home addresses.
While this may look like a design flaw, I'm not in a position to re-engineer the database as there are hundreds of objects and sub-objects depending on the original view.
There are 3 million+ rows in the Persons table so performance needs to be acceptable.
The current query has joins to at least 5 other views.
Please advise as to how I can address this problem.
Many thanks,
-V
Here's a MySQL solution:
SELECT PersonName,
IF(h.State = '' OR h.State IS NULL, w.Street, h.Street) AS Street,
IF(h.State = '' OR h.State IS NULL, w.City, h.City) AS City,
IF(h.State = '' OR h.State IS NULL, w.State, h.State) AS State
FROM Persons AS p
JOIN Addresses AS w ON w.AddressID = p.WorkAddressID
LEFT JOIN Addresses as h ON h.AddressID = p.HomeAddressID
A self join would handle this:
select
p.personname,
case when ha.state is null then wa.street else ha.street end as street,
case when ha.state is null then wa.city else ha.city end as city,
case when ha.state is null then wa.state else ha.state end as state
from
Persons p
inner join addresses wa on p.workaddressid = wa.addressid
left join addresses ha on p.homeaddressid = ha.addressid
This syntax would be for MSSQL
Edit: changed the home to a left join because of the criterion Many rows in Persons table do not have HomeAddressID