SQL join two tables with a link table between - GORM/Grails - sql

I am trying to build an output as such:
Name source source source
Tim Other TV Radio
Where I want to combine a Person table and a Source table:
Person
ID Name
1 Tim
Source
ID Name
1 Other
2 TV
3 Radio
Person_Source
p_id s_id
1 1
1 2
I have a query that builds out each source for time and outputs in multiple lines. I'm looking for a single result in the result set
select source.name
from person left join person_source
on person_source.person_source_id = person.id join source
on source.id = person_source.source_id

You could add a DISTINCT to your query:
select DISTINCT source.name
from person left join person_source
on person_source.person_source_id = person.id join source
on source.id = person_source.source_id

Not sure what flavour of SQL this is supposed to be but you could try the following:
SELECT
p.Name,
MAX(CASE s.ID WHEN 1 THEN s.Name END) AS source1,
MAX(CASE s.ID WHEN 2 THEN s.Name END) AS source2,
MAX(CASE s.ID WHEN 3 THEN s.Name END) AS source3
FROM Person p
INNER JOIN Person_Source ps ON p.ID = ps.p_id
INNER JOIN Source s ON ps.s_id = s.ID
GROUP BY
p.ID,
p.Name
You could also check the sources differently, using their names, like this:
…
MAX(CASE s.Name WHEN 'TV' THEN s.Name END) AS source1,
MAX(CASE s.Name WHEN 'Radio' THEN s.Name END) AS source2,
MAX(CASE s.Name WHEN 'Other' THEN s.Name END) AS source3
…
Note that a SQL query always return a fixed number of columns. You'll need to decide beforehand how many sources should be returned by the query, and if the number should depend on the actual number of possible sources in the Source table, you'll have to build your query dynamically, to include all the necessary sources but no more than necessary.

Related

How to create a query to join three tables and make calculations in SQL?

I'm just at the beginning of my SQL studies and can't figure out how to resolve the next problem.
So, there are three tables:
! given tables
The task is: "Get number of pet type per owner"
Write a query to generate the result below:
! desired output
The best result I have for the moment:
SELECT owners.OWNER_NAME, COUNT(pets.OWNER_ID) AS pets
FROM owners
JOIN pets ON owners.ID = pets.OWNER_ID
JOIN pet_type ON pets.TYPE = pet_type.ID
GROUP BY owners.OWNER_NAME;
It returns first column with owner names and second column with the sum of particular owner pets.
Will appreciate any help.
You need conditional aggregation:
SELECT
o.OWNER_NAME,
SUM(CASE WHEN t.name = 'CAT' THEN 1 ELSE 0 END) CAT,
SUM(CASE WHEN t.name = 'DOG' THEN 1 ELSE 0 END) DOG,
SUM(CASE WHEN t.name = 'SNAKE' THEN 1 ELSE 0 END) SNAKE
FROM owners o
JOIN pets p ON o.ID = p.OWNER_ID
JOIN pet_type t ON p.TYPE = t.ID
GROUP BY o.OWNER_NAME;
I use name as the name of the column describing the type in table pet_type. Change it to the actual name of the column.
Check this. To Get number of pet type per owner, this is sufficient to join only Pets table with the owners table. A DISTINCT count of Pet.Type will give your desired output.
SELECT
owners.ID,
owners.OWNER_NAME,
COUNT(DISTINCT pets.TYPE) AS Num_Pet_Type
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME;
If you wants number of Pet per type, use this below script-
SELECT
owners.ID,
owners.OWNER_NAME,
pets.TYPE,
COUNT(*) AS Num_Of_Pet
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME,pets.TYPE;

How do I get counts for different values of the same column with a single totals row, using Postgres SQL?

