SELECT a column then INSERT that column again randomly - mysql

I've got a table:
player_id|player_name|play_with_id|play_with_name|
I made this table for a game.
Everyone who wants to play can sign up to it.
When they sign up the table stores player_id and player_name
When the period while they can sign up expires I want to assign every player_name to a play_with_name randomly.
So for example.. my structure would like this when they in sign up period:
player_id|player_name|play_with_id|play_with_name|
1 someone1
2 someone2
3 someone3
4 someone4
5 someone5
And this when the period expires:
player_id|player_name|play_with_id|play_with_name|
1 someone1 2 someone2
2 someone2 1 someone1
3 someone3 4 someone4
4 someone4 3 someone3
5 someone5 - -

I can't test this since I don't have a MySQL database handy and SQLFiddle seems to take forever to run anything, but this hopefully gets you there or at least close:
SET #row_num = 0;
SET #last_player_id = 0;
UPDATE P
SET
play_with_id =
CASE
WHEN P.player_id = SQ.player_id THEN SQ.last_player_id
ELSE player_id
END
FROM
Players P
LEFT OUTER JOIN
(
SELECT
#row_num := #row_num + 1 row_num,
#last_player_id last_player_id,
#last_player_id := player_id player_id
FROM
Players
WHERE
MOD(#row_num, 2) = 0
ORDER BY
RAND()
) SQ ON SQ.player_id = P.player_id OR SQ.last_player_id = P.player_id
The code (hopefully) sorts the players randomly then it pairs them based on that order. Every other player in the randomly sorted result is paired with the person right before them.
In MS SQL Server RAND() would only be evaluated once here and wouldn't end up affecting the ORDER BY, but I think that MySQL handles RAND() differently and generates a new value for each row in the result set.

I'm not sure why some client code isn't doing this as opposed to having this operation be done at the database level, but I suppose if you get the strategy for retrieving a randomized row set based on your DB from here, you could then write a stored procedure with a cursor or iterator to loop through the result set of something like:
select player_id, player_name from players order by RAND()
and then loop through the all the table rows to update the play_with_id and play_with_name, where the previously selected player_id <> play_with_id.

Related

DIviding the SQL result into two halves

The SQL query is :
Select ProductName from Products;
The above query returns 5000 rows.
How can the result of 5000 rows be divided into two result sets of 2500 rows each,.i.e., one result set from 1 to 2500 and the other from 2501 to 5000?
Note:
Here ProductName is the primary Key.No ProductID column is present in the table.
It can be done either in the back end or in the front end.
An approach that works for mySQL (based on this answer https://stackoverflow.com/a/4741301/14015737):
Upper half
SELECT *
FROM (
SELECT test.*, #counter := #counter +1 counter
FROM (select #counter:=0) initvar, test
ORDER BY num
) X
WHERE counter <= round(50/100 * #counter);
ORDER BY num;
Lower half
Invert the sort order and remove the rounding
SELECT *
FROM (
SELECT test.*, #counter := #counter +1 counter
FROM (select #counter:=0) initvar, test
ORDER BY num DESC
) X
WHERE counter <= (50/100 * #counter);
ORDER BY num;
In case of an uneven number of records, the middle record is added to the upper half in this example. If you want it the other way around, move the round() to the other statement. If you don't want it at all, remove round().
Dbfiddle example: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=fb70eae0f7f1434a24099b5bb19f0878
If you know the numbers that you want, just use limit:
select ProductName
from Products
order by id
And then either:
limit 2500
limit 2500 offset 2499
If you simply want the results split into half, then you can use:
select t.*
from (select t.*,
ntile(2) over (order by <primary key>) as tile
from t
) t
where tile = 1; -- or 2 for the other half
The easiest and probably fastest approach is to use the table's primary key if you are fine with getting the rows in its order.
Run
select productname, id from products order by id;
and fetch 2500 rows. Then with the last ID, say ID 3456, run
select productname, id from products where id > 3456 order by id;
and fetch 2500 rows again. Etc.
UPDATE: Seeing I got a downvote for this, I'll better explain :-)
The query returns 5000 rows now and the OP doesn't want that many rows, so they want to cut this in halves. But the query may well return 10000 rows next year. Will the OP suddenly be fine with getting 5000 rows at once? This doesn't seem likely. It is more likely that there is an amount of rows that shall not be surpassed. This is why I cut the amount into slices of 2500.
The other approach to number all rows and return the first n rows has a severe drawback: All rows must be read again. Even if it is decided to cut the result in chunks of 100 each, everytime all rows must be read, sorted, numbered, fetched from. Reading all rows from a table and sorting all these rows is a lot of work for a DBMS.

Keep the newest one field value until it changes then keeping its newest field value

I have a few tables that have millions of records where a sensor was sending multiple 0 and 1 values and this data was logged to the table even though we only needed it to keep the very first 1 or 0 per each 1 to 0 or 0 to 1 change.
Adjustments have been made so we only now get the 1 and 0 values on each change and not every one second or whatever but I need to cleanup the unnecessary records from the tables.
I've done some research and testing and I'm having trouble figuring out what method to use here to delete the records not needed. I was trying to figure out how to retain the previous value record using variables and also created row numbers but it's not working as I need it to.
I created an SQLFiddle here and tried some logic per the example post MySQL - How To Select Rows Depending on Value in Previous Row (Remove Duplicates in Each Sequence). I keep getting back no results from this and when I tried running it on a large local MySQL table, and I got an error wto I have to increase the MySQL Workbench read query timeout to 600 or it lost connection.
I also found the "MySql - How get value in previous row and value in next row?" post and tried some variations of it and also "How to get next/previous record in MySQL?" and I've come up with total failure getting the expected results.
The Data
The data in the tables has a TimeStr column and a Value column just as in the screen shot and on the SQLFiddle link I posted with a small sample of the data.
Each record will never have the same TimeStr value but I really only need to keep the very first record time wise when the sensor either turned ON or OFF if that clarifies.
I'm not sure if the records will need an incremental row number added to get the expected results since it only has the TimeStr and the Value records otherwise.
My Question
Can anyone help me determine a method that I can use on a couple large tables to delete the records from a table where there are subsequent and duplicate Value values so the tables only has the very first 1 or 0 records where those actually change from a 1 to 0 or 0 to 1?
I will accept an answer that also results in just the records needed—but any that perform fast would be even more greatly appreciated.
I can easily put those into a temp table, drop the original table, and then create and insert the needed records only into the original table.
Expected Results
| TimeStr | Value |
|----------------------|-------|
| 2018-02-13T00:00:00Z | 0 |
| 2018-02-13T00:00:17Z | 1 |
| 2018-02-13T00:00:24Z | 0 |
| 2018-02-13T00:00:28Z | 1 |
Select t.timestr, t.value from (
SELECT s.*, #pv x1, (#pv := s.value) x2
FROM sensor S, (select #pv := -1) x
ORDER BY TimeStr ) t
where t.x1 != t.x2
See http://sqlfiddle.com/#!9/8d0774/122
Try this :
SET #rownum = 0;
SET #rownum_x = 0;
SELECT b.rownum, b.TimeStr, b.Value
FROM
(
SELECT #rownum := #rownum+1 as rownum, TimeStr, Value
FROM sensor
ORDER BY TimeStr
) b
LEFT JOIN (
SELECT #rownum_x := #rownum_x+1 as rownum_x, TimeStr as TimeStr_x, Value as Value_x
FROM sensor
ORDER BY TimeStr
) x ON b.rownum = x.rownum_x + 1
where b.Value <> x.Value_x or x.Value_x is null
order by b.TimeStr
The result I got is
You want the first record for each value when it appears. This suggests variables. Here is one way that only involves sorting and no joining:
select t.*
from (select t.*,
(case when value = #prev_value then value
when (#save_prev := #prev_value) = NULL then NULL
when (#prev_value := value) = NULL then NULL
else #save_prev
end) as prev_value
from (select t.*
from sensor t
order by timestr
) t cross join
(select #prev_value := -1) params
) t
where prev_value <> value;
Notes:
The subquery for ordering only seems to be needed since MySQL 5.7.
The case is just a way to introduce serialized code. When using a variable it should only be used on one expression.
This only requires one sort -- and if you have an index, that doesn't even need to be a sort.
Here is a SQL Fiddle.

Replace mysql user defined variable

I have a query which works great given that the result is only one number, but now I need to allow for multiple rows to be returned and the query cannot handle that because it uses a user define variable... here is original procedure
CREATE DEFINER=`root`#`%` PROCEDURE `MapRank`(pTankID bigint,pMapID int, pColor int(2))
BEGIN
SET #RankNumber:=0;
select RankNumber
from
(select
TankID,
#RankNumber:=#RankNumber+1 as RankNumber,
MapID,
Color
from MAPDATA WHERE MapID = pMapID order by Rank DESC, TotalPP DESC) Query1 where TankID = pTankID AND COLOR = pColor ;
END
this returns a single number, essentially counting the number of records down it is, giving me the "row" location.
now I need to change it to give me all rows with out the where for mapid and color, so that I can see all ranks for all mapid/color combo
this is what I have that currently does not work
SET #RankNumber:=0;
select
RankNumber,MapID,COlor
from
(select
TankID,
#RankNumber:=#RankNumber + 1 as RankNumber,
MapID,
Color
from
MAPDATA
order by TotalPP DESC) Query1
where
TankID = 18209 ORDER BY RankNumber
the yielding query result looks as such:
1062 3 1
3544 3 0
6717 17 1
6752 17 3
7453 3 2
7860 17 0
7984 17 2
9220 3 3
if I run manually lets say, map id 3 and color 3 which says rank number is 9220 with the FIRST query I get this
6022
I need this to be able to be done possibly from multiple MySQL connections so ideally done without use of a temporary variable since its possible another person may come in and use that... any help would be great.
After digging and messing more I have found the solution to be to set the variable back to zero from within the outer select.. and since user defined variable are connection level and I utilize pooling we should never have an issue.
SET #RankNumber:=0;
select
RankNumber,MapID,COlor, #RankNumber:=0
from
(select
TankID,
#RankNumber:=#RankNumber + 1 as RankNumber,
MapID,
Color
from
MAPDATA
order by MapID, Rank DESC, TotalPP DESC ) Query1
where
TankID = pTankID ORDER BY RankNumber;

SQL : How to calculate limited rows and reset the counter

I am dealing with an issue and need some expert advice on to achieve the problem, my sql query generates output with two columns, 1st column displays id (for e.g. abc-123 in following table) and next column displays corresponding result to the id stored in db which is pass or fail.
I need to implement, when resolution is pass it should display success attempt, in following example, abc-123 failed 1st time however def-456 passed in next attempt thus success rate is 50%, now counter should reset and go to next row where there is pass thus it should show 100%, again when code hits pass counter resets then goes next and displays 33% bec there are two fail and one pass at the end, how it can be achieved in sql? (id and resolution are column names)
**date** **id resolution**
6/6/2012 abc-123 fail 50%
6/7/2012 abc-456 pass
6/8/2012 abc-789 pass 100%
6/9/2012 abc-799 fail 33%
6/10/2012 abc-800 fail
6/1/2012 abc-900 pass
Thanks
SELECT
*
FROM
table
INNER JOIN
(
SELECT
MIN(g.id) AS first_id,
MAX(g.id) AS last_id,
COUNT(*) AS group_size
FROM
table AS p
INNER JOIN
table AS g
ON g.id > COALESCE(
(SELECT MAX(id) FROM table WHERE id < p.id AND resolution = 'pass'),
''
)
AND g.id <= p.id
WHERE
p.resolution = 'pass'
GROUP BY
p.id
)
AS groups
ON table.id >= groups.first_id
AND table.id <= groups.last_id
There's more than one way to do it:
SELECT st.*,
#prev:=#counter + 1,
#counter:= CASE
WHEN st.resolution = 'pass'
THEN 0
ELSE #counter + 1
END c,
CASE WHEN #counter = 0
THEN CONCAT(FORMAT(100/#prev, 2), '%')
ELSE '-'
END res
FROM so_test st, (SELECT #counter:=0) sc
Here's proof of concept.

How can I optimize my query (rank query)?

For the last two days, I have been asking questions on rank queries in Mysql. So far, I have working queries for
query all the rows from a table and order by their rank.
query ONLY one row with its rank
Here is a link for my question from last night
How to get a row rank?
As you might notice, btilly's query is pretty fast.
Here is a query for getting ONLY one row with its rank that I made based on btilly's query.
set #points = -1;
set #num = 0;
select * from (
SELECT id
, points
, #num := if(#points = points, #num, #num + 1) as point_rank
, #points := points as dummy
FROM points
ORDER BY points desc, id asc
) as test where test.id = 3
the above query is using subquery..so..I am worrying about the performance.
are there any other faster queries that I can use?
Table points
id points
1 50
2 50
3 40
4 30
5 30
6 20
Don't get into a panic about subqueries. Subqueries aren't always slow - only in some situations. The problem with your query is that it requires a full scan.
Here's an alternative that should be faster:
SELECT COUNT(DISTINCT points) + 1
FROM points
WHERE points > (SELECT points FROM points WHERE id = 3)
Add an index on id (I'm guessing that you probably you want a primary key here) and another index on points to make this query perform efficiently.