Alternative to case statement - sql

I need your suggestions on writing an SQL query other than using CASE statement in Oracle.I have a table named SYSTEM_SPECS with following data
Customer_Id Disk_space_allocated
C001 44G
C002 1300G
C003 1503G
C004 1780G
I wrote following SQL query using Oracle case statement to get count of customer_id based on ranges for disk_space_allocated
select
case
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 300
then '1-300'
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 500
then '300-500'
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 700
then '500-700'
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 900
then '700-900'
else '900+'
end as diskallocated,
count(*) as number_of_customers
from SYSTEM_SPECS
group by
case
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 300
then '1-300'
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 500
then '300-500'
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 700
then '500-700'
when to_number(substr(disk_space_allocated,0,length(disk_space_allocated) -1 )) <= 900
then '700-900'
else '900+'
end;
Can this query be written is some other form?

Firstly your logic is wrong you are calculating <=700 and saying it between 500-700, if it is <=700 then by default it is <=500 and <=300. then we can not call it between 500-700. Try below query
select
count(*) as number_of_customers,b.var as "Less Than below value"
from SYSTEM_SPECS a,
(select 300 + (level-1)*200 var
from dual
connect by level <= 4) b
where to_number(substr(a.disk_space_allocated,0,length(a.disk_space_allocated) -1 )) <= b.var
group by b.var
order by b.var
Final answer for your question with the help of #Alex
with ranges as (
select case when level = 1 then 0 else 100 end + (level-1) * 200 low_value,
case when level = 5 then 99999999 else 100 + (level) * 200 end high_value
from dual
connect by level <= 5
)
select r.low_value ||'-'|| r.high_value as diskallocated,
count(customer_id) as number_of_customers
from ranges r
left join system_specs ss
on to_number(substr(ss.disk_space_allocated, 1, length(ss.disk_space_allocated) -1 )) > r.low_value
and to_number(substr(ss.disk_space_allocated, 1, length(ss.disk_space_allocated) -1 )) <= r.high_value
group by r.low_value, r.high_value
order by r.low_value, r.high_value;

Related

Select all days in a week, when calendarweek is given

