Inclusion of both inclusive and exclusive range in the same table - sql

i have the following table generated by SQL TABLE A
timeinterval count(exclusive range)
0-6 2
0-12 5
0-18 10
i want a table like this TABLE B
timeinterval count(exclusive range) count(inclusive range)
1-6 2 2
1-12 5 3
1-18 10 5
i have already generated table A and need table B. can i do something in SQL where i can add a query in the code for table A and do something like this (0-12)-(0-6) for 2nd row in table B.
code used for generating table A is
with ranges as
(
select 6 as val, 1 as count_all
union all
select 12, 1
union all
select 18, 1
union all
select 24, 1
union all
select 30, 1
union all
select 36, 1
union all
select 42, 1
union all
select 48, 1
union all
select 1, 0
)
select case when ranges.count_all = 0
then 'more'
else convert (varchar(10), ranges.val)
end [MetLifeExperienceMonths],
sum (case when (ranges.count_all = 0 and GoldListHistogram.MetLifeExperienceMonths>=1)
or
(GoldListHistogram.MetLifeExperienceMonths<= ranges.val and GoldListHistogram.MetLifeExperienceMonths>=1)
then 1 end) [count],
count(EmployeeID) as 'Total'
into yy
from GoldListHistogram
cross join ranges
where MetLifeExperienceMonths > 0
group by ranges.val, ranges.count_all
i need to modify the query such that i can subtract first two rows value for "count(exclusive range)" for every row staring from the 2nd row..like for 0-12(time interval) row i need to output a value that is difference of the first two rows..like row(i)=count(i)-count(i-1).
first column gives the time interval in 5 years (in months) second column calculates no. of employees in the exclusive range like (0-6,0-12,0-18)..6 ,12,18 being no. of months third column calculates no. of employees in the exclusive range like (0-6,6-12,12-18)

Could you not just add a start value to ranges? Something like:
with ranges as
(
select 6 as val, 0 as start, 1 as count_all
union all
select 12, 7, 1
union all
select 18, 13, 1
union all
select 24, 19, 1
union all
select 30, 25, 1
union all
select 36, 31, 1
union all
select 42, 37, 1
union all
select 48, 43, 1
union all
select 1, 49, 0
)
select case when ranges.count_all = 0
then 'more'
else convert (varchar(10), ranges.val)
end [MetLifeExperienceMonths],
sum (case when (ranges.count_all = 0 and GoldListHistogram.MetLifeExperienceMonths>=1)
or
(GoldListHistogram.MetLifeExperienceMonths=1)
then 1 else 0 end) [count inclusive],
sum (case when (ranges.count_all = 0 and GoldListHistogram.MetLifeExperienceMonths>=1)
or
(GoldListHistogram.MetLifeExperienceMonths=ranges.start)
then 1 else 0 end) [count exclusive],
count(EmployeeID) as 'Total'
into yy
from GoldListHistogram
cross join ranges
where MetLifeExperienceMonths > 0
group by ranges.val, ranges.count_all;

Related

Count based on a date condition in PLSQLDev

