MySQL : Having doesn't work - mysql

I am using MySQL. Here is my schema:
Table b
Column Name | Type | Primary key
id | int | Yes
seq | int | Yes
amt | int
Dummy data
id | seq | amt
1 | 1 | 4000
1 | 2 | 3000
1 | 3 | 2000
1 | 4 | 5000
2 | 1 | 4000
2 | 2 | 3000
3 | 1 | 2000
3 | 2 | 5000
I want to select the record with equivalent id and max value of seq.
HERE is my SQL
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
AND b.seq =
(SELECT max(b.seq) FROM b WHERE b.id = 1)
But I wonder if there is more elegant way of achieving what I want.
For example,
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
HAVING b.seq = max(b.seq)
But it doesn't work as expected. It returns 0 rows.

The HAVING clause is to be used with the GROUP BY clause, which is missing in your query. To add a GROUP BY clause to your query, we'll have to include all the fields in the query that don't have an aggregate function, so everything other than seq:
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
GROUP BY b.id, b.amt
HAVING b.seq = MAX(b.seq)
Now that will obviously not give your the correct results, because you only want to group by id and not amt. Another problem is that you cannot use the fields that are not in the GROUP BY clause in either the SELECT or HAVING clauses, so you cannot use the seq in those two places, and the query above will give you an error.
If your goal is to get the record for id = 1, then your first query is OK, or better to use the query in juergen's answer. But if your real goal is to select one record for each group, then you can do it like this:
SELECT b.id, b.seq, b.amt
FROM b
INNER JOIN (SELECT id, MAX(seq)
FROM b
GROUP BY id) bb ON bb.id = b.id AND bb.seq = b.seq
The result will be:
id | seq | amt
1 | 4 | 5000
2 | 2 | 3000
3 | 2 | 5000

Order the data and take only the first record
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
ORDER BY seq desc
limit 1

Given your simple example, how about this:
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1 ORDER BY b.seq DESC limit 1;

SQL HAVING Clause
HAVING filters records that work on summarized GROUP BY results.
HAVING applies to summarized group records, whereas WHERE applies to individual records.
Only the groups that meet the HAVING criteria will be returned.
HAVING requires that a GROUP BY clause is present.
WHERE and HAVING can be in the same query.

Related

Selecting Most Recent Date Relative to Another Table

