count after join on multiple tables and count of multiple column values - sql

Please help me with below problem.
table 1 employee details
emp name empno.
---------------------------------
John 1234
Joe 6789
table 2 employee assignment
empno assignmentstartdate assignmentenddate assignmentID empassignmentID
-----------------------------------------------------------------------------
1234 01JAN2017 02JAN2017 A1 X1
6789 01jan2017 02JAN2017 B1 Z1
table 3 employee assignment property
empassignmentID assignmentID propertyname propertyvalue
-------------------------------------------------------------------
X1 A1 COMPLETED true
X1 A1 STARTED true
Z1 B1 STARTED true
Z1 B1 COMPLETED false
Result wanted: (count of completed and started for each employee)
emp name emp no. COMPLETED STARTED
------------------------------------------
John 1234 1 1
Joe 6789 0 1
Currently with my query it is not putting count correctly for propertyvalue if I run for one employee it works correctly but not for multiple employees.
Please help.
SELECT empno ,
empname ,
(SELECT COUNT(A.propertyvalue)
FROM employeedetails C ,
employees_ASSIGNMENT RCA,
employee_assignment_property A
WHERE TRUNC(startdate) >= '14jun2017'
AND TRUNC(endate) <= '20jun2017'
AND RCA.empno = C.empno
AND RCA.empassignmetid = A.empassignmetid
AND rca.EMPNO IN ('1234','6789')
AND RCA.assignmentid = A.assignmentid
AND A.Name = 'COMPLETED'
AND A.propertyvalue = 'true') ,
(SELECT COUNT(A.propertyvalue)
FROM employeedetails C ,
employees_ASSIGNMENT RCA,
employee_assignment_property A
WHERE TRUNC(startdate) >= '14jun2017'
AND TRUNC(endate) <= '20jun2017'
AND RCA.empno = C.empno
AND RCA.empassignmetid = A.empassignmetid
AND rca.EMPNO IN ('1234','6789')
AND RCA.assignmentid = A.assignmentid
AND A.Name = 'STARTED'
AND A.propertyvalue = 'true')FROM employeedetails WHERE EMPNO IN
('1234','6789') GROUP BY C.empno ,
C.EMPNAME

I think you are simply looking for this:
SELECT DET.empname
, COUNT(CASE WHEN PROP.propertyname = 'COMPLETED' THEN 1 END) COMP_COUNT
, COUNT(CASE WHEN PROP.propertyname = 'STARTED' THEN 1 END) START_COUNT
FROM employeedetails DET
INNER JOIN employees_ASSIGNMENT ASS
ON ASS.empno = DET.empno
INNER JOIN employee_assignment_property PROP
ON PROP.empassignmentID = ASS.empassignmentID
AND PROP.assignmentID = ASS.assignmentID
GROUP BY DET.empname
Just add a WHERE clause if you need one.

if you want you result as a query without CTEs this should work:
select empName,
empNo,
(select employee_details.empNo, count(employee_assignment.assId)
from employee_details as t1
join employee_assignment on (t1.empno = employee_assignment.empno)
join employee_assignment_property on (employee_assignment.assId = employee_assignment_property.assId)
where employee_assignment.ptop = 'COMPLETED'
and t.empNo = t1.empNo
group by t1.empNo ) as [COMPLETED],
(select employee_details.empNo, count(employee_assignment.assId)
from employee_details as t1
join employee_assignment on (t1.empno = employee_assignment.empno)
join employee_assignment_property on (employee_assignment.assId = employee_assignment_property.assId)
where employee_assignment.ptop = 'STARTED'
and t.empNo = t1.empNo
group by t1.empNo ) as [STARTED],
from employee_details as t

If you don't want to do a dirty query composed of subqueries, you can try creating a view (if your database permits it).
What does it mean : I'll be useless in front of this. In summary, a view is a temporary table.
Hope this helps

this should work using CTEs:
Using Common Table Expressions
with numComplet()
as
(
select tbl1.empNo, count(tbl2.assId)
from tbl1
join tbl2 on (tbl1.empno = tbl2.empno)
join tbl3 on (tbl2.assId = tbl3.assId)
where tbl2.ptop = 'COMPLETED'
group by tbl1.empNo
),
with numStarted()
as
(
select tbl1.empNo, count(tbl2.assId)
from tbl1
join tbl2 on (tbl1.empno = tbl2.empno)
join tbl3 on (tbl2.assId = tbl3.assId)
where tbl2.ptop = 'STARTED'
group by tbl1.empNo
)
select *
from tbl1
join numComplet on (tbl1.empNo = numComplet.empNo)
join numStarted on (tbl1.empNo = numStarted.empNo)
I put down table names as tbl[1|2|3]

Related

Conditionally Replace Default Null Values from Left Join

