T-SQL Select Join 3 Tables - sql

I'm currently working on a select query in T-SQL on SQL Server 2012. It's a complex query, I want to query a list from 3 tables. The result should look something like this:
Desired Output:
ProjectId | Title | Manager | Contact | StatusId
----------+-------------+-----------+-----------+-----------
1 | projectX | 1123 | 4453 | 1
2 | projectY | 2245 | 5567 | 1
3 | projectZ | 3335 | 8899 | 1
My 3 Tables:
1) Project: ProjectId, ProjectDataId, MemberVersionId
2) ProjectData: ProjectDataId, Title, StatusId
3) Members: MemberId, MemberVersionId, MemberTypeId, EmployeeId
The tricky part is, to implement versioning. Thus, over time the project Members can change, and it should always be possible to return to a previous version, that's why I use MemberVersionId as a foreign key inbetween Project and Members. The tables Project and ProjectData a linked with ProjectDataId.
Hence, 1 Project has 1 OfferData and 1 Project has N Members.
Some sample data:
Project
ProjectId | ProjectDataId | MemberVersionId |
----------+---------------+-----------------+
1 | 2 | 1 |
2 | 3 | 1 |
3 | 4 | 1 |
ProjectData
ProjectDataId | Title | StatusId
--------------+-------------+-----------
2 | projectX | 1
3 | projectY | 1
4 | projectZ | 1
Members: MemberTypeId 1 = Manager, MemberTypeId 2 = Contact, 3 = Other
MemberId | MemberVersionId | MemberTypeId | EmployeeId |
---------+-----------------+--------------+------------+
1 | 1 | 1 | 1123 |
2 | 1 | 2 | 4453 |
3 | 1 | 3 | 9999 |
4 | 2 | 1 | 2245 |
5 | 2 | 2 | 5567 |
6 | 2 | 3 | 9999 |
7 | 3 | 1 | 3335 |
8 | 3 | 2 | 8899 |
9 | 3 | 3 | 9999 |
My current query looks like this:
SELECT ProjectId, Title, EmployeeId AS Manager, EmployeeId AS Contact, StatusId
FROM [MySchema].[Project] a,
[MySchema].[ProjectData] b,
[MySchema].[Members] c
WHERE a.ProjectDataId = b.ProjectDataId
AND a.MemberVersionId = c.MemberVersionId
Unfortunately this doesn't work yet. Do you know how to solve this issue?
Thanks

Something like this?
SELECT
p.ProjectId,
pd.Title,
mm.EmployeeId AS Manager,
mc.EmployeeId AS Contact,
pd.StatusId
FROM
[MySchema].[Project] p
INNER JOIN [MySchema].[ProjectData] pd ON pd.ProjectDataId = p.ProjectDataId
INNER JOIN [MySchema].[Members] mm ON mm.MemberVersionId = p.MemberVersionId AND mm.MemberTypeId = 1
INNER JOIN [MySchema].[Members] mc ON mc.MemberVersionId = p.MemberVersionId AND mc.MemberTypeId = 2;

You can try this:
SELECT ProjectId, Title, C.EmployeeId AS Manager, d.EmployeeId AS Contact, StatusId
FROM [MySchema].[Project] a
INNER JOIN [MySchema].[ProjectData] b ON A.ProjectDataId=B.ProjectDataId
LEFT JOIN (SELECT * FROM [MySchema].[Members] WHERE MemberTypeID=1) c ON a.MemberVersionId=c.MemberVersionId
LEFT JOIN (SELECT * FROM [MySchema].[Members] WHERE MemberTypeID=2) d ON a.MemberVersionId=d.MemberVersionId

You must select members two times, one for the manager and another for contact:
SELECT ProjectId, Title, m.EmployeeId AS Manager, c.EmployeeId AS
Contact, StatusId
FROM [MySchema].[Project] a,
[MySchema].[ProjectData] b,
[MySchema].[Members] m
[MySchema].[Members] c
WHERE a.ProjectDataId = b.ProjectDataId
AND a.MemberVersionId = m.MemberVersionId and m.MemberTypeId = 1
AND a.MemberVersionId = c.MemberVersionId and c.MemberTypeId = 2

try this,
SELECT ProjectId, Title, cmanager.EmployeeId AS Manager, ccon.EmployeeId AS
Contact, StatusId
from [MySchema].[ProjectData] b
inner join [MySchema].[Project] a on b.ProjectDataId=a.ProjectDataId
left join [MySchema].[Members] cmanager on cmanager.MemberVersionId =
a.MemberVersionId and cmanager.MemberTypeId=1
left join [MySchema].[Members] ccon on ccon.MemberVersionId =
a.MemberVersionId and ccon.MemberTypeId=2

