Group by the Sub-Query which is derived from WITH CLAUSE [duplicate] - sql

This question already has answers here:
ORA-00979 not a group by expression
(10 answers)
Closed 7 years ago.
I'm getting error for my below query, while I'm trying to grouping the sub-query. I built the sub-query using the WITH Clause.
Please correct me
WITH Student AS (SELECT * FROM CLASS WHERE SEX='M')
SELECT NAME, AGE, STATUS, SUM(TOTAL)
(SELECT
NAME,
'15' AS AGE,
CASE WHEN ATTENDANCE > 50 AND ATTENDANCE < 60 THEN 'GOOD'
WHEN ATTENDANCE > 60 'GREAT'
ELSE 'BAD' END AS STATUS
SUM (MARK) AS TOTAL
FROM STUDENT
GROUP BY NAME, ATTENDANCE ) A
GROUP BY NAME, AGE, STATUS
Error: SQL Query not properly ended

I think you are missing a from clause:
WITH Student AS (SELECT * FROM CLASS WHERE SEX='M')
SELECT NAME, AGE, STATUS, SUM(TOTAL)
FROM (SELECT NAME, '15' AS AGE,
(CASE WHEN ATTENDANCE > 50 AND ATTENDANCE < 60 THEN 'GOOD'
WHEN ATTENDANCE > 60 'GREAT'
ELSE 'BAD'
END) AS STATUS,
SUM(MARK) AS TOTAL
FROM STUDENT
GROUP BY NAME, ATTENDANCE
) A
GROUP BY NAME, AGE, STATUS
The query is much longer than it needs to be to achieve the output you want, but this seems to be the problem you have.

Related