I am joining 3 tables. In tables 2 and 3, records do not exist for some of the IDs in table 1. This of course, yields null values for these instances in my joined table.
I know I can replace these nulls with coalesce, but I don't know how to replace the nulls conditionally. In my reprex below, I would like to replace a null with an ID's max total (if an ID has a non-null total under a different job title) or replace a null with 0 (if an ID has null totals under both of their job titles).
Reprex code below:
/*sample table a (contains full data - 2 records for each ID)*/
data table_a;
input id title $ region $ calls;
cards;
1 manager south 30
1 agent north 20
2 manager west 20
2 agent south 25
;
run;
/*sample table b (missing an agent record for ID 1 -- will result in null total sales for ID 1's agent record in joined table)*/
data table_b;
input id title $ sales;
cards;
1 manager 20
2 manager 5
2 agent 3
;
run;
/*sample table c (missing both records for ID 2 - will result in null total_leads for ID 2 in joined table)*/
data table_c;
input id title $ leads;
cards;
1 manager .
1 agent 10
;
run;
/*join tables*/
proc sql;
create table reprex as
select a.id,
a.region,
a.calls,
a.title,
coalesce(b.total_sales, 0) as total_sales, /*this replaces all nulls as 0, but I'd like to replace
b.sales, them conditionally */
coalesce(c.total_leads, 0) as total_leads,
c.leads
from table_a as a
left join (select sum(coalesce(sales, 0)) as total_sales, sales, id, title from table_b group by id) b
on a.id = b.id and a.title = b.title
left join (select sum(coalesce(leads, 0)) as total_leads, leads, id, title from table_c group by id) c
on a.id = c.id and a.title = c.title;
quit;
Ended up nesting subqueries to get what I was after:
proc sql;
create table reprex as
select id, region, title,
calls,
case
when max_total_calls is null then 0
else max_total_calls
end as total_calls,
sales,
case
when max_total_sales is null then 0
else max_total_sales
end as total_sales,
leads,
case
when max_total_leads is null then 0
else max_total_leads
end as total_leads
from
(select *,
max(total_sales) as max_total_sales,
max(total_leads) as max_total_leads,
max(total_calls) as max_total_calls
from
(select a.id, a.region, a.calls, a2.total_calls, a.title,
b.sales, c.total_leads, c.leads
from table_a as a
left join (select sum(coalesce(calls, 0)) as total_calls, calls, id, title from table_a group by id) a2
on a.id = a2.id and a.title = a2.title
left join (select sum(coalesce(sales, 0)) as total_sales, max(coalesce(sales,0)) as max_sales, sales, id, title from table_b group by id) b
on a.id = b.id and a.title = b.title
left join (select sum(coalesce(leads, 0)) as total_leads, leads, id, title from table_c group by id) c
on a.id = c.id and a.title = c.title)
group by id);
quit;

Avg function from 2 tables

I'm pretty new at SQL, I looking to get an AVG function from two queries (join to one query by groups).
my query template.
WITH group1 AS (
SELECT
ID AS ID
,date AS date
,TYPE AS TYPE
,GROUP_M AS GROUP_M
,Value AS Value
,AVG (NULLIF( Value , 0)) OVER (PARTITION BY TYPE ) AS TYPE_AVG
FROM
(
SELECT
a4.id AS ID
,a0.data AS date
,GET_ATTRIBUTE_VALUE ( a12.attributes , Upper('TYPE'), '=') AS TYPE
,GET_ATTRIBUTE_VALUE ( a12.attributes , Upper('GROUP_M'), '=') AS GROUP_M
,a4.value AS Value
,a12.attributes AS attributes
FROM
SESSION a2
LEFT JOIN P_id a0 ON a0.id = a2.id
INNER JOIN P_SET a3 ON a3.id = a2.id
WHERE
a2.name = 'REVIEW'
AND a2.test_name = '4M'
AND a0.data >= TRUNC(SYSDATE) - 7
)
),
group2 AS (
SELECT
,wch.id AS E_id
,e.entity AS Entity
FROM
F_machine wch
INNER JOIN F_Entity e ON e.entity = wch.entity
INNER JOIN F_Run_card lrc ON lrc.lotoperkey = wch.lotoperkey
INNER JOIN F_Route r ON r.route=lrc.route AND r.facility = lrc.facility AND r.latest_version = 'Y'
WHERE
e.entity Like 'E%'
AND e.entity Is Not Null
AND wch.end_time >= TRUNC(SYSDATE) - 14
)
SELECT *
FROM group1
JOIN group2 ON group1.id = group2.E_id
;
my end goal is to have one more column - average of value by machine and type
AVG (NULLIF( {value} , 0)) OVER (PARTITION BY {machine} , {TYPE} )
thanks and hope everything is clear :)

