MySql UPDATE Multiple Values - mysql

I want to update the sort_id for all my users. For example, since there are 10 users, I want to specify sort_id from 0-9 respectively. I can do it using foreach in PHP, but it causes a lot of performance and time problems. Is there a method to do it without running each update query again?
UPDATE users SET sort_id=LAST_INSERT_ID(sort_id)+1 WHERE id IN(100,101,102,103,104)
what I really want to do
users
#id - #sort_id
100, 0
101, 1
102, 2
103, 3
104, 4

I don't know why you want to store redundant data which can be calculated by the value of another column from the same table. Data redundancy leads to data anomalies and corruption and should be always avoided in relational database systems.
If you need sort_id only on client side, just use a simple select.
SELECT id, RANK() OVER (ORDER BY ID) - 1 as sort_id
FROM users WHERE id BETWEEN 100 and 104
If you really want to store the sort_id, then use UPDATE with a subquery:
UPDATE users AS u JOIN
(SELECT id, RANK() OVER (ORDER BY id) - 1 AS sort_id
FROM users WHERE id BETWEEN 100 AND 104) as s
ON u.id=s.id SET u.sort_id=s.sort_id

You can use the row_number() function:
UPDATE users u
SET sort_id = t.rn
FROM (
SELECT id, ROW_NUMBER() OVER(ORDER BY id) rn
FROM users
) t
WHERE t.id = u.id
The subquery assigns a sequential number to the rows, which is then used to update the sort_id column.

Related

MySQL with counters

My query:
SELECT name FROM users
It's possible add a counter for each differente name. For example:
mark 1
john 1
mark 2
louis 1
john 2
Ann 1
Thank you
If you are running MySQL 8.0, you can use row_number() for this:
SELECT name, ROW_NUMBER() OVER(PARTITION BY name ORDER BY id) rn FROM users
This assumes that you have another column, called id, which can be used to sort the records (that would typically be the primary key of your table).
If you don't have such column, you can remove the ORDER BY clause of the window function - but the results of the query are then unstable, meaning that is not guaranteed that a given row will consistently get the same row number over consecutive executions of the same query. Depending on your use case, this might, or might not be what you want.
In earlier versions of MySQL, you can emulate row_number() with a correlated subquery:
select name,
1 + (select count(*) from users u1 where u1.name = u.name and u1.id < u.id) rn
from users u

In query with joins and multi-table/field ORDER BY, how to set LIMIT offset to start from a particular row identified by a unique id field?

