Oracle sql order by with case statement - sql

I am facing difficulty in understanding oracle(12c) sql order by clause with case statement.
I have a table with the below data,
SELECT DEPT_NO, DEPT_NAME FROM SORTNG_LOGIC;
DEPT_NO DEPT_NAME
---------- --------------------
1 FINANCE
2 ACCOUNT
3 HUMAN RESOURCE
4 AUDIT
5 TRAINING
I am executing the below sql query for this table to add custom order, on oracle sql developer.
SELECT DEPT_NO, DEPT_NAME FROM SORTNG_LOGIC ORDER BY (
CASE DEPT_NAME
WHEN 'ACCOUNT' THEN '1'
WHEN 'AUDIT' THEN '2'
WHEN 'FINANCE' THEN '3'
ELSE '4' END
)DESC;
This is giving the below result :
DEPT_NO DEPT_NAME
---------- --------------------
3 HUMAN RESOURCE
5 TRAINING
1 FINANCE
4 AUDIT
2 ACCOUNT
But I expected that, the result should be
DEPT_NO DEPT_NAME
---------- --------------------
5 TRAINING
3 HUMAN RESOURCE
1 FINANCE
4 AUDIT
2 ACCOUNT
As I am sorting the dept_name in descending order, I thought'Training' should be above 'human resource'.
Where is my understanding going wrong? Could someone please explain this in detail?

If you want the department name in descending order, then you have to include that information in the query:
ORDER BY (CASE DEPT_NAME
WHEN 'ACCOUNT' THEN 1
WHEN 'AUDIT' THEN 2
WHEN 'FINANCE' THEN 3
ELSE 4
END) DESC,
DEPT_NAME DESC;
There is no reason for the value of the CASE to be a character string. The logic really calls for a number. If you use strings, then values larger than 9 will not work as you expect them to.

Try this with decode function, basically does the same thing.
SELECT DEPT_NO, DEPT_NAME
FROM SORTNG_LOGIC
ORDER BY
decode (DEPT_NAME,'ACCOUNT','1','AUDIT','2','FINANCE','3','4') DESC;

Related

sql query with group by order by with inner select clause [duplicate]

My query looks like below with the foll. output
Current Output
Role Cases prepped % Completed
State Member 1 10 5 50%
State Member 2 10 7 70%
State President 10 2 20%
Summary 30 14 46.6%
Output Expected
Role Cases prepped % Completed
State President 10 2 20%
State Member 1 10 5 50%
State Member 2 10 7 70%
Summary 30 14 46%
Roles table
id name
30 State President
40 State Member
This is my query,
SELECT COALESCE(ROLE, 'Summary') ROLE,
count(*) AS cases,
SUM(CASE WHEN PREPARED = 'Y' THEN 1
ELSE 0
END) AS prepped,
round(avg(case when prepared = 'Y' then 100 else 0 end),2)||'%' as % Completed
FROM
(SELECT CASE
WHEN r.id = 30 THEN r.name
ELSE r.name || ' ' || u.case_member_id
END AS ROLE,
bi.prepared
FROM cases c
LEFT JOIN case_inventory ci ON ci.case_id = c.id
AND c.id = ci.case_id
AND c.delete_date IS NULL
AND ci.case_id =40
Left JOIN users u ON ci.assigned_to = u.id
Left JOIN ROLES r ON u.role_id = r.id
Left JOIN user_cases_map uc ON c.id = uc.case_id
AND uc.id = 1572919346)
GROUP BY ROLLUP (ROLE);
I now want to order the rows with respect to the role. The 1st record should be the State president and then followed by state memebr 1. state member 2. and so on. I tried to have an order by in the inner clause but it did not help. It doesnt have any effect. Adding in the outer select also doesnt change anything. Any help highly appreciated. Thank you.
You could do something like this. I don't have your input data, so I used SCOTT.EMP instead.
Notice a few things. I grouped by JOB, and I used GROUPING(JOB) both in SELECT (to add the label TOTAL for the summary row) and in ORDER BY. Since I reuse the column name JOB in SELECT (for the output column), in ORDER BY I must be careful to qualify the column name JOB (to make it clear I am referring to the input table column, not to the column in SELECT - which would be the default if column names in ORDER BY were not qualified). The need to qualify column names in ORDER BY, then, forced me to alias the table in the FROM clause (otherwise I would have had to carry the full table name everywhere).
Using the GROUPING function in SELECT (rather than NVL) is particularly important if JOB can be null. You don't want the group for null job to be labeled TOTAL - you only want that for the rollup row. This point confuses even a lot of very advanced programmers.
I show how you can "manually" decide the order: PRESIDENT first, then MANAGER, and then all other jobs (ordered alphabetically). If you have the order of priority saved somewhere, for example in a table, you can join to that table and use the ordering column instead of the "manual" CASE expression in my query.
select case grouping(job) when 0 then job else 'TOTAL' end as job
, sum(sal) as total_salary
from scott.emp e
group by rollup(job)
order by grouping(e.job) -- to get the total in the last row
, case e.job when 'PRESIDENT' then 1 when 'MANAGER' then 2 end
, e.job
;
JOB TOTAL_SALARY
--------- ------------
PRESIDENT 5000
MANAGER 8275
ANALYST 6000
CLERK 4150
SALESMAN 5600
TOTAL 29025