certainly something very simple, but for an application I would like to know how, if I know the calendar week, I can display the first to the last day of the week per row.
Currently, I am only shown the day in which content is present.
I would like to have 7 days displayed (as date, not necessarily with name) whether they are empty or not.
SELECT
MIN( TO_CHAR(LP_BELEGUNG.GEN_DATUM,'DD.MM.YYYY')) AS GRD_ROW_ID
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 1 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_1
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 2 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_2
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 3 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_3
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 99 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_4
FROM
LP_BELEGUNG
WHERE
TO_CHAR(LP_BELEGUNG.GEN_DATUM, 'WW') = 37 --the calendar week
If you want one row per day, for a week number in a given year, then you can generate all the dates in that week and use an outer join to look for matching rows in your table, if there are any.
Unfortunately Oracle doesn't supply a simple way to get a date from a week number, but based on how the WW element is defined you can start from the first day of the year and add the appropriate number of days to get the start of the week:
select trunc(sysdate, 'YYYY') + (7 * 37) - 7 from dual;
TRUNC(SYSDATE,'YYYY')+(7*37)-7
10-SEP-22
... where 37 is the week number, and I've assumed you're looking at the current year (if not, use a fixed date like date '2022-01-01' instead of trunc(sysdate)).
You can then get all the days in that week with a hierarchical query:
select trunc(sysdate, 'YYYY') + (7 * 37) + level - 8
from dual
connect by level <= 7;
TRUNC(SYSDATE,'YYYY')+(7*37)+LEVEL-8
10-SEP-22
11-SEP-22
12-SEP-22
13-SEP-22
14-SEP-22
15-SEP-22
16-SEP-22
Then use those values in an inline view or CTE, and left-join to your table using a date range (to allow for non-midnight times but still allowing an index on that column to be used), grouping by the date:
with cte (dt) as (
select trunc(sysdate, 'YYYY') + (7 * 37) + level - 8
from dual
connect by level <= 7
)
SELECT
TO_CHAR(cte.dt, 'DD.MM.YYYY') AS GRD_ROW_ID
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 1 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_1
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 2 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_2
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 3 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_3
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 99 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_4
FROM
cte
LEFT JOIN
LP_BELEGUNG
ON
LP_BELEGUNG.GEN_DATUM >= cte.dt AND LP_BELEGUNG.GEN_DATUM < cte.dt + 1
GROUP BY
cte.dt
ORDER BY
cte.dt
With some sample data to mimic your original result, that gives:
GRD_ROW_ID
ANZAHL_ART_1
ANZAHL_ART_2
ANZAHL_ART_3
ANZAHL_ART_4
10.09.2022
0
0
0
0
11.09.2022
0
0
0
0
12.09.2022
0
0
0
0
13.09.2022
0
0
0
0
14.09.2022
0
0
0
0
15.09.2022
0
0
0
0
16.09.2022
1
0
0
7
fiddle
Here is a set of dates counted and divided to days of the week using to_char and pivot.
select *
from
(
select dt
,to_char(dt, 'D') as dow
from t
) t
pivot (count(dt) for dow in('1', '2', '3', '4', '5', '6', '7')) p
'1'
'2'
'3'
'4'
'5'
'6'
'7'
1
1
0
0
1
3
1
Fiddle
Use conditional aggregation:
SELECT TO_CHAR(MIN(GEN_DATUM),'DD.MM.YYYY') AS GRD_ROW_ID,
COUNT( DISTINCT
CASE
WHEN ART = 1
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_1_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 1
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_1_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 1
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_1_DAY7,
COUNT( DISTINCT
CASE
WHEN ART = 2
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_2_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 2
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_2_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 2
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_2_DAY7,
COUNT( DISTINCT
CASE
WHEN ART = 3
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_3_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 3
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_3_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 3
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_3_DAY7,
COUNT( DISTINCT
CASE
WHEN ART = 99
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_4_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 99
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_4_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 99
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_4_DAY7
FROM LP_BELEGUNG
WHERE TO_CHAR(GEN_DATUM, 'WW') = 37

Oracle SQL Show all month of a year, with or without value ORA-01841

I have a problem with which I despair, I have data distributed over days, and would like to display this for the entire year in months and once in weeks.
My problem with the months that I get in the select my data displayed (for January, September) but I want that all months for a selected year are displayed, even if they are empty. For this I have made myself a "WITH" (copied) and now try to join this, but get an ORA-01841 error.
And how do I implement the whole construct to display only the weeks.
WITH MONAT_ZAEHLER (MZ) AS
(
SELECT
TO_CHAR(ADD_MONTHS(TO_DATE('01.2022','MM.YYYY'),LEVEL -1),'Month', 'NLS_DATE_LANGUAGE = GERMAN') AS GRD_ROW_ID
FROM
DUAL
CONNECT BY LEVEL <= 12
)
SELECT
TO_CHAR(GEN_DATUM,'Month', 'NLS_DATE_LANGUAGE = GERMAN') AS GRD_ROW_ID
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 1 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "1"
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 2 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "2"
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 3 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "3"
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 99 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "99"
FROM
LP_BELEGUNG
FULL OUTER JOIN MONAT_ZAEHLER ON TRUNC(LP_BELEGUNG.GEN_DATUM, 'Month') = MONAT_ZAEHLER.MZ
WHERE
TO_CHAR(GEN_DATUM, 'YYYY') = '2022'
GROUP BY
TO_CHAR(GEN_DATUM,'Month', 'NLS_DATE_LANGUAGE = GERMAN')
The error is because you're converting the month to a name string in the CTE, then trying to convert it again for the GRD_ROW_ID alias.
The solution is basically the same as your previous question, but now you want the CTE to have one row per month - which you are doing, but you should leave it as a date type in the CTE, not convert it to a string there:
with cte (dt) as (
select add_months(date '2022-01-01', level - 1)
from dual
connect by level <= 12
)
... then convert that actual date value to a string:
SELECT
TO_CHAR(cte.dt, 'Month', 'NLS_DATE_LANGUAGE = GERMAN') AS GRD_ROW_ID
...
... and outer join to your actual table as before, using a date range:
FROM
cte
LEFT JOIN
LP_BELEGUNG
ON
LP_BELEGUNG.GEN_DATUM >= cte.dt AND LP_BELEGUNG.GEN_DATUM < add_months(cte.dt, 1)
GROUP BY
cte.dt
ORDER BY
cte.dt
... this time looking for values where the the GEN_DATUM is greater than or equal to cte.dt value (again, as before), which is midnight on the first day of the first day of the month; and less than add_months(cte.dt, 1), which is midnight on the first day of the first day of the following month. So for January, that will be >= 2022-01-01 00:00:00 and < 2022-02-01 00:00:00, which is all possible dates and times during that month.
GRD_ROW_ID
ANZAHL_ART_1
ANZAHL_ART_2
ANZAHL_ART_3
ANZAHL_ART_4
Januar
0
0
0
0
Februar
0
0
0
0
März
0
0
0
0
April
0
0
0
0
Mai
0
0
0
0
Juni
0
0
0
0
Juli
0
0
0
0
August
0
0
0
0
September
1
1
1
7
Oktober
0
0
0
0
November
0
0
0
0
Dezember
0
0
0
0
fiddle
To get a row for every week of the year you would do something similar again, but in blocks of 7 days:
with cte (dt) as (
select date '2022-01-01' + 7 * (level - 1)
from dual
connect by level <= 53
)
SELECT
TO_CHAR(cte.dt, 'YYYY-WW') AS GRD_ROW_ID
...
FROM
cte
LEFT JOIN
LP_BELEGUNG
ON
LP_BELEGUNG.GEN_DATUM >= cte.dt AND LP_BELEGUNG.GEN_DATUM < cte.dt + 7
AND LP_BELEGUNG.GEN_DATUM < add_months(trunc(cte.dt, 'YYYY'), 12)
GROUP BY
cte.dt
ORDER BY
cte.dt
which has an extra check in the join to stop it including data from week 53 which is actually in the following year - which I'm guessing you woudl want to do.
fiddle

sum for each customers and create a summary table

I have Table A :
Customer_ID Card_number Amount_of_deals
1 221 100
1 222 350
2 223 200
3 334 700
3 344 650
4 544 1500
I want to create a new table with ranges of the amount and count of customers in each range.
the new table should be :
Range Number_of_customers
0-500 2
500-1000 0
1000-1500 2
How can I create this table?
Thanks in advance
You can use case expression with group by :
select (case when amount >= 0 and amount <= 500 then '0-500'
when amount > 500 and amount <= 1000 then '501-1000'
when amount > 1000 and amount <= 1500 then '1001-1500'
else '1500+'
end) as Range, count(distinct custmore_id) as Number_of_customers
from table t
group by (case when amount >= 0 and amount <= 500 then '0-500'
when amount > 500 and amount <= 1000 then '501-1000'
when amount > 1000 and amount <= 1500 then '1001-1500'
else '1500+'
end);
If you are using SQL Server then you can use apply & use SELECT .. INTO statement :
select range, count(distinct custmore_id) as Number_of_customers
into new_table
from table t cross apply
( values (case when amount >= 0 and amount <= 500 then '0-500'
when amount > 500 and amount <= 1000 then '501-1000'
when amount > 1000 and amount <= 1500 then '1001-1500'
else '1500+'
end)
) tt(range)
group by range;
Because you want 0 values, you need a left join somewhere. I would recommend:
select v.range, count(c.customer_id)
from (values ('0-500', 0, 500),
('501-1000', 500, 1000),
('1001-1500', 1000, 1500)
) v(range, lo, hi) left join
(select customer_id, sum(amount_of_deals) as num
from a
group by customer_id
) c
on c.num > v.lo and c.num <= v.hi
group by v.range
order by min( v.lo );
Here is a db<>fiddle.

Oracle SQL - How to get counts based up on dates into multiple columns in ORACLE

I have data like this in a Oracle table
REGID SESSION_START_DATETIME USAGEID
1 7/11/2016 1
1 6/10/2016 1
1 6/09/2016 1
1 5/04/2016 1
1 5/04/2016 1
1 5/04/2016 1
I need the output like
REGID 0-30_days_usagecount 31-60_days_usagecount 61-90_days_usagecount
1 1 2 3
usagecount is basically count(usage_id)... how to write a query for this problem?
Please help
Here is a way to do this using the PIVOT operator.
with
inputs (REGID, SESSION_START_DATETIME, USAGEID) as (
select 1 , to_date('7/11/2016', 'mm/dd/yyyy'), 1 from dual union all
select 1 , to_date('6/10/2016', 'mm/dd/yyyy'), 1 from dual union all
select 1 , to_date('6/09/2016', 'mm/dd/yyyy'), 1 from dual union all
select 1 , to_date('5/04/2016', 'mm/dd/yyyy'), 1 from dual union all
select 1 , to_date('5/04/2016', 'mm/dd/yyyy'), 1 from dual union all
select 1 , to_date('5/04/2016', 'mm/dd/yyyy'), 1 from dual
)
select * from (
select regid, session_start_datetime,
case when trunc(sysdate) - session_start_datetime between 0 and 30
then '0-30_days_usagecount'
when trunc(sysdate) - session_start_datetime between 31 and 60
then '31-60_days_usagecount'
when trunc(sysdate) - session_start_datetime between 61 and 90
then '61-90_days_usagecount'
end
as col
from inputs
)
pivot ( count(session_start_datetime)
for col in ( '0-30_days_usagecount', '31-60_days_usagecount',
'61-90_days_usagecount'
)
)
;
REGID '0-30_days_usagecount' '31-60_days_usagecount' '61-90_days_usagecount'
---------- ---------------------- ----------------------- -----------------------
1 1 2 3
1 row selected.
SELECT regid,
SUM(
CASE
WHEN session_start_dt <= (sysdate - 30)
THEN usage_id
ELSE 0
END) T1,
SUM(
CASE
WHEN session_start_dt > (sysdate - 30)
AND session_start_date <= (sysdate -60)
THEN usage_id
ELSE 0
END) T2,
SUM(
CASE
WHEN session_start_dt > (sysdate - 60)
AND session_start_date <= (sysdate -90)
THEN usage_id
ELSE 0
END) T3
FROM temp
GROUP BY regid;
Try this
select (select count(USAGEID) from tablename where DATEDIFF(day, SESSION_START_DATETIME, CURDATE()) < 31 and REGID = A. REGID) AS 0-30_days_usagecount, (select count(USAGEID) from tablename where DATEDIFF(day, SESSION_START_DATETIME, CURDATE()) > 30 and DATEDIFF(day, SESSION_START_DATETIME, CURDATE()) < 61 and REGID = A. REGID) AS 31-60_days_usagecount, (select count(USAGEID) from tablename where DATEDIFF(day, SESSION_START_DATETIME, CURDATE()) > 60 and DATEDIFF(day, SESSION_START_DATETIME, CURDATE()) < 91 and REGID = A. REGID) AS 61-90_days_usagecount from tablename A group by A.REGID

Same query with different parameters

I have a query to calculate a sum over last 12 months like:
select part_no,
count(part_no) r12
from t1
where (t1.created<=sysdate and t1.created>=add_months(sysdate,-12)
Is it possible to create a query that also shows rolling 6 and rolling 3 in the same query like:
part_no r12 r6 r3
-----------------
100 8 2 1
200 12 1 0
300 10 4 4
If you just want to know COUNT of all items for last 12, 6 and 3 you can change your query as follows.
SELECT part_no
,COUNT(CASE WHEN t1.created <= sysdate
AND t1.created >= add_months(sysdate, -12) THEN 1
ELSE NULL
END) r12
,COUNT(CASE WHEN t1.created <= sysdate
AND t1.created >= add_months(sysdate, -6) THEN 1
ELSE NULL
END) r6
,COUNT(CASE WHEN t1.created <= sysdate
AND t1.created >= add_months(sysdate, -3) THEN 1
ELSE NULL
END) r3
FROM t1
GROUP BY part_no
You can calculate date difference (in months) in subquery and group by it. But don't forget about query performance.
You probably can try something like this:
SELECT part_no,
SUM( IF(t1.created>=add_months(sysdate,-12), 1, 0) ) r12,
SUM( IF(t1.created>=add_months(sysdate,-6), 1, 0) ) r6,
SUM( IF(t1.created>=add_months(sysdate,-3), 1, 0) ) r3
FROM t1
WHERE t1.created<=sysdate
GROUP BY part_no