Joining a derived table postgres - sql

I have 4 tables:
Competencies: a list of obviously competencies, static and a library
Competency Levels: refers to an associated group of competencies and has a number of competencies I am testing for
call_competency: a list of all 'calls' that have recorded the specified competency
competency_review_status: proving whether each call_competency was reviewed
Now I am trying to write this query to count a total and spit out the competency, id and whether a user has reached the limit. Everything works except for when I add the user. I am not sure what I am doing wrong, once I limit call competency by user in the where clause, I get a small subset that ONLY exists in call_competency returned when I want the entire list of competencies.
The competencies not reached should be false, ones recorded appropriate number true. A FULL list from the competency table.
I added the derived table, not sure if this is right, obviously it doesn't run properly, not sure what I'm doing wrong and I'm wasting time. Any help much appreciated.
SELECT comp.id, comp.shortname, comp.description,
CASE WHEN sum(CASE WHEN crs.grade = 'Pass' THEN 1 ELSE CASE WHEN crs.grade = 'Fail' THEN -1 ELSE 0 END END) >= comp_l.competency_break_level
THEN TRUE ELSE FALSE END
FROM competencies comp
INNER JOIN competency_levels comp_l ON comp_l.competency_group = comp.competency_group
LEFT OUTER JOIN (
SELECT competency_id
FROM call_competency
WHERE call_competency.user_id IN (
SELECT users.id FROM users WHERE email= _studentemail
)
) call_c ON call_c.competency_id = comp.id
LEFT OUTER JOIN competency_review_status crs ON crs.id = call_competency.review_status_id
GROUP BY comp.id, comp.shortname, comp.description, comp_l.competency_break_level
ORDER BY comp.id;

(Shooting from the hip, no installation to test)
It looks like the below should do the trick. You apparently had some of the joins mixed up, with a column from a relation that was not referenced. Also, the CASE statement in the main query could be much cleaner.
SELECT comp.id, comp.shortname, comp.description,
(sum(CASE WHEN crs.grade = 'Pass' THEN 1 WHEN crs.grade = 'Fail' THEN -1 ELSE 0 END) >= comp_l.competency_break_level) AS reached_limit
FROM competencies comp
JOIN competency_levels comp_l USING (competency_group)
LEFT JOIN (
SELECT competency_id, review_status_id
FROM call_competency
JOIN users ON id = user_id
WHERE email = _studentemail
) call_c ON call_c.competency_id = comp.id
LEFT JOIN competency_review_status crs ON crs.id = call_c.review_status_id
GROUP BY comp.id, comp.shortname, comp.description
ORDER BY comp.id;

Related

How do I return the next follow on record based on dates in a SQL Server query?