The simplest solution to your problem would be introducing additional field to Project table. You'd either call it LatestMemberVersion (int, holds the currently highest MemberVersionId), which would by the most up to date version of the relationship, your you can add even simpler IsLatestMemberVersion (bit, holds 1 if the record is the latest/active). You can compute both of them using ROW_NUMBER() OVER statement.
Then, the query would change to:
SELECT ProjectId, Title, EmployeeId AS Manager, EmployeeId AS Contact, StatusId
FROM [MySchema].[Project] a,
[MySchema].[ProjectData] b ON a.ProjectDataId = b.ProjectDataId
[MySchema].[Members] c ON a.MemberVersionId = c.MemberVersionId
WHERE
a.[IsLatestMemberVersion] = 1 -- alternative is a.[LatestMemberVersion] = a.[MemberVersionId]
Additionally, there are two more things you can try:
you might want to borrow ideas from data warehousing, namely you will want to have combination of Slowly Changing Dimension Type 1 and 2
you can try to use SQL Server features, such as Change Data Tracking. But I have no experience with that, so it's possible it'll lead to nowhere.
And one last piece of advice, if you can, never write join conditions into the WHERE clause. It is not readable and can lead to problems when you suddenly change JOIN to LEFT JOIN. Microsoft itself recommends using ON instead of WHERE when applicable.

Related

How can I get join results as JSON in a PostgreSQL query?

I have the query below that almost works: It returns 3 rows one of which should have first_nation populated (other two should be NULL). But they all get the same data for first_nation. What I need is the person.id from the outer where to be a part of the WHERE in the inner query but I don't think that's doable. Any help would be appreciated.
Or another way, I'd like the results of the JOIN to be JSON rather than appearing as additional columns.
SELECT person.id,
(
SELECT row_to_json(x)
FROM (
SELECT ref_first_nations_gov.id
FROM ref_first_nations_gov JOIN person ON person.first_nation_id = ref_first_nations_gov.id
WHERE person.application_id = 1 AND person.archived = false
) x
) AS first_nation
FROM person
WHERE application_id = 1 AND archived = false;
EDIT: Sample Data
SELECT id, application_id, first_nation_id FROM person WHERE application_id = 1;
id | application_id | first_nation_id
----+----------------+-----------------
4 | 1 |
1 | 1 |
2 | 1 |
3 | 1 | 1
What the query above gives me:
id | first_nation
----+--------------
4 | {"id":1}
1 | {"id":1}
3 | {"id":1}
What I want
id | first_nation
----+--------------
4 |
1 |
3 | {"id":1}
Even though I don't have how to test this right now, I don't think you need a subquery.
Try something like this.
SELECT p.id, row_to_json(r.id) FROM person p
FULL OUTER JOIN ref_first_nations_gov r on r.id = p.first_nation_id
WHERE p.application_id = 1 AND p.archived = false;

Multiple select from CTE with different number of rows in a StoredProcedure

