Case and Decode - oracle

I have written a query to display the customer id, login id and sum of credit debt amount of the customer. If the customer due amount is to be debited a virtual column must be created and display as Due from customer else Due to Customer. Please help me solve the query.
I am attaching the table Structure and values along with my query.
-- Customer Table
CREATE TABLE isbs_customer_mst
(
cust_id VARCHAR2(30) NOT NULL,
login_id VARCHAR2(30) NOT NULL,
cust_nm VARCHAR2(30),
cust_addr VARCHAR2(300),
CONSTRAINT isbs_customer_mst_pk PRIMARY KEY (cust_id)
);
--Values
INSERT INTO ISBS_CUSTOMER_MST (CUST_ID, LOGIN_ID, CUST_NM, CUST_ADDR)
VALUES ('CUST0000000001', 'USER1', 'User Login ID 1', '143/1 Uthamar Gandhi Salai, Nungambakkam, Chennai - 34');
INSERT INTO ISBS_CUSTOMER_MST (CUST_ID, LOGIN_ID, CUST_NM, CUST_ADDR)
VALUES ('CUST0000000002', 'USER2', 'User Login ID 2', '143/2 Uthamar Gandhi Salai, Nungambakkam, Chennai - 34');
INSERT INTO ISBS_CUSTOMER_MST (CUST_ID, LOGIN_ID, CUST_NM, CUST_ADDR)
VALUES ('CUST0000000003', 'USER3', 'User Login ID 3', '143/3 Uthamar Gandhi Salai, Nungambakkam, Chennai - 34');
INSERT INTO ISBS_CUSTOMER_MST (CUST_ID, LOGIN_ID, CUST_NM, CUST_ADDR)
VALUES ('CUST0000000004', 'USER4', 'User Login ID 4', '143/4 Uthamar Gandhi Salai, Nungambakkam, Chennai - 34');
--Credit Debit Table
CREATE TABLE isbs_acct_ledger_det
(
acct_ledger_id VARCHAR2(30),
cust_id VARCHAR2(30),
credit_debit_amt VARCHAR2(30) NOT NULL,
credit_debit_dttm TIMESTAMP NOT NULL,
CONSTRAINT isbs_acct_ledger_det_pk PRIMARY KEY (acct_ledger_id),
CONSTRAINT isbs_acct_ledger_det_fk FOREIGN KEY (cust_id) REFERENCES
isbs_customer_mst (cust_id)
);
-- Values
INSERT INTO ISBS_ACCT_LEDGER_DET (ACCT_LEDGER_ID, CUST_ID, CREDIT_DEBIT_AMT, CREDIT_DEBIT_DTTM)
VALUES ('ACC0000000001', 'CUST0000000001', -1000.25, TO_DATE('01-10-2008 11:00:00', 'DD-MM-YYYY HH24:MI:SS'));
INSERT INTO ISBS_ACCT_LEDGER_DET (ACCT_LEDGER_ID, CUST_ID, CREDIT_DEBIT_AMT, CREDIT_DEBIT_DTTM)
VALUES ('ACC0000000002', 'CUST0000000002', -256.75, TO_DATE('01-10-2008 11:00:00', 'DD-MM-YYYY HH24:MI:SS'));
INSERT INTO ISBS_ACCT_LEDGER_DET (ACCT_LEDGER_ID, CUST_ID, CREDIT_DEBIT_AMT, CREDIT_DEBIT_DTTM)
VALUES ('ACC0000000003', 'CUST0000000002', 100.25, TO_DATE('05-10-2008 11:00:00', 'DD-MM-YYYY HH24:MI:SS'));
-- Query
SELECT c.CUST_NM
, c.LOGIN_ID
, SUM(a.CREDIT_DEBIT_AMT) "Outstanding Amt"
, CASE WHEN a.CREDIT_DEBIT_AMT <= -9999.99
THEN 'Due to Cust'
ELSE 'Due from Cust' END "Due"
FROM ISBS_CUSTOMER_MST c
JOIN ISBS_ACCT_LEDGER_DET a
ON c.CUST_ID = a.CUST_ID
GROUP BY c.CUST_NM, c.LOGIN_ID, a.CREDIT_DEBIT_AMT;
Thanks in advance

