mysql select sum of rows by comparing two relations - mysql

I have data from tests with two lists of parts, called in and out. I need to select SUM of test values for each part after the last test where the part went in but didn't come out.
IN LIST OUT LIST TEST
+--------+-----------+ +--------+------------+ +------+-------+
| testid | in_partid | | testid | out_partid | | test | value |
+--------+-----------+ +--------+------------+ +------+-------+
| 1 | 10 | | 1 | 10 | | 1 | 1 |
| 1 | 20 | | 1 | 20 | | 2 | 10 |
| 2 | 10 | | 2 | 10 | | 3 | 100 |
| 2 | 20 | | | | | | |
| 3 | 10 | | 3 | 10 | | | |
| 3 | 20 | | 3 | 20 | | | |
+--------+-----------+ +--------+------------+ +------+-------+
SUM is pretty straightforward, but can I limit it to those rows where testid is greater than testid for the last inspection where part went in but not out?
In this example, part 10 should SUM all three test values, because it's included in all lists, but part 20 should only return value for test 3, as in test 2 it was not included in both in and out lists.
partid sum(value)
10 111
20 100
Can I do with with mysql, or do I need to include php in the mix?

I think your sample output is incorrect from your logic. I think partid 20 should return 101 as it is present in both lists for both tests 1 and 3. Assuming I'm right in that, this query should return the desired results
SELECT in_partid,SUM(value)
FROM (
SELECT DISTINCT in_partid,inl.testid
FROM in_list inl
INNER JOIN out_list outl ON in_partid=out_partid AND inl.testid=outl.testid
) as tests_passed
INNER JOIN tests ON tests_passed.testid=test
GROUP BY in_partid
EDIT: based on OP's comment my assumption above was wrong and was actually a requirement. Accordingly here is a query that I think fulfils the requirements:
SELECT tests_passed.in_partid,SUM(value)
FROM (
SELECT DISTINCT inl.in_partid,IFNULL(last_failed_test,0) as last_failed_test
FROM in_list inl LEFT JOIN (
SELECT in_partid,MAX(inl.testid) as last_failed_test
FROM in_list inl
LEFT JOIN out_list outl ON in_partid=out_partid AND inl.testid=outl.testid
WHERE outl.testid IS NULL
GROUP BY in_partid
) AS last_passed
ON inl.in_partid=last_passed.in_partid
) as tests_passed
INNER JOIN tests ON tests_passed.last_failed_test<test
GROUP BY tests_passed.in_partid
This returns the sample results given above for the sample data supplied.

Related

Loop through each record of a table and preform calculations on all other records