SQL correct query or not

given these relationships, how could you query the following:
The tourists (name and email) that booked at least a pension whose rating is greater than 9, but didn't book any 3 star hotel with a rating less than 9.
Is the following correct?
SELECT Tourists.name, Tourists.email
FROM Tourists
WHERE EXISTS (
SELECT id FROM Bookings
INNER JOIN Tourists ON Bookings.touristId=Tourists.id
INNER JOIN AccomodationEstablishments ON Bookings.accEstId=AccomodationEstablishments.id
INNER JOIN AccomodationTypes ON AccomodationEstablishments.accType=AccomodationTypes.id
WHERE AccomodationTypes.name = 'Pension' AND
AccomodationEstablishments.rating > 9
) AND NOT EXISTS (
SELECT id FROM Bookings
INNER JOIN Tourists ON Bookings.touristId=Tourists.id
INNER JOIN AccomodationEstablishments ON Bookings.accEstId=AccomodationEstablishments.id
INNER JOIN AccomodationTypes ON AccomodationEstablishments.accType=AccomodationTypes.id
WHERE AccomodationTypes.name = 'Hotel' AND
AccomodationEstablishments.noOfStars = 3 AND
AccomodationEstablishments.rating < 9
)
I would do this using aggregation and having:
SELECT t.name, t.email
FROM Bookings b INNER JOIN
Tourists t
ON b.touristId = t.id INNER JOIN
AccomodationEstablishments ae
ON b.accEstId = ae.id INNER JOIN
AccomodationTypes a
ON ae.accType = a.id
GROUP BY t.name, t.email
HAVING SUM(CASE WHEN a.name = 'Pension' AND ae.rating > 9 THEN 1 ELSE 0 END) > 0 AND
SUM(a.name = 'Hotel' AND ae.noOfStars = 3 AND ae.rating < 9 THEN 1 ELSE 0 END)= 0;
Your method also works, but you probably need t.id in the subqueries.

Case or If, What to choose in SQL

How can i manage this query. I want to have only one person in the query. If there is an id in empId column, then the name of him. Otherwise the name of the boss.
Product empId teamId
----------------------
A 1 3
B 2 4
C 3
D 2 3
E 4
User Table
Id Name
-----------
1 Jim
2 Carrey
3 Bill
4 Clinton
Team Table
Team_Id BossId
-----------
3 3
4 4
The result should look like:
Product user.name
-----------------
A Jim
B Carrey
C Bill
D Carrey
E Clinton
Use CASE or the COALESCE() function:
SELECT
x.Product
, COALESCE(emp.Name, boss.Name) AS Name
FROM
TableX AS x
LEFT JOIN
User AS emp
ON emp.Id = x.empId
LEFT JOIN
User AS boss
ON boss.Id = x.bossId
Updated:
SELECT
x.Product
, COALESCE(emp.Name, boss.Name) AS Name
FROM
TableX AS x
LEFT JOIN
User AS emp
ON emp.Id = x.empId
LEFT JOIN
Team As t
JOIN
User AS boss
ON boss.Id = t.bossId
ON t.team_Id = x.teamId
Something like this:
SELECT
Table1.Product,
CASE
WHEN Table1.empId IS NULL
THEN Boss.name
ELSE Emp.name
END
FROM
Table1
LEFT JOIN [User] AS Emp
ON Emp.Id =Table1.empId
LEFT JOIN Team
ON Team.Team_Id =Table1.teamId
LEFT JOIN [User] AS Boss
ON Boss.Id=Team.BossId
SELECT Y.Product, U.Name
FROM YourTable AS Y
JOIN Users AS U ON Y.empId = U.Id
UNION
SELECT Y.Product, U.Name
FROM YourTable AS Y
JOIN Team AS T ON Y.teamId = T.Team_Id
JOIN Users AS U ON T.BossId = U.Id
WHERE Y.empId IS NULL;
-- SET search_path='tmp';
DROP TABLE tmp.products;
CREATE TABLE products
( product CHAR(1)
, emp_id INTEGER
, team_id INTEGER
);
INSERT INTO products(product,emp_id,team_id)
VALUES ('A',1,3), ('B',2,4), ('C',NULL,3), ('D',2,3), ('E',NULL,4);
DROP TABLE tmp.names;
CREATE TABLE names
(id INTEGER
, zname varchar
);
INSERT INTO names(id,zname)
VALUES ( 1, 'Jim') ,( 2, 'Carrey') ,( 3, 'Bill') ,( 4, 'Clinton') ;
DROP TABLE tmp.teams;
CREATE TABLE teams
( team_id INTEGER NOT NULL
, boss_id INTEGER NOT NULL
);
INSERT INTO teams(team_id,boss_id) VALUES ( 3,4) , (4,4);
WITH lutser(prod,id,team) AS
(
SELECT k1.product AS prod
, k1.emp_id AS id
, k1.team_id AS team
FROM tmp.products k1
UNION
SELECT k2.product AS prod
, t.boss_id AS id
, k2.team_id AS team
FROM tmp.products k2
JOIN tmp.teams t ON t.team_id = k2.team_id
WHERE k2.emp_id IS NULL
)
SELECT l.prod
, l.id
, l.team
, n.zname
FROM lutser l
JOIN names n ON n.id = l.id
;
extra bonus point for a recursive version of this CTE ...