Suppose I have four tables: tbl1 ... tbl4. Each has a unique numerical id field. tbl1, tbl2 and tbl3 each has a foreign key field for the next table in the sequence. E.g. tbl1 has a tbl2_id foreign key field, and so on. Each table also has a field order (and other fields not relevant to the question).
It is straightforward to join all four tables to return all rows of tbl1 together with corresponding fields from the other three fields. It is also easy to order this result set by a specific ORDER BY combination of the order fields. It is also easy to return just the row that corresponds to some particular id in tbl1, e.g. WHERE tbl1.id = 7777.
QUESTION: what query most efficiently returns (e.g.) 100 rows, starting from the row corresponding to id=7777, in the order determined by the specific combination of order fields?
Using ROW_NUMBER or (an emulation of it in MySQL version < 8) to get the position of the id=7777 row, and then using that in a new version of the same query to set the offset in the LIMIT clause would be one approach. (With a read lock in between.) But can it be done in a single query?
# FIRST QUERY: get row number of result row where tbl1.id = 7777
SELECT x.row_number
FROM
(SELECT #row_number:=#row_number+1 AS row_number, tbl1.id AS id
FROM (SELECT #row_number:=0) AS t, tbl1
INNER JOIN tbl2 ON tbl2.id = tbl1.tbl2_id
INNER JOIN tbl3 ON tbl3.id = tbl2.tbl3_id
INNER JOIN tbl4 ON tbl4.id = tbl3.tbl4_id
WHERE <some conditions>
ORDER BY tbl4.order, tbl3.order, tbl2.order, tbl1.order
) AS x
WHERE id=7777;
Store the row number from the above query and use it to bind :offset in the following query.
# SECOND QUERY : Get 100 rows starting from the one with id=7777
SELECT x.field1, x.field2, <etc.>
FROM
(SELECT #row_number:=#row_number+1 AS row_number, field1, field2
FROM (SELECT #row_number:=0) AS t, tbl1
INNER JOIN tbl2 ON tbl2.id = tbl1.tbl2_id
INNER JOIN tbl3 ON tbl3.id = tbl2.tbl3_id
INNER JOIN tbl4 ON tbl4.id = tbl3.tbl4_id
WHERE <same conditions as before>
ORDER BY tbl4.order, tbl3.order, tbl2.order, tbl1.order
) AS x
LIMIT :offset, 100;
Clarify question
In the general case, you won't ask for WHERE id1 > 7777. Instead, you have a tuple of (11,22,33,44) and you want to "continue where you left off".
Two discussions, with
That is messy, but not impossible. See Iterating through a compound key . Ig gives an example of doing it with 2 columns; 4 columns coming from 4 tables is an extension of such.
A variation
Here is another discussion of such: https://dba.stackexchange.com/questions/164428/should-i-store-data-pre-ordered-rather-than-ordering-on-the-fly/164755#164755
In actually implementing such, I have found that letting the "100" (LIMIT) be flexible can be easier to think through. The idea is: reach forward 100 rows (with LIMIT 100,1). Let's say you get (111,222,333,444). If you are currently at (111, ...), then deal with id2/3/4. If it is, say, (113, ...), then do WHERE id1 < 113 and leave off any specification of id2/3/4. This means fetching less than 100 rows, but it lands you just shy of starting id1=113.
That is, it involves constructing a WHERE clause with between 1 and 4 conditions.
In all cases, your query says ORDER BY id1, id2, id3, id4. And the only use for LIMIT is in the probe to figure out how far ahead the 100th row is (with LIMIT 100,1).
I think I can dig out some old Perl code for that.

Easiest way to get all records after a specific row SQL

I wrote a SQL query (below) that selects the next 25 records after the record with postID 201. (You don't have to read it)
SELECT
title,
content,
table_with_rn.userID,
table_with_rn.username,
postID,
post_timestamp
FROM
(
SELECT
title,
content,
posts.userID,
users.username,
postID,
post_timestamp,
#rownum := #rownum + 1 AS row_number2
FROM
(
posts
INNER JOIN users ON posts.userID = users.userID
)
CROSS JOIN(
SELECT
#rownum := 0
) AS r
ORDER BY
post_timestamp
DESC
) AS table_with_rn
WHERE
row_number2 >(
SELECT
row_number
FROM
(
SELECT
postID,
#rownum := #rownum + 1 AS row_number
FROM
(
posts
INNER JOIN users ON posts.userID = users.userID
)
CROSS JOIN(
SELECT
#rownum := 0
) AS r
ORDER BY
post_timestamp
DESC
) AS twn
WHERE
postID = 201
)
LIMIT 25
It sorts the table and then creates a column that holds the row number of each row. It then select the row number of the record with the specific postID, before selecting the records with greater row numbers from a duplicate table.
This query works fine, but it seems very complicated for a task that sounds rather simple. Is there a better/more efficient/simpler way of doing it?
Note: I realise I could skip the whole row_number thing and just use postID, since it is incremental, but I would like to keep my options open if I ever decide I don't want my pk to be an integer any more.
Note2: This is MySQL.
I am assuming that there is some column with which to determine whether a record is before or after the record with postID 201. From scanning your query, I'd say you have a timestamp column by which you want to order (to ignore the incremental nature of post ID).
If that is the case, one can employ a self join on the table some_table (for simplicity) where the timestamp columns of both table instance are compared. But one set of colums, is reduced to the timestamp of the record with postID = 201.
In other words, our join condition is 'all records of the table which have a timestamp larger than the one of the record with postID 201' which is the condition OP specified.
The result set now only contains records whose timestamp is larger than the one of postID 201 which we limit to only contain 25 entries. To get the ones directly after postID 201, we order by timestamp again.
The query could look like this:
SELECT
larger.*
FROM
some_table smaller
JOIN
some_table larger
ON
smaller.timestamp < larger.timestamp
AND smaller.postID = 201
ORDER BY larger.timestamp ASC
LIMIT 25

Get last but one row for each ID

I am using query like
select * from audittable where a_id IN (1,2,3,4,5,6,7,8);
For each ID its returning 5-6 records. I wanted to get the last but one record for each ID.
Can i do this in one sql statement.
Try this query
SELECT
*
FROM
(SELECT
#rn:=if(#prv=a_id, #rn+1, 1) as rId,
#prv:=a_id as a_id,
---Remaining columns
FROM
audittable
JOIN
(SELECT #rn:=0, #prv:=0) t
WHERE
a_id IN (1,2,3,4,5,6,7,8)
ORDER BY
a_id, <column> desc)tmp --Replace column with the column with which you will determine it is the last record
WHERE
rId=1;
If your database is having DateCreated or any column in which you are saving the DateTime as well like when your data is inserted for a particular row then you may use query like
select at1.* from audittable at1 where
datecreated in( select max(datecreated) from audittable at2
where
at1.id = at2.id
order by datecreated desc
);
You may also use LIMIT function as well.
Hope you understand and works for you.
In SQLite, you have the columns a_id and b. For each a_id you get a set of b's. Let you want
to get the latest/highest (maximum in terms of row_id, date or another naturally increasing index) one of b's
SELECT MAX(b), *
FROM audittable
GROUP BY a_id
Here MAX help to get the maximum b from each group.
Bad news that MySQL doesn't associate MAX b with other *-columns of the table. But it still can be used in case of simple table with a_id and b columns!

select A, B , C group by B with A from the row that has the highest C

I have collected informations from different sources about certain IDs that should match a single name. Some sources are more trustworthy than others in giving the correct name for a given ID.
I created a table (name, id, source_trustworthiness) and I want to get the most trustworthy name for each ID.
I tried
SELECT name, id, MAX( source_trustworthiness )
FROM table
GROUP BY id
this returns th highest trustworthiness available for each ID but with the first name it finds, regarless of its trustworthiness.
Is there a way I can get that right ?
Mysql has special functionality to help:
SELECT * FROM (
SELECT name, id, source_trustworthiness
FROM table
ORDER BY 3 DESC ) x
GROUP BY id
Although this wouldn't even execute in other databases (not naming all non-aggregate columns in the GROUP BY clause), with mysql it returns the first row encountered for each unique value of the grouped by columns. By ordering the rows greatest first, the first row for each id will be the most trustworthy.
Since this question is tagged mysql, this query is OK. Not only is it really simple, it's also quite fast.
SELECT a.*
FROM TableName a
INNER JOIN
(
SELECT id, MAX(source_trustworthiness) max_val
FROM TableName
GROUP BY ID
) b ON a.ID = b.ID AND
a.source_trustworthiness = b.max_val