SQL correlated query + department average - sql

I am new on this, so a little about my aspirations :
I am technical engineer, and wanting to improve my SQL skills, and get to advanced/master level for the purpose of going down the business analyst route, leveraging data analysis to give me an advantage in the role.
I have just about go my head around correlated sub queries, ableit confusing termonology, and 9/10 being able to mentally remember what to write(not without trial and error), anyway my problem/question is as below:
I am using the adventure works DB and am using the below script to return workers who have a rate above the avg for their department
select Concat(v_dep.FirstName, ' ', v_dep.LastName) as Full_name, eph.Rate
from HumanResources.vEmployeeDepartment v_dep
join HumanResources.EmployeePayHistory eph
on v_dep.BusinessEntityID = eph.BusinessEntityID
where eph.Rate > (select AVG(eph2.rate)
from HumanResources.vEmployeeDepartment v_dep2
join HumanResources.EmployeePayHistory eph2
on v_dep2.BusinessEntityID = eph2.BusinessEntityID
where v_dep2.Department = v_dep.Department);
However, what I want to do for comparative purposes is have an additional column next to their rate, showing me the average rate for the department they belong to. I have been trolling through the web trying to construct a concise question to encapsulate what I have described but have been struggling.
Any advice would be great !

It is time you learned about window functions:
select x.*
from (select Concat(v_dep.FirstName, ' ', v_dep.LastName) as Full_name,
eph.rate,
avg(eph.rate) over (partition by v_dep.BusinessEntityID) as avg_rate
from HumanResources.vEmployeeDepartment v_dep join
HumanResources.EmployeePayHistory eph
on v_dep.BusinessEntityID = eph.BusinessEntityID
) x
where rate > avg_rate;
Note: I'm not intimately familiar with the database. However, a table with the name EmployeePayHistory sounds like it has multiple rows per employee. If so, you probably want to filter based on the time or the "current" rate.

Related

How to count the number of bank accounts that belong to one user in Oracle SQL

I've got a few questions regarding the use of GROUP BY in SQL,
How do you count the number of open bank accounts that belong to one specific user?
I tried to write the most correct sentence possible so that the SQL could count all the associated accounts of each user, instead the result is somewhat easy? It just shows 1s in all the queries...
SELECT DISTINCT RPAD(CLI.NOMBRE || ' ' ||CLI.APELLIDOS,30) "Nombre y Apellidos",
SUM(CUE.SALDO) "Saldo", COUNT(CUE.COD_CUENTA) "Cuentas Abiertas"
from CLIENTE CLI,
CUENTA CUE
WHERE CLI.COD_CLIENTE = CUE.COD_CLIENTE
GROUP BY CLI.NOMBRE, CLI.APELLIDOS, CUE.SALDO, CUE.COD_CUENTA
In my case, I tried looking for users with name and surnames and also count the accounts that users have opened, instead, the query repeats names and the "Counter" shows 1s as result
[Result][1] ;
[The E/R Diagram][2]
Thanks in advance!!!
[1]: https://i.stack.imgur.com/AQDac.png
[2]: https://i.stack.imgur.com/jlVte.png
You are grouping by SALDO and COD_CUENTA and that prevents traditional aggregation counts to show the result you want.
However, you can use a window function: you need to tell the COUNT() function about the scope it needs to operate adding OVER(PARTITION BY cli.cod_cliente).
For example:
select distinct
rpad(cli.nombre || ' ' || cli.apellidos, 30) as "nombre y apellidos",
sum(cue.saldo) as "saldo",
count(cue.cod_cuenta) over(partition by cli.cod_cliente) as "cuentas abiertas"
from cliente cli
join cuenta cue on cli.cod_cliente = cue.cod_cliente
group by cli.nombre, cli.apellidos, cue.saldo, cue.cod_cuenta
NOTE: Please use modern join syntax, not those joins from the 80s. I updated your query.

Need help finding only Employees and Sales Persons in SQL

