postgres inner join sum for when no relation exists - ruby

I am trying to run a simple query where I get a sum of gardeners who have zero support visits. I would like to add other cases, but first I would like to get this zero thing right. This is what I have come up with:
Gardener.from(Gardener.joins(:support_visits).group(:gardener_id).select("CASE WHEN count(gardener_id) = 0 THEN 1 END AS zero_count"),:t).select("sum(t.zero_count) as tot_zero_count")
Which gives me the following sql query:
SELECT sum(t.zero_count) as tot_zero_count
FROM
(
SELECT CASE WHEN count(gardener_id) = 0 THEN 1 END AS zero_count
FROM "gardeners" INNER JOIN "support_visits"
ON "support_visits"."gardener_id" = "gardeners"."id"
GROUP BY gardener_id
) t
The query runs but gives me nil.

Your current query uses count(gardener_id) = 0 which is never true because any group will have at least one record. Instead, you can LEFT JOIN the gardeners table to the support_visits table. Any gardener which does not appear in the support_visits table will not match to a visit, leaving a null value which you can then count.
SELECT SUM(CASE WHEN "support_visits"."gardener_id" IS NULL THEN 1 ELSE 0 END) AS zero_count
FROM "gardeners" LEFT JOIN "support_visits"
ON "support_visits"."gardener_id" = "gardeners"."id"
Here is the Ruby ActiveRecord code for this query:
Gardener.from(Gardener.joins("LEFT JOIN `support_visits` ON gardeners.id = support_visits.gardener_id").select("SUM(CASE WHEN "support_visits"."gardener_id" IS NULL THEN 1 ELSE 0 END) AS zero_count"))

Related

i have sql script which uses two query to get the result that i expect , how can i achieve this with the single query ,example below

query1 where condition = "condition1" ; queryresult1 = number1
query2 where condition = "condition2" ; queryresult2 = number2
I want number1-number2 , how can i make this possible with just a single query
Assuming i understand your question correctly, easiest way is joining two table and perform your whatever calculation.
select valueone, valuetwo, valueone - valuetwo as finalresult
from (select 18 as valueone from dual where 1 = 1) -- query one)
inner join (select 6 as valuetwo from dual where 1 = 1) -- query two)
on 1 = 1 -- join condition
where 1 = 1; -- some condition

Get default value (and context column) when group by returns no records in Oracle

