Query:
SELECT department_id "Dept", hire_date "Date", last_name "Name",
LISTAGG(last_name, '; ') WITHIN GROUP (ORDER BY hire_date, last_name)
OVER (PARTITION BY department_id) as "Emp_list"
FROM employees
WHERE hire_date < '01-SEP-2003'
ORDER BY "Dept", "Date", "Name";
Dept Date Name Emp_list
----- --------- --------------- --------------------------------
30 07-DEC-02 Raphaely Raphaely; Khoo
30 18-MAY-03 Khoo Raphaely; Khoo
40 07-JUN-02 Mavris Mavris
50 01-MAY-03 Kaufling Kaufling; Ladwig
50 14-JUL-03 Ladwig Kaufling; Ladwig
70 07-JUN-02 Baer Baer
90 13-JAN-01 De Haan De Haan; King
90 17-JUN-03 King De Haan; King
100 16-AUG-02 Faviet Faviet; Greenberg
100 17-AUG-02 Greenberg Faviet; Greenberg
110 07-JUN-02 Gietz Gietz; Higgins
110 07-JUN-02 Higgins Gietz; Higgins
Doubt:
What is significance of Partition over here?
Here is a short answer. You should read the documentation on analytic functions, though.
The function list_agg() -- as with many Oracle functions -- can be either an aggregation function or an analytic function. I assume you know what an aggregation function is. The group by clause puts all rows with the same value of the aggregation variables and summarizes them into a single row.
Analytic functions are similar, but different of course. They also find groups of rows that are similar to each other. However, they don't summarize them on one row. They simply add a new column with that information. The partition by clause specifies how the groups are created.
So, this query is combining all the last_names in rows with the same department_id. The last_names are ordered by hiring date and then the name. This list is then attached to every line in the same table. You can compare your query to a similar aggregation query:
SELECT department_id "Dept",
LISTAGG(last_name, '; ') WITHIN GROUP (ORDER BY hire_date, last_name)
FROM employees
WHERE hire_date < '01-SEP-2003'
GROUP BY department_id;
Related
I need to create a report that displays information about what part of the salary in percentage terms each employee receives within the department in which they work, using analytical functions.
SELECT EMPLOYEES.FIRST_NAME, EMPLOYEES.LAST_NAME, EMPLOYEES.DEPARTMENT_ID,
DEPARTMENTS.DEPARTMENT_NAME, EMPLOYEES.SALARY,
(SALARY/SUM(SALARY)) * 100 over (partition by DEPARTMENT_ID) AS "PercentWithinDepartment"
FROM HR.EMPLOYEES
FULL JOIN HR.DEPARTMENTS ON EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID
I get an "ORA-00923 FROM keyword not found where expected" error but I think it's not my only mistake within this task.
I cannot provide a code snippet of database but this can be run against the HR sample schema.
My request is to help me figure out mistake to complete this task properly.
The immediate issue is that your over clause is in the wrong place:
(SALARY/SUM(SALARY)) * 100 over (partition by DEPARTMENT_ID)
should be
(SALARY/SUM(SALARY) over (partition by DEPARTMENT_ID)) * 100
But the reference to DEPARTMENT_ID in there is ambiguous as that column is in both tables, so it should be:
(SALARY/SUM(EMPLOYEES.SALARY) over (partition by DEPARTMENTS.DEPARTMENT_ID)) * 100
You might want to consider using table aliases to make the code a bit shorter.
You may also want to round (or truncate/floor) the percentages to, say, one or two decimal places.
I'm not sure you really want a full outer join though - that will include all departments with no employees (e.g. 'Recruiting'). A left outer join looks more appropriate - so 'Kimberley Grant', who is not linked to any department, will still be included.
I have to calculate percent about what part of the salary employee get within all organization and im confused why do i get "not a single-group group function" error here
Because the version you added as a comment has:
SALARY/SUM(EMPLOYEES.SALARY)*100
which is the aggregate form of the function, not the analytic form. As you don't want to aggregate here, you still need an over clause to make it analytical, but that can be empty:
SALARY/SUM(EMPLOYEES.SALARY) over () * 100
And you can of course do both together:
select employees.first_name,
employees.last_name,
employees.department_id,
departments.department_name,
employees.salary,
salary / sum(employees.salary) over (partition by departments.department_id) * 100 as "PercentWithinDepartment",
salary / sum(employees.salary) over () * 100 as "PercentWithinOrganization"
from hr.employees
left join hr.departments on employees.department_id = departments.department_id;
FIRST_NAME LAST_NAME DEPARTMENT_ID DEPARTMENT_NAME SALARY PercentWithinDepartment PercentWithinOrganization
-------------------- ------------------------- ------------- ------------------------------ ---------- ----------------------- -------------------------
Jennifer Whalen 10 Administration 4400 100 .636389933
Michael Hartstein 20 Marketing 13000 68.4210526 1.88024299
Pat Fay 20 Marketing 6000 31.5789474 .867804455
...
Kimberely Grant 7000 100 1.01243853
You might also want to add an order by clause...
I don't have HR schema (but I have Scott) so - here's how. I'm using SQL*Plus' formatting capabilities to make it look prettier.
SQL> set numformat 999g990d00
SQL> break on deptno on dname
SQL> compute sum of pct_sal on deptno
SQL>
SQL> select e.deptno, d.dname, e.ename, e.sal,
2 sum(e.sal) over (partition by e.deptno) dept_sal,
3 --
4 round((e.sal / sum(e.sal) over (partition by e.deptno)) * 100, 2) pct_sal
5 from emp e join dept d on d.deptno = e.deptno
6 order by e.deptno, e.ename;
Result:
DEPTNO DNAME ENAME SAL DEPT_SAL PCT_SAL
----------- -------------- ---------- ----------- ----------- -----------
10,00 ACCOUNTING CLARK 2.450,00 8.750,00 28,00
KING 5.000,00 8.750,00 57,14
MILLER 1.300,00 8.750,00 14,86
*********** ************** -----------
sum 100,00
20,00 RESEARCH ADAMS 1.100,00 10.915,00 10,08
FORD 3.000,00 10.915,00 27,49
JONES 2.975,00 10.915,00 27,26
SCOTT 3.000,00 10.915,00 27,49
SMITH 840,00 10.915,00 7,70
*********** ************** -----------
sum 100,02
30,00 SALES ALLEN 1.600,00 9.400,00 17,02
BLAKE 2.850,00 9.400,00 30,32
JAMES 950,00 9.400,00 10,11
MARTIN 1.250,00 9.400,00 13,30
TURNER 1.500,00 9.400,00 15,96
WARD 1.250,00 9.400,00 13,30
*********** ************** -----------
sum 100,01
14 rows selected.
SQL>
(Sum isn't exactly 100% because of rounding.)
I might be mistaking, but it seems to me that all you need is to use ratio_to_report analytic function. Try this one please
SELECT E.FIRST_NAME, E.LAST_NAME, E.DEPARTMENT_ID,
D.DEPARTMENT_NAME, E.SALARY,
100 * ratio_to_report(e.salary) over (partition by d.department_id)
FROM HR.EMPLOYEES e
JOIN hr.departments d
on e.department_id = d.department_id
order by d.department_id;
I have several tables that relate to employees and the total amount of sales in dollars and I have to follow these instructions right here:
The company wants to see the total sale amount per sales person (salesman) for all orders. Assume that some online orders do not have any sales person involved. Display the salesman full name and the total sale amount for that employee.
Include also the Total Sale amount for all Orders without involvement of a salesman.
Exclude sales persons whose first name end on y (like Lily or Daisy).
Include only those sales persons who were responsible for more than 4 million dollars in sale.
Sort the result so that bigger sales are shown first.
Note; Do NOT use LIKE operator.
I already have a query statement written in which it shows the employee id instead of the employee name which is what is needed for this query question to be answered properly. In this case the salesman id "," are sales people without names so for ex. sales over the internet therefore no specific name. How do I make it so that I keep the "," but instead show the names of the employee and not their ID Here is the statement:
SELECT NVL(to_char(o.salesman_id), ',') AS "Emp Name", RPAD(to_char(SUM(i.quantity * i.unit_price),'$999,999,999.99'), 16, ' ') AS "Total Sale"
FROM (orders o
INNER JOIN order_items i ON i.order_id = o.order_id)
GROUP BY o.salesman_id
HAVING SUM(i.quantity * i.unit_price) > 4000000.00
ORDER BY 2 DESC;
Heres the output of the query:
Looks like outer join to me.
I don't have your tables so I'll use Scott's emp to illustrate it.
SQL> select empno, ename from emp where deptno = 10;
EMPNO ENAME
---------- ----------
7782 CLARK
7839 KING
7934 MILLER
I'll use those EMPNO values instead of your 62 and 64 (as displayed on the screenshot). your_query CTE represents output you currently have. Then outer join it with table that contains employee names (emp in my case), but leave , for unknown employees:
SQL> with
2 your_query (emp_name, total_sale) as
3 (select ',' , 100 from dual union all
4 select '7782', 200 from dual union all
5 select '7839', 300 from dual
6 )
7 select nvl(e.ename, q.emp_name) ename,
8 q.total_sale
9 from your_query q left join emp e on q.emp_name = to_char(e.empno);
ENAME TOTAL_SALE
---------- ----------
CLARK 200
KING 300
, 100
SQL>
I have two tables - one called "customer_data" and one called "weighted_average_rate_charged".
Customer_data
First Name
Last Name
City
Daily_Rate
Total_paid
Jim
Carter
Dallas
$100.00
$600.00
James
Franklin
Houston
$150.00
$900.00
Lily
Smith
Dallas
$90.00
$540.00
Frank
Masters
Dallas
$95.00
$680.00
Jennifer
Brown
Houston
$100.00
$590.00
Weighted_Average_Rate_Charged
I have used the following query:
select
City,
sum(Daily_Rate * Total_Paid)/sum(Total_Paid) as WA_Rate
from customer_data
group by
City
To get my weighted average per city. Now, what i want to do is take the max number, which according to the output below is 130.2013:
City
WA_Rate
Houston
$130.2013
Dallas
$95.1648
And essentially create a new table which is the same as customer_data but with an additional column called "Max_WA_daily" which shows $130.2013 for all entries in that table.
Example:
First Name
Last Name
City
Daily_Rate
Total_paid
Max_WA_daily
Jim
Carter
Dallas
$100.00
$600.00
$130.2013
James
Franklin
Houston
$150.00
$900.00
$130.2013
Lily
Smith
Dallas
$90.00
$540.00
$130.2013
Frank
Masters
Dallas
$95.00
$680.00
$130.2013
Jennifer
Brown
Houston
$100.00
$590.00
$130.2013
I have tried a range of different join options and nested select statements, but cant get this to work. Any ideas please?
We could use analytic functions here, with a two tier aggregation approach:
WITH cte AS (
SUM(Daily_Rate * Total_Paid) OVER (PARTITION BY City) /
SUM(Total_Paid) OVER (PARTITION BY City) AS WA_Rate_City
FROM customer_data
)
SELECT First, Last, City, Daily_Rate, Total_Paid,
MAX(WA_Rate_City) OVER () AS Max_WA_daily
FROM cte;
The first CTE turns out the WA rates partitioned by each city. The query below that finds the max such value, but across the entire table.
Windows functions can be used here. It allows us to have aggregations on different levels within one view [table] which you need.
Additionally, I would suggest not to use Subquery and use Common Table Expressions (CTE)
Step 1: Calculate the weighted average [the one you already did]
Explaination of Step 1:
Here in the OVER() clause you have defined the aggregation level just like you did in the group by above. Which means you can still select all the other colums [first name, last name, daily rate, total paid and NOT have them in the Partition BY clause [the beauty of windows functions]. In windows functions you define the aggregation layer within Partition by which is City in your case)
STEP 2: Get Max() Value [The one you need]
Explaination: here we didn't add the aggregation layer as we just need the max value from the column weighted average.Hence partition by and OVER is left empty
With Weighted_Average AS (
Select
city,
first_name,
last_name,
city,
daily_rate,
total_paid,
sum(daily_rate * total_paid) OVER(PARTITION BY city)
/
sum(total_paid) OVER(PARTITION BY city) AS weighted_average
FROM
customer_data
group by
city
)
Select
*,
MAX(weighted_average) OVER() AS max_weighted_average
FROM
Weighted_Average
select
City,sum(Daily_Rate*Total_paid)/sum(Total_Paid) as WA_Rate
from customer_data
group by City
select ,
(select sum(Daily_RateTotal_paid)/sum(Total_Paid) as WA_Rate from customer_data
where City in ('Dallas')
group by City) as Max_WA_Rate from customer_data
Try the Following
select FistName,LastName,City,Daily_Rate,Total_Paid,
(select max(WA_Rate) from Weight_Average) as Max_Wa_Rate
from customer_data
I have the following sql query that I m not able to process since this afternoon.
I have seen lot of threads about this issue but I m not getting it right, I believe that I m miss understanding this one topic that is shaming my day.
SELECT
cor.c_order_id, cor.totallines,cor.documentno,cbp.name
FROM
c_orderline col
LEFT JOIN c_order cor ON cor.c_order_id = col.c_order_id
LEFT join c_bpartner cbp on cbp.c_bpartner_id = cor.c_bpartner_id
WHERE
cor.issotrx = 'Y'
and cor.docstatus not in ('DR','IP')
AND cor.salesrep_id = 1037317
and col.qtyordered <> 0
and cor.dateordered between SYSDATE - 30 and SYSDATE + 30
AND col.c_orderline_id NOT IN (
SELECT
cil.c_orderline_id
FROM
c_invoiceline cil
WHERE
cil.c_orderline_id IS NOT NULL
)
group by cor.c_order_id, cor.documentno
order by cor.c_order_id, cor.documentno
What I m doing wrong ??
A simplified example, based on Scott's EMP table.
This is what you did:
SQL> select deptno, job
2 from emp
3 group by deptno;
select deptno, job
*
ERROR at line 1:
ORA-00979: not a GROUP BY expression
As you can see, Oracle marked the culprit - the JOB column.
Therefore, if you want to use a GROUP BY clause (which is correct, but - you'd get the same result using DISTINCT; these two should not be used together. GROUP BY is usually used with aggregates such as min, max, avg and such), then put all columns into the GROUP BY:
SQL> select deptno, job
2 from emp
3 group by deptno, job;
DEPTNO JOB
---------- ---------
20 CLERK
30 SALESMAN
20 MANAGER
30 CLERK
10 PRESIDENT
30 MANAGER
10 CLERK
10 MANAGER
20 ANALYST
9 rows selected.
Or, as I said - using DISTINCT:
SQL> select distinct deptno, job
2 from emp;
DEPTNO JOB
---------- ---------
20 CLERK
30 SALESMAN
20 MANAGER
30 CLERK
10 PRESIDENT
30 MANAGER
10 CLERK
10 MANAGER
20 ANALYST
9 rows selected.
SQL>
group by is used for aggregate functions like sum(), max(), min() etc. What value are you calculating in groups?
remove the group by and your query looks good besides that
To correct the error this query is producing you need to
group by cor.c_order_id, cor.totallines,cor.documentno,cbp.name
Group by all the columns that are in the select statement and are not part of aggregate function.
What else is a problem?
Here is the DEMO
In this demo you will see that the query works(with the correction I have suggested), but as I asked, what else is a problem to you. Do elaborate so we can help. Cheers!
I need some help in combining my three codes in one anonymous block and also I need to put a substitution value that a user will use. any idea how I can do that ?? I am using oracle 10g express, here are my codes:
CODE 1: Display department name and number of employees per department for every department that is in "Americas".
select department_name, count(employee_id) "number of employees"
from employees, departments, locations, countries, regions
where employees.DEPARTMENT_ID = departments.DEPARTMENT_ID
AND departments.location_ID = locations.location_ID
AND locations.country_ID = countries.country_ID
AND countries.region_ID = regions.region_ID
AND regions.region_name = 'Americas'
group by department_name
CODE 2: What are the average salaries per position?
SELECT job_ID, AVG(salary) as "avg salary"
FROM employees
GROUP BY job_ID
CODE 3:What are the average salaries per department?
SELECT department_ID, AVG(salary) as "avg salary"
FROM employees
GROUP BY department_ID
Combining completely different datasets into a single block is not possible in Oracle. Anonymous blocks are for executing a series of PL/SQL commands. A query (i.e. anything that returns back a result set, ex: SELECT ...)can only return a single result so it would need to be three separate queries.
If you're using SQL*Plus all of this can be done from the command line and/or with a .sqlscript file. You can also specify parameters on the command line as well (I couldn't tell if thats what you meant for the first query but I added it anyway).
Script (foo.sql):
-- Set page size to something larger than the expected result set.
-- NOTE: You don't want to put 0 (unlimited) as it supresses the column headers.
SET pagesize 50000
-- Get the parameter from the command line:
VAR region VARCHAR2(256)
EXEC :region := '&1'
SELECT 'Processing for region: ' || :region
FROM dual;
-- Query 1
promp "Employee detail in specified region:"
select department_name, count(employee_id) "number of employees"
from HR.employees, HR.departments, HR.locations, HR.countries, HR.regions
where employees.DEPARTMENT_ID = departments.DEPARTMENT_ID
AND departments.location_ID = locations.location_ID
AND locations.country_ID = countries.country_ID
AND countries.region_ID = regions.region_ID
AND regions.region_name = :region
group by department_name
;
-- Query 2:
promp "Average salary by position:"
SELECT job_ID, AVG(salary) as "avg salary"
FROM HR.employees
GROUP BY job_ID;
-- Query 3:
promp "Average salaries by department:"
SELECT department_ID, AVG(salary) as "avg salary"
FROM HR.employees
GROUP BY department_ID;
exit
Output:
$ sqlplus myuser/mypass#lvm01xe #sample.sql Americas
SQL*Plus: Release 11.2.0.2.0 Production on Sun Nov 18 09:11:11 2012
Copyright (c) 1982, 2010, Oracle. All rights reserved.
Connected to:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production
PL/SQL procedure successfully completed.
'PROCESSINGFORREGION:'||:REGION
--------------------------------------------------------------------------------
Processing for region: Americas
"Employee detail in specified region:"
DEPARTMENT_NAME number of employees
------------------------------ -------------------
Administration 1
Accounting 2
IT 5
Purchasing 6
Executive 3
Shipping 45
Finance 6
Marketing 2
8 rows selected.
"Average salary by position:"
JOB_ID avg salary
---------- ----------
IT_PROG 5760
AC_MGR 12000
AC_ACCOUNT 8300
ST_MAN 7280
PU_MAN 11000
AD_ASST 4400
AD_VP 17000
SH_CLERK 3215
FI_ACCOUNT 7920
FI_MGR 12000
PU_CLERK 2780
SA_MAN 12200
MK_MAN 13000
PR_REP 10000
AD_PRES 24000
SA_REP 8350
MK_REP 6000
ST_CLERK 2785
HR_REP 6500
19 rows selected.
"Average salaries by department:"
DEPARTMENT_ID avg salary
------------- ----------
100 8600
30 4150
7000
90 19333.3333
20 9500
70 10000
110 10150
50 3475.55556
80 8955.88235
40 6500
60 5760
10 4400
12 rows selected.
Disconnected from Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production
Obviously, anything which is aggregated by the same yardstick can be combined easily. Hence you can merge the third query with your first one like this:
select department_name
, count(employee_id) as "number of employees"
, avg(salary) as "avg salary"
from employees, departments, locations, countries, regions
where employees.DEPARTMENT_ID = departments.DEPARTMENT_ID
AND departments.location_ID = locations.location_ID
AND locations.country_ID = countries.country_ID
AND countries.region_ID = regions.region_ID
AND regions.region_name = :region_name
group by department_name
I have adapted your code to pass in a bind variable, :region_name. There are various way of specifying variables in Apex. Find out more.
There is no meaningful way of including your second query. The values for JOB_ID cross the values of DEPARTMENT_ID, so there's no common key to join on.