Update max value from a mysql table - mysql

I'm trying to update a table's value using max(col) in a subquery, but for some reason, it's updating all values that match the user_id column I'm using.
This is my table structure:
user_id | steps | in_date
--------+-------+-----------
8 |10 | 1522246892
8 |10 | 1522250713
7 |10 | 1522250799
And this is my query:
UPDATE userdata
SET steps = (steps + 20)
WHERE user_id = 8
AND in_date = (SELECT max(in_date) WHERE user_id = 8);
I expected it to update only the second column, but it instead updates both columns with user_id = 8. Why isn't it working as expected? What am I doing wrong?
Edit: Thanks to Manoj's comment, I changed the query to the following, and it works:
UPDATE userdata
SET steps = (steps + 20)
WHERE user_id = 8
ORDER BY in_date DESC LIMIT 1;
Doing it his way is even better, since I don't have to run two queries, and already gets the highest one by id.

You will encounter sql error "You can't specify target table 'userdata' for update in FROM clause" when you use same table in the sub-query to update the same table.
Its strange that you say its running because,
- you missed from clause in your sub-query
- you can't use same table
Can you please be more specific.

seems you missed the from clause in subselect and for avoid conflit between select and updated value you could build a temp table using ainner subselect
UPDATE userdata
SET steps = (steps + 20)
WHERE user_id = 8
AND in_date = ( select max_date from (
SELECT max(in_date) max_date FROM userdata WHERE user_id = 8) t);
and in this way the AND clause could be always true

Related

SQL: update value based on following rows data

I have a table customer_service_types in phpmyadmin with these fields:
id (int) | customer_id (int) | service_type_int (int) | price (decimal) | created (datetime) | card (tinyint)
Here are some entries:
The application when the user adds a new service for a customer with a custom created date, is creating another service with id : 3 (payment) with today's date. I want to update all payments with the date of the following services.
So what I want to do is update the created field of the payments with the created value of the following services for specific client. So for example I need to update created of id: 168 with the value of created for the same customer_id but only of the following row if it has service_type_id != 3.
Tried so far:
UPDATE customer_service_types cst
SET cst.created = (SELECT created
FROM customer_service_types cst2
WHERE cst.id = (cst2.id - 1) AND cst2.service_type_id <> 3
)
WHERE cst.service_type_id = 3;
But I get this error:
You can't specify target table 'cst' for update in FROM clause
And I don't know if this query will produce the desired result
You can transfer your subquery to a Derived Table form instead. Try the following:
UPDATE customer_service_types AS cst
JOIN
(
SELECT id, created
FROM customer_service_types
WHERE service_type_id <> 3
) AS cst2
ON cst.id = (cst2.id - 1)
SET cst.created = cst2.created
WHERE cst.service_type_id = 3;
You can use a join:
UPDATE customer_service_types cst JOIN
customer_service_types cstnext
ON cstnext.id = cst.id and cstnext.service_type_id <> 3
SET cst.created = cstnext.created
WHERE cst.service_type_id = 3;

ColdFusion Query too slow

