SELECT a column not in GROUP BY clause - mysql

I'm trying to write a query that would select only the rows that have events which where the only events in that year.
Eg:
Year Event
2011 A
2011 B
2012 C
2013 B
2013 D
2014 D
So, I would like to get the rows 2012 C and 2014 D in the results.
I tried doing a GROUP BY on Year, but that wouldn't let me select the Event column.
2011 and 2013 have 2 events, so these shouldn't be in the results.
Please help.
EDIT: I could write a nested query to get the only the rows having count(Year) = 1 with GROUP BY Year, but I'm unable to get the Event column selected in the outer query
SELECT Year, Event from table where Year in (SELECT Year from table GROUP BY Year Having count(*) = 1) as count;

There is no need for using a subquery or nested query. You can simply GROUP By Year field and use HAVING COUNT(Year)=1 to find the required rows. So, the applicable query will be:
SELECT Year, Event
FROM table_name
GROUP BY Year
HAVING COUNT(Year)=1
You can find the executable solution sample at:
http://sqlfiddle.com/#!9/b47044/11
Logic:
When you group by Yearit aggregates all rows with same year. So, count will be 2 for 2011.
You can check this by running:
SELECT Year, Event, COUNT(Year) as event_count
FROM table_name
GROUP BY Year
You can see this intermediate step in execution, at: http://sqlfiddle.com/#!9/b47044/10
This above solution will only work for MySQL version < 5.7. For higher versions find the solution below.
For 5.7 and greater the ONLY_FULL_GROUP_BY SQL mode is enabled by default so this will fail. Either you can update this mode( Refer answers under SELECT list is not in GROUP BY clause and contains nonaggregated column .... incompatible with sql_mode=only_full_group_by ) or alternatively you can use ANY_VALUE() function to refer to the non-aggregated column, so update query that will work in MySQL 5.7 and greater is:
SELECT Year, ANY_VALUE(Event)
FROM table_name
GROUP BY Year
HAVING COUNT(Year)=1;
You can find executable example at: https://paiza.io/projects/e/tU-7cUoy3hQUk2A7tFfVJg
Reference: https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/miscellaneous-functions.html#function_any-value

You have a minor mistake in the query, the count(*) which is used in having clause should also be in the select clause
SELECT Year, Event from table where Year in (
SELECT Year from (
SELECT Year,count(*) from table GROUP BY Year Having count(*) = 1)temp
);

Only those Year and events need to be filtered which contains single event that Year
Inner Query would give you only years which have one event
Outer query would select the events of those years
SELECT Year, Event from table where Year in
(SELECT Year from table GROUP BY Year Having count(*) = 1);

Good Question,
You don't even need a subquery to get the desired output. Concatenate all the event names into one string, then search for comma , in the string, If comma , is found, this year has more than one events, otherwise only one.
SELECT Year, GROUP_CONCAT(Event) AS Event FROM Events GROUP BY (year) having
INSTR(Event, ",") = 0;

SELECT Year, Event
FROM table
WHERE Year in (SELECT Year
FROM table
GROUP BY Year
HAVING count(*) = 1);

Related

I need the way to aggregate based on column value using MySQL

I am learning Charts in Laravel, i need to draw Line graph for daily Student Attendance for those students come late or on time. I tried to write MYSQL query but it doesn't work
I tried subquery on same table to get data for daily students and i also need 7 dates only not full date, like date is stored in db as 09/08/2019 but i need it as 08 as date.
SELECT Date, COUNT(*) AS TimeStudent
FROM attendance WHERE `Attendance`='OnTime' AND (SELECT COUNT(*) AS
LateStudent FROM attendance
WHERE `Attendance`='Late'
GROUP BY `Date`
ORDER BY LateStudent DESC)
GROUP BY `Date`
ORDER BY TimeStudent DESC
but i got
[Err] 1241 - Operand should contain 1 column(s)
, because i can't use to fetch Date again in subquery while use it after where clause. Any one help me plz.
Here is a way to aggregate based on column value.
This query will give you count of on time and late student for a particular date.
SELECT
`Date`,
DATE_FORMAT(`Date`, '%d') AS Month_Date, -- You can modify it as per your requirement
SUM(IF(`Attendance` = 'OnTime', 1, 0)) AS OnTime_Count,
SUM(IF(`Attendance` = 'Late', 1, 0)) AS Late_Count
FROM attendance
WHERE `Date` >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY `Date`;