As part of my vessel hire database I have a view that returns a selection of information about the vessels we have and also their fixture commitments. In our system, any vessel on fixture has that commitment flagged as "Live" and any fixture commitment for that vessel which comes AFTER the offhire date will be flagged as "FollowOn" (The live and follow on flagging is performed by the user on the front end).
The problem
A vessel can have multiple FollowOn commitments and so I need to find a way to return only the next commitment flagged as FollowOn after the offhire date of the current Live commitment.
The Query (So far)
SELECT
uve.[Id],
uve.[ViewId],
uve.[VesselId],
ve.[Name],
/***********************
#Fixture (Current)
***********************/
CAST((CASE WHEN fix1.[Id] IS NULL THEN 0 ELSE fix1.[Id] END) AS INT) AS [FixtureId],
fix1.[Date],
fix1.[FirmUntil],
fix1.[Charterer],
ch1.[ChartererName],
/***********************
#Fixture (Next)
***********************/
fix2.FollowOn,
fix2.Id AS NextFixtureId,
fix2.[Date] AS NextOnhire,
CAST((CASE WHEN fix2.FollowOn IS NULL THEN 0 ELSE fix2.FollowOn END) AS BIT) AS FollowOn,
fix2.[FirmUntil] AS NextOffhire,
ch2.ChartererName AS NextChartererName,
CAST((CASE WHEN fix1.[Live] IS NULL THEN 0 ELSE fix1.[Live] END) AS BIT) AS Live
FROM UserVessels AS uve
LEFT JOIN Vessel AS ve ON ve.Id = uve.VesselId
LEFT JOIN VesselTypes AS vt ON vt.[Id] = ve.[Type]
LEFT JOIN (SELECT [Id], [Date], [FirmUntil], [PeriodAmount], [PeriodFrequency], [Charterer], [Port], [Live],[FollowOn], [Broker], [WorkRole], [VesselId]
FROM Fixture
WHERE Live = '1' AND FollowOn = '0') AS fix1 ON fix1.[VesselId] = ve.Id
LEFT JOIN (SELECT [Id],[Date],[Live],[FirmUntil],[FollowOn], [Charterer], [Port], [WorkRole], [VesselId]
FROM Fixture AS f2
WHERE f2.[FollowOn] = '1') AS fix2 on fix2.[VesselId] = ve.Id
LEFT JOIN Charterer AS ch1 ON ch1.Id = fix1.Charterer
LEFT JOIN Charterer AS ch2 ON ch2.Id = fix2.Charterer
As you can see, the heavy lifting for what I'm trying to achieve happens in the join where I have a subquery looking at the FollowOn flag. This isn't ideal because if there is more than one fixture with followon then you get lots of rows. I need only the immediate next followon after the offhire of "fix1" so that I can keep each vessel as a single row and was wondering how that can be achieved?
So if you've got a list of all of the follow on fixtures, as alluded to in the comments you can use a row_number window function to get the next one:
LEFT JOIN (SELECT [Id],[Date],[Live],[FirmUntil],[FollowOn], [Charterer], [Port], [WorkRole], [VesselId], ROW_NUMBER() OVER (PARTITION BY VesselId ORDER BY Date) [RNum]
FROM Fixture AS f2
WHERE f2.[FollowOn] = '1') AS fix2 on fix2.[VesselId] = ve.Id AND fix2.RNum = 1
However the outer apply might be quicker. You'd need to test it.

Looking for a way to not show duped rows using a SQL query

