Select on same table without subselect - sql

I have payment, period and event tables. For each employee, month and year, I want to return payment.value (SALARY) and payment.value (ADDITIONAL, like a bonus) on same row, depending of event number. The event number 10015 represent the ADDITIONAL, and event number 4986 represent the SALARY.
I was able to reach my goal:
SELECT payment.employee_id EMPLOYEE_ID, payment.value SALARY,
(SELECT payment.value ADDITIONAL FROM payment
INNER JOIN period ON payment.period_id = period.id
INNER JOIN event ON payment.event_id = event.id
WHERE period.month = 7
AND period.year = 2021
AND payment.employee_id = 71
AND event.number = 10015
) ADDITIONAL
FROM payment
INNER JOIN period ON payment.period_id = period.id
INNER JOIN event ON payment.event_id = event.id
WHERE period.month = 7
AND period.year = 2021
AND payment.employee_id = 71
AND event.number = 4986
Result:
But now I'm trying to refactor my query so I don't have nested SELECTS. How can I do that?

You can use aggregation:
SELECT p.employee_id,
SUM(CASE WHEN e.number = 4986 THEN p.value END) as SALARY,
SUM(CASE WHEN e.number = 10015 THEN p.value END) as ADDITIONAL
FROM payment p JOIN
period pe
ON p.period_id = pe.id JOIN
event e
ON p.event_id = e.id
WHERE pe.month = 7 AND
pe.year = 2021
p.employee_id = 71 AND
e.number IN (4986, 10015)
GROUP BY p.employee_id;
Note: This is not 100% equivalent to you query, but I think it is what you want to do. This returns one row with salary and additional on one row. If there are multiple rows for the employee's salary in the period, then this returns one row whereas yours would return each row separately.

Related

How to calculate SQL percentages based on join of two tables, with only one row showing for each assignment

How do I calculate the percentages of those who successfully subscribed? If someone with uID (1 for example) has Not Yet, but then Sub then this is a 100% conversion.
I want to calculate the percentages of each assignmentID group. There can be multiple users in each assignmentID group.
My Query:
SELECT assignmentID,
(SELECT count(assignment)
FROM group JOIN Subscribed ON group.uID = Subscribed.uID
WHERE assignment ='test' and status ='Sub') /
(SELECT count(assignment) FROM group JOIN Subscribed ON group.uID = Subscribed.uID) testconversion,
(SELECT count(assignment)
FROM group JOIN Subscribed ON group.uID = Subscribed.uID
WHERE assignment ='control' and status ='Sub') /
(SELECT count(assignment) FROM group JOIN Subscribed ON group.uID = Subscribed.uID) controlconversion
FROM group JOIN Subscribed ON group.uID = Subscribed.uID
GROUP BY assignmentID
Subscribed
uID Status
1 Not Yet
1 Sub
3 Not Yet
4 Not Yet
5 Sub
Group
uID Assignment AssignmentID
1 test 1
2 test 2
1 control 1
4 test 2
5 test 1
Expected Output:
AssignmentID testconversion controlconversion
1 100% 0%
2 50% null
This looks like a join and aggregation:
select g.assignmentid,
(countif(g.assigned = 'test' and s.status = 'sub') /
nullif(countif(g.assigned = 'test'), 0)
) as test_conversion,
(countif(g.assigned = 'control' and s.status = 'sub') /
nullif(countif(g.assigned = 'control'), 0)
) as control_conversion,
from subscribers s join
grouped g
using (uid)
group by g.assignmentid

altering query in db2 to fix count from a join

I'm getting an aggregated count of records for orders and I'm getting the expected count on this basic query:
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)||'-'||substr(g.EXTD1d,7,2) ) between current_Date - 180 DAY AND current_Date
But as soon as I add back in my joins and joined values then my count goes from 1 (which it should be) to over 200. All I need from these joins is the customer ID and the manager number. so even if my count is high, I'm basically just trying to say "for this cstnoc, give me the slsupr and xlsno"
How can I perform this below query without affecting the count? I only want my count (sales_180 and velocity) coming from the custgroup table based on my where clause, but I then just want one value of the xcstno and xslsno based on the cstnoc.
SELECT
count(*) as sales_180,
180/count(*) as velocity,
c.xslsno as CustID,
cr.slsupr as Manager
FROM custgroup g
inner join customers c
on g.cstnoc = c.xcstno
inner join managers cr
on c.xslsno = cr.xslsno
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)||'-'||substr(g.EXTD1d,7,2) ) between current_Date - 180 DAY AND current_Date
GROUP BY c.xslsno, cr.slsupr
You are producing multiple rows when joining, so your count is now counting all the resulting rows with all that [unintended] multiplicity.
The solution? Use a table expression to pre-compute your count, and then you can join it to the other tables, as in:
select
g2.sales_180,
g2.velocity,
c.xslsno as CustID,
cr.slsupr as Manager
from customers c
join managers cr on c.xslsno = cr.xslsno
join ( -- here the Table Expression starts
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)
||'-'||substr(g.EXTD1d,7,2) )
between current_Date - 180 DAY AND current_Date
) g2 on g2.cstnoc = c.xcstno
You can also use a Common Table Expression (CTE) that will produce the same result:
with g2 as (
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)
||'-'||substr(g.EXTD1d,7,2) )
between current_Date - 180 DAY AND current_Date
)
select
g2.sales_180,
g2.velocity,
c.xslsno as CustID,
cr.slsupr as Manager
from customers c
join managers cr on c.xslsno = cr.xslsno
join g2 on g2.cstnoc = c.xcstno