I am trying to run an SQL query which would fetch me all people who are
1. Only employees,
2. Employees and a sales person and
3. Only sales persons.
I am working on the Oracle E-Business Suite. So far, my query returns only those people who are employees only and those people who are employees and also a sales person. Here is what I've managed so far:
select distinct PAF.LAST_NAME,
PAF.START_DATE "HIRE_DATE",
PAF.EMPLOYEE_NUMBER,
PPT.SYSTEM_PERSON_TYPE "PERSON_TYPE",
JRS.SALES_CREDIT_TYPE_ID,
JRS.SALESREP_NUMBER
from PER_ALL_PEOPLE_F PAF,
PER_PERSON_TYPES PPT,
PER_PERSON_TYPE_USAGES_F PPTU,
JTF_RS_DEFRESOURCES_VL JRDV,
JTF_RS_SALESREPS JRS
where PAF.PERSON_ID = PPTU.PERSON_ID
and PPTU.PERSON_TYPE_ID = PPT.PERSON_TYPE_ID
and PPT.SYSTEM_PERSON_TYPE in ('EMP','OTHER')
and JRDV.category in ('EMPLOYEE','OTHER')
and (JRS.SALESREP_NUMBER(+) = PAF.EMPLOYEE_NUMBER)
and sysdate between PAF.EFFECTIVE_START_DATE and PAF.EFFECTIVE_END_DATE;
This is what I want to achieve
I have to include those people who are ONLY salespersons. Basically, there should be some rows which have no Employee_Number but only SALESREP_NUMBER. What am I doing wrong?
Pretty sure your issue lies here:
AND PAF.EMPLOYEE_NUMBER = JRS.SALESREP_NUMBER
Like the comment above says, you don't give much info. But, an educated guess would be that the equivalence indicated above would make that person an employee AND a salesperson. Maybe something more like:
AND (
PAF.EMPLOYEE_NUMBER = JRS.SALESREP_NUMBER
OR
( PAF.EMPLOYEE_NUMBER AND JRS.SALESREP_NUMBER IS NULL)
OR
( PAF.EMPLOYEE_NUMBER IS NULL AND JRS.SALESREP_NUMBER)
)
Or maybe just delete that clause?

How to sum employee paychecks from multiple jobs into one table

table featuring fictitious employee data, area ( in hectares (ha)) cleared (noted in french as superficie), rate for 1 hectare of cleared land on the specific lot (french:taux) and amount due( expr1) for that lot.
My problem here is that I want the total amount due for each Worker, not the amount due for each worker for each lot. Totals for Sirs Alain, Jacques, Paul, Roger and Tanguay should normally be 4066, 4082 , 5638, 5811 and 3131 , respectively.
My code so far is this
SELECT tbl_Employés.Num_deb, tbl_Employés.Prénom, tbl_Employés.Nom, tbl_Employés.Age, tbl_Employés.DEP, tbl_Employés.Expérience, tbl_Employés.Adresse, Tbl_terrain.superficie, Tbl_terrain.Taux, [superficie]*[taux] AS [Montant à payer]
FROM tbl_Employés INNER JOIN Tbl_terrain ON tbl_Employés.Num_deb = Tbl_terrain.Num_deb
ORDER BY tbl_Employés.Nom;
I have so far tried to use GROUP BY Numéro_terrain, which returns an error that my query does not include the specified expression ''Num_deb'' as a part of an aggregation fuction
I woul greatly appreciate any imput. I am very sorry if you have a hard time understanding some words, as I am doing my best to translate everything from french.
One method is to include all the columns in the GROUP BY that are not arguments to aggregation functions:
SELECT e.Num_deb, e.Prénom, e.Nom, e.Age, e.DEP, e.Expérience, e.Adresse,
SUM([superficie]*[taux]) AS [Montant à payer]
FROM tbl_Employés as e INNER JOIN
Tbl_terrain as t
ON e.Num_deb = t.Num_deb
GROUP BY e.Num_deb, e.Prénom, e.Nom, e.Age, e.DEP, e.Expérience, e.Adresse
ORDER BY e.Nom;

Cannot perform an aggregate function on an expression containing an aggregate or a subquery