How to do two select with joins from the cte's which returns total number of columns in the two selects?
I tried doing union but that appends to the same list and there is no way to differentiate for further use.
WITH campus AS
(SELECT DISTINCT CampusName, DistrictName
FROM dbo.file
),creditAcceptance AS
(SELECT CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal, COUNT(id) AS N
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%') AND (CollegeCreditEarnedFinal = 'Yes') AND (CollegeCreditAcceptedFinal = 'Yes')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
),eligibility AS
(SELECT CampusName, EligibilityStatusFinal, COUNT(id) AS N, CollegeCreditAcceptedFinal
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
)
SELECT a.CampusName, c.[EligibilityStatusFinal], SUM(c.N) AS creditacceptCount
FROM campus as a FULL OUTER JOIN creditAcceptance as c ON a.CampusName=c.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName ,c.EligibilityStatusFinal
Union ALL
SELECT a.CampusName , b.[EligibilityStatusFinal], SUM(b.N) AS eligible
From Campus as a FULL OUTER JOIN eligibility as b ON a.CampusName = b.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName,b.EligibilityStatusFinal
Expected output:
+------------+------------------------+--------------------+
| CampusName | EligibilityStatusFinal | creditacceptCount |
+------------+------------------------+--------------------+
| M | G | 1 |
| E | NULL | NULL |
| A | G | 4 |
| B | G | 8 |
+------------+------------------------+--------------------+
+------------+------------------------+----------+
| CampusName | EligibilityStatusFinal | eligible |
+------------+------------------------+----------+
| A | G | 8 |
| C | G | 9 |
| A | T | 9 |
+------------+------------------------+----------+
As you can see here CTEs can be used in a single statement only, so you can't get the expected output with CTEs.
Here is an excerpt from Microsoft docs:
A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE
statement that references some or all the CTE columns. A CTE can also
be specified in a CREATE VIEW statement as part of the defining SELECT
statement of the view.
You can use table variables (declare #campus table(...)) or temp tables (create table #campus (...)) instead.

SQL - How to get the count of each distinct value?

I have 3 table
**room**
room_id | nurse_needed
----------------------
1 | 2
2 | 3
3 | 1
**doctor_schedule**
doctor_schedule_id| room_id
---------------------------
1 | 1
2 | 2
3 | 3
*nurse_schedule*
nurse_schedule_id | doctor_schedule_id
--------------------------------------
1 | 1
2 | 1
3 | 2
Each Room needs a number of nurse, A doctor work in Room and a nurse work with doctor's schedule. I want to count how many nurse in each room.
The result should be:
room_id | nurse_needed|nurse_have_in_room
---------------------------------------------
1 | 2 | 2
2 | 3 | 1
3 | 1 | 0
Hmmm . . .
select r.*,
(select count(*)
from doctor_schedule ds join
nurse_schedule ns
on ds.doctor_schedule_id = ns.doctor_schedule_id
where ds.room_id = r.room_id
) as nurse_have_in_room
from room r;
select room.*,
(select count(*) from
dotor_schedule docs,
nurse_schedule nurs
where docs.doctor_schedule_id=nurs.dcotor_schedule_id
group by docs.room_id) as nurse_have_in_room
from room;
Result of join on doctor_schedule_id between doctor_schedule and
nurse_schedule
nurse_schedule_id | doctor_schedule_id room_id
--------------------------------------+------------
1 | 1 | 1
2 | 1 | 1
3 | 2 | 2
We group by room_id and then get the result.
select r.room_id,
r.nurse_needed,
ns.nurses_scheduled,
ns.dist_nurses_scheduled
from room r
left join (select ds.room_id,
count(1) nurses_schedule,
count(distinct ns.nurse_schedule_id) dist_nurses_scheduled
from doctor_schedule ds
join nurse_schedule ns
on ds.doctor_schedule_id = ns.doctor_schedule_id
group by ds.room_id) as ns
on r.room_id = ns.room_id
Left join so you find rooms with no nurses scheduled.
Count(distinct ns.nurse_schedule_id) if needed to see how many different nurses make up the count.
Normally you have a time component in there too. Something like "where r.roomdate = ns.date"

SQL orderby if statement if one field is empty use the other one

I have a query with combination of two table and connected with ID:
First table is category
ID|code|parent
1 |cd01|0
2 |cd02|0
3 |cd03|0
and the second table is category_description
ID|name |lang_id
1 |cat01|1
1 |cat01|2
1 |cat01|3
2 |cat02|2
2 |cat02|3
3 |cat03|1
3 |cat03|2
and now there is an instance that category has no entry in category_description with lang_id = 1 so when I list as grid, I use code instead of name and I want to order by is statement that when name is not set I will use the code.
| ID | Name |
------------------------
| 1 | cat01 - cd01 |
| 2 | cd02 |
| 3 | cat03 - cd03 |
Code:
SELECT c.code, cd.name
FROM category c, category_description cd
WHERE cd.ID = c.ID
GROUP BY cd.ID
ORDER BY
if cd.name is not set use c.code
Any help is very appreciated, thanks in advance.

SQL - Limiting to one row for matching results

Considering the tables below, how would I write a query that returns profession.profession when the profession.profession_id is present in contractor_has_profession.profession_id, but limiting it to one result for each profession.profession
So in this example the result would be [Coder, Database, Frontend]
contractor_has_profession
contractor_id | profession_id
1 | 5
2 | 5
3 | 5
4 | 2
5 | 1
profession
profession_id | profession
1 | Frontend
2 | Database
3 | Graphics
4 | Sound
5 | Coder
SELECT p.profession
FROM profession p
WHERE EXISTS(SELECT *
FROM contractor_has_profession c
WHERE c.profession_id = p.profession_id)
Hmm, this should be sufficient:
select distinct p.profession
from profession p
inner join contractor_has_profession c
where p.profession_id = c.profession_id
or if I'm wrong here, then try:
select p.profession
from profession p
inner join contractor_has_profession c
where p.profession_id = c.profession_id
group by p.profession