SELECT
AEC.gwd_people.id_people,
AEC.gwd_people.uid_people,
AEC.gwd_people.cod_people,
AEC.gwd_people.name_people,
AEC.gwd_people.surname_people,
AEC.gwd_people.email,
AEC.gwd_people.people_status,
AEC.gwd_people.people_type,
AEC.gwd_people.facility_reference,
AEC.gwd_people.sc_id_sap,
AEC.gwd_people.c_id_sap,
AEC.gwd_people.descr_people,
AEC.gwd_people.cod_sector,
AEC.gwd_people.descr_sector,
AEC.gwd_people.cod_org_sector,
AEC.gwd_people.descr_org_sector,
AEC.gwd_people.cod_company,
AEC.gwd_people.descr_company,
AEC.gwd_people.cod_company_sap,
AEC.gwd_people.cod_department,
AEC.gwd_department.descr_department,
AEC.gwd_people.cod_subdepartment,
AEC.gwd_people.descr_subdepartment,
AEC.gwd_people.cod_cdc,
AEC.gwd_cost_center.descr_cdc,
AEC.gwd_people.cod_category_job,
AEC.gwd_people.descr_category_job,
AEC.gwd_people.cod_people_job,
AEC.gwd_people.descr_people_job,
AEC.gwd_people.cod_position,
AEC.gwd_people.descr_position,
AEC.gwd_people.uohr,
AEC.gwd_people.qual_contract,
AEC.gwd_people.level_position,
AEC.gwd_people.cod_manager,
AEC.gwd_people.cod_validator,
AEC.gwd_people.cod_country,
AEC.gwd_people.descr_country,
AEC.gwd_people.cod_region_area,
AEC.gwd_people.descr_region_area,
AEC.gwd_people.descr_city,
AEC.gwd_people.descr_site,
AEC.gwd_people.address_1,
AEC.gwd_people.address_2,
AEC.gwd_people.descr_building,
AEC.gwd_people.descr_room,
AEC.gwd_people.validity_date,
AEC.aec_workstation.cod_workstation,
AEC.aec_workstation.geometry,
AEC.aec_workstation.drawing,
AEC.gwd_people.tax_code,
AEC.gwd_people.phone_1,
AEC.gwd_people.phone_2,
AEC.gwd_people.phone_3,
AEC.gwd_people.phone_4,
AEC.gwd_people.ext_email_1,
AEC.gwd_people.flagvip,
AEC.gwd_people.hiring_date,
AEC.gwd_people.cease_date,
AEC.gwd_people.cid_resp_liv_1,
AEC.gwd_people.cid_resp_liv_2,
AEC.gwd_people.id_resp,
AEC.gwd_people.descr_resp,
AEC.gwd_people.id_ref,
AEC.gwd_people.descr_ref,
AEC.gwd_people.descr_ext_people,
AEC.gwd_people.ext_email_2,
AEC.gwd_people.descr_sede,
(CASE WHEN AEC.aec_r_workstation_people.cod_people IS NULL
THEN AEC.gwd_people.idplan
ELSE NULL
END) AS idplan,
(CASE WHEN AEC.aec_r_workstation_people.cod_people IS NOT NULL
THEN SUBSTRING(AEC.aec_workstation.cod_workstation, 5, 7)
ELSE NULL
END) AS idplan_wrkst,
(CASE WHEN AEC.aec_r_workstation_people.cod_people IS NULL
THEN AEC.view_iam_r_unitp_building.IDEDIFICIO
ELSE NULL
END) AS cod_building,
(CASE WHEN AEC.aec_r_workstation_people.cod_people IS NOT NULL
THEN SUBSTRING(AEC.aec_workstation.cod_workstation, 5, 3)
ELSE NULL
END) AS cod_building_wrkst,
(CASE WHEN AEC.aec_r_workstation_people.cod_people IS NOT NULL
THEN AEC.aec_workstation.id_room
ELSE NULL
END) AS id_room_wrkst,
(CASE WHEN AEC.aec_r_workstation_people.cod_people IS NOT NULL
THEN AEC.aec_workstation.id_room
ELSE NULL
END) AS id_room_wrkst2
FROM AEC.gwd_people
LEFT OUTER JOIN AEC.view_iam_r_unitp_building ON
AEC.view_iam_r_unitp_building.IDUNITPROD = AEC.gwd_people.cod_sector
LEFT OUTER JOIN AEC.aec_r_workstation_people ON AEC.gwd_people.cod_people =
AEC.aec_r_workstation_people.cod_people
LEFT OUTER JOIN AEC.aec_workstation ON AEC.aec_workstation.cod_workstation
= AEC.aec_r_workstation_people.cod_workstation
LEFT OUTER JOIN AEC.gwd_department ON AEC.gwd_department.cod_department =
AEC.gwd_people.cod_department
LEFT OUTER JOIN AEC.gwd_cost_center ON AEC.gwd_cost_center.cod_cost_center
= AEC.gwd_people.cod_cdc
This is my query and I'm using SQL Server 13, it returns 6752 rows, 44 of them are duped. I've tried everything I know to avoid showing those duped entries but I'm out of ideas, so I'm looking for some helpful tips :-) One of the biggest problem is taht all fields are necessary, so I can't get rid of "AEC.aec_workstation.geometry" that causes problems with SELECT DISTINCT.
Find a PK value from your first table that's returning a duplicate row and start with the following query:
SELECT
COUNT(1)
FROM
AEC.gwd_people
WHERE
AEC.gwd_people.PrimaryKeyColumn = 'SomeValue'
Now start adding joins one by one, checking the result of the COUNT(1) each time:
SELECT
COUNT(1)
FROM
AEC.gwd_people
LEFT OUTER JOIN AEC.view_iam_r_unitp_building ON AEC.view_iam_r_unitp_building.IDUNITPROD = AEC.gwd_people.cod_sector
WHERE
AEC.gwd_people.PrimaryKeyColumn = 'SomeValue'
And then...
SELECT
COUNT(1)
FROM
AEC.gwd_people
LEFT OUTER JOIN AEC.view_iam_r_unitp_building ON AEC.view_iam_r_unitp_building.IDUNITPROD = AEC.gwd_people.cod_sector
LEFT OUTER JOIN AEC.aec_r_workstation_people ON AEC.gwd_people.cod_people = AEC.aec_r_workstation_people.cod_people
WHERE
AEC.gwd_people.PrimaryKeyColumn = 'SomeValue'
Until you see the amount of rows jump up when you don't expect it to. You are most likely:
Not considering that duplicate rows can be expected.
Missing another join column on a table.
Having duplicate rows on a table.
... or combination of these.
Your table design makes it a bit hard to understand their relations. This is what it looks like to me:
gwd_department {1:n} gwd_people
gwd_people {m:n} aec_workstation
gwd_people {m:n} view_iam_r_unitp_building
gwd_people {?:n} gwd_cost_center
So for a person linked to 3 aec_workstations and 4 view_iam_r_unitp_buildings, you'd produce 3 x 4 = 12 result rows. Is there no further relation between an aec_workstation and a view_iam_r_unitp_building? If not, then why do you combine them in your query?
I don't know whether cod_cdc is supposed to be short for cod_cost_center or something different. If this is an m:n relation, too, you are doing the same thing again with gwd_cost_center related to aec_workstation and view_iam_r_unitp_building.
Having said this: Either add the missing criteria or ask yourself what you want to select after all.

