QUERY ERROR : ORA-00937: not a single-group group function - sql

I'm trying to query the percentage of correct answers in my oracle db
SELECT
(Count(P2.Id) /(
SELECT Count(*) FROM POSTS P WHERE P.OwnerUserId = 1
AND P.PostTypeId = 2)* 100) AS AcceptedPercentage
FROM
POSTS P1
INNER JOIN
POSTS P2 ON P1.AcceptedAnswerId = P2.Id
WHERE
P2.OwnerUserId = 1
AND
P2.PostTypeId = 2;
But it gives me this error, how can I fix this?

I think you can simplify it using a hierarchical query, however, without any sample data or expected output its difficult to confirm what is expected:
SELECT CASE
WHEN num_posts > 0
THEN num_accepted_answers/num_posts*100
END
AS AcceptedPercentage
FROM (
SELECT Count(CASE WHEN LEVEL = 2 THEN 1 END)
AS num_accepted_answers,
Count(CASE WHEN OwnerUserId = 1 AND PostTypeId = 2 THEN 1 END)
AS num_posts
FROM POSTS
WHERE LEVEL <= 2
START WITH
OwnerUserId = 1
AND PostTypeId = 2
CONNECT BY
PRIOR Id = AcceptedAnswerId
)
Which, for the sample data:
CREATE TABLE posts (id, owneruserid, posttypeid, acceptedanswerid) AS
SELECT 1, 1, 2, NULL FROM DUAL UNION ALL
SELECT 2, 2, 2, 1 FROM DUAL UNION ALL
SELECT 3, 1, 2, NULL FROM DUAL UNION ALL
SELECT 4, 2, 2, 3 FROM DUAL;
Outputs:
ACCEPTEDPERCENTAGE
100
For your query, you can move the main aggregation to a sub-query:
SELECT CASE
WHEN (SELECT Count(*) FROM POSTS P WHERE P.OwnerUserId = 1 AND P.PostTypeId = 2) > 0
THEN total
/(SELECT Count(*) FROM POSTS P WHERE P.OwnerUserId = 1 AND P.PostTypeId = 2)
* 100
END
AS AcceptedPercentage
FROM (
SELECT Count(P2.Id) AS total
FROM POSTS P1
INNER JOIN POSTS P2
ON P1.AcceptedAnswerId = P2.Id
WHERE P2.OwnerUserId = 1
AND P2.PostTypeId = 2
);
db<>fiddle here

Related

Can't figure out how to full join two queries with different values in both tables