So I have a list of children and I want to create a list of how many boys and girls there are in each school and a final total count of how many there are.
My query including logic
select sch.id as ID, sch.address as Address, count(p.sex for male) as boycount, count(p.sex for female) as girlcount
from student s
join school sch on sch.studentid = s.id
join person p on p.studentid = s.id
Obviously I know this query wont work but I dont know what to do further from here. I thought about nested query but im having difficulty getting it to work.
I found a similar question for postgres 9.4
Postgres nested SQL query to count field. However I have Postgres 9.3.
Final result would be like :
WARNING
Depending on the data type of the school ID, you may get an error with this union. Consider casting the school ID as a varchar if it is of type INT.
SELECT
sch.id as ID, /*Consider casting this as a varchar if it conflicts with
the 'Total' text being unioned in the next query*/
sch.address as Address,
SUM(CASE
WHEN p.sex = 'male'
THEN 1
ELSE 0
END) AS BoyCount,
SUM(CASE
WHEN p.sex = 'female'
THEN 1
ELSE 0
END) AS GirlCount
FROM
student s
JOIN school sch
ON sch.studentid = s.id
JOIN person p
ON p.studentid = s.id
UNION ALL
SELECT
'Total' as ID,
NULL as Address,
SUM(CASE
WHEN p.sex = 'male'
THEN 1
ELSE 0
END) AS BoyCount,
SUM(CASE
WHEN p.sex = 'female'
THEN 1
ELSE 0
END) AS GirlCount
FROM
person p

SQL JOIN on one field or the other

Trying to order a family by father's name or, if there is no father, then the mother's name where the names are in a separate "person" table, something like:
SELECT DISTINCT family.myid FROM family
JOIN person
ON family.father_id = person.myid OR
family.mother_id = person.myid
ORDER BY person.surname,
person.given_name;
In this version, the families without fathers end up unsorted at the bottom. Would like families without fathers to appear in the order by the mother's name. Sqlite SQL will suffice.
Basically, you need a separate join for the fathers and the mothers:
select f.*
from family f left join
person d
on f.father_id = d.myid left join
person m
on f.mother_id = m.myid
order by (case when d.myid is null then m.surname else d.surname end),
(case when d.myid is null then m.given_name else d.given_name end);
Because a value could be missing, this should be a left join.
COALESCE should work
ORDER BY COALESCE(NULLIF(b.surname, ''), c.surname),
COALESCE(NULLIF(b.given_name, ''), c.given_name)

Need help with SQL Query

Say I have 2 tables:
Person
- Id
- Name
PersonAttribute
- Id
- PersonId
- Name
- Value
Further, let's say that each person had 2 attributes (say, gender and age). A sample record would be like this:
Person->Id = 1
Person->Name = 'John Doe'
PersonAttribute->Id = 1
PersonAttribute->PersonId = 1
PersonAttribute->Name = 'Gender'
PersonAttribute->Value = 'Male'
PersonAttribute->Id = 2
PersonAttribute->PersonId = 1
PersonAttribute->Name = 'Age'
PersonAttribute->Value = '30'
Question: how do I query this such that I get a result like this:
'John Doe', 'Male', '30'
SELECT p.name, p1.Value, p2.Value
FROM Person p, PersonAttribute p1, PersonAttribute p2
WHERE p.Id = p1.PersonId AND p.Id = p2.PersonId
AND p1.Name = 'Gender' AND p2.Name = 'Age'
I think you need redesign your schema.
Why not?
Person
- Id
- Name
- Gender
- Birthday
...
SELECT p.Name, g.Value, a.Value
FROM Person p INNER JOIN PersonAttribute g ON p.Id = g.Id AND g.Name = "Gender" INNER JOIN PersonAttribute a ON p.Id = a.Id AND a.Name = "Age"
Storing Name Value pairs does give flexibility but is very cumbersome to query.
Take a look at http://www.simple-talk.com/community/blogs/philfactor/archive/2008/05/29/56525.aspx
Leaving the design aside, you can always PIVOT the result but you need to know how many attributes you are selecting out in advance.
There's no easy way to do this.
The concept of a pivot table (already mentioned by another answer) is basically what you are looking for, except that pivot tables require you to know the names of the columns you wish to use. Clearly this is a problem when you want to exploit the power of such a table design!
In my previous life, I just settled on X number of columns, like 20-30, and if they didn't exist, then the row set included a bunch of null values. No big deal.
select piv.name,
max(case piv.a_name when 'Gender' then piv.a_value else null end) as Gender,
max(case piv.a_name when 'Age' then piv.a_value else null end) as Age,
max(case piv.a_name when 'Hobby' then piv.a_value else null end) as Hobby
from
(select p.name as name, pa.name as a_name, pa.value as a_value
from person p, personattribute pa
where p.id = pa.personid) piv
group by piv.name
This would generate output like so:
name | gender | age | hobby
-----------+--------+-----+---------
Bob Swift | Male | | Reading
John Doe | Male | 30 |
(2 rows)
Which is pretty damned close to what you are looking for. I would leave the rest of it up to your application-layer.
I also highly recommend that you include the attribute NAME as part of the return value, to provide context to the VALUEs.
These types of so-called Entity-Attribute designs often end up having to rely on a combination of server-specific functions, stored procedures, and hard-coded queries.
You need to JOIN the two tables. Wikipedia provides a pretty good explanation of JOIN: http://en.wikipedia.org/wiki/Join_%28SQL%29
SELECT Name, g.Value, a.Value
FROM Person,
PersonAttribute g INNER JOIN ON g.Name = "Gender",
PersonAttribute a INNER JOIN ON a.Name = "Age"