I want to calculate a value for NO_TOP_RATING in my table working
The calculation for NO_TOP_RATING is made by:
For each row, get all other rows that fall within the previous year from ANNDATS_CONVERTED for that record, and have the same ESTIMID as that record.
From those, find the lowest IRECCD value.
Then, count the number of times that the same ANALYST has an IRECCD that matches the lowest IRECCD calculated.
NOTE: This should omit the current row being calculated (so to find the value for row id 1, do not use this row in the calculations) and any records where ANALYST is blank should be ignored altogether.
TABLE working:
| ID | ANALYST | ESTIMID | ANNDATS_CONVERTED | IRECCD | NO_TOP_RATING |
---------------------------------------------------------------------------------
| 1 | DAVE | Brokerage000 | 1998-07-01 | 2 | |
| 2 | DAVE | Brokerage000 | 1998-06-28 | 2 | |
| 3 | DAVE | Brokerage000 | 1998-07-02 | 4 | |
| 4 | DAVE | Brokerage000 | 1998-07-04 | 3 | |
| 5 | SAM | Brokerage000 | 1998-06-14 | 1 | |
| 6 | SAM | Brokerage000 | 1998-06-28 | 4 | |
| 7 | | Brokerage000 | 1998-06-28 | 1 | |
| 8 | DAVE | Brokerage111 | 1998-06-28 | 5 | |
So - when calculating NO_TOP_RATING for record #1:
record #1 would not be included in the calculation, because I want to omit it from the calculation
record #7 would not be included in the calculation at all, because ANALYST is blank
Record #8 would not be included in the calculation, because ESTIMID is not the same as record #1
EXPECTED RESULT:
TABLE working:
| ID | ANALYST | ESTIMID | ANNDATS_CONVERTED | IRECCD | NO_TOP_RATING |
---------------------------------------------------------------------------------
| 1 | DAVE | Brokerage000 | 1998-07-01 | 2 | 0 |
| 2 | DAVE | Brokerage000 | 1998-06-28 | 2 | 0 |
| 3 | DAVE | Brokerage000 | 1998-07-02 | 4 | 0 |
| 4 | DAVE | Brokerage000 | 1998-07-04 | 3 | 0 |
| 5 | SAM | Brokerage000 | 1998-06-14 | 1 | 0 |
| 6 | SAM | Brokerage000 | 1998-06-28 | 4 | 1 |
| 7 | | Brokerage000 | 1998-06-28 | 1 | |
| 8 | DAVE | Brokerage111 | 1998-06-28 | 5 | 0 |
Here is the MySQL I have so far:
UPDATE `working`
SET `working`.`NO_TOP_RATING` =
(
SELECT COUNT(`ID`) FROM (SELECT `ID`,`IRECCD`,`ESTIMID` FROM `working`) AS BB
WHERE
`IRECCD` =
(
SELECT COUNT(`ID`) FROM (SELECT `ID`,`IRECCD`,`ESTIMID`, `ANALYST` FROM `working`) AS ZZ
WHERE
`IRECCD` =
-- this calculates the LOWEST number with same `ESTIMID`
(
SELECT MIN(`IRECCD`)
FROM (SELECT `ID`,`IRECCD`,`ANNDATS_CONVERTED`,`ESTIMID` FROM `working`) AS CC
WHERE
`ANNDATS_CONVERTED` >= DATE_SUB(`ANNDATS_CONVERTED`,INTERVAL 1 YEAR)
AND
`working`.`ESTIMID` = BB.`ESTIMID`
)
-- END this calculates the LOWEST number with same `ESTIMID`
AND
`working`.`ANALYST` = ZZ.`ANALYST`
)
)
WHERE `working`.`ANALYST` != ''
This is working in PHP, looping through each record and evaluating all the other records for each. This involves looping and takes a very long time on a large database. I am trying to achieve the same result with MySQL.
I took a few steps to solve this. The first thing I did was write a JOIN that got all of the rows I needed. I joined the table to itself on several conditions:
The estimid matched
The id value was not the same
The analyst column was not null in either table
The anndats_converted of one table was within the previous year of the other table.
To test, I selected the id from both tables to make sure I was getting proper pairings:
SELECT w.id, wo.id
FROM working w
JOIN working wo
ON w.estimid = wo.estimid
AND w.id != wo.id
AND w.analyst IS NOT NULL
AND wo.analyst IS NOT NULL
AND wo.anndats_converted BETWEEN DATE_SUB(w.anndats_converted, INTERVAL 1 YEAR) AND w.anndats_converted
ORDER BY w.id;
A brief result set showed the following pairings:
| id | id |
+----+----+
| 1 | 2 |
| 1 | 5 |
| 1 | 6 |
| 2 | 5 |
| 2 | 6 |
This seems to match what you wanted. For id #1, row 1 is excluded (because it is being calculated) rows 3 and 4 do not fall in the proper date range, row 7 is null and row 8 is a different estimid.
Then, I used an aggregate function to calculate the minimum ireccd by grouping by the first table:
SELECT w.id, w.analyst, MIN(wo.ireccd) AS min_ireccd
FROM working w
JOIN working wo
ON w.estimid = wo.estimid
AND w.id != wo.id
AND w.analyst IS NOT NULL
AND wo.analyst IS NOT NULL
AND wo.anndats_converted BETWEEN DATE_SUB(w.anndats_converted, INTERVAL 1 YEAR) AND w.anndats_converted
GROUP BY w.id;
The next part was also tricky so I'll explain it in two steps. I joined the above query with the original table, with the only condition that the analyst column matched. What this did was create a Cartesian Product, in a way. The query looked like this:
SELECT *
FROM working w
LEFT JOIN(
SELECT w.id, w.analyst, MIN(wo.ireccd) AS min_ireccd
FROM working w
LEFT JOIN working wo
ON w.estimid = wo.estimid
AND w.id != wo.id
AND w.analyst IS NOT NULL
AND wo.analyst IS NOT NULL
AND wo.anndats_converted BETWEEN DATE_SUB(w.anndats_converted, INTERVAL 1 YEAR) AND w.anndats_converted
GROUP BY w.id) temp ON temp.analyst = w.analyst;
And I saw all possible pairings for each person, like this:
| id | analyst | ireccd | id | analyst | min_ireccd |
+----+---------+--------+----+---------+------------+
| 1 | DAVE | 2 | 8 | DAVE | null |
| 1 | DAVE | 2 | 4 | DAVE | 1 |
| 1 | DAVE | 2 | 1 | DAVE | 1 |
| 1 | DAVE | 2 | 2 | DAVE | 1 |
| 1 | DAVE | 2 | 3 | DAVE | 1 |
Notice that compares the first DAVE with all other rows of DAVE in the table. ALSO NOTE I changed the above inner query to include an outer join so that all rows were considered. If there was nothing to calculate, the min_ireccd would be null.
The last thing I did was use that result set, and count the number of times the ireccd matched the min_ireccd. I grouped by id, so in the above sample set, it never matches, so the count would be 0. Here is the final query. It leaves null values (row 7) as null because that's what your expected results show:
SELECT w.*, SUM(w.ireccd = temp.min_ireccd) AS NO_TOP_RATING
FROM working w
LEFT JOIN(
SELECT w.id, w.analyst, MIN(wo.ireccd) AS min_ireccd
FROM working w
LEFT JOIN working wo
ON w.estimid = wo.estimid
AND w.id != wo.id
AND w.analyst IS NOT NULL
AND wo.analyst IS NOT NULL
AND wo.anndats_converted BETWEEN DATE_SUB(w.anndats_converted, INTERVAL 1 YEAR) AND w.anndats_converted
GROUP BY w.id) temp ON temp.analyst = w.analyst
GROUP BY w.id;
These are the results I got:

MySQL query SELECT FROM 2 tables, COUNT the most used

I have this 2 tables and I need to return the moset used office. Note: 1 office can be used by more than 1 guys and the column ido from TableB is populate from TableA
Probaly is a query with group by and desc limit 1
TableA
| ido| office | guy |
---------------------
| 1 | office1| guy1|
| 2 | office2| guy2|
| 3 | office1| guy3|
| 4 | office1| guy4|
| 5 | office5| guy5|
| 6 | office2| guy6|
TableB
| idb| vizit | ido|
---------------------
| 1 | date | 4 |
| 2 | date | 2 |
| 3 | date | 5 |
| 4 | date | 6 |
| 5 | date | 1 |
| 6 | date | 6 |
Thanks!
You were correct in that GROUP BY, LIMIT and DESC are useful here; it leads to a fairly straight forward query;
SELECT TableA.office
FROM TableA
JOIN TableB
ON TableA.ido = TableB.ido
GROUP BY TableA.office
ORDER BY COUNT(*) DESC
LIMIT 1
What it does is basically create rows with all valid combinations, counting the number of generated rows per office. A plain descending sort by that count will give you the most frequently used office.
An SQLfiddle to test with.

Group by with where clause with having count equal to 1

Objective: Find all rows where (1) the number of messages for a number is 1, and (2) the length of the message is less than 5 characters in length. I can do each separately, but having difficulty when I combine the two conditions in one SQL query.
Sample Database Table:
+-----+----------+----------+
| id | number | message |
+-----+----------+----------+
| 1 | 100 | Test |
| 2 | 100 | Testing |
| 3 | 100 | Testing |
| 4 | 200 | Test |
| 5 | 201 | Test |
| 6 | 201 | Test |
| 7 | 250 | Testing |
| 8 | 251 | Test |
| 9 | 300 | Testing |
| 10 | 300 | Testing |
+-----+----------+----------+
Should just return rows 200 and 251. Tried the following, but no luck:
SELECT * FROM `reports` WHERE LENGTH(message) < 5 GROUP BY number HAVING count(*) = '1'
Returns rows but rows contains counts > 1.
Ok, I made the wrong change a few hrs ago - this time I have the data just like yours. Ah, and NOW I see what you're having a problem with. You don't understand the order MySQL interprets your SELECT; it's first doing the WHERE to limit the results THEN it does the GROUP BY. Working as designed.