Query from multiple tables with multiple where conditions in the tables

I'm trying to get a count of all speakers who are active regarding that item as well as the total of speakers who correlate to a certain item. The first LEFT JOIN for the total speakers works, but the other for ONLY the active speakers regarding that item doesn't, any help is appreciated. The SQLFiddle is here
http://sqlfiddle.com/#!3/b579d/1
But when I try to add in the portion where you would get the number of active speakers
(LEFT JOIN (SELECT COUNT (tbl_SpeakerCard_Log.SpeakerName)
WHERE tbl_Speaker_Log.Spoken = 0)
ON tbl_AgendaList.AID = tbl_SpeakerCard_Log.AID)
under the previous LEFT JOIN I get an error. I'm 100% sure the query is wrong in some form, but I'm not sure how to approach it.
*NOTE: Spoken/Active are interchangeable, I just use different wording to clarify what I'm looking for.
EDIT: This is the desired output
http://imgur.com/yP1FKxg
You can use conditional aggregation to do this:
SELECT
AgendaList.AID,
AgendaList.Item,
COUNT(SpeakerList.SPID) as SpeakerTotal,
SUM(CASE WHEN SpeakerList.Spoken = 0 THEN 1 ELSE 0 END) as ActiveSpeakers
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
GROUP BY AgendaList.AID, AgendaList.Item;
Sample SQL Fiddle
Or you could use count instead of sum (which might be clearer):
COUNT(CASE WHEN Spoken = 0 THEN Spoken END) as ActiveSpeakers
SQL FIDDLE
WITH sTotal AS (
SELECT AgendaList.AID, AgendaList.Item, COUNT( SpeakerList.SPID) as SpeakerTotal
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
GROUP BY AgendaList.AID, AgendaList.Item
),
sActive AS (
SELECT AgendaList.AID, AgendaList.Item, COUNT( SpeakerList.SPID) as SpeakerActive
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
WHERE SpeakerLIST.Spoken = 0
GROUP BY AgendaList.AID, AgendaList.Item
)
SELECT sTotal.*, sActive.SpeakerActive
FROM sTotal left join
sActive on sTotal.AID = sActive.AID

Join one column to two tables

I have a database that holds info for accounts, posts, and which posts a user likes.
AccountData
id || username
PostData
id || text || accountid
LikesDislikesData
liked(bool) || accountid || postid
I have a view set up because I need specific data from the DB to bind inside of my app. Here is the code I am using:
SELECT trippin.PostData.id, trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname, trippin.PostData.__createdat as CreatedAt,
SUM(CASE WHEN likes.liked = 1 THEN 1 ELSE 0 END) as Likes,
SUM(CASE WHEN likes.liked = 0 THEN 1 ELSE 0 END) as DisLikes
FROM trippin.PostData
INNER JOIN trippin.AccountData ON trippin.PostData.accountid = trippin.AccountData.id
INNER JOIN trippin.CategoryData ON trippin.CategoryData.id = trippin.PostData.categoryid
LEFT OUTER JOIN trippin.LikesDislikesData likes ON likes.postid = trippin.PostData.id
GROUP BY (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.PostData.id), (trippin.categorydata.categoryname), (trippin.PostData.__createdat)
The problem is that, any time I add a join to (likes.accountid = trippin.AccountData.id), rows are either duplicated or the output is just incorrect.
I think it might be a design issue, but I am not sure and cannot find anything that helps my exact problem.
So basically, each post is made by a user. Then, each post is liked or disliked (or nothing) by other users. I need all of this data inside of a view so I can pass it to my app.
select postID
, sum(case when liked = true then 1 else 0 end) as liked
, sum(case when liked = true then 1 else 0 end) as disliked
from trippin.LikesDislikesData
group by postID
The above statement should give you liked and disliked by post ID. Call it a subquery and join to your main:
SELECT trippin.PostData.id, trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname, trippin.PostData.__createdat as CreatedAt,
Likes,
DisLikes
FROM trippin.PostData
INNER JOIN trippin.AccountData ON trippin.PostData.accountid = trippin.AccountData.id
INNER JOIN trippin.CategoryData ON trippin.CategoryData.id = trippin.PostData.categoryid
LEFT OUTER JOIN (
select postID
, sum(case when liked = true then 1 else 0 end) as liked
, sum(case when liked = true then 1 else 0 end) as disliked
from trippin.LikesDislikesData
group by postID)
likes ON likes.postid = trippin.PostData.id
Should work for what you are after. I removed your group by clause as you'll no longer need the sums syntax. Quite often you'll find creating a subquery to do counts and sums by ID and joining that to the main query by that ID will be the easiest solution to obtain counts...atleast without having to group by the entire select statement