I have two queries that look like this:
SELECT
sem.Sem_Jahr,
sem.Sem_KW,
COUNT(*) AS Seminars,
bearb.MA_ID
FROM acc_seminar.t_Seminar sem
JOIN acc_seminar.t_Seminar_Thema semth ON sem.Sem_SemTh_ID = semth.SemTh_ID
JOIN acc_ma.t_Mitarbeiter bearb ON sem.Sem_Berb_MA_ID = bearb.MA_ID
WHERE sem.Sem_Sto != 1 AND semth.SemTh_Typ = 7 AND sem.Sem_Jahr = #Jahr and MA_ID = 372
GROUP BY bearb.MA_ID, sem.Sem_KW, sem.Sem_Jahr
the second query is exactly the same, except the condition is WHERE sem.Sem_Sto != 1 AND semth.SemTh_Typ = 7 AND sem.Sem_Jahr = #Jahr and MA_ID = 372
KW refers to week
I want to show results from both queries, combined. The problem is that the first query may have seminar count value for KW 2, but the second one would have NULL. The problem is, I can't figure out how to join them to get the following desired result:
KW | Seminars from query 1 | Seminars from query 2
----------------------------------------------------
2 | NULL | 5
3 | 8 | NULL
4 | 1 | 4
What I tried:
I tried just putting UNION between these two, but then I only get results from first query.
I also tried to write first query normally and then doing a FULL OUTER JOIN with second query as subquery in JOIN, but then I get results for the first query and results from second query only where the week matches with row from first query.
This whole request seems so banal to me, but I just can't figure it out, it doesn't click in my head on how to join them. Any suggestions?
Alway aim for a minimal, reproducable example. My sample data has way less joins, but should still show your issue and possible solutions.
Sample data
create table data
(
year int,
week int,
flag bit
);
insert into data (year, week, flag) values
(2021, 1, 0),
(2021, 1, 1),
(2021, 1, 1),
(2021, 2, 0),
(2021, 2, 0),
(2021, 2, 0),
(2021, 2, 0),
(2021, 3, 1);
Issue reproduction
Second query as subquery:
select coalesce(f.year, t.year) as year,
coalesce(f.week, t.week) as week,
count(1) as countFalse,
t.countTrue
from data f
full join ( select d.year,
d.week,
count(1) as countTrue
from data d
where d.flag = 1
group by d.year,
d.week ) t
on t.year = f.year
and t.week = f.week
where f.flag = 0 --> issue: week 3 not available for flag = 0, results limited...
group by f.year,
t.year,
f.week,
t.week,
t.countTrue
order by f.year,
f.week;
Result missing week = 3:
year week countFalse countTrue
---- ---- ---------- ---------
2021 1 1 2
2021 2 4 null
Solution 1
Isolate both queries in common table expressions (cte_false, cte_true) and join them without where clause in final select.
with cte_false as
(
select d.year,
d.week,
count(1) as countFalse
from data d
where d.flag = 0
group by d.year,
d.week
),
cte_true as
(
select d.year,
d.week,
count(1) as countTrue
from data d
where d.flag = 1
group by d.year,
d.week
)
select coalesce(f.year, t.year) as year,
coalesce(f.week, t.week) as week,
f.countFalse,
t.countTrue
from cte_false f
full join cte_true t
on t.year = f.year
and t.week = f.week;
Solution 2
Perform all calculations first (cte_count), then use pivot to transform the data.
with cte_count as
(
select d.year,
d.week,
d.flag,
count(1) as countFlag
from data d
group by d.year,
d.week,
d.flag
)
select piv.year,
piv.week,
piv.[0] as countFalse,
piv.[1] as countTrue
from cte_count cc
pivot (max(cc.countFlag) for cc.flag in ([0], [1])) piv;
Result
year week countFalse countTrue
---- ---- ---------- ---------
2021 1 1 2
2021 2 4 null
2021 3 null 1
Fiddle to see things in action.
You can do this using conditional aggregation:
SELECT sem.Sem_Jahr,
sem.Sem_KW,
SUM(CASE WHEN sem.Sem_Sto <> 1 AND semth.SemTh_Typ = 7 AND sem.Sem_Jahr = #Jahr and MA_ID = 372 THEN 1 ELSE 0 END) AS Seminars,
SUM( <whatever the second condition is> THEN 1 ELSE 0 END),
bearb.MA_ID
FROM acc_seminar.t_Seminar sem JOIN
acc_seminar.t_Seminar_Thema semth
ON sem.Sem_SemTh_ID = semth.SemTh_ID JOIN
acc_ma.t_Mitarbeiter bearb
ON sem.Sem_Berb_MA_ID = bearb.MA_ID
GROUP BY bearb.MA_ID, sem.Sem_KW, sem.Sem_Jahr;
If I speculate that the difference is one of the columns, such as semth.SemTh_Typ = 8, then this can be simplified by moving common conditions to the WHERE clause:
SELECT sem.Sem_Jahr,
sem.Sem_KW,
SUM(CASE WHEN semth.SemTh_Typ = 7 THEN 1 ELSE 0 END) AS Seminars,
SUM(CASE WHEN semth.SemTh_Typ = 8 THEN 1 ELSE 0 END),
bearb.MA_ID
FROM acc_seminar.t_Seminar sem JOIN
acc_seminar.t_Seminar_Thema semth
ON sem.Sem_SemTh_ID = semth.SemTh_ID JOIN
acc_ma.t_Mitarbeiter bearb
ON sem.Sem_Berb_MA_ID = bearb.MA_ID
WHERE sem.Sem_Sto <> 1 AND
semth.SemTh_Typ IN (7, 8) AND
sem.Sem_Jahr = #Jahr AND
MA_ID = 372
GROUP BY bearb.MA_ID, sem.Sem_KW, sem.Sem_Jahr;

Oracle SQL: How to select only ID‘s which are member in specific groups?

I want to select only those ID‘s which are in specific groups.
For example:
ID GroupID
1 11
1 12
2 11
2 12
2 13
Here I want to select the ID's which are in the groups 11 and 12 but in no other groups.
So the result should show just the ID 1 and not 2.
Can someone provide a SQL for that?
I tried it with
SELECT ID FROM table
WHERE GroupID = 11 AND GroupID = 12 AND GroupID != 13;
But that didn't work.
You can use aggregation:
select id
from mytable
group by id
having min(groupID) = 11 and max(groupID) = 12
This having condition ensures that the given id belongs to groupIDs 11 and 12, and to no other group. This works because 11 and 12 are sequential numbers.
Other options: if you want ids that belong to group 11 or 12 (not necessarily both), and to no other group, then:
having sum(case when groupId in (11, 12) then 1 end) = count(*)
If numbers are not sequential, and you want ids in both groups (necessarily) and in no other group:
having
max(case when groupID = 11 then 1 end) = 1
and max(case when groupID = 12 then 1 end) = 1
and max(case when groupID in (11, 12) then 0 else 1 end) = 0
SELECT t.id FROM table t
where exists(
SELECT * FROM table
where group = 11
and t.id = id
)
and exists(
SELECT * FROM table
where group = 12
and t.id = id
)
and not exists(
SELECT * FROM table
where group = 13
and t.id = id
)
group by t.id
One method is conditional aggregation:
select id
from t
group by id
having sum(case when groupid = 1 then 1 else 0 end) > 0 and
sum(case when groupid = 2 then 1 else 0 end) > 0 and
sum(case when groupid in (1, 2) then 1 else 0 end) = 0 ;
You can use GROUP BY with HAVING and a conditional COUNT:
SELECT id
FROM table_name
GROUP BY ID
HAVING COUNT( CASE Group_ID WHEN 11 THEN 1 END ) > 0
AND COUNT( CASE Group_ID WHEN 12 THEN 1 END ) > 0
AND COUNT( CASE WHEN Group_ID NOT IN ( 11, 12 ) THEN 1 END ) = 0
Or you can use collections:
CREATE TYPE int_list IS TABLE OF NUMBER(8,0);
and:
SELECT id
FROM table_name
GROUP BY id
HAVING int_list( 11, 12 ) SUBMULTISET OF CAST( COLLECT( group_id ) AS int_list )
AND CARDINALITY( CAST( COLLECT( group_id ) AS int_list )
MULTISET EXCEPT int_list( 11, 12 ) ) = 0
(Using collections has the advantage that you can pass the collection of required values as a single bind parameter whereas using conditional aggregation is probably going to require dynamic SQL if you want to pass a variable number of items to the query.)
Both output:
| ID |
| -: |
| 1 |
db<>fiddle here
Use joins:
SELECT DISTINCT c11.ID
FROM (SELECT ID FROM WORK_TABLE WHERE GROUPID = 11) c11
INNER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID = 12) c12
ON c12.ID = c11.ID
LEFT OUTER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID NOT IN (11, 12)) co
ON co.ID = c11.ID
WHERE co.ID IS NULL;
The INNER JOIN between the first two subqueries ensures that rows exist for both GROUPID 11 and 12, and the LEFT OUTER JOIN and WHERE verify that there are no rows for any other GROUPIDs.
dbfiddle here

