Dividing counts on db2? - sql

Hello I have a query where I am pulling the count of new hires who have downloaded our app and the count of new hires.
I am trying to divide the two to find the percentage of those who have downloaded the app
but whenever I run my query my result is 0
both data and driver_id are varchar
here is my query
SELECT ROUND(COUNT(DATA) / (
SELECT COUNT(D2.DRIVER_ID)
FROM DRIVER D2
WHERE ACTIVE_IN_DISP = 'True'
AND START_DATE >= '10/1/2015'
), 4)
FROM CUSTOM_DATA C
,DRIVER D
WHERE CUSTDEF_ID = '50'
AND SRC_TABLE_KEY = DRIVER_ID
AND ACTIVE_IN_DISP = 'True'
AND START_DATE >= '10/1/2015'
thanks in advance!

Problem is you are getting truncate to INT
convert your data to float before the division
SELECT ROUND( COUNT(DATA) *1.0 /
(SELECT COUNT(D2.DRIVER_ID)
....

Just use conditional aggregation. It is easier and less prone to error:
SELECT ROUND(AVG(CASE WHEN CUSTDEF_ID = '50' THEN 1.0 ELSE 0
END), 4)
FROM CUSTOM_DATA C JOIN
DRIVER D
ON SRC_TABLE_KEY = DRIVER_ID
WHERE ACTIVE_IN_DISP = 'True' AND
START_DATE >= '2015-10-01';
Not having to repeat the logic in the WHERE using a subquery makes the query much less prone to error. Plus, it is probably faster to get rid of the subquery as well.

Related

SQL Rowwise comparison between groups

Question
The following is a snippet of my data:
Create Table Emps(person VARCHAR(50), started DATE, stopped DATE);
Insert Into Emps Values
('p1','2015-10-10','2016-10-10'),
('p1','2016-10-11','2017-10-11'),
('p1','2017-10-12','2018-10-13'),
('p2','2019-11-13','2019-11-13'),
('p2','2019-11-14','2020-10-14'),
('p3','2020-07-15','2021-08-15'),
('p3','2021-08-16','2022-08-16');
db<>fiddle.
I want to use T-SQL to get a count of how many persons fulfil the following criteria at least once - multiples should also count as one:
For a person:
One of the dates in 'started' (say s1) is larger than at least one of the dates in 'ended' (say e1)
s1 and e1 are in the same year, to be set manually - e.g. '2021-01-01' until '2022-01-01'
Example expected response
If I put the date range '2016-01-01' until '2017-01-01' somewhere in a WHERE / HAVING clause, the output should be 1 as only p1 has both a start date and an end date that fall in 2016 where the start date is larger than the end date:
s1 = '2016-10-11', and e1 = '2016-10-10'.
Why can't I do this myself
The reason I'm stuck is that I don't know how to do this rowwise comparison between groups. The question requires comparing values across columns (start with end) across rows, within a person ID.
Use conditional aggregation to get the maximum start date and the minimum stop date in the given range.
select person
from emps
group by person
having max(case when started >= '2016-01-01' and started < '2017-01-01'
then started end) >
min(case when stopped >= '2016-01-01' and stopped < '2017-01-01'
then stopped end);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=45adb153fcac9ce72708f1283cac7833
I would choose to use a self-outer-join with an exists correlation, it should be pretty much the most performant, all things being equal.
select Count(*)
from emps e
where exists (
select * from emps e2
where e2.person = e.person
and e2.stopped > e.started
and e.started between '20160101' and '20170101'
and e2.started between '20160101' and '20170101'
);
You said you plan to set the dates manually, so this works where we set the start date in one CTE, and the end date in another CTE. Then we calculate the min/max for each, and use that criteria in the query where statement.
with min_max_start as (
select person,
min(started) as min_start, --obsolete
max(started) as max_start
from emps
where started >= '2016-01-01'
group by person
),
min_max_end as (
select person,
min(stopped) as min_stop,
max(stopped) as max_stop --obsolete
from emps
where stopped < '2017-01-01'
group by person
)
select count(distinct e.person)
from emps e
join min_max_start mms
on e.person = mms.person
join min_max_end mme
on e.person = mme.person
where mms.max_start> mme.min_stop
Output: 1
Try the following:
With CTE as
(
Select D.person, D.started, T.stopped,
case
when Year(D.started) = Year(T.stopped) and D.started > T.stopped
then 1
else 0
end as chk
From
(Select person, started From Emps Where started >= '2016-01-01') D
Join
(Select person, stopped From Emps Where stopped <= '2017-01-01') T
On D.person = T.person
)
Select Count(Distinct person) as CNT
From CTE
Where chk = 1;
To get the employee list who met the criteria use the following on the CTE instead of the above Select Count... query:
Select person, started, stopped
From CTE
Where chk = 1;
See a demo from db<>fiddle.

Query for fuel usage with a subquery

Searched Stackoverflow, and was not able to find an answer to my question (maybe it's there, but did not see one).
Have the following query which lists the mileage used, fuel cost, and fuel quantity for multiple vehicles stored at a location in the MAIN table. Also have a sub-query to calculate the cost per mile - and in that subquery is a WHERE clause to not calculate unless the fuel_qty > 0 (cannot divide by zero, unless you are Chuck Norris - ha ha). Also need to display a zero for the fuel_qty (in line 3 of this query) if it is a zero value. Am getting an error with this query - saying that it is "not a single-group group function". Is there something which I am missing or not seeing?
Have tried adding cost_per_mile to the group by clause, but received an "invalid identifier" error. Then also added a group by clause to the subquery - but that also did not work.
select cost.mileage_useage
, cost.fuel_cost
, cost.fuel_qty
, (select (sum(cost1.mileage_usage / cost1.fuel_qty) * cost1.fuel_cost)
from cost cost1
where cost1.fuel_qty > 0) as cost_per_mile
from cost
inner join main on main.equip_no = cost.equip_no
where main.stored_loc = 4411
group by
cost.mileage_useage
, cost.fuel_cost
, cost.fuel_qty
Why doesn't this do what you want?
select c.mileage_useage, c.fuel_cost, c.fuel_qty,
(sum(c.mileage_usage) * c.fuel_cost /
nullif(c.fuel_qty, 0)
) as cost_per_mile
from cost c inner join
main m
on m.equip_no = c.equip_no
where main.stored_loc = 4411
group by c.mileage_useage, c.fuel_cost, c.fuel_qty
Believe I found an answer - thank you for all your help! This takes into consideration if the mileage useage = 0 or is a negative number. Also if the fuel quantity = 0 then that portion of the equation is not possible to divide by a zero value. It may look a little strange, but this works!
select cost.mileage_useage
, cost.fuel_cost
, cost.fuel_qty
, ( sum(((CASE WHEN cost.mileage_usage = 0 THEN 1
WHEN cost.mileage_usage < 0 THEN TO_NUMBER(NULL)
ELSE cost.mileage_usage END)
/ DECODE(eq_cost.fuel_qty,0, 1, eq_cost.fuel_qty))
* eq_cost.fuel_cost )) as cost_per_mile
from cost
inner join main on main.equip_no = cost.equip_no
where main.stored_loc = 4411
group by cost.mileage_useage
, cost.fuel_cost
, cost.fuel_qty
You can further simplify it as following:
select cost.mileage_useage
, cost.fuel_cost
, cost.fuel_qty
, sum((CASE WHEN cost.mileage_usage = 0 THEN eq_cost.fuel_cost
WHEN cost.mileage_usage > 0 THEN cost.mileage_usage * eq_cost.fuel_cost END)
/ (case when eq_cost.fuel_qty = 0 then 1 else eq_cost.fuel_qty end)) as cost_per_mile
from cost
inner join main on main.equip_no = cost.equip_no
where main.stored_loc = 4411
group by cost.mileage_useage
, cost.fuel_cost
, cost.fuel_qty;
Cheers!!

Multiple AND conditions in case statement

I've got an Oracle table CUST with Fields YEAR and STATUS. I'm trying to write a statement that will select the records where ((CUST.YEAR = '2017' and CUST.STATUS = 'ACTIVE') and where (CUST.YEAR = '2018' and CUST.STATUS = 'ACTIVE')). When both of these statements are true, I want to return '1' else '0'
select *
from cust
where cust.year = '2017' and cust.status = 'Active'
returns the correct number of rows (394).
select *
from cust
where cust.year = '2018' and cust.status = 'Active'
returns the correct number of rows (451).
Here's where the wheels fall off (due to my inexperience with SQL scripting). I've tried to combine the two select statements and I either gets tens of thousands of rows or errors because of incorrect syntax. This is before even attempting to use a case statement to return a comparative result (if both of these conditions, then '1' else '0').
I realize this is probably pretty elementary stuff but the syntax is beyond me as of now. Would someone be kind enough to help me construct this statement?
The few times I've posted to this forum I've learned things that help to make me more self-sufficient, so I offer my thanks in advance.
You can take advantage of IN here:
select *
from cust
where cust.year IN ('2017', '2018') and
cust.status = 'Active'
The way I understood it, or might be what you're looking for, i.e.
select *
from cust
where (cust.year = '2017' and cust.status = 'Active')
or (cust.year = '2018' and cust.status = 'Active');
which - as William says - leads to
where cust.status = 'Active'
and cust.year in ('2017', '2018')
If I have understood your requirement correctly you want to determine whether your table has records for both pairs of conditions, that is , whether you have active records for both 2017 and 2018. The solutions provided so far will assert whether either condition is true but not both.
So here is a solution which satisfies your actual requirement. We have a WITH clause which selects one active record for each year (which is all your need). The inline view then counts how many records it found. If the count is two then you have active records for both years.
with tst as (
select cust.cust_id, cust.year
from cust
where cust.year = '2017'
and cust.status = 'Active'
group by cust.cust_id, cust.year
union all
select cust.cust_id, cust.year
from cust
where cust.year = '2018'
and cust.status = 'Active'
group by cust.cust_id, cust.year
)
select cust_id
, case when cnt = 2 then 1 else 0 end as your_condition
from ( select cust_id, count(*) as cnt
from tst
group by cust_id )
/

very slow oracle select statement

i have a select statement that contains hundred thousands if data, however the execution time is very slow which take longer than 15 minutes. Is the any way that i can improve the execution time for this select statement.
select a.levelP,
a.code,
a.descP,
(select nvl(SUM(amount),0) from ca_glopen where code = a.code and acc_mth = '2016' ) ocf,
(select nvl(SUM(amount),0) from ca_glmaintrx where code = a.code and to_char(doc_date,'yyyy') = '2016' and to_char(doc_date,'yyyymm') < '201601') bcf,
(select nvl(SUM(amount),0) from ca_glmaintrx where jum_amaun > 0 and code = a.code and to_char(doc_date,'yyyymm') = '201601' ) debit,
(select nvl(SUM(amount),0) from ca_glmaintrx where jum_amaun < 0 and code = a.code and to_char(doc_date,'yyyymm') = '201601' ) credit
from ca_chartAcc a
where a.code is not null
order by to_number(a.code), to_number(levelP)
please help me for the way to up speed my query and result.TQ
Your primary problem is that most of your subqueries use functions on your search criteria, including some awkward ones on your dates. It's much better to flip that around and explicitly qualify the expected range, by supplying actual dates (a one month range is usually a small percentage of total rows, so this is very likely to hit an index).
SELECT Chart.levelP, Chart.code, Chart.descP,
COALESCE(GL_SUM.ocf, 0),
COALESCE(Transactions.bcf, 0),
COALESCE(Transactions.debit, 0),
COALESCE(Transactions.credit, 0),
FROM ca_ChartAcc Chart
LEFT JOIN (SELECT code, SUM(amount) AS ocf
FROM ca_GLOpen
WHERE acc_mth = '2016') GL_Sum
ON GL_Sum.code = Chart.code
LEFT JOIN (SELECT code,
SUM(amount) AS bcf,
SUM(CASE WHEN amount > 0 THEN amount) AS debit,
SUM(CASE WHEN amount < 0 THEN amount) AS credit,
FROM ca_GLMainTrx
WHERE doc_date >= TO_DATE('2016-01-01')
AND doc_date < TO_DATE('2016-02-01')) Transactions
ON Transactions.code = Chart.code
WHERE Chart.code IS NOT NULL
ORDER BY TO_NUMBER(Chart.code), TO_NUMBER(Chart.levelP)
If you only need a few codes, it may yield better results to push those values into the subqueries as well (although note that the optimizer is likely to perform this for you).
It may be possible to remove the calls to TO_NUMBER(...) from the ORDER BY clause; however, this depends on the format of the values, since how they were encoded may change the ordering of results.

SQL Month comparison to declared variable

select
sc.locationid, --here to get a result
(
if month(date()) between 8 and 12 then #semTerm = 'S1'
else #semTerm = 'S2'
end if
)as #semTerm
from student_class sc
where #semTerm = sc.semester
;
In db2 student management system. Read only access. Desired outcome is If Jan thru June, S2 else if Aug thru Dec, S1. Trying to setup a variable based on the current date stamp where the month is segregated then assigned to a variable then compared against a column in student_class table.
Have tried case statements as well with no luck. Unable to declare #semTerm without error above select statement. Looked at where clause solution also. Am I out in left field? Seems simple but struggling with the syntax. Part of a larger statement with locationID as one column in student_class.
You can't really use an IF statement in a simple SELECT statement, you must use CASE:
select
sc.locationid, --here to get a result
case
when month(current date) between 8 and 12 then 'S1'
when month(current date) between 1 and 6 then 'S2'
else ''
end as semTerm
from
student_class sc
If you want to find only the students for the current semester, then you would want to move the CASE statement into the WHERE clause:
select
sc.locationid, --here to get a result
sc.semester,
...
from
student_class sc
where
sc.semester = case
when month(current date) between 8 and 12 then 'S1'
when month(current date) between 1 and 6 then 'S2'
end
A CASE expression is generally the way to implement conditional logic in a SELECT statement. However, it would be more efficient if you didn't have to recalculate it on every row, right?
One approach would be to "precalculate" it in a common table expression, and join to that as your selection criteria:
with v (semester) as
( values
case
when month(current date) > 7 then 'S1'
when month(current date) < 7 then 'S2'
else null
end
)
select
sc.locationid,
sc.semester,
...
from
student_class sc
join
v on sc.semester = v.semester;
OR
If you you find that this current semester value would be useful in many other places, another approach could be to create a 'global session variable' to hold the value. (except on z/OS)
create or replace variable
v_sememster char(2)
default
case
when month(current date) > 7
then 'S1'
when month(current date) < 7
then 'S2'
else null
end;
Then you can have a very simple query:
select
sc.locationid,
sc.semester,
...
from
student_class sc
where
sc.semester = v_semester;