I need help to do a count based on a date condition.
I have a DB similar to the following:
ManDB
ID
report_date
traffic_v
traffic_ul
traffic_dl
a
1/12/2021
0
0
100
a
2/12/2021
0
0
100
a
3/12/2021
100
0
100
a
4/12/2021
100
0
100
b
1/12/2021
0
100
100
b
2/12/2021
0
0
0
b
3/12/2021
0
100
0
b
4/12/2021
100
100
0
I need you to count the data to zero, for which I have the query:
SELECT
ID AS SECTOR,
SUM(TRAFFIC) TRAFICO_VOZ,
SUM(TRAFFIC_DL_G) + SUM(TRAFFIC_DL_E) TRAFFIC_DL,
SUM(TRAFFIC_UL_G) + SUM(TRAFFIC_UL_E) TRAFFIC_UL
FROM
MainDB
GROUP BY ID
HAVING SUM(TRAFFIC) = 0
OR (SUM(TRAFFIC_DL_G) + SUM(TRAFFIC_DL_E)) = 0
OR (SUM(TRAFFIC_UL_G) + SUM(TRAFFIC_UL_E)) = 0
But I need you to count me from the current date backwards, how many days has it been zero
You should only count me from the last record in zero.
So you should get the following result:
Expected result
ID
traffic_v
count_v
traffic_ul
count_ul
traffic_dl
count_dl
a
200
0
0
4
400
0
b
100
0
200
0
0
3
I do not know how to set the condition so that it detects the date on which I began to have zero records and perform the count of days until the current date.
In cases where the register is different from zero, the count must be restarted.
The db is updated daily.
the counts are displayed correctly with the query, as I only care about zero data.
try to use SUM / CASE, but it counts me from the minimum date that it finds at zero, regardless of having a different record
You can use a MODEL clause:
SELECT id,
count_traffic_v,
sum_traffic_v,
count_traffic_ul,
sum_traffic_ul,
count_traffic_dl,
sum_traffic_dl
FROM (
SELECT *
FROM (
SELECT m.*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY report_date DESC) AS rn
FROM mainDB m
)
MODEL
PARTITION BY (id)
DIMENSION BY (report_date)
MEASURES (
rn,
traffic_v,
0 AS count_traffic_v,
0 AS sum_traffic_v,
traffic_ul,
0 AS count_traffic_ul,
0 AS sum_traffic_ul,
traffic_dl,
0 AS count_traffic_dl,
0 AS sum_traffic_dl
)
RULES AUTOMATIC ORDER (
count_traffic_v[report_date] = CASE traffic_v[cv()]
WHEN 0
THEN COALESCE(count_traffic_v[cv() - 1] + 1, 1)
ELSE 0
END,
sum_traffic_v[report_date] = CASE traffic_v[cv()]
WHEN 0
THEN 0
ELSE COALESCE(sum_traffic_v[cv() - 1], 0) + traffic_v[cv()]
END,
count_traffic_ul[report_date] = CASE traffic_ul[cv()]
WHEN 0
THEN COALESCE(count_traffic_ul[cv() - 1] + 1, 1)
ELSE 0
END,
sum_traffic_ul[report_date] = CASE traffic_ul[cv()]
WHEN 0
THEN 0
ELSE COALESCE(sum_traffic_ul[cv() - 1], 0) + traffic_ul[cv()]
END,
count_traffic_dl[report_date] = CASE traffic_dl[cv()]
WHEN 0
THEN COALESCE(count_traffic_dl[cv() - 1] + 1, 1)
ELSE 0
END,
sum_traffic_dl[report_date] = CASE traffic_dl[cv()]
WHEN 0
THEN 0
ELSE COALESCE(sum_traffic_dl[cv() - 1], 0) + traffic_dl[cv()]
END
)
)
WHERE rn = 1;
Which, for the sample data:
CREATE TABLE maindb (ID, report_date, traffic_v, traffic_ul, traffic_dl) AS
SELECT 'a', DATE '2021-12-01', 0, 0, 100 FROM DUAL UNION ALL
SELECT 'a', DATE '2021-12-02', 0, 0, 100 FROM DUAL UNION ALL
SELECT 'a', DATE '2021-12-03', 100, 0, 100 FROM DUAL UNION ALL
SELECT 'a', DATE '2021-12-04', 100, 0, 100 FROM DUAL UNION ALL
SELECT 'b', DATE '2021-12-01', 0, 100, 100 FROM DUAL UNION ALL
SELECT 'b', DATE '2021-12-02', 0, 0, 0 FROM DUAL UNION ALL
SELECT 'b', DATE '2021-12-03', 0, 100, 0 FROM DUAL UNION ALL
SELECT 'b', DATE '2021-12-04', 100, 100, 0 FROM DUAL;
Outputs:
ID
COUNT_TRAFFIC_V
SUM_TRAFFIC_V
COUNT_TRAFFIC_UL
SUM_TRAFFIC_UL
COUNT_TRAFFIC_DL
SUM_TRAFFIC_DL
a
0
200
4
0
0
400
b
0
100
0
200
3
0
db<>fiddle here

sql grouping grades