2 independent left join queries won't work together

Ultimately I want my output to be a pivot query similar to below which I am comfortable doing
e.g.
Date CO RU ER AB
1/1/18 5 20 0 0
2/1/18 0 5 0 0
3/1/18 0 0 0 0
4/1/18 1 0 0 0
However, to get to that point I want to fill my data set with zero where no data exists
The table holds data similar to the following
Date/time Process_type Status
1/1/18 10:05 150 RU
2/1/18 14:00 150 CO
4/1/18 18:00 100 ER
On any given day there could be no processes.
I have written 2 queries whose purpose was to fill the gaps in the data. ie. fill days and statuses with zero counts where that combination does not exist.
This is a date range query that ensures zeroes are returned if the count is zero for dates where there is no info. This would give something similar to below
Date Count
1/1/18 25
2/1/18 5
3/1/18 0
4/1/18 1
This is a status query that ensures zeroes are returned if that zero is not status is not present
status count
AB 0
RU 2
CO 25
ER 0
I want to join the 2 queries so that I will get zeroes for both dates and status if the count is zero.
Date Status Count
1/1/18 AB 0
1/1/18 CO 0
1/1/18 ER 0
1/1/18 RU 0
2/1/18 AB 0
2/1/18 CO 6
3/1/18 ER 0
4/1/18 RU 1
When I join them up in Query 3 it wont run and gets the following error. I have tried a few different ways with no joy.
Error report -
SQL Error: ORA-00904: "TD"."TMP_DATE": invalid identifier
00000 - "%s: invalid identifier"
Query 1
--
-- Working out dates with nulls if zero count
--
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 5
)
select
count(pi.crtd_tstmp),
td.tmp_date
from
tmp_dates td
left join procedure_instance pi
on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
group by
td.tmp_date
order by
tmp_date;
Query 2
--
-- Working with Categories with zero if no category
--
with status_table as (
select 'CO' as instanceid from dual union
select 'RU' as instanceid from dual union
select 'ER' as instanceid from dual union
select 'AB' as instanceid from dual
)
select
count(pi.crtd_tstmp),
st.instanceid
from
status_table st
left join procedure_instance pi
on (st.instanceid = pi.stat and proc_oid = 150)
group by
st.instanceid
order by
st.instanceid;
Query 3
--
-- join together
--
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 5
),
status_table as (
select 'CO' as instanceid from dual union
select 'RU' as instanceid from dual union
select 'ER' as instanceid from dual union
select 'AB' as instanceid from dual
)
select
count(pi.crtd_tstmp),
td.tmp_date,
st.instanceid
from
tmp_dates td,
status_table st
left join procedure_instance pi
on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
left join procedure_instance pi
on (st.instanceid = pi.stat and proc_oid = 150)
group by
td.tmp_date,
st.instanceid
order by
tmp_date;
Maybe something like this with only one table in the from clause?
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 5
),status_table as (select 'CO' as instanceid from dual union
select 'RU' as instanceid from dual union
select 'ER' as instanceid from dual union
select 'AB' as instanceid from dual)
select count(pi.crtd_tstmp), td.tmp_date, st.instanceid
from tmp_dates td
left join procedure_instance pi on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
right join status_table st on (st.instanceid = pi.stat and proc_oid = 150)
group by td.tmp_date,st.instanceid
order by tmp_date;
I decided to get round this a different way, I used the pivot to fill the process statutes instead of using query 2 and the second join. Works exactly as wanted now.
select Process_created,
nvl(Complete, 0) as Complete,
nvl(Error, 0) as Error,
nvl(Running, 0) as Running,
nvl(Abort, 0) as Abort
from(
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 30
)
select
count(pi.crtd_tstmp) as number_of,
td.tmp_date as Process_created,
pi.stat as status
from
tmp_dates td
left join procedure_instance pi
on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
group by
td.tmp_date,pi.stat
order by
tmp_date)src
pivot(
sum(number_of)
for status in ('CO' as Complete, 'ER' as Error, 'RU' as Running, 'AB' as Abort )) piv order by process_created;
Try this and let me know what errors you get
--
-- join together
--
WITH tmp_dates
AS (
SELECT trunc(sysdate) - LEVEL + 1 AS tmp_date
FROM dual connect BY LEVEL <= 5
)
,status_table
AS (
SELECT 'CO' AS instanceid FROM dual
UNION
SELECT 'RU' AS instanceid FROM dual
UNION
SELECT 'ER' AS instanceid FROM dual
UNION
SELECT 'AB' AS instanceid FROM dual
)
SELECT count(pi.crtd_tstmp)
,td.tmp_date
,st.instanceid
FROM tmp_dates td
,status_table st
LEFT JOIN procedure_instance pi ON (
td.tmp_date = trunc(pi.crtd_tstmp)
AND proc_oid = 150
)
LEFT JOIN procedure_instance pi2 ON (
st.instanceid = pi2.stat
AND proc_oid = 150
)
GROUP BY td.tmp_date
,st.instanceid
ORDER BY td.tmp_date;