SELECT date and then putting it into group

I am trying to select dates from my DB and then group it as months and years for example:
May 2019
June 2018
etc.
DB
-the date is type date
I have this code:
SELECT datum, count(*) FROM zapasy GROUP BY datum
Which makes it on each day, but I don't want that so I searched how to make it group as months and years, not just days
SELECT datum, count(*) FROM zapasy GROUP BY MONTH(datum), YEAR(datum)
and I came up with this, however, I am getting this stupid error
#1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'zapasy_db_test.zapasy.datum' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
And I have no idea what is wrong with the code
Please, give me some advice.
Thank you
You need to have the items you're grouping by in your select.
So
SELECT MONTH(datum), YEAR(datum), count(*) from ... group by MONTH(datum), YEAR(datum)
instead of datum, otherwise a date column you're selecting, will have a day in it, which you'd get from just datum, and that has nothing to do with the count and group and would be wrong, or the grouping wouldn't work at all.
I notice that your desired output is in this format May 2019 etc. You know that DATE datatype is like this, yyyy-mm-dd so if you really, really want to extract the month-year in that format, you can try the following:
SELECT DATE_FORMAT(datum, '%b %Y') AS 'Monthyear', COUNT(*)
FROM zapasy
GROUP BY Monthyear;

related to query using SQL

In oracle sql, how to get the count of newly added customers only for the month of april and may and make sure they werent there in the previous months
SELECT CUSTOMER ID , COUNT(*)
FROM TABLE
WHERE DATE BETWEEN '1-APR-2018' AND '31-MAY-2018' AND ...
If we give max (date) and min(date), we can compare the greater date to check if this customer is new , correct?
expected output is month count
april ---
may ---
should show the exact count how many new customers joined in these two months
One approach is to use aggregation:
select customer_id, min(date) as min_date
from t
group by customer_id
having min(date) >= date '2018-04-01 and
min(date) < date '2018-06-01';
This gets the list of customers (which your query seems to be doing). To get the count, just use count(*) and make this a subquery.

Appointments per month, December missing?