sql order by with an inner select clause and group by rollup

My query looks like below with the foll. output
Current Output
Role Cases prepped % Completed
State Member 1 10 5 50%
State Member 2 10 7 70%
State President 10 2 20%
Summary 30 14 46.6%
Output Expected
Role Cases prepped % Completed
State President 10 2 20%
State Member 1 10 5 50%
State Member 2 10 7 70%
Summary 30 14 46%
Roles table
id name
30 State President
40 State Member
This is my query,
SELECT COALESCE(ROLE, 'Summary') ROLE,
count(*) AS cases,
SUM(CASE WHEN PREPARED = 'Y' THEN 1
ELSE 0
END) AS prepped,
round(avg(case when prepared = 'Y' then 100 else 0 end),2)||'%' as % Completed
FROM
(SELECT CASE
WHEN r.id = 30 THEN r.name
ELSE r.name || ' ' || u.case_member_id
END AS ROLE,
bi.prepared
FROM cases c
LEFT JOIN case_inventory ci ON ci.case_id = c.id
AND c.id = ci.case_id
AND c.delete_date IS NULL
AND ci.case_id =40
Left JOIN users u ON ci.assigned_to = u.id
Left JOIN ROLES r ON u.role_id = r.id
Left JOIN user_cases_map uc ON c.id = uc.case_id
AND uc.id = 1572919346)
GROUP BY ROLLUP (ROLE);
I now want to order the rows with respect to the role. The 1st record should be the State president and then followed by state memebr 1. state member 2. and so on. I tried to have an order by in the inner clause but it did not help. It doesnt have any effect. Adding in the outer select also doesnt change anything. Any help highly appreciated. Thank you.
You could do something like this. I don't have your input data, so I used SCOTT.EMP instead.
Notice a few things. I grouped by JOB, and I used GROUPING(JOB) both in SELECT (to add the label TOTAL for the summary row) and in ORDER BY. Since I reuse the column name JOB in SELECT (for the output column), in ORDER BY I must be careful to qualify the column name JOB (to make it clear I am referring to the input table column, not to the column in SELECT - which would be the default if column names in ORDER BY were not qualified). The need to qualify column names in ORDER BY, then, forced me to alias the table in the FROM clause (otherwise I would have had to carry the full table name everywhere).
Using the GROUPING function in SELECT (rather than NVL) is particularly important if JOB can be null. You don't want the group for null job to be labeled TOTAL - you only want that for the rollup row. This point confuses even a lot of very advanced programmers.
I show how you can "manually" decide the order: PRESIDENT first, then MANAGER, and then all other jobs (ordered alphabetically). If you have the order of priority saved somewhere, for example in a table, you can join to that table and use the ordering column instead of the "manual" CASE expression in my query.
select case grouping(job) when 0 then job else 'TOTAL' end as job
, sum(sal) as total_salary
from scott.emp e
group by rollup(job)
order by grouping(e.job) -- to get the total in the last row
, case e.job when 'PRESIDENT' then 1 when 'MANAGER' then 2 end
, e.job
;
JOB TOTAL_SALARY
--------- ------------
PRESIDENT 5000
MANAGER 8275
ANALYST 6000
CLERK 4150
SALESMAN 5600
TOTAL 29025

Display a blank row between every unique row?

