Not a group by expression in my oracle SQL query - sql

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!

Related

Getting percent of employee's salary within department

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;

How to display employee names instead of employee id while keeping the ","

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>

How to count number of results rows plsql

I've created this query in PL SQL where i have to check how much time an operation takes.
set line 2222
set pages 0
set feedback off
col EVENT for a35
col SQL_ID for a13
col ET for 9999
col mymac for a15
col username for a12
SELECT MAX(a.last_call_et)
FROM gv\\$session a, gv\\$sqlarea c
where c.address= a.sql_address
and c.hash_value = a.sql_hash_value
and a.username='XXXX'
and a.last_call_et>600
order by a.last_call_et;
I want to modify this query in a way that show me how many rows I've got from result. For example, if my query result will have two rows, I want to show '2'
I think you can use count aggregate function along with existing max aggregate function as following:
SELECT MAX(a.last_call_et),
count(1) as total_rows --<-- this
FROM gv\\$session a, gv\\$sqlarea c
where c.address= a.sql_address
and c.hash_value = a.sql_hash_value
and a.username='XXXX'
and a.last_call_et>600
-- order by a.last_call_et; this order by is of no use
Cheers!!
As it is SQL*Plus, then a little bit of its formatting could do the job. For example:
SQL> break on report
SQL> compute count of ename on report
SQL>
SQL> select ename from emp where deptno = 10;
ENAME
----------
CLARK
KING
MILLER
----------
3
SQL>

adding column in Oracle SQL table that is random sample from another column/table

I have a preexisting table (MYORIGTABLE) in SQL and I would like to add a column which is a random sample from a column in another table (RANDNUM10). The other table is a 1 column sample of random numbers I've predetermined and would like to sample from; column name is RANDVAL. I've tried the following
select
(select randval from
(SELECT randval
FROM RANDNUM10
ORDER BY dbms_random.value)
where rownum=1) as mynum
from MYORIGTABLE
But unfortunately this just gives me the same random number which was sampled from RANDNUM10 repeated for the entire length of MYORIGTABLE. How can I perform the sampling so that every line of MYORIGTABLE gets a newly generated sample?
Thanks,
Christie
This is your query:
select (select randval
from (SELECT randval
FROM RANDNUM10
ORDER BY dbms_random.value
)
where rownum = 1
) as mynum
from MYORIGTABLE;
A common problem with database optimizers -- in this situation -- that that it optimizes away the multiple calls to the subquery. It ends up replacing the subquery with one call.
One way around this is to use a correlated subquery. For this, I would recommend removing one level of nesting:
select (select max(randval) keep (dense_rank first order by dbms_rnadom.value)
from randnum10
where myorigtable.id is not null -- or whatever
) as mynum
from MYORIGTABLE;
I think this will work so the subquery is called 10 times, although a more complex correlation clause might be needed.
Something like this, perhaps?
Test case:
SQL> create table myorigtable (id number);
Table created.
SQL> create table randnum10 (randval number);
Table created.
SQL> insert into myorigtable select level from dual connect by level <= 10;
10 rows created.
SQL> insert into randnum10 select round(dbms_random.value(1, 100)) from dual connect by level <= 10;
10 rows created.
SQL> alter table myorigtable add randval number;
Table altered.
Update:
SQL> update myorigtable m set
2 m.randval = (select x.randval
3 from (select randval, row_number() over (order by randval) rn
4 from randnum10
5 ) x
6 where x.rn = m.id
7 );
10 rows updated.
SQL> select * from myorigtable;
ID RANDVAL
---------- ----------
1 5
2 18
3 29
4 31
5 31
6 62
7 66
8 87
9 94
10 98
10 rows selected.
SQL>
Here is one approach. I assume your RANDNUM10 table has many rows (more rows than MYORIGTABLE), you want to sample the random numbers without replacement (select DISTINCT random values from RANDNUM10) - and therefore the random numbers table has enough rows - at least as many as MYORIGTABLE - and you need to add the column to the result set of a SELECT query, rather than to the stored table. (If you need to store the result in an additional column, then you must first add the column if it doesn't already exist, and use the SELECT query below in an UPDATE statement.)
The strategy is simple: associate arbitrary row numbers to the smaller table (with values 1, 2, ..., N where N is the row count), and associate random row numbers to the rows in the random numbers table. Both can be done with ROW_NUMBER()... - the first one can be ordered by NULL since all the randomness we need will come from the other table's row numbers anyway. Then JOIN the two tables on the row number thus added to both tables.
For illustration I created MYORIGTABLE as a copy of SCOTT.EMP, selecting only a few columns from that table. Here's what the query looks like, and the output using that as MYORIGTABLE.
Setup:
create table myorigtable as select empno, ename, job, deptno from scott.emp;
create table randnum10 ( randval ) as
select dbms_random.value() from dual connect by level <= 1000;
Query and output:
select a.empno, a.ename, a.job, a.deptno, b.randval
from (
select empno, ename, job, deptno, row_number() over (order by null) as rn
from myorigtable
) a
inner join
(
select randval, row_number() over (order by dbms_random.value()) as rn
from randnum10
) b
on a.rn = b.rn
;
EMPNO ENAME JOB DEPTNO RANDVAL
----- ---------- --------- ------- ----------
7369 SMITH CLERK 20 .991998901
7499 ALLEN SALESMAN 30 .448133242
7521 WARD SALESMAN 30 .136242235
7566 JONES MANAGER 20 .443347421
7654 MARTIN SALESMAN 30 .836931008
7698 BLAKE MANAGER 30 .361437867
7782 CLARK MANAGER 10 .433786615
7788 SCOTT ANALYST 20 .285173363
7839 KING PRESIDENT 10 .063840361
7844 TURNER SALESMAN 30 .825888729
7876 ADAMS CLERK 20 .818415041
7900 JAMES CLERK 30 .150180080
7902 FORD ANALYST 20 .442389099
7934 MILLER CLERK 10 .086995415

Multiple where statement returning undesired result

I'm trying to retrieve employees within depts 10 and 30 with a salary greater than 1500. This is the statement I'm running.
select ename, sal
from emp
where deptno = 10
or deptno = 30
and sal > 1500;
It returns:
Name Salary
---------- --------------
NAME1 1600
NAME2 2850
NAME3 2450
NAME4 5000
NAME5 1300
When I run the query without the OR for only one dept, it returns NAME1 and NAME2, when I run it for the other dept it returns NAME3 and NAME4.
My question is, is it the OR statement that's giving me the incorrect NAME5? To me that query should work, I tried using AND instead of OR and it gives me no results. Any help would be appreciated.
AND has precedence over OR in a SQL WHERE clause, so what you have is equivalent to
where deptno = 10 or (deptno = 30 and sal > 1500)
Try putting parentheses around the OR to see if it gives you the results you expect.
where (deptno = 10 or deptno = 30) and sal > 1500
(Also, it would clarify things a little bit if you included the department number in your SELECT so you can see which department each row is coming from.)
Your problem is the lack of parentheses. But really the best way to write the query uses in:
select ename, sal
from emp
where deptno in (10, 30) and
sal > 1500;