max in one column and min in another column - mysql

For example, if Column A and Column B have values:
+---+---+
| A | B |
+---+---+
| 2 | 1 |
| 5 | 1 |
| 6 | 1 |
| 1 | 2 |
| 5 | 2 |
| 0 | 2 |
| 2 | 3 |
| 7 | 3 |
| 4 | 3 |
| 5 | 4 |
+---+---+
From each group of B, I want to get the highest number from A. However, I don't want to include results where the number in B is higher, yet has a smaller A value than the previous one. I know this doesn't make sense in words, but this is what I want the final result to look like:
+---+---+
| A | B |
+---+---+
| 6 | 1 |
| 7 | 3 |
+---+---+
So far I have something like "select max(a), b from table1 group by b" but this doesn't omit the ones where B is higher but the max A is smaller. I know that I could just peruse the results of that query in PHP and remove the ones where the A value is smaller than the previous A value, but I want to put it all in the mysql query if possible.

This technique joins the table against the aggregated version of itself, but the join is offset by one, so that every row is joined to the knowledge of the previous-B's MAX(A) value. It then matches rows where the current A is greater than any of those, and if it doesn't find any, it doesn't include the row. We then aggregate the final selection to get the results you are after.
SELECT
MAX(source_row.A) as A,
source_row.B
FROM ab as source_row
LEFT JOIN (SELECT MAX(A) as A, B FROM ab GROUP BY B) AS one_back
ON one_back.B = source_row.B-1
WHERE (one_back.A IS NULL)
OR one_back.A < source_row.A
GROUP BY B
I have tested this :-)
edit: extra insight
I wanted to share a little insight into how I come up with these kind of solutions; 'cause I think it's important for folks to start to "think in sets"... that's the best advice I ever read regarding JOINS, that you need to envision the intermediate "sets" that your query was working with. To illustrate this, here is a representation of the intermediate "set" that is the critical part of this query; it is the table as it exists "joined" to the aggregated version of itself off-by-one.
+------+------+------------+------------+
| A | B | one_back.B | one_back.A |
+------+------+------------+------------+
| 2 | 1 | NULL | NULL |
| 5 | 1 | NULL | NULL |
| 6 | 1 | NULL | NULL |
| 1 | 2 | 1 | 6 |
| 5 | 2 | 1 | 6 |
| 0 | 2 | 1 | 6 |
| 2 | 3 | 2 | 5 |
| 7 | 3 | 2 | 5 |
| 4 | 3 | 2 | 5 |
| 5 | 4 | 3 | 7 |
+------+------+------------+------------+
And then the set as it actually is created in-memory (the full join'd version is never fully in memory, since MySQL can eliminate rows as soon as it knows they are not going to "make the cut":
+------+------+------------+------------+
| A | B | one_back.B | one_back.A |
+------+------+------------+------------+
| 2 | 1 | NULL | NULL |
| 5 | 1 | NULL | NULL |
| 6 | 1 | NULL | NULL |
| 7 | 3 | 2 | 5 |
+------+------+------------+------------+
And then, of course, it aggregates the results from there into the final form, selecting only the A and B from the original rows.

A simpler solution would be to use a variable to store the value of a from the previous row and make the comparison on each iteration. This also accounts for the case where you might have gaps in the b column, where numbers aren't exactly in perfect sequential order:
SELECT #val:=a.a AS a, a.b
FROM
(
SELECT MAX(a) AS a, b
FROM tbl
GROUP BY b
) a
WHERE a.a > IFNULL(#val,-1)

Select Z.a, Z.b from
(select a, b, rank() over (order by b) as ranker from (select max(a) a, b from table1 group by b) Y) Z left join
(select a, b, rank() over (order by b) as ranker from (select max(a) a, b from table1 group by b) Y1) Z1
on Z.ranker = Z1.ranker + 1
where Z.a > isnull(Z1.a, -100000)

Related

SQL: Create Column (MySQL/PHPMyAdmin)

With the query:
SELECT TableA.ID, TableA.SensorID, TableA.Value, SensorIDs.Name, TableA.timestamp
FROM TableA
JOIN SensorIDs
ON TableA.SensorID = SensorIDs.ID // column 'Name' is in 'SensorIDs'
My result table looks like this:
ID | SensorID | Value | Name | timestamp
1 | 1 | 5 | A | 1000
2 | 2 | 10 | B | 1000
3 | 3 | 0 | C | 1000
4 | 1 | 1 | A | 2000
5 | 2 | 2 | B | 2000
6 | 3 | 6 | C | 2000
[..]
Is there a way to change my SQL query to get a table like this:
A | B | C | timestamp
5 | 10 | 0 | 1000
1 | 2 | 6 | 2000
Something with GROUP BY maybe?
EDIT: In the forseeable future there will be only these 3 values for 'Name'.
EDIT: RDBMS: MySQL-native (InnoDB), PHPMyAdmin
EDIT: Forgot to add column "SensorID" in the result.
I found the answer, by creating a PIVOT table with the tutorial I found here:
https://ubiq.co/database-blog/how-to-create-pivot-table-in-mysql/
SELECT time,
sum(IF(SensorID=1, Value, NULL)) AS Sensor1,
sum(IF(SensorID=2, Value, NULL)) AS Sensor2,
sum(IF(SensorID=3, Value, NULL)) AS Sensor3,
sum(IF(SensorID=4, Value, NULL)) AS Sensor4
FROM TableA
GROUP BY time

Mysql why 2th column isn't sorted from 1 to 3?

Why ordering isn't working right for 2th column. Can some one explain, please.
select a,b from d:
+------+------+
| a | b |
+------+------+
| 1 | 3 |
| 1 | 3 |
| 2 | 1 |
| 2 | 1 |
| 3 | 2 |
| 3 | 2 |
| 3 | 2 |
+------+------+
select a,b from d order by a,b;
+------+------+
| a | b |
+------+------+
| 1 | 3 |
| 1 | 3 |
| 2 | 1 |
| 2 | 1 |
| 3 | 2 |
| 3 | 2 |
| 3 | 2 |
+------+------+
I think it's ordering correctly. In your order by you have asked system to order by First column first so it have ordered then you have asked it to order by second column so it have
1. It have to keep ordering of first column.
2. Order by second column too
So it does ordering within group means if ..
Table Test
A| B
--------
1 1
1 3
1 2
Select * from test order by A, B
Output
A | B
1 1
1 2
1 3
Hope this clears your doubt.
Each record/row in the output has to be consistent. When you use order by, you are printing record/row which is sorted on the basis of specific column value. You can't sort individual column by breaking the consistency of of a row. Otherwise it will create a havoc, imagine something like 'select bank_account_id, balance from bank_record order by bank_account_id, balance;'. What do you think would happen if bank_account_id and balance is sorted individually?

How to get the count of a grouped by rows - SQL

So I have this table
table: "tbl_hash"
------------------------------
| id | hash1 | hash2 | hash3 |
------------------------------
| 1 | a | b | c |
| 2 | a | b | c |
| 3 | a | g | d |
| 4 | a | g | d |
| 5 | a | g | d |
------------------------------
I only want to group them by hash1, hash2, and hash3. count them and only return the count which is higher by 2.
So I have this query to get the values I wanted:
select CONCAT(hash1, hash2, hash3) as hashes, COUNT(*) as count from `tbl_hash` group by hashes having `count` > 2 limit 5
^^ the query above works perfectly..
But what If I wanted to get the data and count for each row? Expected output:
--------------------------------------
| id | hash1 | hash2 | hash3 | count |
--------------------------------------
| 1 | a | b | c | 2 |
| 2 | a | b | c | 2 |
| 3 | a | g | d | 3 |
| 4 | a | g | d | 3 |
| 5 | a | g | d | 3 |
--------------------------------------
I'm also planning on converting those to a query builder using DB::table...
You may join your original table to a subquery which finds the counts for each group:
SELECT t1.*, t2.cnt
FROM tbl_hash t1
INNER JOIN
(
SELECT hash1, hash2, hash3, COUNT(*) AS cnt
FROM tbl_hash
GROUP BY hash1, hash2, hash3
HAVING COUNT(*) > 2
) t2
ON t1.hash1 = t2.hash1 AND
t1.hash2 = t2.hash2 AND
t1.hash3 = t2.hash3;
Note that what I wrote above would completely filter off any original records belonging to a hash1/hash2/hash3 group which did not have a count greater than 2. If you instead want all records, with the count, then remove the HAVING clause.
As a side note, in databases which support analytic functions, such as SQL Server and Oracle, we could write a much less verbose query using COUNT as an analytic function. At some point, mainstream versions of MySQL will also support this. But for now, we are stuck with doing a join.

Most frequent values by two columns SQL

I am trying to figure out the most occurring values within a table in groups.
This is for SQL
Part | location | PartDesc
-----+----------+-------------
A | 2 | Part A
A | 2 | Part A
A | 2 | Part A
A | 1 | Part A
A | 1 | Part A
B | 1 | Part B
B | 2 | Part B
So the output needs to show
Part | Location | PartDesc | Occurrence
-----+----------+----------+--------------
A | 2 | Part A | 3
A | 1 | Part A | 2
B | 1 | Part B | 1
B | 2 | Part B | 1
So far I have
Select Part, count(*) as occurrence
from table1
group by Part
order by count(*desc)
SELECT
Part,
Location,
PartDesc,
COUNT(*) AS Occurrence
FROM
table1
GROUP BY
Part,
Location,
PartDesc
ORDER BY
Occurrence DESC
Thanks.

MySQL: limit query or subquery (in left/inner join?)

Apologise in advance, I'm novice in (My)SQL - this should be an easy question for expert DBAs - but I don't even know where to start finding a solution at all. I'm not even sure if I applied LEFT JOIN in the correct way below.
My (DB) structure is quite simple:
I have testsuites, and several testcases are linked to each testsuite ("logical entities")
During testcase kick-off, I'm creating an entry for each testsuite in the testsuiteinstance table - and one entry in testcaseinstance for each testcase.
My goal is to fetch the last 10 testcaseinstances of all testcases belonging to a certain testsuite
This is the query I use to fetch all testcaseinstances:
SELECT * FROM testcaseinstance AS tcinst
LEFT JOIN testsuiteinstance tsinst ON tsinst.id=tcinst.testsuiteinstance_id
LEFT JOIN testsuite ts ON ts.id=tsinst.testsuite_id
WHERE ts.id = 349 ORDER BY tcinst.id DESC;
So, let's say I have two testcases in a testsuite and both testcase was executed 100 times each. This query gives me 200 rows. If I put "LIMIT 10" at the end, I will only get the last 10 rows for one testcase type, but I want 20 rows (the last 10-10 belonging to the two testcases)
I'd appreciate some description beside the solution query or a pointer to a "tutorial" I can start looking at related to the topic (whatever would that be :D)
Thanks in advance!
Here's one approach; consider this (slightly contrived) example...
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
Let's say we want to return the top 3 even numbers and the top 3 odd numbers from this list. Ignoring for the moment that there's another, simpler, solution to this particular example we can instead do something like this...
SELECT x.*
, COUNT(*) rank
FROM ints x
JOIN ints y
ON MOD(y.i,2) = MOD(x.i,2)
AND y.i >= x.i
GROUP
BY i
ORDER
BY MOD(x.i,2) DESC
, x.i DESC;
+---+------+
| i | rank |
+---+------+
| 9 | 1 |
| 7 | 2 |
| 5 | 3 |
| 3 | 4 |
| 1 | 5 |
| 8 | 1 |
| 6 | 2 |
| 4 | 3 |
| 2 | 4 |
| 0 | 5 |
+---+------+
From here, the process of grabbing just the top 3 from each group becomes trivial...
SELECT x.*
, COUNT(*) rank
FROM ints x
JOIN ints y
ON MOD(y.i,2) = MOD(x.i,2)
AND y.i >= x.i
GROUP
BY i
HAVING rank <=3
ORDER
BY MOD(x.i,2),x.i DESC;
+---+------+
| i | rank |
+---+------+
| 8 | 1 |
| 6 | 2 |
| 4 | 3 |
| 9 | 1 |
| 7 | 2 |
| 5 | 3 |
+---+------+
...and this can be simplified to...
SELECT x.*
FROM ints x
JOIN ints y
ON MOD(y.i,2) = MOD(x.i,2)
AND y.i >= x.i
GROUP
BY i
HAVING COUNT(*) <=3;
+---+
| i |
+---+
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+