You probably need to remove a.CREDIT_DEBIT_AMT from group by and use CASE expression on SUM().
SELECT c.cust_nm,
c.login_id,
SUM(a.credit_debit_amt) "Outstanding Amt",
CASE
WHEN SUM(a.credit_debit_amt) <= -9999.99 THEN 'Due to Cust'
ELSE 'Due from Cust'
END "Due"
FROM isbs_customer_mst c
JOIN isbs_acct_ledger_det a
ON c.cust_id = a.cust_id
GROUP BY c.cust_nm,
c.login_id ;

You need to add case statement in group by clause:
SELECT c.CUST_NM , c.LOGIN_ID , SUM(a.CREDIT_DEBIT_AMT) "Outstanding Amt" , CASE WHEN a.CREDIT_DEBIT_AMT <= -9999.99 THEN 'Due to Cust' ELSE 'Due from Cust' END "Due" FROM ISBS_CUSTOMER_MST c JOIN ISBS_ACCT_LEDGER_DET a ON c.CUST_ID = a.CUST_ID GROUP BY c.CUST_NM, c.LOGIN_ID, CASE WHEN a.CREDIT_DEBIT_AMT <= -9999.99 THEN 'Due to Cust' ELSE 'Due from Cust' END "Due";
As it is the rule of aggregate functions to have all other selected attributes in group by clause

Why would you use sin function when you are dealing with sum. Sin gives the sin value of the parameter
Whereas abs returns the positive number, likewise
Select abs(-5) from dual ;
Will return 5 as output.
I dont think your requirement needs both these functions.

Related

How to insert comma delimited values in one column in oracle?

I have a table by the name of personal_info and it contains id,name and phone_number as columns. So the following is table structure which I want to store data.
id
name
phone_number
1
ali
03434444, 03454544, 0234334
So how to store data in phone_number column in comma delimited format and how to filter that column in where clause for example
Select * from personal_info where phone_number = 03454544 ;
And which datatype is suitable for phone_number column.
Well, the real good practice would rather be to have another table PHONE with a 1xN association (for example a PHONE_ID primary key, and ID and PHONE columns.)
You may then have the result you want with a view based on your two tables and using the LISTAGG operator : https://fr.wikibooks.org/wiki/Oracle_Database/Utilisation_de_fonctions/fonction_LISTAGG, but this will be much efficient to work with, especially if you want WHERE clauses based on your phone numbers.
Use LIKE with the delimiters:
Select *
from personal_info
where ', ' || phone_number || ', ' LIKE '%, ' || '03454544' || ', %';
However
You should consider changing your data structure to store the phone numbers in a separate table:
CREATE TABLE phone_numbers (
person_id REFERENCES personal_info (id),
phone_number VARCHAR2(12)
);
And then you can get the data using a JOIN
SELECT pi.*,
pn.phone_number
FROM personal_info pi
INNER JOIN phone_numbers pn
ON (pi.id = pn.person_id)
WHERE pn.phone_number = '03434444'
or, if you want all the phone numbers:
SELECT pi.*,
pn.phone_numbers
FROM personal_info pi
INNER JOIN (
SELECT person_id,
LISTAGG(phone_number, ', ') WITHIN GROUP (ORDER BY phone_number)
AS phone_numbers
FROM phone_numbers
GROUP BY person_id
HAVING COUNT(CASE WHEN phone_number = '03434444' THEN 1 END) > 0
) pn
ON (pi.id = pn.person_id)
db<>fiddle here
VARCHAR2 is suitable for phone numbers.
You can get the values this way:
WITH personal_info AS
(
SELECT 1 AS ID, 'Ali' AS NAME, '03434444, 03454544, 0234334' AS phone_number FROM dual
)
SELECT *
FROM (SELECT id, name, TRIM(regexp_substr(phone_number, '[^,]+', 1, LEVEL)) AS phone_number
FROM personal_info
CONNECT BY LEVEL <= LENGTH (phone_number) - LENGTH(REPLACE(phone_number, ',' )) + 1)
WHERE phone_number = '03454544';
Wrong data model, it isn't normalized. You should create a new table:
create table phones
(id_phone number constraint pk_phone primary key,
id_person number constraint fk_pho_per references person (id_person),
phone_number varchar2(30) not null
);
Then you'd store as many numbers as you want, one-by-one (row-by-row, that is).
If you want to do it your way, store it just like that:
insert into person (id, name, phone_number)
values (1, 'ali', '03434444, 03454544, 0234334');
One option of querying such data is using the instr function:
select * from person
where instr(phone_number, '03434444') > 0;
or like:
select * from person
where phone_number like '%'% || '03434444' || '%'
or split it into rows:
select * from person a
where '03434444' in (select regexp_substr(b.phone_number, '[^,]+', 1, level)
from person b
where b.id_person = a.id_person
connect by level <= regexp_count(b.phone_number, ',') + 1
)
I'd do it my way, i.e. with a new table that contains only phone numbers.