SELECT month(dateofappointment), COUNT(*) 'NumberOfAppointments'
FROM appointment
WHERE YEAR(dateofappointment) = '2016'
GROUP BY MONTH(dateofappointment)
this shows me all months but December is not there because there weren't any appointments made in that year. how do i show December as being 0?
To solve these types of queries it often helps to express them as a series of requirements, this can make it easier to resolve.
When the results don't come out as expected, update your requirements statements with new requirements as you identify them, then try again:
As I see it now you have 2 requirements:
Return a single row for each month of the year of 2016
For each row show a count of the appointments for the corresponding month
Ok so that was verbose, but you see what you are missing from your query is a statement that defines the '1 row for each month of the year 2016' So you need to build that recordset first, either manually or through recursion.
MySQL does not currently support recursive Common Table Expressions, this is a trivial concept in many other RDBMSs
But if MySQL doesn't support recursion, what are our options? Here are some other attempts on SO:
МуSQL Get a list of dates in month, year
How to generate a dynamic sequence table in MySQL?
generate an integer sequence in MySQL
This might sound a bit of a hack, but you can use any table in your database that has more than 12 rows and has an auto-incrementing field, oh and was seeded to start at 1 (or below). Forget about whether this is right or wrong, it will work:
SELECT Id
FROM LogEvent -- An arbitrary table that I know has records starting from 1
WHERE Id BETWEEN 1 AND 12
So that is hacky, but we can implement a row count function so that we can use any table with 12 or more rows, regardless of ids or seeding, stole this from: MySQL get row number on select - Answer by Mike Cialowicz
SET #rank=0;
SELECT #rank:=#rank+1 AS rank
FROM orders
WHERE rank <= 12
Now we can either union the missing rows from this result set to the original query or use a join operator. First solution using union.
It is common to use UNION ALL to inject missing rows to a recordset because it separates the expected result query from the exceptional or default results. Sometimes this syntax makes it easier to interpret the expected operation
SET #rank = 0;
SELECT month(dateofappointment) as Month, COUNT(*) 'NumberOfAppointments'
FROM appointment
WHERE YEAR(dateofappointment) = '2016'
GROUP BY MONTH(dateofappointment)
UNION ALL
SELECT rank, 0
FROM (
SELECT #rank:=#rank+1 AS rank
FROM rows
WHERE #rank < 12
) months
WHERE NOT EXISTS (SELECT dateofappointment
FROM appointment
WHERE YEAR(dateofappointment) = '2016' AND MONTH(dateofappointment) = months.rank)
ORDER BY Month
But it makes for an ugly query. You could also join on the months query with a left join on the count of appointments, but here the intention is harder to identify.
SET #rank = 0;
SELECT months.rank, COUNT(appointment.dateofappointment)
FROM (
SELECT #rank:=#rank+1 AS rank
FROM rows
WHERE #rank < 12
) months
LEFT OUTER JOIN appointment ON months.rank = Month(appointment.dateofappointment) AND YEAR(dateofappointment) = '2016'
GROUP BY months.rank
I have saved these queries into a SqlFiddle so you can see the results:
http://sqlfiddle.com/#!9/99d485/4
As I pointed out above, this is trivial in MS SQL and Oracle RDBMS, where we can generate sequences of values dynamically through recursive Common Table Expressions (CTEs) For the players at home here is an implementation in MS SQL Server 2014. The example is a little more evolved, using a from and to date to filter the results dynamically
-- Dynamic MS SQL Example using recursive CTE
DECLARE #FromDate Date = '2016-01-01'
DECLARE #ToDate Date = '2016-12-31'
;
WITH Months(Year, Month, Date) AS
(
SELECT Year(#FromDate), Month(#FromDate), #FromDate
UNION ALL
SELECT Year(NextMonth.Date), Month(NextMonth.Date), NextMonth.Date
FROM Months
CROSS APPLY (SELECT DateAdd(m, 1, Date) Date) NextMonth
WHERE NextMonth.Date < #ToDate
)
SELECT Months.Year, Months.Month, COUNT(*) as 'NumberOfAppointments'
FROM Months
LEFT OUTER JOIN appointment ON Year(dateofappointment) = Months.Year AND Month(dateofappointment) = Months.Month
GROUP BY Months.Year, Months.Month

Get total of rows in a mysql table grouped by months and years

I'd like to create some statistics and I want to them in an array grouped by months and years.
Got an MySQL table_users with column user_id.
I want build up a mysql query to list how many members we had in total at the end of each month. The result of the query should be:
Year: Month: Members:
--------------------------
2014 12 11345
2015 1 17939
2015 2 25003
2015 3 32667
There is also the column user_signupdate with the UNIX timestamp when the user_id was added. Whatever I've tried so far, I'm getting only the growing of user_id's, but I'd like to get the total of all user_id's we had for each month and year.
Is it possible to count and group this with only one MySQL query?
The following code will perform simple arithmetic calculation to generate the members running total. See SQL Fiddle demo.
select
t1.year,
t1.month,
(#rtotal := #rtotal + t1.members) AS members
from
(select year(user_signupdate) as year, month(user_signupdate) as month, count(user_id) as members
from table_users
group by year(user_signupdate), month(user_signupdate)
order by year(user_signupdate), month(user_signupdate)) as t1, (Select #rtotal:=0) as rt