How to sum a count of bookings to display total bookings for location and total value for location

I am writing a report that needs to show the number of bookings taken for a location with the total value of those bookings.
How do I sum the bookings column and show only one row for the location, that includes the columns set out in the example of expected data?
Select Statement Below:
SELECT
Locations.Description as LocationsDesc,
Locations.LocationGUID,
Venues.VenueName,
Venues.VenueGUID,
count (Bookings.BookingID) as Bookings,
Departments.DepartmentName,
Departments.DepartmentGUID,
sum(SalesTransactionDetails.NetDetailValue) as NetDetailValue,
sum(SalesTransactionDetails.DetailValue) as DetailValue,
SUM(CASE When Salestransactionlines.itemtype = 1 Then SalesTransactionDetails.NetDetailValue Else 0 End ) as RentalFee,
SUM(CASE When Salestransactionlines.itemtype = 2 Then SalesTransactionDetails.NetDetailValue Else 0 End ) as ExtraFee,
SalesTransactions.SalesTransactionGUID
FROM BookingLinesDetails
INNER JOIN Bookings ON BookingLinesDetails.BookingGUID=Bookings.BookingGUID
INNER JOIN Locations ON BookingLinesDetails.LocationGUID=Locations.LocationGUID
INNER JOIN Venues on Venues.Venueguid = Locations.Venueguid
INNER JOIN SalesTransactionDetails ON BookingLinesDetails.BookingLinesDetailGUID=SalesTransactionDetails.BookingLinesDetailGUID
INNER JOIN SalesTransactionLines ON SalesTransactionDetails.SalesTransactionLineGUID=SalesTransactionLines.SalesTransactionLineGUID
INNER JOIN SalesTransactions ON SalesTransactionLines.SalesTransactionGUID=SalesTransactions.SalesTransactionGUID
INNER JOIN Departments on Departments.DepartmentGUID = Locations.DepartmentGUID
WHERE
BookingLinesDetails.StartDateTime >= dbo.InzDateOnly(#pFromDate) and
BookingLinesDetails.StartDateTime < DateAdd(day,1,dbo.inzDateOnly(#pToDate)) and
Departments.DepartmentGUID in (Select GUID from dbo.InzSplitGUID(#DepartmentID)) and
(#IncludeAllLocationGroupsInVenues <> 0 or (#IncludeAllLocationGroupsInVenues = 0 )) and
Venues.VenueGUID in (Select GUID from dbo.InzSplitGUID(#VenueID)) and
salesTransactions.Status = 1 and -- remove cancelled
salestransactions.receiptonly = 0
GROUP BY
Locations.Description,
Locations.LocationGUID,
Venues.VenueName,
Venues.VenueGUID,
Departments.DepartmentName,
Departments.DepartmentGUID,
SalesTransactions.SalesTransactionGUID
The output is currently:
Desired output is:
LocationsDesc LocationGUID VenueGUID Bookings DepartmentName NetDetailValue DetailValue ExtraFee
Location - Deck Room 348A43F12 7DAD77BE 33 Aquatics Centre 2059.46 2162.5 0
I have attempted several versions of Count and sum. I believe I need to make the query a derived table and then select from that, but am not sure how to go about it, even if that is the answer.
Thank you in advance.

Return a Count of 0 When No Rows

OK, I've looked this up and tried a number of solutions, but can't get it to work. I'm a bit of a novice. Here's my original query - how can I get it to return 0 for an account when there are no results in the student table?
SELECT a.NAME
,count(s.student_sid)
FROM account a
JOIN inst i ON a.inst_sid = i.root_inst_sid
JOIN inst_year iy ON i.inst_sid = iy.inst_sid
JOIN student s ON iy.inst_year_sid = s.inst_year_sid
WHERE s.demo = 0
AND s.STATE = 1
AND i.STATE = 1
AND iy.year_sid = 16
AND a.account_sid IN (
20187987
,20188576
,20188755
,52317128
,20189249
)
GROUP BY a.NAME;
Use an outer join, moving the condition on that table into the join:
select a.name, count(s.student_sid)
from account a
join inst i on a.inst_sid = i.root_inst_sid
join inst_year iy on i.inst_sid = iy.inst_sid
left join student s on iy.inst_year_sid = s.inst_year_sid
and s.demo = 0
and s.state = 1
where i.state = 1
and iy.year_sid = 16
and a.account_sid in (20187987, 20188576, 20188755, 52317128, 20189249)
group by a.name;
count() does not count null values, which s.student_sid will be if no rows join from student.
You need to LEFT JOIN and then SUM() over the group where s.student_sid is not null:
select
a.name,
sum(case when s.student_sid is null then 0 else 1 end) as student_count
from account a
join inst i on a.inst_sid = i.root_inst_sid
join inst_year iy on i.inst_sid = iy.inst_sid
left join student s
on iy.inst_year_sid = s.inst_year_sid
and s.demo = 0
and s.state = 1
where i.state = 1
and iy.year_sid = 16
and a.account_sid in (20187987, 20188576, 20188755, 52317128, 20189249)
group by a.name;
This is assuming that all of the fields in the student table that you are filtering on are optional. If you don't want to enforce removal of records where, say, s.state does not equal 1, then you need to move the s.state=1 predicate into the WHERE clauses.
If, for some reason, you are getting duplicate student IDs and students are being counted twice, then you can change the aggregate function to this:
count(distinct s.student_id) as student_count
...which is safe to do as count(distinct ...) ignores null values.

How can I return 3 COUNT columns from the same table when JOIN with other tables as well?

I would like to know how I can return several COUNTs for the following:
I have 27 work sites, each site has an employee with a contract, I have wrote a script to return one actual column of data (Work sites, 27 in total), I then added a COUNT to count how many contracts/staff I have in each site. I have had to use 3 tables to get the data.
what I would like to do now is add two more columns, one that shows how many contracts I have "Under 35 hours" and one that shows how many I have "Over 35 hours"
This is what I have to return site names and total contracted hours:
SELECT
LOCATION.LocationName,
COUNT (EB_MINMAX_VIEW.UnitQuan) AS 'Total Contracts'
FROM
LOCATION
JOIN
eb_view on eb_view.locationcounter = location.locationcounter
JOIN
EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
GROUP BY
LOCATION.LocationName
Then if i want to return contacts under 35 hours then i have to write this:
SELECT
LOCATION.LocationName,
COUNT (EB_MINMAX_VIEW.UnitQuan) AS 'Total Contracts'
FROM
LOCATION
JOIN
eb_view on eb_view.locationcounter = location.locationcounter
JOIN
EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
WHERE
UnitQuan < 35
GROUP BY
LOCATION.LocationName
and this will give me the number of contracts less than 35 for all sites, but I want to include this in the final table i.e. site name, number of total contracts per site, number of contrast < 35 for all sites, and a column for number of contracts > 35 for each site.
Maybe this helps (I didn't test it):
SELECT s1.LocationName,
s1.TotalContracts AS cntAll,
s2.TotalContracts AS cntLess35,
s3.TotalContracts AS cntGreater35
FROM(SELECT LOCATION.LocationName,
COUNT(EB_MINMAX_VIEW.UnitQuan) TotalContracts
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
GROUP
BY LOCATION.LocationName
) s1
LEFT
JOIN(SELECT LOCATION.LocationName,
COUNT(EB_MINMAX_VIEW.UnitQuan) TotalContracts
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
WHERE UnitQuan < 35
GROUP
BY LOCATION.LocationName
) s2
ON s1.LocationName = s2.LocationName
LEFT
JOIN(SELECT LOCATION.LocationName,
COUNT(EB_MINMAX_VIEW.UnitQuan) TotalContracts
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
WHERE UnitQuan > 35
GROUP
BY LOCATION.LocationName
) s3
ON s1.LocationName = s3.LocationName
Another alternative:
SELECT LOCATION.LocationName,
SUM(all),
SUM(less35),
SUM(greater35)
FROM(
SELECT LOCATION.LocationName,
1 AS all,
CASE WHEN UnitQuan < 35 THEN 1 ELSE 0 END less35,
CASE WHEN UnitQuan > 35 THEN 1 ELSE 0 END greater35
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
)
GROUP BY LOCATION.LocationName
This is a simpler form of DirkNM's query:
SELECT l.LocationName,
count(*) AS all,
SUM(CASE WHEN ebmm.UnitQuan < 35 THEN 1 ELSE 0 END) as less35,
SUM(CASE WHEN ebmm.UnitQuan > 35 THEN 1 ELSE 0 END) greater35
FROM LOCATION JOIN
eb_view eb
ON eb.locationcounter = l.locationcounter JOIN
EB_MINMAX_VIEW ebmm
on ebmm.ebcounter = eb.ebcounter
GROUP BY l.LocationName