Oracle SQL - Remove Null rows in aliased results

Hi devs I'm developing a small pro bono project that's use oracle sql but I'm not able to hide the null results.
Table Structure:
CREATE TABLE "church-members" (
ID NUMBER(10),
NAME varchar(30) NOT NULL,
LOGIN varchar(20) NOT NULL,
PASS varchar(12) NOT NULL,
REGISTER_YEAR_MONTH varchar(15) NOT NULL,
USER_SCORE NUMBER(10),
PRIMARY KEY (ID));
The queries:
INSERT INTO "church-members" VALUES
('1', 'John Doe', 'John', 'Xo8*d_d%f58*', '202204','1');
INSERT INTO "church-members" VALUES
('2', 'Mary Doe', 'Mary', 'dLoc&257dsew', '202203','2');
INSERT INTO "church-members" VALUES
('3', 'Robertson III', 'Robertson', 'koIIf59*Liu*', '202203','7');
INSERT INTO "church-members" VALUES
('4', 'Sonia MacDonald', 'Sonia', 'fYhfgtdjfi%', '202204','4');
INSERT INTO "church-members" VALUES
('5', 'Boris Johnston', 'Boris', 'do*&flddkIK%', '202201','2');
INSERT INTO "church-members" VALUES
('6', 'Ruth Henderson', 'Ruth', 'dF6%*&', '202202','2');
The Select:
SELECT
ID,
NAME,
LOGIN,
MAX(CASE WHEN REGISTER_YEAR_MONTH = '202203' THEN TO_CHAR(USER_SCORE) ELSE '' END) AS "MARCH SCORE",
MAX(CASE WHEN REGISTER_YEAR_MONTH = '202204' THEN TO_CHAR(USER_SCORE) ELSE '' END) AS "APRIL SCORE "
FROM
"church-members"
GROUP BY
ID,
NAME,
LOGIN
And the result fiddle:
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=a4deac5e3eefb17dca97661552458a61
I got to this point using the information obtained in the answer from this link:
Select more than one column and remove NULL values from result
Looking at the fiddle example, the results with IDs 5 and 6 should not be showing because both are nulls.
But still null results are being shown...
Can anyone help me to solve it?
Add a HAVING clause requiring that each matching ID have at least data for at least one of the two months:
SELECT ID, NAME, LOGIN,
MAX(CASE WHEN REGISTER_YEAR_MONTH = '202203' THEN TO_CHAR(USER_SCORE) END) AS "MARCH SCORE",
MAX(CASE WHEN REGISTER_YEAR_MONTH = '202204' THEN TO_CHAR(USER_SCORE) END) AS "APRIL SCORE"
FROM "church-members"
GROUP BY ID, NAME, LOGIN
HAVING COUNT(CASE WHEN REGISTER_YEAR_MONTH IN ('202203', '202204') THEN 1 END) > 0;

Oracle PLSQL generating absences