I have a simple query like:
SELECT employee, ITEM_TYPE, COUNT(ITEM_TYPE)
FROM hr_database
So the output may look like
BOB MUGS 4
BOB PENCILS 10
CAT MUGS 2
CAT PAPERCLIPS 7
SAL MUGS 11
But for readability, I want to put a blank row between each user in the output(i.e for readability), like this :
BOB MUGS 4
BOB PENCILS 10
CAT MUGS 2
CAT PAPERCLIPS 7
SAL MUGS 11
Is there a way to do this in Oracle SQL ? So far, I found this link but it doesn't match what I need . I'm thinking to use a WITH in the query?
You can do it in the database, but this type of processing should really be done at the application layer.
But, it is kind of an amusing trick to figure out how to do it in the database, and that is your specific question:
WITH e AS (
SELECT employee, ITEM_TYPE, COUNT(ITEM_TYPE) as cnt
FROM hr_database
GROUP BY employee, ITEM_TYPE
)
SELECT (case when cnt is not null then employee end) as employee,
item_type, cnt
FROM (select employee, item_type, cnt, 1 as x from e union all
select distinct employee, NULL, NULL, 2 as x from e
) e
ORDER BY e.employee, x;
I emphasize, though, that this is really for amusement and perhaps for understanding better how SQL works. In the real world, you do this type of work at the application layer.
A summary of how this works. The union all brings in one additional row for each employee. The x is a priority for sorting -- because you have to sort the result set to get the proper ordering. The case statement is needed to prevent the employee from being in the first column. cnt should never be NULL for the valid rows.
You can try like this with normal union & distinct
select emp,item_type,cnt from
(select distinct ' ' as emp,' ' as item_type ,' ' as cnt, employee
from hr_database
union
select employee as emp,item_type ,to_char(count(item_type)) as cnt, employee
from hr_database
group by employee,item_type)a
order by a.employee

I need to Create 3 Variables from 1 Variable SQL Oracle SAP

I’m working on creating three variables in the my Universe. The variables are Applications, Operating System, and Physical Location. The problem I’m running into is that all three of these is pulled from the same table and all of them are within the Value.
So the Table BMC_CORE_BMC_BASERELATIONSHIP has the variable Name. I am trying to pull all of the destination instance id's for the operating system when name is equal to SYSTEMOS.
My first guest was to use derived tables having each of the three a separate table with the following coding
“Operating System Relationship” Derive Table
SELECT
BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID
FROM
BMC_CORE_BMC_BASERELATIONSHIP
Where
BMC_CORE_BMC_BASERELATIONSHIP.Name = 'SYSTEMOS'
“Physical Location Relationship” Derive Table
SELECT
BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID
FROM
BMC_CORE_BMC_BASERELATIONSHIP
Where
BMC_CORE_BMC_BASERELATIONSHIP.Name = 'ELEMENTLOCATION'
“Applications Relationship” Derive Table
SELECT
BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID
FROM
BMC_CORE_BMC_BASERELATIONSHIP
Where
BMC_CORE_BMC_BASERELATIONSHIP.Name = 'APPLICATIONSYSTEMCOMPUTER'
However when I try pulling the variable in with the following syntax, only one of the variables will show up in Webi. The others are being nulled I figured out.
App Var
Case
WHEN CI_RELATED_CI.INSTANCEID ="Applications Relationship".DESTINATION_INSTANCEID
Then CI_RELATED_CI.NAME
END
Location Var
CASE
WHEN CI_RELATED_CI.INSTANCEID = "Physical Location Relationship".DESTINATION_INSTANCEID
THEN CI_RELATED_CI.NAME
END
OS Var
Case
WHEN CI_RELATED_CI.INSTANCEID ="Operating System Relationship".DESTINATION_INSTANCEID
Then CI_RELATED_CI.NAME
END
I was trying to think of a way to pull all of these into one derived table as separate variables, but so far I have been unsuccessful. Any thoughts or suggestions would be appreciated. I'm new so let me know if there is anything I can do to make my questions better.
you can use case like below or Decode,
select Case
WHEN BMC_CORE_BMC_BASERELATIONSHIP.Name = 'SYSTEMOS'
Then BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID
WHEN BMC_CORE_BMC_BASERELATIONSHIP.Name = 'ELEMENTLOCATION'
Then BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID
When BMC_CORE_BMC_BASERELATIONSHIP.Name = 'APPLICATIONSYSTEMCOMPUTER'
Then BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID
Else 'Ur Comments'
END
from BMC_CORE_BMC_BASERELATIONSHIP;
or Decode
select Decode( BMC_CORE_BMC_BASERELATIONSHIP.Name,'SYSTEMOS',BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID,'ELEMENTLOCATION',BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID,'APPLICATIONSYSTEMCOMPUTER',BMC_CORE_BMC_BASERELATIONSHIP.DESTINATION_INSTANCEID,else) from BMC_CORE_BMC_BASERELATIONSHIP;
am able to get the out put
create table BMC_CORE_BMC_BASERELATIONSHIP (name1 varchar2(100),id number);
insert into BMC_CORE_BMC_BASERELATIONSHIP (name1,id) values('SYSTEMOS',1);
insert into BMC_CORE_BMC_BASERELATIONSHIP (name1,id) values('ELEMENTLOCATION',2);
insert into BMC_CORE_BMC_BASERELATIONSHIP (name1,id) values('APPLICATIONSYSTEMCOMPUTER',3);
select Case
WHEN Name1 = 'SYSTEMOS'
Then id
WHEN Name1 = 'ELEMENTLOCATION'
Then id
When Name1 = 'APPLICATIONSYSTEMCOMPUTER'
Then id
Else id
END
from BMC_CORE_BMC_BASERELATIONSHIP ;
o/p #
1
2
3
select Decode(Name1,'SYSTEMOS',id,'ELEMENTLOCATION',id,'APPLICATIONSYSTEMCOMPUTER',id,id)
from BMC_CORE_BMC_BASERELATIONSHIP;
o/p #
1
2
3
Not sure if I understand you correctly and this is what you want... This is Oracle example where each column has own name or label. You can create table as select - just add create table as before the select and your column names will be the label names:
SELECT ename, empno,
(CASE deptno WHEN 10 THEN 'Accounting' END) dept10
, (CASE deptno WHEN 20 THEN 'Research' END) dept20
, (CASE deptno WHEN 30 THEN 'Sales' END) dept30
, (CASE deptno WHEN 40 THEN 'Operations' END) dept40
FROM scott.emp
ORDER BY ename
/
ENAME EMPNO DEPT10 DEPT20 DEPT30 DEPT40
---------------------------------------------------------------------
ADAMS 7876 Research
ALLEN 7499 Sales
BLAKE 7698 Sales
CLARK 7782 Accounting
.....

