MySQL updating all records having max value within group - mysql

Apologies if this has been answered elsewhere and I'm just not seeing it; this is the closest I've found, but it isn't quite what I'm trying to do.
MySQL - updating all records to match max value in group
I have a table on a production web server with about 15,000 rows. There are many records sharing an item_name with another record, but item_meters is (normally, but not always) a unique value for each row. item_id is always unique. Currently every record has a value of "0" in the item_flag column.
I would like to update all records with the largest item_meters value within each item_name group to have an item_flag value of "1".
Here is a simplified version of the table ordered by item_id ASC:
----------------------------------------------
mytable
----------------------------------------------
item_id | item_name | item_meters | item_flag
--------+-----------+-------------+-----------
001 | aaa | 224 | 0
002 | aaa | 359 | 0
003 | aaa | 456 | 0
004 | bbb | 489 | 0
005 | bbb | 327 | 0
006 | bbb | 215 | 0
007 | ccc | 208 | 0
008 | ccc | 756 | 0
009 | ccc | 756 | 0
--------+-----------+-------------+-----------
The desired result would be a table with "1" in the item_flag column for each "aaa" having the largest item_meters, each "bbb" having the largest item_meters, each "ccc" having the largest item_meters, etc. like this:
----------------------------------------------
mytable
----------------------------------------------
item_id | item_name | item_meters | item_flag
--------+-----------+-------------+-----------
001 | aaa | 224 | 0
002 | aaa | 359 | 0
003 | aaa | 456 | 1
004 | bbb | 489 | 1
005 | bbb | 327 | 0
006 | bbb | 215 | 0
007 | ccc | 208 | 0
008 | ccc | 756 | 1
009 | ccc | 756 | 0
--------+-----------+-------------+-----------
(In case there are 2 or more records having the same item_name and the same item_meters (e.g. item_id 008 and 009 above), the desired result would be for the record with the numerically lower item_id (item_id is always unique), to have an item_flag value of "1" while the row with a numerically higher item_id would still have an item_flag value of "0")
Also of note, even though this database is running behind a production web server with new rows added every day, there will be no need to update the table every time a new row is added. It is something that will only be required once, regardless of whether new rows are later added outside of the parameters. The reason I mention this, is because execution speed is not a big concern since the query will only be executed once.
Thank you in advance! Please let me know if I can provide more info or clarify my question in any way.

