I've got this table
CREATE TABLE `subevents` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(150) DEFAULT NULL,
`content` text,
`class` tinyint(4) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=MyISAM
Each row can have a different value in the 'class' field.
I'd like to select any number of rows, ordered randomly, as long as the sum of the values in the 'class' field is equal to 100.
How could I accomplish it directly in the MySQL query without doing it later in PHP?
Thanks everybody!
By "ordered randomly" I assume you mean that the order of the rows doesn't matter but no row can be used more than once. So you are looking for a combination of rows in which the sum of class equals 100. Use the brute force method. Randomly generate possible solutions until you find one that works.
delimiter //
CREATE PROCEDURE subsetsum(total)
BEGIN
DECLARE sum INTEGER;
REPEAT
CREATE OR REPLACE VIEW `solution`
AS SELECT * FROM `subevents`
WHERE 0.5 <= RAND();
SELECT SUM(`class`) INTO sum FROM `solution`;
UNTIL sum = total END REPEAT;
END
//
delimiter ;
CALL subsetsum(100); /* For example */
SELECT * FROM `solution`;
I have tested this with tables having a TINYINT column of random values and it is actually reasonably fast. The only problem is that there is no guarantee that subsetsum() will ever return.
I don't think this is possible with only SQL...the only thing which comes to my mind is to redo a the sql query as long the sum isn't 100
But I have no clue how to select a random number of rows at once.
Related
Here is my table schema
MariaDB [a1]> show create table bad_numbers_final;
+-------------------+-------------------------------------------
| Table | Create Table
+-------------------+-------------------------------------------
| bad_numbers_final | CREATE TABLE bad_numbers_final (
id int(11) NOT NULL AUTO_INCREMENT,
phone varchar(255) NOT NULL,
phone_count varchar(255) DEFAULT NULL,
PRIMARY KEY (id,phone)
) ENGINE=InnoDB AUTO_INCREMENT=126956 DEFAULT CHARSET=latin1 |
+-------------------+-------------------------------------------
Now I want to insert a phone if that same number exists in the table then it just increase the phone_count otherwise add a new row in the table . So for that I use a stored procedure and write in it
BEGIN IF ('phone'='10083121224') THEN UPDATE bad_numbers_final SET phone_count= phone_count+10 WHERE 'phone'='10083121224' ;
ELSE INSERT INTO bad_numbers_final (phone,phone_count) VALUES ('10083121224',10);END IF;END
But when I execute this procedure it always insert a new row in the table in place of increasing the phone_count because the value ofphone which I'm inserting that value is already in the table so it just have to increase the phone_count but its not doing so. Please give any suggestions. Thanks in advance.
In 'phone'='10083121224' you are comparing two different strings. You should compare phone='10083121224'.
Also, phone_count is a varchar(255). No. It should be a number (int
or whatever) It doesnt make sense as varchar.You would concatenate "10" as a string this way. So if you had "0" before, after, you will have "010" and then "01010" and so on.
Then you can do this:
UPDATE bad_numbers_final SET phone_count= phone_count+10 WHERE phone='10083121224' ;
Where instead of '10083121224' my guess is that you will need to place a parameter for the procedure.
Also perhaps you want to change the phone datatype as number as well
I've got an answer of my own question i.e. I've done some changes in my if condition i.e.
BEGIN
IF (select phone from bad_numbers_final WHERE phone='10083121224') THEN
UPDATE bad_numbers_final SET phone_count= phone_count+10 WHERE phone='10083121224';
ELSE INSERT INTO bad_numbers_final (phone,phone_count) VALUES ('10083121224',10);
END IF;
END
the problem is that my if condition isn't working because it is not getting from where the field phone is coming and it is just about to insert one sub query inside the if condition and here it is working fine and thank you for all of your suggestions.
I have a table in my database with 2 rows, Level, and Experience.
CREATE TABLE `player_xp_for_level` (
`Level` TINYINT(3) UNSIGNED NOT NULL,
`Experience` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`Level`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM
;
The level experience up to level 80 have been filled in by a predefined index.
However I would like the stats for level 81 to be based on the experience of level 80 but just multiplied by *1.0115
Basically I'm looking for a query that inserts one row at a time.
Checks the previous Experience, and then modifies it and inserts it.
Workflow:: Checks previous row, updates values (previous experience*1.0115) and inserts.
If you want only to show higher score without affecting data in the database. You could use CASE expression in the SELECT statement:
SELECT player, lvl,
CASE
WHEN lvl BETWEEN 80 AND 255 THEN score * 1.0115
ELSE score
END as score
FROM player_xp_for_level
As you have posted additional info, I've updated my answer with the INSERT statement. There also you could use CASE expression in following:
INSERT INTO player_xp_for_level (lvl, score)
VALUES (#lvl, CASE WHEN #lvl BETWEEN 80 AND 255 THEN #score * 1.0115 ELSE #score END);
Assuming the structure of your table, which isn't clear from the question, then something like this?
UPDATE
player_xp_for_level
SET
xp = xp * 1.0115
WHERE
player_level BETWEEN 80 AND 255;
From the minimum code you provided, I think this is what you want:
UPDATE player_xp_for_level
SET name_of_value_column = name_of_value_column * 1.0115
WHERE name_of_level_column BETWEEN 80 AND 255;
The Issue
I have a stored proc in a DB server that's bringing back a value of 5064803 when that record does not exist and the value should be 5064800 as per the query that builds the value of the variable.
I'm not sure if this is an issue with the value being of the FLOAT data type and the value in the record of the table ending in a double-zero or what but I cannot figure it out easily.
The table data types match those from the sensors that are set but this particular value from this sensor never actually gets set to a data type and it's usually always either a 1-8 digit INT with no decimal but I'd like to keep the data types the same as the correlated sensor just in case.
I've broke down the proc and I'm able to recreate the problem easily so I will post the detail below for those that may be able to help me figure out the issue and any workaround, etc.
The SQL Data
Create Table
delimiter $$
CREATE TABLE `number` (
`TimeInt` varchar(10) NOT NULL,
`TimeStr` datetime NOT NULL,
`IsInitValue` int(11) NOT NULL,
`Value` float NOT NULL,
`IQuality` int(11) NOT NULL,
UNIQUE KEY `uk_Times` (`TimeInt`,`TimeStr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$
Insert Data
INSERT INTO `Number` (`TimeInt`,`TimeStr`,`IsInitValue`,`Value`,`IQuality`) VALUES ('1502618950','2017-08-13 10:09:10',1,5064800,0);
INSERT INTO `Number` (`TimeInt`,`TimeStr`,`IsInitValue`,`Value`,`IQuality`) VALUES ('1502618796','2017-08-13 10:06:36',0,5064800,3);
INSERT INTO `Number` (`TimeInt`,`TimeStr`,`IsInitValue`,`Value`,`IQuality`) VALUES ('1502617167','2017-08-13 09:39:27',1,5063310,0);
INSERT INTO `Number` (`TimeInt`,`TimeStr`,`IsInitValue`,`Value`,`IQuality`) VALUES ('1502613355','2017-08-13 08:35:55',0,5063310,3);
INSERT INTO `Number` (`TimeInt`,`TimeStr`,`IsInitValue`,`Value`,`IQuality`) VALUES ('1502612814','2017-08-13 08:26:54',1,0,0);
INSERT INTO `Number` (`TimeInt`,`TimeStr`,`IsInitValue`,`Value`,`IQuality`) VALUES ('1502609015','2017-08-13 07:23:35',0,0,3);
The SQL Query Breakdown
SET #bStartTime = '2017-08-13 09:24:16';
SET #bEndTime = '2017-08-13 10:06:31';
SET #LastNumber = (SELECT Value FROM Number ORDER BY TimeStr DESC LIMIT 1);
SET #NowNumber = (SELECT Value FROM Number WHERE TimeStr BETWEEN #bStartTime AND #bEndTime ORDER BY TimeStr DESC LIMIT 1);
SELECT #NowNumber;
SELECT #LastNumber;
Recreating the Issue
So based on The SQL Query Breakdown above, once all the data is in the table and then I run the queries within the SELECT queries alone within the #NowNumber and/or #LastNumber variables, I get the correct result of 5064800. However, if I run the entire SET statements for both of those to have it set the query and then just do a SELECT of those variable, it brings back the wrong result of 5064803.
So for example if I run SELECT Value FROM Number ORDER BY TimeStr DESC LIMIT 1 then the correct value is returned. If I run SET #LastNumber = (SELECT Value FROM Number ORDER BY TimeStr DESC LIMIT 1); and then run SELECT #LastNumber; I get the incorrect value returned.
Server System Specs
This particular MySQL Server is running the x86 version of 5.5.50 on Windows Server 2008 with 144 GB of RAM for some quick specs.
Question
I'd like to know what is causing this, and if there is a workaround to the problem either with or without changing the data type of the column assuming that's the issue when it's returned as a variable rather than just a straight query result.
I'll be happy to disclose more technical specs of the environment if needed but I've included what I think it important for the question. Perhaps this is a version bug or there's something obvious that causes this that I cannot see easily so I'm hoping someone can help me with this or explain why this is or is not possible with MySQL.
Sorry, declares can only be used in stored procedures in MySQL. I found this article which may help. It explains how MySQL rounds when storing digits and recommends using doubles. Try changing your floats to doubles.
MySql FLOAT datatype and problems with more then 7 digit scale
If I have a table like this:
CREATE TABLE `Suppression` (
`SuppressionId` int(11) NOT NULL AUTO_INCREMENT,
`Address` varchar(255) DEFAULT NULL,
`BooleanOne` bit(1) NOT NULL DEFAULT '0',
`BooleanTwo` bit(1) NOT NULL DEFAULT '0',
`BooleanThree` bit(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`SuppressionId`),
)
Is there a set-based way in which I can select all records which have exactly one of the three bit fields = 1 without writing out the field names?
For example given:
1 10 Pretend Street 1 1 1
2 11 Pretend Street 0 0 0
3 12 Pretend Street 1 1 0
4 13 Pretend Street 0 1 0
5 14 Pretend Street 1 0 1
6 14 Pretend Street 1 0 0
I want to return records 4 and 6.
You could "add them up":
where cast(booleanone as unsigned) + cast(booleantwo as unsigned) + cast(booleanthree as unsigned) = 1
Or, use tuples:
where ( (booleanone, booleantwo, booleanthree) ) in ( (0b1, 0b0, 0b0), (0b0, 0b1, 0b0), (0b0, 0b0, 0b1) )
I'm not sure what you mean by "set-based".
If your number of booleans can vary over time and you don't want to update your code, I suggest you make them lines and not columns.
For example:
CREATE TABLE `Suppression` (
`SuppressionId` int(11) NOT NULL AUTO_INCREMENT,
`Address` varchar(255) DEFAULT NULL,
`BooleanId` int(11) NOT NULL,
`BooleanValue` bit(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`SuppressionId`,`BooleanId`),
)
So with 1 query and a 'group by' you can check all values of your booleans, however numerous they are. Of course, this makes your tables bigger.
EDIT: Just came out with another idea: why don't you have a checksum column added, whose value would be the sum of all your bits? So you would update it at every write into your table, and just check this one in your select
If you
must use this denormalized way of representing these flags, and you
must be able to add new flag columns to your table in production, and you
cannot rewrite your queries by hand when you add columns,
then you must figure out how to write a program to write your queries.
You can use this query to retrieve a result set of boolean-valued columns, then you can use that result set in a program to write a query involving all those columns.
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'Suppression'
AND COLUMN_NAME LIKE 'Boolean%'
AND DATA_TYPE = 'bit'
AND NUMERIC_PRECISION=1
The approach you have proposed here will work exponentially more poorly as you add columns, unfortunately. Any time a software engineer says "exponential" it's time to run away screaming. Seriously.
A much more scalable approach is to build a one-to-many relationship between your Suppression rows and your flags. Add this table.
CREATE TABLE SuppressionFlags (
SuppressionId int(11) NOT NULL,
FlagName varchar(31) NOT NULL,
Value bit(1) NOT NULL DEFAULT '0',
PRIMARY KEY (SuppressionID, FlagName)
)
Then, when you want to insert a row with some flag variables, do this sequence of queries.
INSERT INTO Suppression (Address) VALUES ('some address');
SET #SuppressionId := LAST_INSERT_ID();
INSERT INTO SuppressionFlags (SuppressionId, FlagName, Value)
VALUES (#SuppressionId, 'BooleanOne', 1);
INSERT INTO SuppressionFlags (SuppressionId, FlagName, Value)
VALUES (#SuppressionId, 'BooleanTwo', 0);
INSERT INTO SuppressionFlags (SuppressionId, FlagName, Value)
VALUES (#SuppressionId, 'BooleanThree', 0);
This gives you one Suppression row with three flags set in the SuppressionFlags table. Note the use of #SuppressionId to set the Id values in the second table.
Then to find all rows with just one flag set, do this.
SELECT Suppression.SuppressionId, Suppression.Address
FROM Suppression
JOIN SuppressionFlags ON Suppression.SuppressionId = SuppressionFlags.SuppressionId
GROUP BY Suppression.SuppressionId, Suppression.Address
HAVING SUM(SuppressionFlags.Value) = 1
It gets a little trickier if you want more elaborate combinations. For example, if you want all rows with BooleanOne and either BooleanTwo or BooleanThree set, you need to do something like this.
SELECT S.SuppressionId, S.Address
FROM Suppression S
JOIN SuppressionFlags A ON S.SuppressionId=A.SuppressionId AND A.FlagName='BooleanOne'
JOIN SuppressionFlags B ON S.SuppressionId=B.SuppressionId AND B.FlagName='BooleanTwo'
JOIN SuppressionFlags C ON S.SuppressionId=C.SuppressionId AND C.FlagName='BooleanThree'
WHERE A.Value = 1 AND (B.Value = 1 OR C.Value = 1)
This common database pattern is called the attribute / value pattern. Because SQL doesn't easily let you use variables for column names (it doesn't really have reflection) this kind of way of naming your attributes is your best path to extensibility.
It's a little more SQL. But you can add as many new flags as you need, in production, without rewriting queries or getting a combinatorial explosion of flag-matching. And SQL is built to handle this kind of query.
Is there an easy and simple way to create a result table that has specified columns but zero rows? In set theory this is called an empty set, but since relational databases use multidimensional sets the term doesn't fit perfectly. I have tried these two queries, but both deliver exactly one row and not zero rows:
SELECT '' AS ID;
SELECT null AS ID;
But what I want is the same result as this query:
SELECT ID FROM sometable WHERE false;
I'm searching for a more elegant way because I don't want to have a table involved, so the query is independent from any database scheme. Also a generic query might be a bit faster (not that it would matter for such a query).
SELECT "ID" LIMIT 0;
Without any real tables.
Do note that most (My)SQL clients simply will display "Empty set". However, it actually does what you want:
create table test.test_table
select "ID" limit 0;
show create table test.test_table\G
Table: test_table
Create Table: CREATE TABLE `test_table` (
`ID` varchar(2) character set latin1 NOT NULL default ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
SELECT * FROM (SELECT NULL AS ID) AS x WHERE 1 = 0
You can use the DUAL pseudo-table.
SELECT whatever FROM DUAL WHERE 1 = 0
Check the documentation (look for the DUAL section).