Adding up the rows of a column in a query

Here is my current query:
SELECT sac.cred, s.status, (SELECT NVL (csl.census_dates, tl.census_dates)
FROM schema.sections cs, schema.sections_ls csl, schema.terms tl
WHERE cs.course_sections_id = csl.course_sections_id(+)AND csl.pos(+) = 1 AND cs.term = tl.terms_id
AND tl.pos = 1 AND cs.course_sections_id = cs2.course_sections_id AND ROWNUM = 1)AS censusDate,
(SELECT NVL (p.ssn, 'xxx-xx-xxxx') FROM schema.person p
WHERE p.id = sac.person_id) AS ssn,
//schema.person_name(sac.person_id, 'FML') as fml,
//schema.person_name(sac.person_id, 'LF') as lf
FROM schema.student_acad_cred sac JOIN schema.statuses s
ON s.student_acad_cred_id = sac.student_acad_cred_id
JOIN schema.terms tl ON sac.term = tl.terms_id
JOIN schema.student_course_sec scs ON sac.student_course_sec = scs.student_course_sec_id
JOIN schema.course_sections cs2 ON scs.course_section = cs2.course_sections_id
JOIN schema.terms t ON tl.terms_id = t.terms_id
WHERE sac.person_id = '1111111111'
AND (s.status IN ('A', 'N') OR (s.status = 'D' AND final_grade IS NOT NULL))
AND s.pos = '1'AND tl.pos = '1' AND tl.terms_id = 'spring';
And here are the results:
cred status currentDate censusDate ssn
==== ====== =========== ========== ===
3 N 11/16/2011 12/15/2011 xxx-xx-xxxx
4 N 11/16/2011 12/15/2011 xxx-xx-xxxx
3 N 11/16/2011 12/15/2011 xxx-xx-xxxx
4 N 11/16/2011 12/15/2011 xxx-xx-xxxx
1 N 11/16/2011 12/15/2011 xxx-xx-xxxx
Okay, what I am trying to do is use sum() (or some other function) to add up all the credit hours that are pulled. So in this instance the sum of all cred hours would be '15'. Is there a way to do this in query? Ideally I would want something like this:
cred status currentDate censusDate ssn
==== ====== =========== ========== ===
15 N 11/16/2011 12/15/2011 xxx-xx-xxxx
The way to do this is to GROUP BY all of your other columns. Since you can't group by aliased columns directly (in Oracle and most RDMBSes), you have to wrap the whole thing in another query and do the grouping there.
SELECT SUM(cred), status, censusDate, ssn
FROM
(SELECT sac.cred, s.status,
(SELECT NVL (csl.census_dates, tl.census_dates)
FROM schema.sections cs, schema.sections_ls csl, schema.terms tl
WHERE cs.course_sections_id = csl.course_sections_id(+)AND csl.pos(+) = 1 AND cs.term = tl.terms_id
AND tl.pos = 1 AND cs.course_sections_id = cs2.course_sections_id AND ROWNUM = 1)AS censusDate,
(SELECT NVL (p.ssn, 'xxx-xx-xxxx') FROM schema.person p
WHERE p.id = sac.person_id) AS ssn,
//schema.person_name(sac.person_id, 'FML') as fml,
//schema.person_name(sac.person_id, 'LF') as lf
FROM schema.student_acad_cred sac JOIN schema.statuses s
ON s.student_acad_cred_id = sac.student_acad_cred_id
JOIN schema.terms tl ON sac.term = tl.terms_id
JOIN schema.student_course_sec scs ON sac.student_course_sec = scs.student_course_sec_id
JOIN schema.course_sections cs2 ON scs.course_section = cs2.course_sections_id
JOIN schema.terms t ON tl.terms_id = t.terms_id
WHERE sac.person_id = '1111111111'
AND (s.status IN ('A', 'N') OR (s.status = 'D' AND final_grade IS NOT NULL))
AND s.pos = '1'AND tl.pos = '1' AND tl.terms_id = 'spring')
GROUP BY status, censusDate, ssn;
This looks ugly, but doesn't actually have a horrible performance impact.
select groupcol, sum(cred)
from table1
group by rollup(groupcol)