SQL statement for evaluating multiple related tables

I have a Projects table, which lists the client info. Then I have four related jobs tables, for jobs within that project - Roofing, Siding, Gutters and Misc. The four tables have a projectID field to link them to the Projects table, and they all have a 'status' field. A project can have any combination of jobs.
I want to be able to select only the projects where all the job status fields are marked "completed."
There's probably a simple way to do this, but my brain overheats sometimes with SQL.
maybe...:
SELECT * FROM Projects
LEFT JOIN Roofing ON (projectID)
LEFT JOIN Siding ON (projectID)
LEFT JOIN Gutters ON (projectID)
LEFT JOIN Misc ON (projectID)
WHERE (Roofing.status IS NULL OR Roofing.status="completed")
AND (Siding.status IS NULL OR Siding.status="completed")
AND (Gutters.status IS NULL OR Gutters.status="completed")
AND (Misc.status IS NULL OR Misc.status="completed")
select p.*
from project as p
where not exists (select 1 from roofing where projectId = p.projectId and status <> 'completed')
and not exists (select 1 from siding where projectId = p.projectId and status <> 'completed')
and not exists (select 1 from gutters where projectId = p.projectId and status <> 'completed')
and not exists (select 1 from misc where projectId = p.projectId and status <> 'completed')
Alex's approach will get you all the information on one line (if there are no multiple records in the child tables), but if you need it one separtes lines try a union all statement. Just make sure you use the same columns in each union. If you have data in one or more tables that the other tables don't have you would then use null as the value for that column inthe union.
SELECT p1.projectid,'roofing' as JobType FROM Projects p1
JOIN Roofing r ON p1.projectID = r.projectID
union all
SELECT p1.projectid,'gutters' as JobType FROM Projects p1
JOIN gutters g ON p1.projectID = g.projectID
union all
SELECT p1.projectid,'siding' as JobType FROM Projects p1
JOIN Siding s ON p1.projectID = s.projectID
union all
SELECT p1.projectid,'misc' as JobType FROM Projects p1
JOIN Misc m ON p1.projectID = m.projectID
SELECT *
FROM Projects
--Step 3: filter the projects by the results from Step2
WHERE ProjectID not in
(
SELECT ProjectID
FROM
(
--Step 1: gather all the jobs into one bucket
SELECT ProjectID, Status
FROM Roofing
UNION ALL
SELECT ProjectID, Status
FROM Siding
UNION ALL
SELECT ProjectID, Status
FROM Gutters
UNION ALL
SELECT ProjectID, Status
FROM Misc
) as Jobs
--Step 2: find incomplete project IDs
GROUP BY Jobs.ProjectID
HAVING MIN(Jobs.Status) != 'COMPLETED'
OR MAX(Jobs.Status) != 'COMPLETED'
)
Create a view that returns distinct ProjectIds for "all jobs completed" and join that to the Projects table. That way you only have to update the view if the criteria for "all jobs completed" changes, e.g. if a new job is added. The SQL statement for the view can be constructed several ways as shown in the other replies.