Getting distinct rows on SQL query with multiple IIF columns

In SQL Server I am creating a view that shows if a record in table CONTACTS has certain tags in a different table TAGS.
This is my query:
SELECT DISTINCT
contacts.ID, contacts.NAME,
IIF(tags.tag = 'A', 1, 0) as A,
IIF(tags.tag = 'B', 1, 0) as B,
IIF(tags.tag = 'C', 1, 0) as C,
IIF(tags.tag = 'D', 1, 0) as D
FROM
contacts
LEFT JOIN
TAGS ON contacts.ID = TAGS.CONTACT_ID
I would like the results like this:
ID NAME A B C D
------------------------------------
1 BOB 1 0 0 1
1 Charlie 1 0 1 0
but I get
ID NAME A B C D
------------------------------------
1 BOB 1 0 0 0
1 BOB 0 0 0 1
1 Charlie 1 0 0 0
1 Charlie 0 0 1 0
Must be something I overlook, but I can't find it.
You need simply use GROUP BY clause + MAX aggreate instead of DISTINCT
SELECT contacts.ID, contacts.NAME
, MAX(IIF(tags.tag = 'A', 1, 0)) as A
, MAX(IIF(tags.tag = 'B', 1, 0)) as B
, MAX(IIF(tags.tag = 'C', 1, 0)) as C
, MAX(IIF(tags.tag = 'D', 1, 0)) as D
FROM contacts LEFT JOIN
TAGS ON contacts.ID = TAGS.CONTACT_ID
group by
contacts.ID, contacts.NAME
As Gordon Linoff says, use of standard SQL case when is more compatible with all the database (postgres, oracle, sql server ...)
IIF(tags.tag = 'A', 1, 0)
is equivalent to
CASE WHEN tags.tag = 'A' THEN 1 ELSE 0 END
Use group by:
SELECT c.ID, c.NAME,
MAX(CASE WHEN t.tag = 'A' THEN 1 ELSE 0 END) as A,
MAX(CASE WHEN t.tag = 'B' THEN 1 ELSE 0 END) as B,
MAX(CASE WHEN t.tag = 'C' THEN 1 ELSE 0 END) as C,
MAX(CASE WHEN t.tag = 'D' THEN 1 ELSE 0 END) as D
FROM contacts c LEFT JOIN
TAGS t
ON c.ID = t.CONTACT_ID
GROUP BY c.ID, c.NAME;
Note changes the query:
Introduced table aliases. These make the query easier to write and to read.
Removed the SELECT DISTINCT, because you really want a GROUP BY.
Changed IIF() to CASE. I see no reason to use a function designed for backward compatibility to MS Access over the SQL standard function.
SELECT contacts.ID, contacts.NAME
, MAX(IIF(tags.tag = 'A', 1, 0)) as A
, MAX(IIF(tags.tag = 'B', 1, 0)) as B
, MAX(IIF(tags.tag = 'C', 1, 0)) as C
, MAX(IIF(tags.tag = 'D', 1, 0) as D
FROM contacts LEFT JOIN
TAGS ON contacts.ID = TAGS.CONTACT_ID
GROUP BY contacts.ID, contacts.NAME

Left outer join of 3 tables

I am trying to get the count of distinct people of the shaded region.
Table structure is as follows:
customer key
A234 1
A345 4
A12 5
A989 6
HIVE Query:
select count(distinct(a.customer))
from (
select *
from cust
where key in (1,2,3)) c
left outer join (
select *
from cust
where key in (4,5)) a on a.customer= c.customer where c.customer is null
join
(select *
from cust
where key in (6,7,8,9)) d on c.customer = d.customer and d.customer is null;
Error:
missing EOF at 'join' near 'null'
You have a syntax problem because where follows the from clause, and a given select has only one where.
I would just use group by and having. To get the customers:
select c.customer
from cust c
group by c.customer
having sum(case when key in (1, 2, 3) then 1 else 0 end) > 0 and
sum(case when key in (4, 5, 6, 7, 8, 9) then 1 else 0 end) = 0;
You can then count them with a subquery:
select count(*)
from (select c.customer
from cust c
group by c.customer
having sum(case when key in (1, 2, 3) then 1 else 0 end) > 0 and
sum(case when key in (4, 5, 6, 7, 8, 9) then 1 else 0 end) = 0
) c