Just stumped on syntax for this...
I have Two tables in mysql & I need to fetch records from Table A when following criteria are met:
1) Name in Table A matches the name in Table B
AND
2) The price for the most recent day in Table B is less than the record in Table A
So...running the query on example tables below would fetch me these two records:
03-17-2019 Bob 8
03-20-2019 John 10
Essentially, I need to evaluate each row in Table A, check the matching name in Table B that has the most recent date relative to the record under evaluation in Table A, and then determine if the price in Table A is greater than the price for the most recent matching name in Table B. After that, I need to calculate the difference between the prices. So, in the two records above, the difference would be 2 and 4
Table A
Date | Name | Price
03-08-2019 Bob 6
03-25-2019 Bob 2
03-17-2019 Bob 8
03-20-2019 John 10
Table B
Date | Name | Price
03-16-2019 Bob 4
03-28-2019 Bob 9
03-02-2019 Bob 12
03-10-2019 John 6
Thank you for the help!
Join twice the tables, once to get the min date difference and then to get the row with the min date difference:
select a.*
from tablea a
inner join tableb b on b.name = a.name
inner join (
select a.name, min(abs(datediff(b.date, a.date))) mindatediff
from tablea a inner join tableb b
on b.name = a.name
group by a.name
) g on g.name = a.name and abs(datediff(b.date, a.date)) = g.mindatediff
See the demo.
or:
select a.*
from tablea a inner join tableb b
on b.name = a.name
where abs(datediff(b.date, a.date)) = (
select min(abs(datediff(x.date, y.date)))
from tablea x inner join tableb y
where x.name = a.name and y.name = b.name
)
See the demo.
Results:
| date | name | price |
| ---------- | ---- | ----- |
| 2019-03-17 | Bob | 8 |
| 2019-03-20 | John | 10 |
In MySQL 8+, you would use window functions
select ab.*, (price - b_price)
from (select a.*, b.price as b_price,
row_number() over (partition by a.name order by datediff(b.date, a.date) as seqnum
from a join
b
on a.name = b.name and
a.date >= b.date
) ab
where seqnum = 1;

Select two rows where the sum of two rows is equal to a value

Say I have a table as follows:
| id | value |
--------------
| 1 | 6 |
| 2 | 8 |
| 3 | 5 |
| 4 | 12 |
| 5 | 6 |
I want to return the two rows for which added together will equal a certain value
e.g. I want to get 2 rows where the total is 18, so in the above table it should return:
| id | value |
--------------
| 1 | 6 |
| 4 | 12 |
...as the sum of values here is 18. It shouldn't match on the other 3 rows even if they add up to the total as it can only be sum of 2 rows in this case.
Also, if there are multiple pairs that add up to the required value, it should only return the first match.
edit:
Came up with this which seems to do the trick but I'm not sure it's the best method
SELECT *, (t1.value+t2.value) AS total
FROM test t1, test t2
WHERE t1.id != t2.id
HAVING total = 18
LIMIT 1
Here's something similar to what you're after...
SELECT x.id x_id
, y.id y_id
FROM my_table x
JOIN my_table y
ON y.id > x.id
WHERE x.value + y.value = 18
ORDER
BY x.id
, y.id
LIMIT 1;
MySQL doesn't really lend to such queries. The only [admitedly god aweful] solution I can think of is to self-join the table to get the two records, and then put this join in a CTE and use union all to get the two records on separate rows:
WITH summer AS (
SELECT a.id AS a_id, a.value AS a_value, b.id AS b_id, b.value AS b_value
FROM mytable a
JOIN mytable b ON a.id <> b.id AND a.value + b.value = 18
ORDER BY a.id, b.id
LIMIT 1
)
SELECT a_id, a_value
FROM summer
UNION ALL
SELECT b_id, b_value
FROM summer
Here is a simple query for doing it:
SELECT
number1.id, number2.id
FROM
number AS number1
JOIN
number AS number2 ON number1.value + number2.value = 18
AND number2.id > number1.id

Efficient way to get DISTINCT rows of Table A when JOINing with Table B

Simple problem. Given example tables:
Table A:
id | type
---+-----
1 | A
2 | B
3 | C
Table B:
id | a_id | type
---+------+-----
1 | 1 | X
2 | 2 | Y
3 | 1 | X
4 | 3 | Z
(there are additional columns, which I omitted, in order to clarify the problem)
The query:
SELECT a.*
FROM a a
INNER JOIN b b ON b.a_id = a.id
WHERE b.type = 'X'
Result:
id | type
---+-----
1 | A
1 | A
SQL Fiddle: http://sqlfiddle.com/#!2/e6138f/1
But I only want to have distinct rows of Table A. I know, I could do SELECT DISTINCT a.*, but our Table A has about 40 columns, and this SELECT can return 100-10000 rows. Isn't that extremely slow, if the database has to compare each column?
Or is MySQL intelligent enough, to just focus on the Primary Key for the DISTINCT operation?
Thanks in advance :)
Use exists instead of an explicit join:
select a.*
from tablea a
where exists (select 1 from tableb b where b.a_id = a.id and b.type = 'x');
For performance, create an index on tableb(a_id, type).

Grouping and aggregating with fields that don't need it