Apply COUNT function on a subgroup of groups

I made up this weird example trying to illustrate what I want to do (it's kind of stupid, but bear with me):
Consider the following table:
EMPLOYEES
married, certified and religious are just boolean fields (in case of Oracle, they are of type NUMBER(1,0)).
I need to come up with SQL that displays for each hire_year, count of married, certified and religious employees within the following salary categories:
A SALARY > 2000
B SALARY BETWEEN 1000 AND 2000
C SALARY < 1000
Based on the above dataset, here is what I expect to get:
So far, I've only come up with the following SQL:
SELECT
COUNT(CASE WHEN married = 1 THEN 1 END) as MARRIED,
COUNT(CASE WHEN certified = 1 THEN 1 END) as certified,
COUNT(CASE WHEN religious = 1 THEN 1 END) as religious,
hire_year
FROM employees
GROUP BY hire_year;
The result of executing this SQL is:
Which is almost what I need, but I also need to divide these counters further down into the groups based on a salary range.
I guess that some analytic function, that divides groups into the buckets based on some SQL expression would help, but I can't figure out which one. I tried with NTILE, but it expects a positive constant as a parameter, rather than an SQL expression (such as SALARY BETWEEN X and Y).
Nope, no need for analytic functions; they're difficult to have in the same query as an aggregate function anyway.
You're looking for the case statement again, you just have to put it in the GROUP BY.
select hire_year
, sum(married) as married
, sum(certified) as certified
, sum(religious) as religious
, case when salary > 2000 then 'A'
when salary >= 1000 then 'B'
else 'C' end as salary_class
from employees
group by hire_year
, case when salary > 2000 then 'A'
when salary >= 1000 then 'B'
else 'C' end
Note that I've changed your count(case when...) to sum(). This is because you're using a boolean 1/0 so this'll work in the same manner but it's a lot cleaner.
For the same reason I've ignored your between in your salary calculation; there's no particular need for it as if the salary is greater than 2000 the first CASE has already been fulfilled.