have some PLSQL code that generates a list of dates from a range, which seems to be working fine.
As part of a larger project I want to generate a procedure that will create a list of absences for each employee.
My first step is to use the MINUS command to remove all the holidays, which fall into the range of dates. Is there an easy way of doing this instead of comparing each holiday one at a time (there maybe several in the table) against the GENERATED dates.
If possible, I would prefer breaking all these tasks into small procedures or functions for easy debugging and legibility.
If there is an easier way to do this I am open to all suggestions. Thanks in advance for your help, expertise and patience.
ALTER SESSION SET
NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table holidays(
holiday_date DATE,
holiday_name VARCHAR2(20)
);
INSERT into holidays
(holiday_date,
holiday_name)
VALUES
(
TO_DATE('2021/07/21 00:00:00', 'yyyy/mm/dd hh24:mi:ss'), 'July 21 2021');
CREATE OR REPLACE PROCEDURE generate_dates
(
p_start_date IN DATE,
p_end_date IN DATE
)
AS
l_day DATE := p_start_date;
BEGIN
WHILE l_day <= p_end_date
LOOP
DBMS_OUTPUT.PUT_LINE(l_day);
l_day := l_day + 1;
END LOOP;
END generate_dates;
EXEC generate_dates(TRUNC(SYSDATE),TRUNC(SYSDATE+10));
Create table employees(
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(20),
card_num VARCHAR2(10),
work_days VARCHAR2(7)
);
ALTER TABLE employees
ADD ( CONSTRAINT employees_pk
PRIMARY KEY (employee_id));
INSERT INTO employees
(
EMPLOYEE_ID,
first_name,
last_name,
card_num,
work_days
)
WITH names AS (
SELECT 1, 'Jane', 'Doe', 'F123456', 'NYYYYYN' FROM dual UNION ALL
SELECT 2, 'Madison', 'Smith', 'R33432','NYYYYYN'
FROM dual UNION ALL
SELECT 3, 'Justin', 'Case', 'C765341','NYYYYYN'
FROM dual UNION ALL
SELECT 4, 'Mike', 'Jones', 'D564311','NYYYYYN' FROM dual
) SELECT * FROM names
-- check to see if working for that day. Byte=Y for Yes
SELECT SUBSTR( work_days, to_char(TRUNC(SYSDATE), 'D'),1) Work_Day
FROM employees
create table timeoff(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
timeoff_date DATE,
timeoff_type VARCHAR2(1),
constraint timeoff_chk check (timeoff_date=trunc(timeoff_date, 'dd')),
constraint timeoff_pk primary key (employee_id, timeoff_date)
);
INSERT INTO timeoff (EMPLOYEE_ID,TIMEOFF_DATE,TIMEOFF_TYPE
)
WITH dts AS (
SELECT 1, to_date('20210726 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual UNION ALL
SELECT 2, to_date('20210726 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual UNION ALL
SELECT 2, to_date('20210727 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual )
SELECT * FROM dts
CREATE TABLE emp_attendance(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
start_date DATE,
end_date DATE,
week_number NUMBER(2),
create_date DATE DEFAULT SYSDATE
);
create table absences(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
absent_date DATE,
constraint absence_chk check (absent_date=trunc(absent_date, 'dd')),
constraint absence_pk primary key (employee_id, absent_date)
);
INSERT INTO emp_attendance ( EMPLOYEE_ID, START_DATE,END_DATE,WEEK_NUMBER)
WITH dts AS (
SELECT 1, to_date('20210728 13:10:00','YYYYMMDD HH24:MI:SS'),
to_date('20210728 23:15:00','YYYYMMDD HH24:MI:SS'), 30 FROM dual UNION ALL
SELECT 2, to_date('20210728 12:10:10','YYYYMMDD HH24:MI:SS'),
to_date('20210728 20:15:01','YYYYMMDD HH24:MI:SS'), 30 FROM dual)
SELECT * FROM dts
CREATE OR REPLACE TYPE obj_date IS OBJECT (
date_val DATE
);
CREATE OR REPLACE TYPE nt_date IS TABLE OF obj_date;
CREATE OR REPLACE FUNCTION generate_dates(
p_from IN DATE
,p_to IN DATE)
RETURN nt_date PIPELINED
IS
-- normalize inputs to be as-of midnight
v_from DATE :=
TRUNC(NVL(p_from, SYSDATE));
v_to DATE := TRUNC(NVL(p_to, SYSDATE));
BEGIN
LOOP
EXIT WHEN v_from > v_to;
PIPE ROW (obj_date(v_from));
v_from := v_from + 1; -- next. calendar day
END LOOP;
RETURN;
END generate_dates;
INSERT INTO absences
(employee_id, absent_date)
SELECT e.employee_id,
c.date_val
FROM employees e
INNER JOIN table(generate_dates(date '2021-07-20', DATE '2021-07-31')) c
PARTITION BY ( e.employee_id )
ON (SUBSTR(e.work_days,
TRUNC(c.date_val) -
TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
WHERE NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.date_val = h.holiday_date
)
AND NOT EXISTS(
SELECT 1
FROM timeoff t
WHERE e.employee_id = t.employee_id
AND t.timeoff_date = c.date_val
)
AND NOT EXISTS(
SELECT 1
FROM emp_attendance ea
WHERE e.employee_id = ea.employee_id
AND TRUNC(ea.start_date) = c.date_val
)
ORDER BY
e.employee_id,
c.date_val
;
Don't use lots of procedures and/or a functions; just use a single query:
SELECT e.employee_id,
c.day
FROM employees e
INNER JOIN (
WITH calendar ( start_date, end_date ) AS (
SELECT DATE '2021-07-01', DATE '2021-07-30' FROM DUAL
UNION ALL
SELECT start_date + 1, end_date
FROM calendar
WHERE start_date + 1 <= end_date
)
SELECT start_date AS day
FROM calendar
) c
PARTITION BY ( e.employee_id )
ON (SUBSTR(e.work_days, TRUNC(c.day) - TRUNC(c.day, 'IW') + 1, 1) = 'Y')
WHERE NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.day = h.holiday_date
)
AND NOT EXISTS(
SELECT 1
FROM timeoff t
WHERE e.employee_id = t.employee_id
AND t.timeoff_date = c.day
)
ORDER BY
e.employee_id,
c.day
Notes:
This assumes that your work_days column aligns with the ISO week; if it does not then you will need to adjust the substring.
Do not use TO_CHAR(date_value, 'D') as users will get different results depending on their NLS_TERRITORY session setting.
db<>fiddle here

Alternative to window function in MariaDB 10.1

I have a windows function (over, partitioned by) in my code:
FROM (SELECT wp_posts.id,
wp_postmeta.post_id,
post_title,
post_type,
meta_value,
Row_number()
OVER(
partition BY post_title
ORDER BY wp_postmeta.meta_value) rn
but apparently this isn't supported on MariaDB before 10.2 (-I am using 10.1). Could someone please suggest alternative code which is both efficient and works on MariaDB 10.1 also?
dbfiddle provided, unfortunately with only MariaDB 10.2 as the oldest; can't test 10.1 directly here
create table wp_posts (
ID integer primary key auto_increment,
post_title varchar(30),
post_type varchar(30)
);
✓
create table wp_postmeta (
ID integer primary key auto_increment,
post_id integer,
meta_key varchar(30) not null default '_regular_price',
meta_value integer not null
);
✓
insert into wp_posts (post_title, post_type) values
('Apple Pie','Product'),
('French Toast','Product'),
('Shepards Pie','Product'),
('Jam Pie','Product'),
('Jam Pie','Product'),
('Plate','Not a Product'),
('Bucket','Not a Product'),
('Chequebook','Not a Product'),
('French Toast','Product'),
('French Toast','Product'),
('Banana','Product'),
('Banana','Product'),
('Banana','Product');
✓
insert into wp_postmeta (post_id, meta_value) values
(1,10),
(2,5),
(3,9),
(4,8),
(5,11),
(6,12),
(7,10),
(8,6),
(9,1),
(10,1),
(11,7),
(12,2),
(13,2);
✓
-- Deleting all duplicate products in wp_posts table
DELETE FROM wp_posts
WHERE id IN (SELECT id
FROM (SELECT id,
post_title,
post_type,
meta_value
FROM (SELECT wp_posts.id,
wp_postmeta.post_id,
post_title,
post_type,
meta_value,
Row_number()
OVER(
partition BY post_title
ORDER BY wp_postmeta.meta_value) rn
FROM wp_postmeta
JOIN wp_posts
ON wp_postmeta.post_id = wp_posts.id
WHERE wp_posts.post_type = 'Product'
AND wp_postmeta.meta_key = '_regular_price'
) t
WHERE t.rn <> 1) AS aliasx);
✓
db<>fiddle here

Oracle : It give a error in a dynamic view

I have two table one is employee and one is department. I am creating the dynamic view that will rank all departments by salary. The view should pull information from Department and Employee, sum the salary by department, and rank the department by salary.
CREATE TABLE DEPARTMENT
(DEPARTMENT_ID NUMBER PRIMARY KEY,
DEPARTMENT_NAME VARCHAR(30) NOT NULL
);
CREATE TABLE JOBS
(JOB_ID NUMBER PRIMARY KEY,
JOB_TITLE VARCHAR(35) NOT NULL,
MIN_SALARY DECIMAL NOT NULL,
MAX_SALARY DECIMAL NOT NULL
);
CREATE TABLE EMPLOYEES
(EMPLOYEE_ID NUMBER PRIMARY KEY,
FIRST_NAME VARCHAR(20) NOT NULL,
LAST_NAME VARCHAR(25) NOT NULL,
EMAIL VARCHAR(25) NOT NULL,
PHONE_NUMBER VARCHAR(20) NOT NULL,
HIRE_DATE DATE NOT NULL,
JOB_ID NUMBER NOT NULL,
SALARY DECIMAL NOT NULL,
DEPARTMENT_ID NUMBER NOT NULL,
CONSTRAINT emp_job_fk FOREIGN KEY(JOB_ID) REFERENCES JOBS(JOB_ID),
CONSTRAINT emp_department_fk FOREIGN KEY(DEPARTMENT_ID) REFERENCES DEPARTMENT(DEPARTMENT_ID)
);
INSERT INTO DEPARTMENT (DEPARTMENT_ID,DEPARTMENT_NAME)
VALUES(1,'IT');
INSERT INTO DEPARTMENT (DEPARTMENT_ID,DEPARTMENT_NAME)
VALUES(2,'Sales');
INSERT INTO JOBS (JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY)
VALUES (1,'IT Administrator',250000.00,50000.00);
INSERT INTO JOBS (JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY)
VALUES (2,'Salesman',200000.00,40000.00);
Here is I create so far but it give me a error
ORA-00979: not a GROUP BY expression
00979. 00000 - "not a GROUP BY expression"
*Cause:
*Action:
Error at Line: 4 Column: 9
Here is my code
select department_id,department_name,total_salary
from(
select department_id,department_name, SALARY, count(*) as total_salary from(
select dep.department_id , dep.department_name ,emp.SALARY,
DENSE_RANK() OVER (PARTITION BY department_name ORDER BY salary)
from departments dep
inner join employees emp on dep.DEPARTMENT_ID = emp.DEPARTMENT_ID
)
GROUP BY SALARY)
Your query needs to join EMPLOYEES (to get the salaries) to DEPARTMENT (to get the DEPARTMENT_NAME). Calculate the total salary for each department by summing the employee salaries, not counting them. The GROUP BY needs to include the non-aggregated columns.
Then you need to rank the departments by the total salary per department. This query ranks the departments with highest salary = 1. It uses a left join to cater for departments with no employees.
select department_id
, department_name
, total_salary
, rank() over (order by total_salary desc) as dept_rank
from (
select d.department_id
, d.department_name
, sum(e.SALARY) as total_salary
from department d
left join employees e
on e.department_id = d.department_id
group by d.department_id
, d.department_name
)
/

Resources