The approach I take is to first write a query (SELECT statement) that will return the item_id values of the rows we want to update.
As a starting point, get the maximum value for item_meters, a simple query like this:
SELECT m.item_name
, MAX(m.item_meters) AS max_item_meters
FROM my_table m
GROUP BY m.item_name
We can use that query as an inline view in another query, to get the lowest item_id for each of those item_name
SELECT MIN(o.item_id) AS min_item_id
FROM ( SELECT m.item_name
, MAX(m.item_meters) AS max_item_meters
FROM my_table m
GROUP BY m.item_name
) n
JOIN my_table o
ON o.item_name = n.item_name
AND o.item_meters = n.max_item_meters
GROUP BY o.item_name, o.item_meters
And we can use that query as an inline view that gets the whole row associated with the item_id values we returned...
SELECT t.item_id
, t.item_name
, t.item_meters
, t.item_flag
FROM my_table t
JOIN ( SELECT p.min_item_id
FROM ( SELECT MIN(o.item_id) AS min_item_id
FROM ( SELECT m.item_name
, MAX(m.item_meters) AS max_item_meters
FROM my_table m
GROUP BY m.item_name
) n
JOIN my_table o
ON o.item_name = n.item_name
AND o.item_meters = n.max_item_meters
GROUP BY o.item_name, o.item_meters
) p
) q
ON q.min_item_id = t.item_id
Once the SELECT query is working, convert that to an UPDATE statement... replace the SELECT ... FROM with UPDATE, and add a SET clause. (Sometimes, it's necessary to wrap the inline view in yet another SELECT, to avoid MySQL error about disallowing references to the table we are updating.)
UPDATE my_table t
JOIN ( SELECT p.min_item_id
FROM ( SELECT MIN(o.item_id) AS min_item_id
FROM ( SELECT m.item_name
, MAX(m.item_meters) AS max_item_meters
FROM my_table m
GROUP BY m.item_name
) n
JOIN my_table o
ON o.item_name = n.item_name
AND o.item_meters = n.max_item_meters
GROUP BY o.item_name, o.item_meters
) p
) q
ON q.min_item_id = t.item_id
SET t.item_flag = '1'
If the intent is not to UPDATE the existing table, but to return a resultset, we can write a query and do an outer join to that same inline view, and return either a 0 or a 1 for item_flag, testing whether the item_id matches one we want flagged as 1...
SELECT t.item_id
, t.item_name
, t.item_meters
, IF(q.min_item_id IS NULL,0,1) AS `item_flag`
FROM my_table t
LEFT
JOIN ( SELECT p.min_item_id
FROM ( SELECT MIN(o.item_id) AS min_item_id
FROM ( SELECT m.item_name
, MAX(m.item_meters) AS max_item_meters
FROM my_table m
GROUP BY m.item_name
) n
JOIN my_table o
ON o.item_name = n.item_name
AND o.item_meters = n.max_item_meters
GROUP BY o.item_name, o.item_meters
) p
) q
ON q.min_item_id = t.item_id

Related

Sum Log values from table using second table

I have a huge table where a new row could be an "adjustment" to a previous row.
TableA:
Id | RefId | TransId |Score
----------------------------------
101 | null | 3001 | 10
102 | null | 3002 | 15
103 | null | 3003 | 15
104 | 101 | | -5
105 | null | 3004 | 5
106 | 105 | | -10
107 | null | 3005 | 15
TableB:
TransId | Person
----------------
3001 | Harry
3002 | Draco
3003 | Sarah
3004 | Ron
3005 | Harry
In the table above, Harry was given 10 points in TableA.Id=101, deducted 5 of those points in TableA.Id=104, and then given another 15 points in TableA.Id=107.
What I want to do here, is return all the rows where Harry is the person connected to the score. The problem is that there is no name attached to a row where points are deducted, only to the rows where scores are given (through TableB). However, scores are always deducted from a previously given score, where the original transaction's Id is referred to in the tables as "RefId".
SELECT
SUM TableA.Score
FROM TableA
LEFT JOIN TableB ON TableA.Trans=TableB.TransId
WHERE 1
AND TableB.Person='Harry'
GROUP BY TableA.Score
That only gives me the points given to Harry, not the deducted ones. I would like to get the total scored returned, which would be 20 for Harry. (10-5+15=20)
How do I get MySQL to include the negative scores as well? I feel like it should be possible using the TableA.RefId. Something like "if there is a RefId, get the score from this row, but look at the corresponding TableA.Id for the rest of the data".
Select sum(total) AS total
From tableb
Join
(
Select t1.transid, sum(score) AS total
From tablea t1
Join tablea t2 on t1.id = t2.refid
group by t1.transid
) x on x.transid = tableb.transid
Where TableB.Person='Harry'
try this:
select sum(sum1 + sums) as sum_all from (
SELECT t1.id,T1.Score sum1, coalesce(T2.score,0) sums
FROM Table1 t1
inner JOIN Table2 ON T1.TransId=Table2.TransId
left JOIN Table1 t2 ON t2.RefId = t1.id
WHERE Table2.Person='Harry'
)c
DEMO HERE
OUTput:
SUM_ALL
20
If you assume that adjustments don't modify adjustments, you can do this without aggregating all the data:
select sum(a.score + coalesce(aref.score, 0)) as HarryScore
from tableA a left outer join
tableA aref
on a.refId = aref.id left outer join
tableB b
on a.TransId = b.Transid left outer join
tableB bref
on aref.TransId = bref.TransId
where b.Person = 'Harry' or bref.Person = 'Harry';

MySQL same Query multiple tables

I need to query different tables that have the same columns but different content.
Table A:
ID DocDate Type
1 2013-05-01 A
2 2013-05-01 B
3 2013-05-02 D
4 2013-05-04 D
Table B:
ID DocDate Type
1 2013-05-01 F
2 2013-05-03 G
3 2013-05-03 G
4 2013-05-05 H
What I need:
COUNT(Tablea.ID) COUNT(Tableb.ID) DocDate
2 1 2013-05-01
1 NULL 2013-05-02
NULL 2 2013-05-03
1 NULL 2013-05-04
NULL 1 2013-05-05
Any help would be really appreciated.
Try
SELECT d.docdate, a.total totala, b.total totalb
FROM
(
SELECT docdate
FROM tablea
UNION
SELECT docdate
FROM tableb
) d LEFT JOIN
(
SELECT docdate, COUNT(*) total
FROM tablea
GROUP BY docdate
) a ON d.docdate = a.docdate LEFT JOIN
(
SELECT docdate, COUNT(*) total
FROM tableb
GROUP BY docdate
) b ON d.docdate = b.docdate
 ORDER BY d.docdate
Output:
| DOCDATE | TOTALA | TOTALB |
--------------------------------
| 2013-05-01 | 2 | 1 |
| 2013-05-02 | 1 | (null) |
| 2013-05-03 | (null) | 2 |
| 2013-05-04 | 1 | (null) |
| 2013-05-05 | (null) | 1 |
Here is SQLFiddle demo
There are a couple of ways to get this result.
The most efficient query to return the specified rows is likely going to be:
SELECT NULLIF(SUM(c.cnt_a_id),0) AS cnt_a_id
, NULLIF(SUM(c.cnt_b_id),0) AS cnt_b_id
, c.DocDate
FROM (
SELECT COUNT(a.ID) AS cnt_a_id
, 0 AS cnt_b_id
, a.DocDate AS DocDate
FROM Table_A a
GROUP BY a.DocDate
UNION ALL
SELECT 0
, COUNT(b.ID)
, b.DocDate
FROM Table_B b
GROUP BY b.DocDate
) c
GROUP BY c.DocDate
Suitable covering indexes on (DocDate, ID) of each table will benefit performance on large sets.
Another simpler to understand, but more expensive, would be create the UNION of the tables, and then perform the GROUP BY.
SELECT NULLIF(COUNT(c.a_id)) AS cnt_a_id
, NULLIF(COUNT(c.b_id)) AS cnt_b_id
, c.DocDate
FROM (
SELECT a.ID AS a_id
, NULL + 0 AS b_id
, a.DocDate AS DocDate
FROM Table_A a
UNION ALL
SELECT NULL + 0 AS a_id
, b.ID AS b_id
, b.DocDate AS DocDate
FROM Table_B b
) c
GROUP BY c.DocDate
(This second query is less efficient, because of the way MySQL materializes the query in the inline view as a temporary MyISAM table; this second query basically creates a copy of Table_A and Table_B concatenated together, and runs a query against that.
The first query is little different, in that it produces smaller sets to be concatenated together.

Join same table without conditions - mysql

I have a table that has records
id_queue | user_id | id_book | status
69 | 5 | 4 | 1
133 | 3 | 4 | 2
142 | 1 | 4 | 0
I want a query that will give me this result
id_queue | id_queue
69 | 142
133 | null
I've tried something like this
SELECT s1.`id_queue`,s2.`id_queue` FROM `second` as s1
LEFT JOIN `second` as s2 ON s2.`book_id`=4 AND s2.`status` IN (0)
WHERE s1.`book_id`=4 AND s1.`status` IN (1,2,4)
but it keeps bringing me this result.
id_queue | id_queue
69 | 142
133 | 142
I think this is because I don't have anything identical for conditions. What can I do?
The issue is that you are joining on the book_id not the status, so you will always have a matching row.
If you want to hide the second instance of the second id_queue then you can use user defined variables:
select Queue1,
case when seq = 1 then queue2 else null end Queue2
from
(
select
Queue1,
Queue2,
#row:=(case when #prev=Queue2 then #row else 0 end) +1 as Seq,
#prev:=Queue2
from
(
SELECT
s1.`id_queue` Queue1,
s2.`id_queue` Queue2
FROM `second` as s1
LEFT JOIN `second` as s2
ON s1.`id_book` = s2.`id_book`
AND s2.`status` = 0
WHERE s1.`id_book`=4
AND s1.`status` IN (1,2,4)
) src, (SELECT #row:=0, #prev:=null) r
order by Queue1, Queue2
) s1
order by Queue1, Queue2
See SQL Fiddle with Demo
The result is:
| QUEUE1 | QUEUE2 |
-------------------
| 69 | 142 |
| 133 | (null) |
I believe this happens because you're setting multiple values of the same column on multiple columns... this way it may end up seeming as multiple rows.
And it seems that if you put a "DISTINCT" command just after the select it wouldn't cause any value to disappear, as it would check if all the columns of the row were the same to another one.
But you can always try to put the DISTINCT just before the value you don't want to repeat and check if it works.
Wish I could help you more.
Example:
SELECT s1.`id_queue`, DISTINCT (s2.`id_queue`)
FROM `second` as s1
LEFT JOIN `second` as s2 ON s2.`book_id`=4 AND s2.`status` IN (0)
WHERE s1.`book_id`=4 AND s1.`status` IN (1,2,4)

Use 'JOIN' to select top five records from a table that match a specific column in table two

I have two table:
Audit
AUDITID | CUSTOMER | CUSTOMERNUMBER
001 | BILLY | 11111
002 | HOLLY | 12222
003 | HOLLY | 12222
004 | DON | 13333
005 | DON | 13333
Summary
AuditID | Summary | Date
001 | 1 | 30/1/2012
001 | 2 | 1/10/2012
001 | 3 |20/10/2012
004 | 4 | 2/09/2012
004 | 5 | 3/01/2012
I want to select the top five records table for each different AuditID that matches an Audit ID From the Audit table.
The sql script i have so far is :
SELECT Auditid, summary, date
FROM [Summary] SL1
INNER JOIN [Audit] AL1 ON SL1.[AuditID] = AL1.[AuditID]
WHERE AL1.[AuditID] IN (
SELECT TOP 5 AuditID
FROM [Audit] AL2
WHERE AL1.[CustomerNumber] = AL2.[CustomerNumber]
ORDER BY AL2.[AuditID] DESC
)
You can use ROW_NUMBER with PARTITION BY:
WITH CTE AS
(
SELECT Auditid, summary, date,
, RN = ROW_NUMBER() OVER (PARTITION BY SL1.AuditID ORDER BY SL1.Date ASC)
FROM [Summary] SL1
INNER JOIN [Audit] AL1 ON SL1.[AuditID] = AL1.[AuditID]
)
SELECT Auditid, summary, date FROM CTE
WHERE RN <= 5
That returns the TOP 5 records for each AuditID ordered by Date(oldest first, use DESC otherwise).
You have to specify which order you want the "top" records from summary - I chose to do it by summary, you can do by date or something. Also I did top2 to show results using your sample data.
select audit.auditid,Summary,DATE
from
Audit inner join Summary Sum_Tab1 on Audit.AUDITID=Sum_Tab1.AUDITID
where
Sum_Tab1.SUMMARY in
(SELECT top 2 SUMMARY from Summary Sum_Tab2
where Sum_Tab1.AUDITID=Sum_Tab2.AUDITID order by summary)

MySQL: Join a table to itself

I have a table of preferences, called "txp_prefs". I would like to return multiple preferences into a single row; the reason I prefer this to a simple concatenation is that I'm using a plugin in textpattern which can process the single row.
Here is the testing data I have:
------------------------------------------------
|Id | event | name |value |
------------------------------------------------
| 1 | season | season_start | 12/10/2011 |
-----------------------------------------------
| 2 | season | season_end | 29/10/2011 |
------------------------------------------------
| 3 | season | season_countdown | 7 |
------------------------------------------------
| 4 | another | test1 | result1 |
------------------------------------------------
| 3 | | test2 | result2 |
------------------------------------------------
The final result I would like to get is:
----------------------------------------------------------
|event | season_start | season_end | season_countdown |
----------------------------------------------------------
|season | 12/10/2011 | 29/10/2011 | 7 |
----------------------------------------------------------
I can (obviously) create the separate queries to get each result independently; for example
SELECT t1.event, t1.val AS season_start FROM txp_prefs t1 WHERE t1.event="season" AND t1.name="season_start" (to get the season_start)
SELECT t2.event, t2.val AS season_end FROM txp_prefs t2 WHERE t2.event="season" AND t2.name="season_end" (to get the season_end)
But I get errors when I try to join the two together, eg like this:
SELECT t1.event, t1.val AS season_start FROM txp_prefs t1 WHERE t1.event="season" AND t1.name="season_start"
LEFT JOIN
(SELECT t2.event, t2.val AS season_end FROM txp_prefs t2 WHERE t2.event="season" AND t2.name="season_end") t3
ON t1.event=t3.event
The error messages says it is something to do with the join (which I guessed anyway - the two individual queries work.
Any ideas? I have recently figured through joining different tables together, so I assume it is possible to join a table to itself.
Based on the structure given you can use
SELECT
MAX(CASE WHEN name = 'season_start' THEN value END) AS season_start,
MAX(CASE WHEN name = 'season_end' THEN value END) AS season_end,
MAX(CASE WHEN name = 'season_countdown' THEN value END) AS season_countdown
FROM txp_prefs
WHERE event='season'
You can do this by pivoiting. Asper my past project I demostrate you in following query hope will be useful to you.
My table transaction is having following fields
NAME VARCHAR2(10)
branch_code NUMBER(4)
Ruppes NUMBER(4)
SQL> select * from transaction;
NAME branch_code Ruppes
---------- ---------- ----------
Hemang 2602 1000
Hemang 2603 2000
Hemang 2400 3000
Yash 2602 1500
Yash 2603 1200
Yash 2400 1340
Krupesh 2602 1250
Krupesh 2603 2323
Krupesh 2400 8700
9 rows selected.
Now pivoting.
SQL> select branch_code,
2 max( decode( name, 'Hemang', Ruppes, null ) ) "Hemang",
3 max( decode( name, 'Yash', Ruppes, null ) ) "Yash",
4 max( decode( name, 'Krupesh', Ruppes, null ) ) "Krupesh"
5 from
6 (
7 select name, branch_code, Ruppes
8 from transaction
9 )
10 group by branch_code ;
branch_code Hemang Yash Krupesh
---------- ---------- ---------- ----------
2602 1000 1500 1250
2603 2000 1200 2323
2400 3000 1340 8700
select you are looking for is:
SELECT distinct
t0.event,
t1.val AS season_start ,
t2.val as seasson_end,
t3.val as season_countdown
FROM
txp_prefs t0
left outer join
txp_prefs t1
on ( t1.event=t0.event AND t1.name="season_start" )
left outer join
txp_prefs t2
on ( t2.event=t0.event AND t2.name="season_end" )
left outer join
txp_prefs t3
on ( t3.event=t0.event AND t3.name="season_countdown" )
WHERE
t0.event="season"
(the standard way to get only one row is 'distintc' reserved word. Another solution is append 'LIMIT 1' to query, but this is MySQL dependant)
are you sure that your database is right normalized?
see you.