I have a query that I need for it to return a record even when there are no records. In the case where there are records, I simply want those records returned. On the other hand, when there are no records, I need it to still return a record but with the value for the "context" column (the GROUP BY column) equal to the value of the GROUP BY column that did not meet the criteria and a default value for aggregate function/column (e.g., 0). I tried a subquery:
SELECT
(
SELECT
CONTEXT,
SUM(VAL)
FROM
A_TABLE
WHERE
COL = 'absent'
GROUP BY
CONTEXT
)
FROM
DUAL;
but anything greater than one column in the subquery SELECT clause fails w/ a "too many values" message.
I also tried a UNION (with a little more context to more faithfully represent my situation):
SELECT
*
FROM
(
SELECT
CONTEXT,
SUM(VAL)
FROM
A_TABLE
WHERE
COL = 'absent'
GROUP BY
CONTEXT
UNION
SELECT
CONTEXT,
0
FROM
B_TABLE
)
AB_TABLE
INNER JOIN C_TABLE C -- just a table that I need to join to
ON
C.ID = AB_TABLE.C_ID
WHERE
C.ID = 10
AND ROWNUM = 1 -- excludes 2nd UNION subquery result when 1st returns record;
This one does work but I don't know why since the 2nd UNION subquery does not seem to be expressly connected w/ the first (I need the 2nd CONTEXT value to be the same as the 1st for the case where the 1st returns no records). The problem is that the real query does not return any records when I try to implement a similar strategy. I would like to see if there's a better way to approach this problem and perhaps get it to work for the real query (not included as it is too large and somewhat sensitive).
I am not sure I understand the question, but let's try.
I believe what you are saying is this. You have a table called A_TABLE, with columns CONTEXT, VAL, COL (and perhaps others as well).
You want to group by CONTEXT, and get the sum of VAL but only for those rows where COL = 'absent'. Otherwise you want to return a default value (let's say 0).
This can be done with conditional aggregation. The condition is in a CASE expression within the SUM, not in a WHERE clause (as you saw already, if you filter by COL='absent', in a WHERE clause, the query - past the WHERE clause - has no knowledge of the CONTEXT values that don't appear in any rows with COL = 'absent').
If the "default value" was NULL, you could do it like this:
select context, sum(case when col = 'absent' then value end) as val
from a_table
group by context
;
If the default value is anything other than NULL, the temptation may be to use NVL() around the sum. However, if VAL may be NULL, then it is possible that SUM(VAL) is NULL even when there are rows with COL = 'absent'. To address that possibility, you must leave the sum as NULL in those cases, and instead set the value to 0 (or whatever other "default value") only when there are NO rows with COL = 'absent'. Here is one way to do that. Still a standard "conditional" aggregate query:
select context,
case when count(case when col = 'absent' then 1 end) > 0
then sum(case when col = 'absent' then value end)
else 0 -- or whatever "default value" you must assign here
end as val
from a_table
group by context
;
Here's another way you could handle it that avoids the two additional tables (B_TABLE and C_TABLE).
SELECT context
, MAX(val)
FROM (
SELECT context
, SUM(val) as val
FROM a_table
WHERE col = 'absent'
GROUP BY context
UNION
SELECT context
, 0 as val
FROM a_table
) t
GROUP BY context
This assumes the default value you want to return is 0 and that any value in A_TABLE.VAL will be a positive integer.
http://sqlfiddle.com/#!4/c6ca0/20
SELECT b.context
, sum(a.val)
FROM b_table b
LEFT OUTER JOIN a_table a
ON a.context = b.context
AND a.col = 'absent'
GROUP BY b.context

Oracle 11g Count query slowness

I have written one query which provides me a count of item type using group by.
The select query if I fire without group up and count provides results in less than a second on sql developer.
However count query runs for ever that I have to kill the process.
Below is the query ..
WITH permList
AS (SELECT user_perm_level,
item_category,
active
FROM usr_perm_levels
WHERE usr_pk = '7'
UNION ALL
SELECT home_key,
'ALL',
0
FROM user_home_list
WHERE usr_pk = 7
UNION ALL
SELECT home_key,
'UYT',
0
FROM user_home_list
WHERE usr_pk = 7
UNION ALL
SELECT home_key,
'ABR',
0
FROM user_home_list
WHERE usr_pk = 7),
list1
AS (SELECT a.parent_home_key,
b.item_category
FROM user_hier a
INNER JOIN indhome b
ON a.child_home_key = b.user_perm_level),
list2
AS ((SELECT oh.child_home_key,
u.item_category
FROM indhome U
INNER JOIN user_hier oh
ON u.user_perm_level = oh.parent_home_key
AND u.active = 1)
UNION ALL
SELECT user_perm_level,
item_category
FROM indhome)
SELECT type,
Count(*)
FROM inventory
WHERE is_available = 0
AND active = 1
AND EXISTS (SELECT 0
FROM inventory_auth c
WHERE EXISTS (SELECT 0
FROM orglist1
WHERE c.user_perm_level = orglist1.parent_home_key
AND c.item_category =
orglist1.item_category
AND c.active = 1)
OR EXISTS (SELECT 0
FROM orglist2
WHERE c.user_perm_level = orglist2.child_home_key
AND c.item_category =
orglist2.item_category)
AND inventory.item_key = c.item_key
AND inventory.item_category = c.item_category)
GROUP BY type;
In explain plan I see that Cost of group by clause is 145297221173.
Now here inventory table has 10 million records and the exists block which works around inventory table also runs into 15 million records.
Where as views permList, list1 and list2 have records only in 100s.
I have tried various combination but none of them seems to bring better performance. Such as I have tried inner join between inventory and inner query with exists clause but that doesn't help much.

oracle procedure cursor query when case statement

CURSOR BULKUPDATE IS
SELECT SUM(B.ACCOUNT_BALANCE) AS ACCOUNT_BALANCE,C.CIF AS CIF_ID FROM _ACCOUNTS_STAGING2 B JOIN _RELATION_STAGING2 C
ON B.ACCOUNT_IDENTIFICATION_NUMBER = C.ACCOUNT_IDENTIFICATION_NUMBER AND B.SOURCEID=C.SOURCEID JOIN _CUSTOMER_STAGING2 A ON A.CIF=C.CIF AND A.SOURCEID=C.SOURCEID WHERE C.ROLE_ON_ACCOUNT IN
(Select Rollonaccount From _Roleaccount_Master Where Aggregatebalance='Y')
And upper(B.Scheme_Type) In (Select Scheme_Type From _Schema_Type_Master Where
Depository_Account = 'Y') Group By C.Cif;
Rec_Bulkupdate Bulkupdate%Rowtype;
I am using this query to sum account balances based on different cif and source. The question is I want to calculate four different types of sum on the basis of _Schema_Type_Master. For example I want to check now current_account='Y' instead of Depository_Account='Y'
_ACCOUNTS_STAGING2 B JOIN _RELATION_STAGING2 C
ON B.ACCOUNT_IDENTIFICATION_NUMBER = C.ACCOUNT_IDENTIFICATION_NUMBER AND B.SOURCEID=C.SOURCEID JOIN _CUSTOMER_STAGING2 A ON A.CIF=C.CIF AND A.SOURCEID=C.SOURCEID WHERE C.ROLE_ON_ACCOUNT IN
(Select Rollonaccount From _Roleaccount_Master Where Aggregatebalance='Y')
And upper(B.Scheme_Type) In (Select Scheme_Type From _Schema_Type_Master Where
current_account='Y') Group By C.Cif;
Rec_Bulkupdate Bulkupdate%Rowtype;
Is there any way or do I need to write four different cursors for that??
You can remove dipository_account='Y' and current_account='Y' and use case in select as -
SELECT SUM(CASE WHEN Depository_Account = 'Y' THEN B.ACCOUNT_BALANCE ELSE 0 END) AS DIPOSITORY_ACCOUNT_BALANCE,
SUM(CASE WHEN current_account = 'Y' THEN B.ACCOUNT_BALANCE ELSE 0 END) AS CURRENT_ACCOUNT_BALANCE
and then rest of your code. You will get two different columns for sum of Depository account and Current account.
And if filter for dipository_account='Y' and current_account='Y' is required, then use them in where condition with or operator :
AND (dipository_account='Y' or current_account='Y')

Reference parent query column in subquery (Oracle)

How can I reference a column outside of a subquery using Oracle? I specifically need to use it in the WHERE statement of the subquery.
Basically I have this:
SELECT Item.ItemNo, Item.Group
FROM Item
LEFT OUTER JOIN (SELECT Attribute.Group, COUNT(1) CT
FROM Attribute
WHERE Attribute.ItemNo=12345) A ON A.Group = Item.Group
WHERE Item.ItemNo=12345
I'd like to change WHERE Attribute.ItemNo=12345 to WHERE Attribute.ItemNo=Item.ItemNo in the subquery, but I can't figure out if this is possible. I keep getting "ORA-00904: 'Item'.'ItemNo': Invalid Identifier"
EDIT:
Ok, this is why I need this kind of structure:
I want to be able to get a count of the "Error" records (where the item is missing a value) and the "OK" records (where the item has a value).
The way I have set it up in the fiddle returns the correct data. I think I might just end up filling in the value in each of the subqueries, since this would probably be the easiest way. Sorry if my data structures are a little convoluted. I can explain if need be.
My tables are:
create table itemcountry(
itemno number,
country nchar(3),
imgroup varchar2(10),
imtariff varchar2(20),
exgroup varchar2(10),
extariff varchar2(20) );
create table itemattribute(
attributeid varchar2(10),
tariffgroup varchar2(10),
tariffno varchar2(10) );
create table icav(
itemno number,
attributeid varchar2(10),
value varchar2(10) );
and my query so far is:
select itemno, country, imgroup, imtariff, im.error "imerror", im.ok "imok", exgroup, extariff, ex.error "exerror", ex.ok "exok"
from itemcountry
left outer join (select sum(case when icav.itemno is null then 1 else 0 end) error, sum(case when icav.itemno is not null then 1 else 0 end) ok, tariffgroup, tariffno
from itemattribute ia
left outer join icav on ia.attributeid=icav.attributeid
where (icav.itemno=12345 or icav.itemno is null)
group by tariffgroup, tariffno) im on im.tariffgroup=imgroup and imtariff=im.tariffno
left outer join (select sum(case when icav.itemno is null then 1 else 0 end) error, sum(case when icav.itemno is not null then 1 else 0 end) ok, tariffgroup, tariffno
from itemattribute ia
left outer join icav on ia.attributeid=icav.attributeid
where (icav.itemno=12345 or icav.itemno is null)
group by tariffgroup, tariffno) ex on ex.tariffgroup=exgroup and extariff=ex.tariffno
where itemno=12345;
It's also set up in a SQL Fiddle.
You can do it in a sub-query but not in a join. In your case I don't see any need to. You can put it in the join condition.
select i.itemno, i.group
from item i
left outer join ( select group, itemno
from attribute b
group by group itemno ) a
on a.group = i.group
and i.itemno = a.itemno
where i.itemno = 12345
The optimizer is built to deal with this sort of situation so utilise it!
I've changed the count(1) to a group by as you need to group by all columns that aren't aggregated.
I'm assuming that your actual query is more complicated than this as with the columns you're selecting this is probably equivilent to
select itemno, group
from item
where itemno = 12345
You could also write your sub-query with an analytic function instead. Something like count(*) over ( partition by group).
As an aside using a keyword as a column name, in this case group is A Bad Idea TM. It can cause a lot of confusion. As you can see from the code above you have a lot of groups in there.
So, based on your SQL-Fiddle, which I've added to the question I think you're looking for something like the following, which doesn't look much better. I suspect, given time, I could make it simpler. On another side note explicitly lower casing queries is never worth the hassle it causes. I've followed your naming convention though.
with sub_query as (
select count(*) - count(icav.itemno) as error
, count(icav.itemno) as ok
, min(itemno) over () as itemno
, tariffgroup
, tariffno
from itemattribute ia
left outer join icav
on ia.attributeid = icav.attributeid
group by icav.itemno
, tariffgroup
, tariffno
)
select ic.itemno, ic.country, ic.imgroup, ic.imtariff
, sum(im.error) as "imerror", sum(im.ok) as "imok"
, ic.exgroup, ic.extariff
, sum(ex.error) as "exerror", sum(ex.ok) as "exok"
from itemcountry ic
left outer join sub_query im
on ic.imgroup = im.tariffgroup
and ic.imtariff = im.tariffno
and ic.itemno = im.itemno
left outer join sub_query ex
on ic.exgroup = ex.tariffgroup
and ic.extariff = ex.tariffno
and ic.itemno = ex.itemno
where ic.itemno = 12345
group by ic.itemno, ic.country
, ic.imgroup, ic.imtariff
, ic.exgroup, ic.extariff
;
You can put WHERE attribute.itemno=item.itemno inside the subquery. You are going to filter the data anyway, filtering the data inside the subquery is usually faster too.

Resources