Is this possible using inner joins in MySQL?

I'm stuck on trying to build a query that will include a given order_reference cover file from the data sample below only if ALL of the print_item_qty values of the files under the same order_reference are equal to 5.
Is this possible using joins or is this outside the remit of a single query?
I've tried building inner joins but cannot work out how to restrict the results so that I only get a cover row when all the component parts of an order have equal values in the print_item_qty column.
Here is the base SELECT query and sample data:
SELECT c1.`order_id`,c1.name1,c1.name2,c1.`print_item_qty`,c1.`sub_item_type`,
c1.`order_reference` FROM print_items;
+--------------+-------+---------+----------------+---------------+-----------------+
| order_id | name1 | name2 | print_item_qty | sub_item_type | order_reference |
+--------------+-------+---------+----------------+---------------+-----------------+
| 000003201875 | Jason | Bramley | 5 | cover | 1875 |
| 000003201875 | Jason | Bramley | 5 | inner | 1875 |
| 000003201875 | Jason | Bramley | 1 | card | 1875 |
| 000003201876 | Jason | Bramley | 5 | cover | 1876 |
| 000003201876 | Jason | Bramley | 5 | inner | 1876 |
+--------------+-------+---------+----------------+---------------+-----------------+
My desired result for the above sample data would be only the following row:
+--------------+-------+---------+----------------+---------------+-----------------+
| order_id | name1 | name2 | print_item_qty | sub_item_type | order_reference |
+--------------+-------+---------+----------------+---------------+-----------------+
| 000003201876 | Jason | Bramley | 5 | cover | 1876 |
+--------------+-------+---------+----------------+---------------+-----------------+
Any help anyone could give to point me in the right direction would be greatly appreciated.
Many thanks.
So you want to verify that you only select data for orders for which the print_item_qty = 5 for all sub_item_type in that order_reference.
To do this, use a subsquery like
SELECT order_reference,
MAX(print_item_qty),
MIN(print_item_qty)
FROM print_items
GROUP BY order_reference
HAVING MAX(print_item_qty) = 5
AND MIN(print_item_qty) = 5
And join to your original dataset. The subquery will restrict to the ids you want, and joining back will return all rows associated with the order_references for which print_item_qty = 5 for every sub_item_type, eg,
SELECT c1.`order_id`,
c1.name1,
c1.name2,
c1.`print_item_qty`,
c1.`sub_item_type`,
c1.`order_reference`
FROM print_items AS c1
INNER JOIN (SELECT order_reference, MAX(print_item_qty), MIN(print_item_qty) FROM print_items
GROUP BY order_reference HAVING MAX(print_item_qty) = 5 AND MIN(print_item_qty) = 5) AS b
ON c1.order_reference = b.order_reference

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 |
+---+