I have queries inside a cfloop that makes the process very slow. Is there a way to make this query faster?
<cfquery name="GetCheckRegister" datasource="myDB">
SELECT * FROM CheckRegister, ExpenseType
Where PropertyID=10
and ExpenseType.ExpenseTypeID=CheckRegister.ExpenseTypeID
</cfquery>
<CFOUTPUT query=GetCheckRegister>
<cfquery name="GetVendorName" datasource="myDB"> SELECT * FROM Vendors WHERE VendorID=#VendorID#</cfquery>
<!--- I use the vendor name here --->
<cfset local.CreditDate = "" />
<cfquery name="getTenantTransactionDateFrom" dataSource="myDB">
Select TenantTransactionDate as fromDate From TenantTransactions
Where CheckRegisterID = #CheckRegisterID#
Order By TenantTransactionDate Limit 1
</cfquery>
<cfquery name="getTenantTransactionDateTo" dataSource="myDB">
Select TenantTransactionDate as ToDate From TenantTransactions
Where CheckRegisterID = #CheckRegisterID#
Order By TenantTransactionDate desc Limit 1
</cfquery>
<cfif getTenantTransactionDateFrom.fromDate neq "" AND getTenantTransactionDateTo.ToDate neq "">
<cfif getTenantTransactionDateFrom.fromDate eq getTenantTransactionDateTo.ToDate>
<cfset local.CreditDate = DateFormat(getTenantTransactionDateFrom.fromDate, 'mm/dd/yyyy') />
<cfelse>
<cfset local.CreditDate = DateFormat(getTenantTransactionDateFrom.fromDate, 'mm/dd/yyyy') & " - " & DateFormat(getTenantTransactionDateTo.ToDate, 'mm/dd/yyyy') />
</cfif>
</cfif>
<!--- I use the local.CreditDate here --->
<!--- Here goes a table with the data --->
</CFOUTPUT>
cfoutput works like a loop.
As others have said, you should get rid of the loop and use joins. Looking at your inner loop, the code retrieves the earliest and latest date for each CheckRegisterID. Instead of using LIMIT, use aggregate functions like MIN and MAX and GROUP BY CheckRegisterID. Then wrap that result in a derived query so you can join the results back to CheckRegister ON id.
Some of the columns in the original query aren't scoped, so I took a few guesses. There's room for improvement, but something like is enough to get you started.
-- select only needed columns
SELECT cr.CheckRegisterID, ... other columns
FROM CheckRegister cr
INNER JOIN ExpenseType ex ON ex.ExpenseTypeID=cr.ExpenseTypeID
INNER JOIN Vendors v ON v.VendorID = cr.VendorID
LEFT JOIN
(
SELECT CheckRegisterID
, MIN(TenantTransactionDate) AS MinDate
, MAX(TenantTransactionDate) AS MaxDate
FROM TenantTransactions
GROUP BY CheckRegisterID
) tt ON tt.CheckRegisterID = cr.CheckRegisterID
WHERE cr.PropertyID = 10
I'd highly recommend reading up on JOIN's as they're critical to any web application, IMO.
You should get all of your data in one query, then work with that data to output what you want. Multiple connections to a database almost always be more resource-intensive than getting the data in one trip and working with it. To get your results:
SQL Fiddle
Initial Schema Setup:
CREATE TABLE CheckRegister ( checkRegisterID int, PropertyID int, VendorID int, ExpenseTypeID int ) ;
CREATE TABLE ExpenseType ( ExpenseTypeID int ) ;
CREATE TABLE Vendors ( VendorID int ) ;
CREATE TABLE TenantTransactions ( checkRegisterID int, TenantTransactionDate date, note varchar(20) );
INSERT INTO CheckRegister ( checkRegisterID, PropertyID, VendorID, ExpenseTypeID )
VALUES (1,10,1,1),(1,10,1,1),(1,10,2,1),(1,10,1,2),(1,5,1,1),(2,10,1,1),(2,5,1,1)
;
INSERT INTO ExpenseType ( ExpenseTypeID ) VALUES (1), (2) ;
INSERT INTO Vendors ( VendorID ) VALUES (1), (2) ;
INSERT INTO TenantTransactions ( checkRegisterID, TenantTransactionDate, note )
VALUES
(1,'2018-01-01','start')
, (1,'2018-01-02','another')
, (1,'2018-01-03','another')
, (1,'2018-01-04','stop')
, (2,'2017-01-01','start')
, (2,'2017-01-02','another')
, (2,'2017-01-03','another')
, (2,'2017-01-04','stop')
;
Main Query:
SELECT cr.*
, max(tt.TenantTransactionDate) AS startDate
, min(tt.TenantTransactionDate) AS endDate
FROM CheckRegister cr
INNER JOIN ExpenseType et ON cr.ExpenseTypeID = et.ExpenseTypeID
INNER JOIN Vendors v ON cr.vendorID = v.VendorID
LEFT OUTER JOIN TenantTransactions tt ON cr.checkRegisterID = tt.CheckRegisterID
WHERE cr.PropertyID = 10
GROUP BY cr.CheckRegisterID, cr.PropertyID, cr.VendorID, cr.ExpenseTypeID
Results:
| checkRegisterID | PropertyID | VendorID | ExpenseTypeID | startDate | endDate |
|-----------------|------------|----------|---------------|------------|------------|
| 1 | 10 | 1 | 1 | 2018-01-04 | 2018-01-01 |
| 1 | 10 | 1 | 2 | 2018-01-04 | 2018-01-01 |
| 1 | 10 | 2 | 1 | 2018-01-04 | 2018-01-01 |
| 2 | 10 | 1 | 1 | 2017-01-04 | 2017-01-01 |
I only added 2 check registers, but CheckRegisterID 1 has 2 vendors and 2 Expense Types for Vendor 1. This will look like repeated data in your query. If your data isn't set up that way, you won't have to worry about it in the final query.
Use proper JOIN syntax to get the related data you need. Then you can aggregate that data to get the fromDate and toDate. If your data is more complex, you may need to look at Window Functions. https://dev.mysql.com/doc/refman/8.0/en/window-functions.html
I don't know what your final output looks like, but the above query gives you all of the query data in one pass. And each row of that data should give you what you need to output, so now you've only got one query to loop over.
It has been a long time since I did any ColdFusion development but a common rule of thumb would be to not call queries within a loop. Depending on what you are doing, loops can be considered an RBAR (row by agonizing row) operation.
You are essentially defining one query and looping over each record. For each record, you are doing three additional queries aka three additional database network calls per record. The way I see it, you have a couple options:
Rewrite your first query to already include the data you need within each
record check.
Leave your first query the way it is and create functionality that provides more information when the user interacts with the record and do it asynchronously. Something like a "Show Credit Date" link which goes out and gets the data on demand.
Combine the queries in your loop to be one query instead of the two getTenantTransaction... and see if performance improves. This reduces the RBAR database calls from three to two.
You always want to avoid having queries in a loop. Whenever you query the database, you have roundtrip (from server to database and back from database to server) which is slow by nature.
A general approach is to bulk data by querying all required information with as few statements as possible. Joining everything in a single statement would be ideal, but this obviously depends on your table schemes. If you cannot solve it using SQL only, you can transform your queries like this:
GetCheckRegister...
(no loop)
<cfquery name="GetVendorName" datasource="rent">
SELECT * FROM Vendors WHERE VendorID IN (#valueList(GetCheckRegister.VendorID)#)
</cfquery>
<cfquery name="getTenantTransactionDateFrom" dataSource="rent">
Select TenantTransactionDate as fromDate From TenantTransactions
Where CheckRegisterID IN (#valueList(GetCheckRegister.CheckRegisterID)#)
</cfquery>
etc.
valueList(query.column) returns a comma delimited list of the specified column values. This list is then used with MySQL's IN (list) selector to retrieve all records that belong to all the listed values.
Now you would only have a single query for each statement in your loop (4 queries total, instead of 4 times number of records in GetCheckRegister). But all records are clumped together, so you need to match them accordingly. To do this we can utilize ColdFusion's Query of Queries (QoQ), which allows you to query on already retrieved data. Since the retrieved data is in memory, accessing them is quick.
GetCheckRegister, GetVendorName, getTenantTransactionDateFrom, getTenantTransactionDateTo etc.
<CFOUTPUT query="GetCheckRegister">
<!--- query of queries --->
<cfquery name="GetVendorNameSingle" dbType="query">
SELECT * FROM [GetVendorName] WHERE VendorID = #GetCheckRegister.VendorID#
</cfquery>
etc.
</CFOUTPUT>
You basically moved the real queries out of the loop and instead query the result of the real queries in your loop using QoQ.
Regardless of this, make sure your real queries are fast by profiling them in MySQL. Use indices!
Using the main query and the loop to process the data could be faster if:
Using SELECT with only specific fields you need, to avoid fetching so many columns (instead of SELECT *), unless you are using all the fields:
SELECT VendorID, CheckRegisterId, ... FROM CheckRegister, ExpenseType ...
Using less subqueries in the loop, trying to join the table to the main query. For example, using the Vendors table in the main query (if it could be posible to join this table)
SELECT VendorID, CheckRegisterId, VendorName ... FROM CheckRegister, ExpenseType, Vendors ...
Finally, you can estimate the time of the process and detect the performance problem:
ROWS = Number of rows of the result in the main query
TIME_V = Time (ms) to get the result of GetVendorName using a valid VendorId
TIME_TD1 = Time (ms) to get the result of getTenantTransactionDateFrom using a valid CheckRegisterID
TIME_TD2 = Time (ms) to get the result of getTenantTransactionDateTo using a valid CheckRegisterID
Then, you can calculate the resulting time using TOTAL = ROWS * (TIME_V+ TIME_TD1 + TIME_TD2).
For example, if ROWS=10000 , TIME_V = 30, TIME_TD1 = 15, TIME_TD2 = 15 : RESULT = 10000 * (30 + 15 + 15) = 10000 * 60 = 600000 (ms) = 600 (sec) = 10 min
So, for 10000 rows, one milisecond of the loop results in 10 seconds added to the process.
When you have many resulting rows for the main query, you need to minimize the query time of each element in the loop. Each milisecond affects in the performance of the loop. So you need to make sure there are the right indexes for each field filtered for each query in the loop.

How to delete a row where there are only one of the kind in MySql?

I have following data in MySQL table named info:
chapter | section
3 | 0
3 | 1
3 | 2
3 | 3
4 | 0
5 | 0
I would like to delete a row for chapter = n, but only when there is no section>0 for same chapter. So chapter 3 can't be deleted while chapter 4 and 5 can. I know the following doesn't work:
DELETE info WHERE chapter = 3 AND NOT EXISTS (SELECT * FROM info WHERE chapter = 3 AND section>0);
The same table is used twice in the statement. So what is the easiest way to achieve my goal?
You've got the idea right. Here is the syntax:
DELETE
FROM mytable
WHERE chapter NOT IN (
SELECT * FROM (
select tt.chapter
from mytable tt
where tt.section <> 0
group by tt.chapter
) tmp
)
The nested select is a workaround a bug in MySQL.
Demo.
You can run a sub query to return the rows that have sections of more then one and then delete the rows returned from the sub query.
DELETE FROM table1 WHERE table1.chapter Not IN (select chapter from
(SELECT table1.chapter FROM table1 WHERE Table1.section >=1 ) Results);
Example Fiddle based on your question
You could also supply the chapter as well in the sub query where clause if you only want to delete a specfic chapter. If it does not meet the where clause then no records will be deleted.
This should do it.
DELETE FROM Table t
WHERE NOT EXISTS(
SELECT 1
FROM Table t2
Where t2.chapter = t.chapter
And t2.section > 0
)
In my experience Exists generally performs better than In. If you are storing a large amount of records you should take this into consideration.

MySQL variable in WHERE clause

I need a query that find the recommended TV shows for an user, based on the TV Shows he is following.
Do to this I have the following tables:
the table Progress that contains wich show the user is following and the percentage of seen episodes (to solve this problem we can assume I have only one user in the database)
the table Suggested that contains _id1,_id2 and value (value is the strength of the connections between the show with id=_id1 and the show with id=_id2: the more value is great, the more the shows have something in common).
Note that in this table applies the commutative property, so the strength of the connection between id1 and _id2 is the same of _id1 and _id2. Moreover there aren't two rows such as ROW1._id1=ROW2._id2 AND ROW1._id2 = ROW2._id1
the table ShowCache that contains the details about a TV Show, such as name etc..
The following query is what I'm trying to do, but the result is an empty set:
SET #a = 0; //In other tests this line seem to be necessary
SELECT `ShowCache`.*,
(SUM(value) * (Progress.progress)) as priority
FROM `Suggested`,`ShowCache`, Progress
WHERE
((_id2 = Progress.id AND _id1 NOT IN (SELECT id FROM Progress) AND #a:=_id1)//There is a best way to set a variable here?
OR
(_id1 = Progress.id AND _id2 NOT IN (SELECT id FROM Progress) AND #a:=_id2))
AND `ShowCache`._id = #a //I think that the query fails here
GROUP BY `ShowCache`._id
ORDER BY priority DESC
LIMIT 0,20
I know the problem is related to the usage of variables, but I can't solve it. Any help is really appreciated.
PS: the main problem is that (because of the commutative propriety), without variables I need two queries, wich takes about 3 secs to begin executed (the query is more complex than the above). I'm really trying to make a single query to do this task
PPS: I tied also with an XOR operation, that results in an infinite loop?!?!? Here's the WHERE clause I tried:
((_id2=Progress.id AND #a:=_id1) XOR (_id1=Progress.id AND #a:=_id2)) AND `ShowCache`._id = #a
EDIT:
I come up with this WHERE conditions without using any variable:
(_id2 = Progress.id OR _id1 = Progress.id)
AND `ShowCache`._id = IF(_id2 = Progress.id, _id1,_id2)
AND `ShowCache`._id NOT IN (SELECT id FROM Progress)
It works, but it is very slow.
Your attempt to use xor is clever. If you want to get the nonmatching value you want to use bitwise XOR which is ^
Progress.id ^_id1 ^ _id2
3 ^ 2 ^ 3 = 2
2 ^ 2 ^ 3 = 3
You can use this trick to setup a join and really simplify your query (eliminate the OR's and NOT IN's and do it in one query without variables.)
select users.name as username, showcache.name as show_name,
sum(progress * value) as priority from users
inner join progress on users.id = progress.user_id
inner join suggested on progress.show_id in (suggested.id_1, suggested.id_2)
inner join showcache on showcache.id =
(suggested.id_1 ^ suggested.id_2 ^ progress.show_id)
where showcache.id not in
(select show_id from progress where user_id = users.id)
group by showcache.id
order by priority desc;
I also setup a fiddle to demonstrate it:
http://sqlfiddle.com/#!2/2dcd8/24
To break it down. I created a users table with a single user (but the solution will work with multiple users.)
The select and join to progress is straightforward. The join to suggested uses IN as an alternative to writing it with OR
The join to showcache is where the bitwise XOR happens. One of the id's links up to the progress.show_id and we want to use the other one.
It does include a not in to exclude shows already watched from the results. I could have changed it to not exists? but it seems clearer this way.
You're setting #a's value twice within the where clause, meaning that the query is actually boiling down to:
...
WHERE ... AND `ShowCache`._id = _id2
MySQL evalutes variable assignments in a first-encountered order, so you should leave #a constant until the END of the clause, then assign a new value, e.g
mysql> set #a=5;
mysql> select #a, #a+1, #a*5, #a := #a + 1, #a;
+------+------+------+--------------+------+
| #a | #a+1 | #a*5 | #a := #a + 1 | #a |
+------+------+------+--------------+------+
| 0 | 1 | 0 | 1 | 1 |
| 1 | 2 | 5 | 2 | 2 |
| 2 | 3 | 10 | 3 | 3 |
+------+------+------+--------------+------+
Note that #a's value in the first 3 columns remains constant, UNTIL mysql reaches the #a := #a +1, after which #a has a new value
So perhaps your query should be
set #a = 0;
select #temp := #a, ..., #a := _id2
where
((_id2 = Progress.id AND _id1 NOT IN (SELECT id FROM Progress) AND #temp =_id1)
...
etc...

How to use result of an subquery multiple times into an query

A MySQL query needs the results of a subquery in different places, like this:
SELECT COUNT(*),(SELECT hash FROM sets WHERE ID=1)
FROM sets
WHERE hash=(SELECT hash FROM sets WHERE ID=1)
and XD=2;
Is there a way to avoid the double execution of the subquery (SELECT hash FROM sets WHERE ID=1)?
The result of the subquery always returns an valid hash value.
It is important that the result of the main query also includes the HASH.
First I tried a JOIN like this:
SELECT COUNT(*), m.hash FROM sets s INNER JOIN sets AS m
WHERE s.hash=m.hash AND id=1 AND xd=2;
If XD=2 doesn't match a row, the result is:
+----------+------+
| count(*) | HASH |
+----------+------+
| 0 | NULL |
+----------+------+
Instead of something like (what I need):
+----------+------+
| count(*) | HASH |
+----------+------+
| 0 | 8115e|
+----------+------+
Any ideas? Please let me know! Thank you in advance for any help.
//Edit:
finally that query only has to count all the entries in an table which has the same hash value like the entry with ID=1 and where XD=2. If no rows matches that (this case happend if XD is set to an other number), so return 0 and simply hash value.
SELECT SUM(xd = 2), hash
FROM sets
WHERE id = 1
If id is a PRIMARY KEY (which I assume it is since your are using a single-record query against it), then you can just drop the SUM:
SELECT xd = 2 AS cnt, hash
FROM sets
WHERE id = 1
Update:
Sorry, got your task wrong.
Try this:
SELECT si.hash, COUNT(so.hash)
FROM sets si
LEFT JOIN
sets so
ON so.hash = si.hash
AND so.xd = 2
WHERE si.id = 1
I normally nest the statements like the following
SELECT Count(ResultA.Hash2) AS Hash2Count,
ResultA.Hash1
FROM (SELECT S.Hash AS Hash2,
(SELECT s2.hash
FROM sets AS s2
WHERE s2.ID = 1) AS Hash1
FROM sets AS S
WHERE S.XD = 2) AS ResultA
WHERE ResultA.Hash2 = ResultA.Hash1
GROUP BY ResultA.Hash1
(this one is hand typed and not tested but you should get the point)
Hash1 is your subquery, once its nested, you can reference it by its alias in the outer query. It makes the query a little larger but I don't see that as a biggy.
If I understand correctly what you are trying to get, query should look like this:
select count(case xd when 2 then 1 else null end case), hash from sets where id = 1 group by hash
I agree with the other answers, that the GROUP BY may be better, but to answer the question as posed, here's how to eliminate the repetition:
SELECT COUNT(*), h.hash
FROM sets, (SELECT hash FROM sets WHERE ID=1) h
WHERE sets.hash=h.hash
and sets.ID=1 and sets.XD=2;