How to UPDATE basing on SELECT results from 2 queries in mySQL? - mysql

Let assume we have 2 tables:
table name: hids
hid
status
001
2
002
1
003
1
004
2
005
2
...
unique on hid
and:
table name: times
hid
uid
time
001
00001
12345
001
00001
12567
001
00002
12540
001
00003
12541
001
00003
12567
002
00001
12575
...
(no uniques)
The problem is:
For a given user (eg. uid=00001) UPDATE status in "hids" with 0 if:
current status is 2
in the "times" table there isn't any record for any other user with time > (time of the latest entry for uid=00001 and the same hid)
Currently I do it with PHP is a way which is not too effecitve (thousends of atom queries). As the database grows over time (even for several milion records) the code is ineffective due to PHP overhead. Is there any option to make it simpler?

As noted in the comments, you should make the switch to using parameterized prepared statements. Given that you are currently using sprintf to inject your variables into your SQL, it will be a very small change.
You could significantly reduce the overhead of your current process by only returning the maximum time per hid for the given user -
SELECT times.hid, MAX(times.time) AS max_time
FROM times
JOIN hids ON times.hid = hids.hid
WHERE times.uid = 1
AND hids.status = 2
GROUP BY times.hid;
But a better option is to JOIN hids and times and then use NOT EXISTS (or LEFT JOIN IS NULL) to find where there is no other user with a greater time -
UPDATE hids h
JOIN times t ON h.hid = t.hid
SET h.status = 0
WHERE t.uid = 1
AND h.status = 2
AND NOT EXISTS (
SELECT 1 FROM times WHERE hid = t.hid AND uid <> t.uid AND time > t.time
)
Depending on the distribution of your data and how it is indexed you will probably get better performance by pre-calculating the max time per hid -
UPDATE hids h
JOIN (
SELECT t.hid, t.uid, MAX(time) AS max_time
FROM hids h
JOIN times t ON h.hid = t.hid
WHERE t.uid = 1
AND h.status = 2
GROUP BY t.hid, t.uid
) t ON h.hid = t.hid
SET h.status = 0
WHERE NOT EXISTS (
SELECT 1 FROM times WHERE hid = t.hid AND uid <> t.uid AND time > t.max_time
);

Related

Mysql join with one to many relations

claims
c_id claim id
1 201
2 202
3 203
4 204
claim_status
cs_id claim_id status
1 201 created
2 202 created
3 202 submitted
4 203 submitted
5 204 created
If the claim is created and submitted(like claim_id 202) it would not show up if i search with 'created' condition. this is my main requirement. i need result like below
If i search with status='created' i need to get records as below
c_id claim_id cs_id claim_id status
1 201 1 201 created
4 204 5 204 created
If i search with status='submitted' i need to get records as below
c_id claim_id cs_id claim_id status
2 202 3 202 submitted
3 203 4 203 submitted
But i'm unable to achive my result with below query. I'm new to stackoverflow. so please forgive me if i'm wrong in clear posting.
SQL:
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='created'
GROUP BY cs.claim_id
for 'created':
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='created'
for 'submitted':
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='submitted'
To get claims that are created but not submitted try this:
SELECT * from claims c
INNER JOIN claim_status cs
ON c.claim_id = cs.claim_id
WHERE cs.status='created'
AND NOT EXISTS (
SELECT 1
FROM claim_status cs2
WHERE cs2.claim_id = cs.claim_id
AND cs2.status='submitted'
)
Are those your actual tables? why are you handling claim statuses on another table than claims? You should have a "status" column on "claims" and you are done. You could have another table for versioning (if you want to get older states of the claim) and save changes, timestamps, etc, but for the current status is much better to have that on the same table
I know it's not the answer you are asking, but I think it's better to have something like:
SELECT * FROM claims WHERE status = 'created'
or
SELECT * FROM claims WHERE status = 'submitted'
It's faster, cleaner, better, everything (?)
Also, to make them even faster, status should be an integer column and "created" should have an associated number so you can do
SELECT * FROM claims WHERE status = <value that means created>
the same for submitted or any extra status

mySQL Query - select join if latest record in join table contains a specific value