Want to filter SQL Server query

Currently I am pulling out the Facilities where the permitType is 'Hazardous waste' and the licenseStatus is 'Open', but there will be cases where the facilities will have more than one permit type suppose a facility can have two permit types
Hazardous waste (status - OPEN)
AST (OPEN)
so I should not display this facility in my output if it other permit types (Status - OPEN) apart from the 'hazardous waste', but if the other permit type has status - CLOSE then my query should output the Facility i.e if AST(Status - CLOSE) then the facility should be pulled from the database.
I wrote the following query but not sure where to include the condition.
SELECT
e.facilityID
,f.organization_core AS 'Facility Name'
,f.address_full_core AS 'Facility Address'
,a.permitNumber AS 'Permit Number'
,b.permitName AS 'Permit Name'
,a.licenseStatus AS 'Permit Status'
,c.permitType AS 'Permit Type'
FROM
tblPermits a
LEFT JOIN
dbo.tblPermit_Names b ON a.permitID = b.permitID
LEFT JOIN
dbo.tblLKP_Permit_Facilities d ON a.permitID = d.permitID
LEFT JOIN
dbo.tblPermit_Types c ON a.permitTypeID = c.permitTypeID
LEFT JOIN
dbo.tblFacility e ON d.facilityID = e.facilityID
LEFT JOIN
dbo.tblAddresses f ON e.facilityAddressID = f.addressID
WHERE
a.permitTypeID IN (SELECT permitTypeID
FROM dbo.tblPermit_Types
WHERE permitType IN ('Hazardous Waste'))
AND a.licenseStatus = 'Open'
AND isNull(a.deleted, 0) = 0
I think the following query implements your rules. The idea is to focus on the facility and not on all the extra stuff in the tables that you have put in. You need to aggregate by facilityid so you can apply logic to all the permits issued for each one:
SELECT f.facilityID
FROM dbo.tblFacility f join
dbo.tblLKP_Permit_Facilities pf
on pf.facilityID = f.facilityId join
tblPermits p
on pf.permitID = p.permitID join
dbo.tblPermit_Types pt
ON pt.permitTypeID = p.permitTypeID
GROUP BY f.facilityID
HAVING SUM(case when pt.permitType IN ('Hazardous Waste') and p.licenseStatus = 'Open'
then 1 else 0
end) > 0 and
SUM(case when pt.permitType NOT IN ('Hazardous Waste') and p.licenseStatus = 'Close'
then 1 else 0
end) > 0;
Each condition in the having clause is applying one of your rules.
I'm a little confused as to your table structure, but this is what I think you should do.
Your where statement should be id in (Select id from table where status = open and count(id) = 1 group by facility)
AND Type in ('Hazardous Waste')
The first part of the where statement will limit all ids to those that are open with only one open type per facility, the second part limits it to just hazardous waste.
If you have a facility with 10 permits, but only one is active, it will pull it into the list, but if the active permit isn't hazardous waste, it will then exclude it.
Sorry I can't give you exact code.
Also, nix as many of those outer joins as you can. Inner joins are faster, and are more likely to represent the data you want.