I have a table for subjects as follows:
id Subject Grade Ext
100 Math 6 +
100 Science 4 -
100 Hist 3
100 Geo 2 +
100 CompSi 1
I am expecting output per student in a class(id = 100) as follows:
Grade Ext StudentGrade
6 + 1
6 0
6 - 0
5 + 0
5 0
5 - 0
4 + 0
4 0
4 - 1
3 + 0
3 1
3 - 0
2 + 1
2 0
2 - 0
1 + 0
1 1
1 - 0
I would want this done on oracle/sql rather than UI. Any inputs please.
You should generate rows first, before join them with your table like below. I use the with clause here to generate the 18 rows in your sample.
with rws (grade, ext) as (
select ceil(level/3), decode(mod(level, 3), 0, '+', 1, '-', null)
from dual
connect by level <= 3 * 6
)
select r.grade, r.ext, nvl2(t.Ext, 1, 0) studentGrade
from rws r
left join your_table t
on t.Grade = r.Grade and decode(t.Ext, r.Ext, 1, 0) = 1
order by 1 desc, decode(r.ext, null, 2, '-', 3, '+', 1)
You could do something like this. In the WITH clause I generate two small "helper" tables (really, inline views) for grades from 1 to 6 and for "extensions" of +, null and -. In the "extensions" view I also create an "ordering" column to use in ordering the final output (if you are wondering why I included that).
Also in the WITH clause I included sample data - you will have to remove that and instead use your actual table name in the main query.
The idea is to cross-join "grades" and "extensions", and left-outer-join the result to your input data. Count the grades from the input data, grouped by grade and extension, and after filtering the desired id. The decode thing in the join condition is needed because for extension we want to treat null as equal to null - something that decode does nicely.
with
sample_inputs (id, subject, grade, ext) as (
select 100, 'Math' , 6, '+' from dual union all
select 100, 'Science', 4, '-' from dual union all
select 100, 'Hist' , 3, null from dual union all
select 100, 'Geo' , 2, '+' from dual union all
select 100, 'CompSi' , 1, null from dual
)
, g (grade) as (select level from dual connect by level <= 6)
, e (ord, ext) as (
select 1, '+' from dual union all
select 2, null from dual union all
select 3, '-' from dual
)
select g.grade, e.ext, count(t.grade) as studentgrade
from g cross join e left outer join sample_inputs t
on t.grade = g.grade and decode(t.ext, e.ext, 0) = 0
and t.id = 100 -- change this as needed!
group by g.grade, e.ext, e.ord
order by g.grade desc, e.ord
;
OUTPUT:
GRADE EXT STUDENTGRADE
----- --- ------------
6 + 1
6 0
6 - 0
5 + 0
5 0
5 - 0
4 + 0
4 0
4 - 1
3 + 0
3 1
3 - 0
2 + 1
2 0
2 - 0
1 + 0
1 1
1 - 0
It looks like you want sparse data to be filled in as part of joining students and subjects.
Since Oracle 10g the correct way to do this has been with a "partition outer join".
The documentation has examples.
https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6

Is there a way to find active users in SQL?

I'm trying to find the total count of active users in a database. "Active" users here as defined as those who have registered an event on the selected day or later than the selected day. So if a user registered an event on days 1, 2 and 5, they are counted as "active" throughout days 1, 2, 3, 4 and 5.
My original dataset looks like this (note that this is a sample - the real dataset will run to up to 365 days, and has around 1000 users).
Day ID
0 1
0 2
0 3
0 4
0 5
1 1
1 2
2 1
3 1
4 1
4 2
As you can see, all 5 IDs are active on Day 0, and 2 IDs (1 and 2) are active until Day 4, so I'd like the finished table to look like this:
Day Count
0 5
1 2
2 2
3 2
4 2
I've tried using the following query:
select Day as days, sum(case when Day <= days then 1 else 0 end)
from df
But it gives incorrect output (only counts users who were active on each specific days).
I'm at a loss as to what I could try next. Does anyone have any ideas? Many thanks in advance!
I think I would just use generate_series():
select gs.d, count(*)
from (select id, min(day) as min_day, max(day) as max_day
from t
group by id
) t cross join lateral
generate_series(t.min_day, .max_day, 1) gs(d)
group by gs.d
order by gs.d;
If you want to count everyone as active from day 1 -- but not all have a value on day 1 -- then use 1 instead of min_day.
Here is a db<>fiddle.
A bit verbose, but this should do:
with dt as (
select 0 d, 1 id
union all
select 0 d, 2 id
union all
select 0 d, 3 id
union all
select 0 d, 4 id
union all
select 0 d, 5 id
union all
select 1 d, 1 id
union all
select 1 d, 2 id
union all
select 2 d, 1 id
union all
select 3 d, 1 id
union all
select 4 d, 1 id
union all
select 4 d, 2 id
)
, active_periods as (
select id
, min(d) min_d
, max(d) max_d
from dt
group by id
)
, days as (
select distinct d
from dt
)
select d.d
, count(ap.id)
from days d
join active_periods ap on d.d between ap.min_d and ap.max_d
group by 1
order by 1 asc
You need count by day.
select
id,
count(*)
from df
GROUP BY
id