I am trying to write a select statement with a right join (to clients), that will find a specific value in the join table - but ONLY if that is the most recent value for each client (ignoring blanks and nulls).
Clients
Id Name
0 John Doe
1 Frank Smith
2 Sue Smith
3 John Smith
Activity (join table)
ClientId Type Date
0 500 2013-01-01 00:00:08
1 900 2013-01-01 00:00:07
2 NULL 2013-01-01 00:00:06
3 2013-01-01 00:00:05
4 500 2013-01-01 00:00:05
0 800 2013-01-01 00:00:04
1 500 2013-01-01 00:00:03
2 500 2013-01-01 00:00:02
3 500 2013-01-01 00:00:01
4 800 2013-01-01 00:00:00
So this query will at least give me only the client records that have an activity type of 500 (in this case I would get back client 0 and 4):
select * from clients right join activity on activity.clientid = clients.id
where activity.type = 500
HOWEVER, I need to figure out how to make this return ONLY the first record in the above list of records. The logic there is Client #0 is the only client that has 500 as it's latest activity type = 500. The other 3 clients have NULL, blank, or 900 for example as their 'latest' activity type.
I am thinking some magic with ordering (the date would normally be pretty accurate), a 'top' and/or 'limit' and possibly union? Just cant quite wrap my head around it.
Please try this
SELECT activity.id AS activityid
, activity.type
, activity.date
, clients.id AS clientid
, clients.name
FROM activity
LEFT JOIN activity AS other_activities
ON activity.ClientID = other_activities.ClientID
AND activity.date < other_activities.date
LEFT JOIN clients
ON activity.ClientID = clients.id
WHERE activity.type = 500
AND other_activities.ClientID IS NULL;
SELECT * from Activity
INNER JOIN (SELECT MIN(Date) as min_date, clientID
FROM Activity
GROUP BY clientID) temp
ON Activity.clientID = temp.clientID
WHERE date = min_date and type = 500
This will return all clientID's whose most recent activity was of type 500.
This will get you the most recent Activity of type 500 and the client of that activity
SELECT * FROM
(SELECT *
FROM activity
WHERE type=500
ORDER BY date DESC
LIMIT 1) a
LEFT JOIN
clients c
ON (a.clientid = c.id)
of if you only want the result if it's the most recent activity and the type is 500 you can use
SELECT * FROM
(SELECT *
FROM activity
ORDER BY date DESC
LIMIT 1) a
LEFT JOIN
clients c
ON (a.clientid = c.id)
WHERE a.type = 500;
sqlFiddle here to get clients who have the latest activity of type 500
SELECT a1.ClientID,c.name,a1.Type,a1.Date
FROM activity a1
LEFT JOIN clients c ON (c.id = a1.clientid)
WHERE NOT EXISTS (SELECT 1
FROM activity a
WHERE a.clientid = a1.clientid
and a.date > a1.date)
AND a1.type = 500;

selecting max record in mysql

I have a table of multiple transactions. I am trying to get the row of the last transaction.
I am using the following:
select n.AccountNumber, max(PostDate), f.TransAmt
from mp_cycle n, fintrans f
where n.AccountNumber = f.AccountNumber
and TransCode >= '50'
and TransCode <= '59'
group by n.AccountNumber
This is returning the last date for a particular account, but the TransAmt is not for the same record.
ie:
Acct # Date Amt
1 1/1 10.00
1 1/2 11.00
1 1/3 12.00
2 1/2 20.00
2 1/3 21.00
2 1/4 22.00
My select will return the last date for each account, so 1/3 for act # 1 and 1/4 for act # 2, but the Amt field is not the amt that goes with that record.
Any assistance will be greatly appreciated.
There are many ways to solve this problem, one is by joining extra subquery which separate gets the latest PostDate for every AccountNumber. The result of the subquery will then be joined on the other table provided that it should match on two columns: AccountNumber and PostDate.
SELECT a.*, b.*
FROM mp_cycle a
INNER JOIN fintrans b
ON a.AccountNumber = b.AccountNumber
INNER JOIN
(
SELECT AccountNumber, MAX(PostDate) max_date
FROM fintrans
GROUP BY AccountNumber
) c ON b.AccountNumber = c.AccountNumber AND
b.PostDate = c.max_date
-- WHERE ..your conditions here..

