SQL : Having with calculation on some parameters - mysql

I'm currently looking for a way of concidering the value of the difference between some parameters in a HAVING clause.
I explain,
I have a table like :
MyTable
id name date
1 john 7/12
2 doe 7/12
3 john 7/14
4 john 9/13
5 doe 9/14
and i want to make a group by like
GROUP BY name
HAVING 'MyTAble[a].date - MyTAble[b].date > 1 month'
to have (count) :
john (3)
doe (1)
john (1)
doe (1)
I know there is a MONTH() function but if I make a group by name, MONTH(date) I will not have the expected result.
I probably miss an obvious solution (or it's just impossible). Anyway, hope you have an idea.

Lacking more information about your query or database engine, I would say that the answer is to use the where clause rather than group by / having. In my experience, having is used to limit query results based on the value of an aggregate function, which your having clause doesn't use.

Ok,
I guess it's a little bit far-fetched but I found a solution, so I worked on the select clause :
SELECT mytable.name name, mytable.date , CONCAT(mytable.name,DAY(mytable.date)) group_id
I create an id (group_id) that is common to all the entities I want to gather, then I GROUP BY this common id

Related

selected items don't have to appear in the GROUP BY clause or be used in an aggregate function

I was taught and heard that in sql/mysql, items in select clause must appear in the GROUP BY clause or be used in an aggregate function as in here
However, the example below may have changed my mind.
Two tables:
Student (sid is the key)
sid | name | email
========================
99901| mike | mike#a.edu
99902| jane | jane#b.edu
99903| peter| pete#b.edu
Took (sid+oid together is the key, oid stands for offering id)
sid | oid| grade
==================
99901| 1 | 100
99901| 2 | 30
99901| 3 | 40
99902| 4 | 100
99902| 5 | 100
99902| 6 | 40
99903| 6 | 95
Question: I want to find the sid, name and average grade of each student who has taken at least 2 courses.
Answer:
select s.sid, name, avg(grade) as average
from Student as s, Took as t
where s.sid = t.sid
group by s.sid
having count(*) >= 2;
Result:
sid | name | avgerage
=======================
99901| mike | 56.6667
99902| jane | 80.0000
Based on must appear in the GROUP BY clause or be used in an aggregate function, the query should have been incorrect because name is neither in group clause nor an aggregate function.
I looked some posts and this, my understanding is that although name is neither in group clause nor an aggregate function, we group by sid which is the key and each sid only correspond to one name, so it won't return multiple options from which sql doesn't know which one to return. To confirm my understanding, if I select one more column email, it's still ok; but if I select oid, it gives error because each sid corresponds to more than one oid.
Could someone correct my understanding if it is wrong or elaborate more on this statement: must appear in the GROUP BY clause or be used in an aggregate function
Thanks.
First Edit:
Btw, I tested in MySQL 8.0.17
Second Edit:
Just a summary of useful links when you read the answers/comments below.
Functional depedency
SQL standard change
First, you should use proper, explicit JOIN syntax:
select s.sid, s.name, avg(grade) as average
from Student s join
Took t
on s.sid = t.sid
group by s.sid
having count(*) >= 2;
This will work because of something called functional dependencies. Basically, this is the part of the standard that says: If you group by a primary key or unique key, then you can include any of the columns from that table.
Here is documentation on the subject.
That is, because the database knows that s.sid is unique, it is safe to use other columns. This is part of the standard. The only other common database that I am aware of that supports this is Postgres.
You were taught right.
According to the SQL Standard when you use GROUP BY the columns that can appear in the SELECT clause fall into three categories:
Columns included in the GROUP BY clause. In this case you have s.sid.
Aggregated columns. In this case you have avg(grade).
Functionally dependent columns of case #1. Since s.sid is the PK of the table, you can include s.name without aggregating it.
So all good.
However, you should know that MySQL 5.7.4 and older do allow you to include other columns in non-aggregated form. This is a bug/feature of MySQL that I personally find error prone. If you do this, MySQL will silently pick one value randomly without aggregating it and without telling you.
This functionality can be turned on by using the ONLY_FULL_GROUP_BY configuration parameter (as #Shawn pointed out in the comments) in newer versions of MySQL, to allow old/bad queries to run. I would try to avoid using it, though.

SQL Return value based on lowest value in another column

I've tried to search for a solution to this but have been unable to find one. I guess it's basic SQL, but I can't seem to figure it out.
I have a table called people, this table has several columns:
ID Firstname Lastname Birthdate
1 John Stevenson 1860-07-30
2 Eric Johnson 1918-08-25
3 Adam Efron 1914-02-02
4 Michael Gray 1870-07-18
Now I want to make a query that looks at the Birthdate column, finds the lowest value and returns the firstname of the person that has the lowest birthdate (is oldest).
Can someone guide me in the right direction?
THanks in advance!
Use order by like
select Firstname
from people
order by Birthdate
limit 1
This will account for 2 paople having the same birthdate, returning 2 rows.
select Firstname
from people
where birthdate = (select min(birthdate) from people);
Another way to do it efficiently would be this (sqlfiddle):
select p.*, min(p.birthdate) from people p;
NOTE: You will have 1 extra column in the output.

how can i get the number of times a specific word occur in my sql

Please how can I get the number of times, a specific word occur in a table?
I have a table called result and a field called president.
Index - president
1 - James
2 - John
3 - John
4 - John
5 - James
6 - John
I tried using the following codes to get the number of times John occur in my table.
SELECT COUNT(`president`) FROM `result` WHERE `president`="John"
But, it's writing syntax error.
N.B: What I just need is the number of times John appeared which I expected to be four(4).
You don't need to use COUNT on a column. In your case, you want to get
the number of rows where the president is 'John'.
Use the following syntax:
SELECT COUNT(*) FROM `result` WHERE `president` = "John"
P.S. Don't call your table result. It is kind of incorrect in terms of naming and architecture in general. Call it PresidentsHistory or PresidentsList.
Syntax COUNT(`president`) is not correct.
SELECT COUNT(*) FROM `result` WHERE `president` = "John"
You can try this mate:
SELECT
president, COUNT(index) 'occured'
FROM
result
WHERE
president = 'John';
E: This one is specific for 'John' only.
SELECT
president, COUNT(index) 'occured'
FROM
result
GROUP BY
president;
E: To display the count for each result.president in your database. Cheers!
you can try the below query
SELECT COUNT(*) FROM `result` WHERE `president` LIKE "John"

Find lowest value and coresponding code on asingle query mysql

I have a user table as follows
id name age
1 John 21
2. Mathan 23
3. Raj 21
4. Manoj 50
5 Krishnan 91
I want to find minimum age and its corresponding name. How can I do it with rails?
Can I do it in a single query?
Note: More than one names can have single age.
Is there a specific reason why you want to do it in a single query ?
If you can write 2 queries, I think you can just write :
User.where age: User.minimum(:age)
select age, group_concat(name) from table group by age order by age asc limit 1
You will need to process the results later on in ruby, but this gives all you need in one single query. Also i am assuming mysql, so might differ on other rdbms.
It gives exact output in mysql that you want try this
SELECT concat("[",name," ",age,"]") AS name
FROM TABLE
WHERE age =
(SELECT min(age)
FROM TABLE);

Returning query results in predefined order

Is it possible to do a SELECT statement with a predetermined order, ie. selecting IDs 7,2,5,9 and 8 and returning them in that order, based on nothing more than the ID field?
Both these statements return them in the same order:
SELECT id FROM table WHERE id in (7,2,5,9,8)
SELECT id FROM table WHERE id in (8,2,5,9,7)
I didn't think this was possible, but found a blog entry here that seems to do the type of thing you're after:
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY FIND_IN_SET(id,"7,2,5,9,8");
will give different results to
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY FIND_IN_SET(id,"8,2,5,9,7");
FIND_IN_SET returns the position of id in the second argument given to it, so for the first case above, id of 7 is at position 1 in the set, 2 at 2 and so on - mysql internally works out something like
id | FIND_IN_SET
---|-----------
7 | 1
2 | 2
5 | 3
then orders by the results of FIND_IN_SET.
Your best bet is:
ORDER BY FIELD(ID,7,2,4,5,8)
...but it's still ugly.
Could you include a case expression that maps your IDs 7,2,5,... to the ordinals 1,2,3,... and then order by that expression?
All ordering is done by the ORDER BY keywords, you can only however sort ascending and descending. If you are using a language such as PHP you can then sort them accordingly using some code but I do not believe it is possible with MySQL alone.
This works in Oracle. Can you do something similar in MySql?
SELECT ID_FIELD
FROM SOME_TABLE
WHERE ID_FIELD IN(11,10,14,12,13)
ORDER BY
CASE WHEN ID_FIELD = 11 THEN 0
WHEN ID_FIELD = 10 THEN 1
WHEN ID_FIELD = 14 THEN 2
WHEN ID_FIELD = 12 THEN 3
WHEN ID_FIELD = 13 THEN 4
END
You may need to create a temp table with an autonumber field and insert into it in the desired order. Then sort on the new autonumber field.
Erm, not really. Closest you can get is probably:
SELECT * FROM table WHERE id IN (3, 2, 1, 4) ORDER BY id=4, id=1, id=2, id=3
But you probably don't want that :)
It's hard to give you any more specific advice without more information about what's in the tables.
It's hacky (and probably slow), but you can get the effect with UNION ALL:
SELECT id FROM table WHERE id = 7
UNION ALL SELECT id FROM table WHERE id = 2
UNION ALL SELECT id FROM table WHERE id = 5
UNION ALL SELECT id FROM table WHERE id = 9
UNION ALL SELECT id FROM table WHERE id = 8;
Edit: Other people mentioned the find_in_set function which is documented here.
You get answers fast around here, don't you…
The reason I'm asking this is that it's the only way I can think of to avoid sorting a complex multidimensional array. I'm not saying it would be difficult to sort, but if there were a simpler way to do it with straight sql, then why not.
One Oracle solution is:
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY DECODE(id,7,1,2,2,5,3,9,4,8,5,6);
This assigns an order number to each ID. Works OK for a small set of values.
Best I can think of is adding a second Column orderColumn:
7 1
2 2
5 3
9 4
8 5
And then just do a ORDER BY orderColumn