Finding repeated occurrences with ranking functions

Please help me generate the following query i've been struggling with for some time now. Lets' say I have a simple table with month number and information whether there were any failed events in this particular month
Below a script to generate sample data:
WITH DATA(Month, Success) AS
(
SELECT 1, 0 UNION ALL
SELECT 2, 0 UNION ALL
SELECT 3, 0 UNION ALL
SELECT 4, 1 UNION ALL
SELECT 5, 1 UNION ALL
SELECT 6, 0 UNION ALL
SELECT 7, 0 UNION ALL
SELECT 8, 1 UNION ALL
SELECT 9, 0 UNION ALL
SELECT 10, 1 UNION ALL
SELECT 11, 0 UNION ALL
SELECT 12, 1 UNION ALL
SELECT 13, 0 UNION ALL
SELECT 14, 1 UNION ALL
SELECT 15, 0 UNION ALL
SELECT 16, 1 UNION ALL
SELECT 17, 0 UNION ALL
SELECT 18, 0
)
Given the definition of a "repeated failure ":
When event failure occurs during at least 4 months in any 6 months period then the last month with such failure is a "repeated failure" my query should return the following output
Month Success RepeatedFailure
1 0
2 0
3 0
4 1
5 1
6 0 R1
7 0 R2
8 1
9 0
10 1
11 0 R3
12 1
13 0
14 1
15 0
16 1
17 0
18 0 R1
where:
R1 -1st repeated failure in month no 6 (4 failures in last 6 months).
R2 -2nd repeated failure in month no 7 (4 failures in last 6 months).
R3 -3rd repeated failure in month no 11 (4 failures in last 6 months).
R1 -again 1st repeated failure in month no 18 because Repeated Failures should be again numbered from the beginning when new Repeated Failure occurs for the first time in last 6 reporting periods
Repeated Failures are numerated consecutively because based on its number i must apply appropriate multiplier:
1st repated failure - X2
2nd repeated failure - X4
3rd and more repeated failure -X5.
I'm sure this can be improved, but it works. We essentially do two passes - the first to establish repeated failures, the second to establish what kind of repeated failure each is. Note that Intermediate2 can definitely be done away with, I've only separated it out for clarity. All the code is one statement, my explanation is interleaved:
;WITH DATA(Month, Success) AS
-- assuming your data as defined (with my edit)
,Intermediate AS
(
SELECT
Month,
Success,
-- next column for illustration only
(SELECT SUM(Success)
FROM DATA hist
WHERE curr.Month - hist.Month BETWEEN 0 AND 5)
AS SuccessesInLastSixMonths,
-- next column for illustration only
6 - (SELECT SUM(Success)
FROM DATA hist
WHERE curr.Month - hist.Month BETWEEN 0 AND 5)
AS FailuresInLastSixMonths,
CASE WHEN
(6 - (SELECT SUM(Success)
FROM DATA hist
WHERE curr.Month - hist.Month BETWEEN 0 AND 5))
>= 4
THEN 1
ELSE 0
END AS IsRepeatedFailure
FROM DATA curr
-- No real data until month 6
WHERE curr.Month > 5
)
At this point we have established, for each month, whether it's a repeated failure, by counting the failures in the six months up to and including it.
,Intermediate2 AS
(
SELECT
Month,
Success,
IsRepeatedFailure,
(SELECT SUM(IsRepeatedFailure)
FROM Intermediate hist
WHERE curr.Month - hist.Month BETWEEN 0 AND 5)
AS RepeatedFailuresInLastSixMonths
FROM Intermediate curr
)
Now we have counted the number of repeated failures in the six months leading up to now
SELECT
Month,
Success,
CASE IsRepeatedFailure
WHEN 1 THEN 'R' + CONVERT(varchar, RepeatedFailuresInLastSixMonths)
ELSE '' END
AS RepeatedFailureText
FROM Intermediate2
so we can say, if this month is a repeated failure, what cardinality of repeated failure it is.
Result:
Month Success RepeatedFailureText
----------- ----------- -------------------------------
6 0 R1
7 0 R2
8 1
9 0
10 1
11 0 R3
12 1
13 0
14 1
15 0
16 1
17 0
18 0 R1
(13 row(s) affected)
Performance considerations will depend on on how much data you actually have.
;WITH DATA(Month, Success) AS
(
SELECT 1, 0 UNION ALL
SELECT 2, 0 UNION ALL
SELECT 3, 0 UNION ALL
SELECT 4, 1 UNION ALL
SELECT 5, 1 UNION ALL
SELECT 6, 0 UNION ALL
SELECT 7, 0 UNION ALL
SELECT 8, 1 UNION ALL
SELECT 9, 0 UNION ALL
SELECT 10, 1 UNION ALL
SELECT 11, 0 UNION ALL
SELECT 12, 1 UNION ALL
SELECT 13, 0 UNION ALL
SELECT 14, 1 UNION ALL
SELECT 15, 0 UNION ALL
SELECT 16, 1 UNION ALL
SELECT 17, 0 UNION ALL
SELECT 18, 0
)
SELECT DATA.Month,DATA.Success,Isnull(convert(Varchar(10),b.result),'') +
Isnull(CONVERT(varchar(10),b.num),'') RepeatedFailure
FROM (
SELECT *, ROW_NUMBER() over (order by Month) num FROM
( Select * ,(case when (select sum(Success)
from DATA where MONTH>(o.MONTH-6) and MONTH<=(o.MONTH) ) <= 2
and o.MONTH>=6 then 'R' else '' end) result
from DATA o
) a where result='R'
) b
right join DATA on DATA.Month = b.Month
order by DATA.Month