I have the following data:
| ID | Date | Code |
--------------------------
| 1 | 26/02/14 | 10 |
| 1 | 25/02/14 | 11 |
| 1 | 24/02/14 | 10 |
| 2 | 25/02/14 | 13 |
| 2 | 24/02/14 | 11 |
| 2 | 23/02/14 | 10 |
All I want is to group by the ID field and return the maximum value from the date field (i.e. most recent). So the final result should look like this:
| ID | Date | Code |
--------------------------
| 1 | 26/02/14 | 10 |
| 2 | 25/02/14 | 13 |
It seems though that if I want the "Code" field showing in the same query I also have to group or aggregate it as well... which makes sense because there could potentially be more than one value left on that field after the others are grouped/aggregated (even though there won't be in this case).
I thought I could handle this problem by doing the GroupBy and Max in a subquery on just those fields and then do a join on that subquery to bring in the "Code" field I don't want grouped or aggregated:
SELECT Q.ID, Q.MaxOfDate, A.Code
FROM
(SELECT B.ID, Max(B.Date) As MaxOfDate
FROM myTable As B
GROUP BY B.ID) As Q
LEFT JOIN myTable As A ON Q.ID = A.ID;
This isn't working though as it is still only giving me the original number of records I started with.
How do you do grouping and aggregation with fields you don't necessarily want grouped/aggregated?
An alternative to the answer I accepted:
SELECT Q.ID, Q.MaxOfDate, A.Code
FROM
(SELECT B.ID, Max(B.Date) As MaxOfDate
FROM myTable As B
GROUP BY B.ID) As Q
LEFT JOIN myTable As A ON (Q.ID = A.ID) AND (A.Date = Q.MaxOfDate);
Needed to do the LEFT JOIN on the Date field as well as the ID field.
If you want the CODE associated with the Max Date, you will have to use a subquery with a top 1, like this:
SELECT B.ID, Max(B.Date) As MaxOfDate,
(select top 1 C.Code
from myTable As C
where B.ID = C.ID
order by C.Date desc, C.Code) as Code
FROM myTable As B
GROUP BY B.ID

Mysql - Select at least one or select none

I have a table as so...
----------------------------------------
| id | name | group | number |
----------------------------------------
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 3 | james | 2 | 2 |
| 4 | steven | 2 | 5 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
----------------------------------------
I'm running a select like so:
SELECT * FROM table WHERE number IN (2,3);
The problem im trying to solve is that I want to only grab get results from groups that have 1 or more rows of each number. For instance the above query is returning id's 1-2-3-5-6, when I'd like the results to exclude id 3 since the group of '2' can only return 1 result for the number of '2' and not for BOTH 2 and 3, since there's no row with the number 3 for the group 2 i'd like it to not even select id 3 at all.
Any help would be great.
Try it this way
SELECT *
FROM table1 t
WHERE number IN(2, 3)
AND EXISTS
(
SELECT *
FROM table1
WHERE number IN(2, 3)
AND `group` = t.`group`
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
)
or
SELECT *
FROM table1 t JOIN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
) q
ON t.`group` = q.`group`;
or
SELECT *
FROM table1
WHERE `group` IN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
);
Sample output (for both queries):
| ID | NAME | GROUP | NUMBER |
|----|-------|-------|--------|
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
Here is SQLFiddle demo
On this, you can approach from a fun way with multiple joins for what you WANT qualified, OR, apply a prequery to get all qualified groups as others have suggested, but readability is a bit off for me..
Anyhow, here's an approach going through the table once, but with joins
select DISTINCT
T.id,
T.Name,
T.Group,
T.Number
from
YourTable T
Join YourTable T2
on T.Group = T2.Group AND T2.Group = 2
Join YourTable T3
on T.Group = T3.Group AND T3.Group = 3
where
T.Number IN ( 2, 3 )
So on the first record, it is pointing to by it's own group to the T2 group AND the T2 group is specifically a 2... Then again, but testing the group for the T3 instance and T3's group is a 3.
If it cant complete the join to either of the T2 or T3 instances, the record is done for consideration, and since indexes work great for joins like this, make sure you have one index for your NUMBER criteria, and another index on the (GROUP, NUMBER) for those comparisons and the next query sample...
If doing by more than this simple 2, but larger group, prequery qualified groups, then join to that
select
YT2.*
from
( select YT1.group
from YourTable YT1
where YT1.Number in (2, 3)
group by YT1.group
having count( DISTINCT YT1.group ) = 2 ) PreQualified
JOIN YourTable YT2
on PreQualified.group = YT2.group
AND YT2.Number in (2,3)
Maybe this,if I understand you
SELECT id FROM table WHERE `group` IN
(SELECT `group` FROM table WHERE number IN (2,3)
GROUP BY `group`
HAVING COUNT(DISTINCT number)=2)
SQL Fiddle
This will return all ids where BOTH numbers exist in a group.Remove DISTINCT if you want ids for groups where just one numbers is in.