Conditional aggregate queries [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I've seen other solutions. Curious if this method of solving these would work.
TABLES:
attendance_events : date | student_id | attendance
all_students : student_id | school_id | grade_level | date_of_birth | hometown
What percent of students attend school on their birthday?
With agg_join as (SELECT att.date as dates, att.attendance as attendance, als.date_of_birth as DOB, att.student_id as student_id
FROM attendance_events att
join all_students als on att.student_id = als.studentid)
Select count(DISTINCT student_id) as total_students,
count( Distinct case when DOB = dates and attendance = TRUE) as count_of_DOBS,
total_students/ count_of_DOBS as percent_of_student
from agg_join
Which grade level had the largest drop in attendance between yesterday and today?
With agg_join as ( SELECT att.date as dates, att.attendance as attendance, als.grade_level as grade
FROM attendance_events att
join all_students als on att.student_id = als.studentid)
Select grade,
case when dates ( 'd', -1, currentdate) and attendance = True then 1
else 0 end as yesterday_att,
case when dates ( 'd', currentdate) and attendance = True then 1
else 0 end as Today_att,
(Today_att - yesterday_att) * -1 AS DIFF
from agg_join
Group by grade
Order by DIFF DESC
Limit 1
What percent of students attend school on their birthday?
SELECT 100.0
* count(*) FILTER (WHERE EXISTS (SELECT FROM attendance_events a
WHERE a.student_id = s.student_id
AND f_mmdd(a.date) = f_mmdd(s.date_of_birth)
))
/ count(*) AS percentage
FROM all_students s;
Where the custom function f_mmdd() is defined here:
How do you do date math that ignores the year?
See:
Convert numeric result to percentage with decimal digits
About the aggregate FILTER clause:
Aggregate columns with additional (distinct) filters
Which grade level had the largest drop in attendance between yesterday and today?
SELECT s.grade_level
, count(*) FILTER (WHERE a.date = CURRENT_DATE - 1) AS yesterday_attendance
, count(*) FILTER (WHERE a.date = CURRENT_DATE) AS today_attendance
FROM attendance_events a
JOIN all_students s USING (student_id)
WHERE a.date IN (CURRENT_DATE, CURRENT_DATE -1) -- logically redundant
GROUP BY s.grade_level
ORDER BY today_attendance - yesterday_attendance
-- short for: yesterday_attendance - today_attendance DESC
LIMIT 1;
WHERE a.date IN (CURRENT_DATE, CURRENT_DATE -1) is logically redundant, but makes the query much faster.
Read up and try to understand why these work, if you are not solving them yourself.

Write 1 SQL query to combine 3 independent tables with functions: group by and case when

I have 3 tables as follows:
Table school_a with 4 columns:
date: (2020-11-31,...)
note: (20, 30, 40,...)
home: (23.45, 45.34, 65.67, ...)
id: (54326, 87332, ...)
Table school_b with 4 columns:
time: (2020-11-31,...)
grade: (34, 54, 34,...)
homework: (12.32, 34.65,...)
user: ('student', 'professor', 'student',...)
Table school_c with 4 columns:
day: (2020-11-31,...)
number: (34, 54, 34,...)
amount: (10.24 AGE, 11.25 AGE, 12.63 AGE, ...)
title: ('54934-ST-string-student-str.st', '54934-ST-string-teacher-str.st',....)
Sorry for the table presentation but it is not easy to put in a table format.
I created a SQL query to calculate what I need for each table but I do not succeed to combine the 3 queries in one. I cannot figure out the logic that I need to use to combine it.
Here is my SQL code for each table:
SELECT
SUM(home/note) AS kpi,
CASE
WHEN id IN (34564, 87423, 89076, 32145, 87653) THEN 'Student'
WHEN id IN (67543, 87413, 78996, 34215 ) THEN 'Teacher'
ELSE 'Other'
END AS role
FROM school_a
WHERE date >= '2020-08-01' AND date <= '2020-08-31'
GROUP BY role
SELECT
SUM(grade)/COUNT(user) AS kpi,
CASE
WHEN user = 'Student' THEN 'Student'
WHEN user = 'Professor' THEN 'Teacher'
ELSE 'Other'
END AS role
FROM school_b
WHERE time >= '2020-08-01' AND time <= '2020-08-31'
GROUP BY role
SELECT
SUM((REPLACE(amount,' AGE',''))/number) AS kpi,
CASE
WHEN title IN ('41320 - ST-STtr-Student-str.st', '89064 - ST-stRst-str-strr user-strr.str/blablabla/strstr') THEN 'Student'
WHEN title IN ('43789 - ST-STred-Teacher-stee.str', '65283-CH-strstrs-teacher-strr.str--STR') THEN 'Teacher'
ELSE 'Other'
END AS role
FROM school_c
WHERE day >= '2020-08-01' AND day <= '2020-08-31'
GROUP BY role
As you understand I need to measure the kpi for each table that have different columns names and different columns meaning for the full month of August 2020.
When I run separately each query I got what I need now I would like to combine all the 3 queries into one. If I create only one query I got a message such as
Error: ambiguous column name: role
Any feedback to improve my current queries is welcome. Thanks for reading.
----- Edited to clarify the result
The expected result is a table with 2 columns (role and kpi) and 3 rows (Student, Teacher, Other).
Using "union" I got almost what I want: 2 columns (role and kpi) and more than 3 rows as the grouping is school and then role. I want only the role and sum the kpi per role.
Have you tried to simply UNION the different outputs?
SELECT SUM(home/note) AS kpi,
CASE
WHEN id IN (34564, 87423, 89076, 32145, 87653) THEN 'Student'
WHEN id IN (67543, 87413, 78996, 34215 ) THEN 'Teacher'
ELSE 'Other'
END AS role
FROM school_a
WHERE date >= '2020-08-01' AND date <= '2020-08-31'
GROUP BY role
UNION
SELECT SUM(grade)/COUNT(user) AS kpi,
CASE
WHEN user = 'Student' THEN 'Student'
WHEN user = 'Professor' THEN 'Teacher'
ELSE 'Other'
END AS role
FROM school_b
WHERE time >= '2020-08-01' AND time <= '2020-08-31'
GROUP BY role
UNION
SELECT SUM((REPLACE(amount,' AGE',''))/number) AS kpi,
CASE
WHEN title IN ('41320 - ST-STtr-Student-str.st', '89064 - ST-stRst-str-strr user-strr.str/blablabla/strstr') THEN 'Student'
WHEN title IN ('43789 - ST-STred-Teacher-stee.str', '65283-CH-strstrs-teacher-strr.str--STR') THEN 'Teacher'
ELSE 'Other'
END AS role
FROM school_c
WHERE day >= '2020-08-01' AND day <= '2020-08-31'
GROUP BY role
I haven't done any checks in any way but as long as the columns match it shouldn't be a problem.

SQL show entries where a specific field shows up multiple times

First off here is the data that I am currently using:
Name, Phone, Email & InvoiceCost
I am trying to write a query that selects the Name, Phone & Email details where
The user (identified by email) has made 2 or more purchases within the last 180 days
The total cost of all of the invoices within these 180 days comes up to over 500
Below is the SQL that I have come up with so far but I doubt that will be of any use at all:
SELECT Name, Phone, Email, InvoiceCost
FROM INVOICE JOIN
(SELECT INVOICE.Email, count(*) AS TotalCount,
SUM(
CASE WHEN INVOICE.InvoiceDate >= (GETDATE() - 180)
THEN 1
ELSE 0
END) AS LastSixMonths
FROM INVOICE
GROUP BY INVOICE.Email) e
ON INVOICE.Email = e.Email
WHERE (e.TotalCount > 1 AND e.LastSixMonths = 1);
I can't figure out how to incorporate the total cost part, please help!
Assuming you would consider unique combinations of Name, Phone, and Email as individual customers:
SELECT Name, Phone, Email, Count(*) AS TotalCount, Sum(InvoiceCost) AS TotalCost
FROM INVOICE
GROUP BY Email, Name, Phone
WHERE DateDiff(d, InvoiceDate, GETDATE()) <= 180
HAVING Count(*) >= 2 OR Sum(InvoiceCost) > 500.0

Multiple counts in one SQL query with grouping

I want to run a query to get a count of open incident and closed incidents grouped by year and month, the below query works fine without the grouping, but once I add the group it will not work!
SELECT (SELECT COUNT(*) AS Opened FROM Incidents) AS Total
(SELECT COUNT(*) AS Solved FROM Incidents WHERE (MONTH(Closedate)=MONTH(Opendate))) AS Solved
GROUP BY YEAR(Incidents.Opendate)
You can use single a SELECT statement with CASE expression
SELECT YEAR(Incidents.Opendate) AS [Year],
MONTH(Incidents.Opendate) AS [Month],
COUNT(*) AS Total,
SUM(CASE WHEN MONTH(Closedate) = MONTH(Opendate) THEN 1 ELSE 0 END) AS Solved
FROM Incidents
GROUP BY YEAR(Incidents.Opendate), MONTH(Incidents.Opendate)
Try:
SELECT
(SELECT COUNT(*) FROM Incidents) as Total ,
(SELECT COUNT(*) FROM Incidents WHERE (Month(Closedate)=MONTH(Opendate))) as Solved
FROM Incidents
Group by YEAR(Incidents.Opendate)
I have managed to solve it, below is the query
select COUNT(CASE WHEN Month(Closedate)=Month(Opendate) then 1 else null end) AS closed,COUNT(*) AS Opened
from incidents
Group by Year(Opendate), Month(Opendate)
Order by Year(Opendate), Month(Opendate)

SQL Grouping By

Using ORACLE SQL.
I have a table 'Employees' with one of the attributes 'hire_date' . My task (book exercise) is to write a SELECT that will show me how many employees were hired in 1995, 1996, 1997 and 1998 .
Something like:
TOTAL 1995 1996 1997 1998
-----------------------------------------
20 4 5 29 2
Individually is easy to count the number of employees for every year, eg:
SELECT
COUNT(*),
FROM
employees e
WHERE
e.hire_date like '%95'
But I am having difficulties when I have to 'aggregate' the data in the needed format .
Any suggestions ?
I'm assuming your hire_date is a varchar2, since you are doing a "like" clause in your example.
Will a simple table with one row per year do?
If so, try this in Oracle:
select case grouping(hire_date)
when 0 then hire_date
else 'TOTAL'
end hire_date,
count(hire_date) as count_hire_date
from employees
group by rollup(hire_date);
That should give something like:
hire_date count_hire_date
1995 10
1996 20
1997 30
TOTAL 60
If you do need to pivot your results into something like you've shown in your question, then you can do the following if you know the distinct set of years prior to running the query. So for example, if you knew that you only had 1995, 1996 and 1997 in your table, then you could pivot the results using this:
SELECT
MAX(CASE WHEN hire_date = 'TOTAL' THEN ilv.count_hire_date END) total,
MAX(CASE WHEN hire_date = '1995' THEN ilv.count_hire_date END) count_1995,
MAX(CASE WHEN hire_date = '1996' THEN ilv.count_hire_date END) count_1996,
MAX(CASE WHEN hire_date = '1997' THEN ilv.count_hire_date END) count_1997
from (
select case grouping(hire_date)
when 0 then hire_date
else 'TOTAL'
end hire_date,
count(hire_date) as count_hire_date
from employees
group by rollup(hire_date)
) ilv;
This has the obvious disadvantage of you needing to add a new clause into the main select statement for each possible year.
The syntax is not intuitive. This leverages cut'n'paste coding:
SQL> select
2 sum(case when to_char(hiredate, 'YYYY') = '1980' then 1 else 0 end) as "1980"
3 , sum(case when to_char(hiredate, 'YYYY') = '1981' then 1 else 0 end) as "1981"
4 , sum(case when to_char(hiredate, 'YYYY') = '1982' then 1 else 0 end) as "1982"
5 , sum(case when to_char(hiredate, 'YYYY') = '1983' then 1 else 0 end) as "1983"
6 , sum(case when to_char(hiredate, 'YYYY') = '1987' then 1 else 0 end) as "1987"
7 , count(*) as total
8 from emp
9 /
1980 1981 1982 1983 1987 TOTAL
---------- ---------- ---------- ---------- ---------- ----------
1 10 1 0 2 20
Elapsed: 00:00:00.00
SQL>
Here's how I'd do it in MySQL, don't know if this applies to Oracle too:
SELECT
YEAR(hire_date), COUNT(*)
FROM
employees
GROUP BY
YEAR(hire_date)
SELECT NVL(hire_date,'Total'), count(hire_date)
FROM Employees GROUP BY rollup(hire_date);
If you need to PIVOT the data see A_M's answer. If you have years for which you have no data, yet still want the year to show up with a zero count you could do something like the following:
SELECT NVL(a.Year,b.Year), NVL2(a.Year,a.Count,0) FROM
(
SELECT NVL(hire_date,'Total') Year, count(hire_date) Count
FROM Employees GROUP BY rollup(hire_date)
) a
FULL JOIN
(
SELECT to_char(2000 + rownum,'FM0000') Year FROM dual CONNECT BY rownum<=9
) b ON a.Year = b.Year;
Here is some test data.
create table Employees (hire_date Varchar2(4));
insert into Employees values ('2005');
insert into Employees values ('2004');
insert into Employees values ('2006');
insert into Employees values ('2009');
insert into Employees values ('2009');
insert into Employees values ('2005');
insert into Employees values ('2004');
insert into Employees values ('2006');
insert into Employees values ('2006');
insert into Employees values ('2006');
Here's how I would do it in MS SQL - it will be similar in Oracle, but I don't want to try to give you Oracle code because I don't usually write it. This is just to get you a basic skeleton.
Select
Year(e.hire_date),
Count(1)
From
employees e
Group By
Year(e.hire_date)
I realize this is 6 years ago, but I also found another unique way of doing this using the DECODE function in Oracle.
select
count(decode(to_char(hire_date, 'yyyy') , '2005', hire_date, null)) hired_in_2005,
count(decode(to_char(hire_date, 'yyyy') , '2006', hire_date, null)) hired_in_2006,
count(decode(to_char(hire_date, 'yyyy') , '2007', hire_date, null)) hired_in_2007,
count(*) total_emp
from employees
where to_char(hire_date,'yyyy') IN ('2005','2006','2007')