How to transpose recordset columns into rows

I have a query whose code looks like this:
SELECT DocumentID, ComplexSubquery1 ... ComplexSubquery5
FROM Document
WHERE ...
ComplexSubquery are all numerical fields that are calculated using, duh, complex subqueries.
I would like to use this query as a subquery to a query that generates a summary like the following one:
Field DocumentCount Total
1 dc1 s1
2 dc2 s2
3 dc3 s3
4 dc4 s4
5 dc5 s5
Where:
dc<n> = SUM(CASE WHEN ComplexSubquery<n> > 0 THEN 1 END)
s <n> = SUM(CASE WHEN Field = n THEN ComplexSubquery<n> END)
How could I do that in SQL Server?
NOTE: I know I could avoid the problem by discarding the original query and using unions:
SELECT '1' AS TypeID,
SUM(CASE WHEN ComplexSubquery1 > 0 THEN 1 END) AS DocumentCount
SUM(ComplexSubquery1) AS Total
FROM (SELECT DocumentID, BLARGH ... AS ComplexSubquery1) T
UNION ALL
SELECT '2' AS TypeID,
SUM(CASE WHEN ComplexSubquery2 > 0 THEN 1 END) AS DocumentCount
SUM(ComplexSubquery2) AS Total
FROM (SELECT DocumentID, BLARGH ... AS ComplexSubquery2) T
UNION ALL
...
But I want to avoid this route, because redundant code makes my eyes bleed. (Besides, there is a real possibility that the number of complex subqueries grow in the future.)
WITH Document(DocumentID, Field) As
(
SELECT 1, 1 union all
SELECT 2, 1 union all
SELECT 3, 2 union all
SELECT 4, 3 union all
SELECT 5, 4 union all
SELECT 6, 5 union all
SELECT 7, 5
), CTE AS
(
SELECT DocumentID,
Field,
(select 10) As ComplexSubquery1,
(select 20) as ComplexSubquery2,
(select 30) As ComplexSubquery3,
(select 40) as ComplexSubquery4,
(select 50) as ComplexSubquery5
FROM Document
)
SELECT Field,
SUM(CASE WHEN RIGHT(Query,1) = Field AND QueryValue > 1 THEN 1 END ) AS DocumentCount,
SUM(CASE WHEN RIGHT(Query,1) = Field THEN QueryValue END ) AS Total
FROM CTE
UNPIVOT (QueryValue FOR Query IN
(ComplexSubquery1, ComplexSubquery2, ComplexSubquery3,
ComplexSubquery4, ComplexSubquery5)
)AS unpvt
GROUP BY Field
Returns
Field DocumentCount Total
----------- ------------- -----------
1 2 20
2 1 20
3 1 30
4 1 40
5 2 100
I'm not 100% positive from your example, but perhaps the PIVOT operator will help you out here? I think if you selected your original query into a temporary table, you could pivot on the document ID and get the sums for the other queries.
I don't have much experience with it though, so I'm not sure how complex you can get with your subqueries - you might have to break it down.