This query works fine before I try to grab a count of how many Payments (trans_key) there are against each account (ACCT).
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
SELECT CONVERT(VARCHAR,D.ACCT) ClientReference,
ISNULL((SELECT S.SD_DATA
FROM SDDATA AS S
WHERE S.DEBT_KEY=D.DEBT_KEY AND S.SD_KEY=36),' ') Division,
REPLACE(RTRIM(DB.FNAME)+' '+RTRIM(DB.LNAME),',',' ') AccountHolder,
CONVERT(VARCHAR,D.DOR,101) ListedDate,
ISNULL((SELECT SA.SD_DATA
FROM SDDATA AS SA
WHERE SA.DEBT_KEY=D.DEBT_KEY
AND SA.SD_KEY=2),' ') AssignmentType,
CONVERT(VARCHAR,D.BAL) CurrentBalance,
CONVERT(VARCHAR,D.ASSIGNED) PlacementBalance,
CONVERT(VARCHAR,isnull((SELECT -1*SUM(TD.DIST_AMT)
FROM TRANSDIST AS TD,TRANS AS T,TRANS_CODE AS TC
WHERE T.DEBT_KEY=D.DEBT_KEY
AND TD.TRANS_KEY=T.TRANS_KEY
AND TC.TRANS_CODE=T.TRANS_CODE
AND TC.TRANS_BILL=1),0)) AmountRecovered,
CONVERT(VARCHAR,isnull((SELECT (-1*SUM(TD.DIST_AMT)+SUM(DIST_NET))
FROM TRANSDIST AS TD,TRANS AS T,TRANS_CODE AS TC
WHERE T.DEBT_KEY=D.DEBT_KEY
AND TD.TRANS_KEY=T.TRANS_KEY
AND TC.TRANS_CODE=T.TRANS_CODE
AND TC.TRANS_BILL=1),0)) AmountApplied,
CONVERT(VARCHAR,isnull((SELECT (-1*SUM(DIST_NET))
FROM TRANSDIST AS TD,TRANS AS T,TRANS_CODE AS TC
WHERE T.DEBT_KEY=D.DEBT_KEY
AND TD.TRANS_KEY=T.TRANS_KEY
AND TC.TRANS_CODE=T.TRANS_CODE
AND TC.TRANS_BILL=1),0)) SMAFees,
COUNT((select trans_key from trans)) as Payments
FROM DEBT AS D,DEBTOR AS DB,STATUS AS ST
WHERE ST.STAT_KEY=D.STAT_KEY
AND DB.DEBTOR_KEY=D.DEBTOR_KEY
AND D.CLIENT_KEY =43
AND ST.CATEGORY='A'
When messing around with the count aggregate function I was able to grab soem data, but not the correct count. It was just giving me the count of all 'Payments' inside the table not the amount for each ACCT.
I feel like your query is written in a very strange way that will make it difficult to debug. A few thoughts:
Stop Converting everything you are selecting (at least until you get
the results you want). This will make your code more concise and
easier to debug
If possible, stop with the subqueries in your select statement. For example:
ISNULL((SELECT SA.SD_DATA
FROM SDDATA AS SA
WHERE SA.DEBT_KEY=D.DEBT_KEY
AND SA.SD_KEY=2),' ') AssignmentType,
can be converted to
ISNULL(SA.SD_DATA,' ')
...
...
FROM DEBT AS D
LEFT JOIN SDDATA AS SA
ON sa.DEBT_KEY = d.DEBT_KEY
Stop using implied joins
Read up on how GROUP BY works with aggregate functions.
This isn't a solution for your exact problem, but you need to get this query into a workable state and do some basic research if you want to resolve this issue.

Complicated Calculation Using Oracle SQL

I have created a database for an imaginary solicitors, my last query to complete is driving me insane. I need to work out the total a solicitor has made in their career with the company, I have time_spent and rate to multiply and special rate to add. (special rate is a one off charge for corporate contracts so not many cases have them). the best I could come up with is the code below. It does what I want but only displays the solicitors working on a case with a special rate applied to it.
I essentially want it to display the result of the query in a table even if the special rate is NULL.
I have ordered the table to show the highest amount first so i can use ROWNUM to only show the top 10% earners.
CREATE VIEW rich_solicitors AS
SELECT notes.time_spent * rate.rate_amnt + special_rate.s_rate_amnt AS solicitor_made,
notes.case_id
FROM notes,
rate,
solicitor_rate,
solicitor,
case,
contract,
special_rate
WHERE notes.solicitor_id = solicitor.solicitor_id
AND solicitor.solicitor_id = solicitor_rate.solicitor_id
AND solicitor_rate.rate_id = rate.rate_id
AND notes.case_id = case.case_id
AND case.contract_id = contract.contract_id
AND contract.contract_id = special_rate.contract_id
ORDER BY -solicitor_made;
Query:
SELECT *
FROM rich_solicitors
WHERE ROWNUM <= (SELECT COUNT(*)/10
FROM rich_solicitors)
I'm suspicious of your use of ROWNUM in your example query...
Oracle9i+ supports analytic functions, like ROW_NUMBER and NTILE, to make queries like your example easier. Analytics are also ANSI, so the syntax is consistent when implemented (IE: Not on MySQL or SQLite). I re-wrote your query as:
SELECT x.*
FROM (SELECT n.time_spent * r.rate_amnt + COALESCE(spr.s_rate_amnt, 0) AS solicitor_made,
n.case_id,
NTILE(10) OVER (ORDER BY solicitor_made) AS rank
FROM NOTES n
JOIN SOLICITOR s ON s.solicitor_id = n.solicitor_id
JOIN SOLICITOR_RATE sr ON sr.solicitor_id = s.solicitor_id
JOIN RATE r ON r.rate_id = sr.rate_id
JOIN CASE c ON c.case_id = n.case_id
JOIN CONTRACT cntrct ON cntrct.contract_id = c.contract_id
LEFT JOIN SPECIAL_RATE spr ON spr.contract_id = cntrct.contract_id) x
WHERE x.rank = 1
If you're new to SQL, I recommend using ANSI-92 syntax. Your example uses ANSI-89, which doesn't support OUTER JOINs and is considered deprecated. I used a LEFT OUTER JOIN against the SPECIAL_RATE table because not all jobs are likely to have a special rate attached to them.
It's also not recommended to include an ORDER BY in views, because views encapsulate the query -- no one will know what the default ordering is, and will likely include their own (waste of resources potentially).
you need to left join in the special rate.
If I recall the oracle syntax is like:
AND contract.contract_id = special_rate.contract_id (+)
but now special_rate.* can be null so:
+ special_rate.s_rate_amnt
will need to be:
+ coalesce(special_rate.s_rate_amnt,0)