Select columns in same row as MAX(column)

OK, here is my SQL, and I'm using MySQL:
SELECT
assets.id,
IF(max(asset_checkins.time) IS NULL AND max(asset_checkouts.time) IS NOT NULL, 'checked-out',
IF(max(asset_checkouts.time) > max(asset_checkins.time), 'checked-out',
'checked-in')
) 'status',
asset_checkouts.user, asset_checkouts.location
FROM
assets
left outer join asset_checkouts on asset_checkouts.asset = assets.id
left outer join asset_checkins on asset_checkins.asset = assets.id
group by assets.id;
The problem is that the user and location columns are not coming from the row corresponding to the max(psdl_asset_checkouts.time) for that asset.
Instead I get:
id status user location
15 checked-out 1 4<-this
16 checked-out 1 4
When I want to get:
id status user location
15 checked-out 1 7<-this
16 checked-out 1 4
Here is the asset_checkouts table, the value I want is the "7"; the value corresponding to the max(time) for asset 15.
id user asset time location
3 1 15 7/30/12 12:29 4
14 1 15 7/31/12 11:01 7
My thought is that I will need to do a sub-select, but I'm not sure the best way of doing it.
It's not pretty but it works. It will return the latest status of the asset and the user and location where it was either checked out or checked in. This might have worked a little better if the checkins and checkouts were in the same table.
SELECT id,`status`,user,location FROM
(
SELECT a.id, 'checked-out' `status`,co.`time`, co.user, co.location
FROM assets a left join asset_checkouts co on a.id=co.asset
UNION ALL
SELECT a.id, 'checked-in' `status`, ci.`time`, ci.user, ci.location
FROM assets a left join asset_checkins ci on a.id=ci.asset
WHERE NOT ci.`time` IS NULL
) U
WHERE `time` = GREATEST((SELECT COALESCE(MAX(`time`),0)
FROM asset_checkouts co
WHERE co.asset=U.id),
(SELECT COALESCE(MAX(`time`),0)
FROM asset_checkins ci
WHERE ci.asset=U.id))

I want some logic in this query using MYSQL

I have a two tables first one is called teams and second one is called cpd and I want this result required (see result screen below). I tried myself but was not successful (see practice query below).
teams table
id name sub_cat_id
1 SACRAMENTO KINGS 19
2 KINGS 19
3 MIMAMI HEAT 19
4 HEAT 20
5 KITE 20
cpd table
id team_id status added_date
1 3 1 2012-05-26
2 3 1 2012-05-27
3 3 0 2012-05-28
practice Query
SELECT
t.`id`,t.`name`,IFNULL(cpd.status,0) AS resultStatus,IFNULL(cpd.added_date,CURDATE()) AS added_date
FROM `teams` t
LEFT JOIN cpd ON cpd.team_id = t.id
WHERE t.`sub_cat_id` = 19 OR cpd.added_date = CURDATE()
Result Screen (Required only those rows are black color in screen)
Update
Explanation ?
I am trying to get those rows who they are related with sub_cat_id = 19 like this in team table
Join team table with cpd table for cpd.status filed
cpd.status must be related with current date in cpd table like 2012-05-28
There are more than one way to get the desired result:
For example:
SELECT t.`id`,t.`name`,
IFNULL(cpd.status,0) AS resultStatus,
IFNULL(cpd.added_date,CURDATE()) AS added_date
FROM `teams` t
INNER JOIN cpd ON (cpd.team_id = t.id AND cpd.status = 0)
WHERE t.`sub_cat_id` = 19
OR
cpd.added_date = CURDATE()
Your JOIN ON cpd.team_id = t.idonly matches one tuple with the cpd table so for the other tuples date is set as NULL (because you are doing LEFT JOIN) and hence the where query gives only one tuple
SELECT
t.id,t.name,IFNULL(cpd.status,0) AS resultStatus,IFNULL(cpd.added_date,CURDATE()) AS added_date
FROM teams t
LEFT JOIN cpd ON cpd.team_id = t.id
WHERE t.sub_cat_id = 19 OR cpd.added_date = CURDATE()
GROUP BY t.id