I have a query of this form:
$query = "SELECT `id`, `value` FROM `table`
WHERE `id` IN "."('".$mod."') ORDER BY FIELD (`id`,'".$mod."')";
$mod is an Array of values that gets transformed into a string like this:
$mod = implode("','", $mod);
The values in $mod are simple non-negative integers but they sometimes repeat... I need the query to return a result that has all rows including duplicates in order i.e.
when $mod contains the following set of id's {35,21,34,14,35,21} the query should return these rows:
$result id value
row1 35 "value35"
row2 21 "value21"
row3 34 "value34"
row4 14 "value14"
row5 35 "value35"
row6 21 "value21"
You need a temporary table containing your set of id's. Use an incrementing primary key for the order.
CREATE TEMPORARY TABLE setofids ( setorder int auto_increment primary key, id int);
INSERT INTO setofids (id) VALUES (35),(21),(34),(14),(35),(21);
Then join to your table:
SELECT table.id, table.value FROM table
JOIN setofids USING (id)
ORDER BY setofids.setorder
Related
I would like to ask for a solution on how to insert n rows based on the values of a field in another table.
Consider the following tables:
CREATE TABLE Input (
ID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(128),
Ticket_Piece INT
);
CREATE TABLE Output (
Ticket_ID INT AUTO_INCREMENT PRIMARY KEY,
Transaction_ID INT,
Ticket_Number VARCHAR(23) UNIQUE,
FOREIGN KEY (Transaction_ID)
REFERENCES Input (ID)
ON UPDATE CASCADE
ON DELETE CASCADE
);
If a row from the Input table has n in the Ticket_Number column, then n rows should be inserted into the Output table, with Ticket_Number having values "ID-1" through "ID-n" (e.g. (4, "D", 5) in Input should result in rows with ticket numbers "4-1" through "4-5" being added to Output). How can rows for Output be generated in a range of numbers based on the Ticket_Piece column using PHP and MySQL?
For example, with the input:
INSERT INTO Input (ID, Name, Ticket_Piece)
VALUES
(1, 'A', 2),
(2, 'B', 1),
(3, 'C', 3)
;
the result should be:
Ticket_ID
Transaction_ID
Ticket_Number
1
1
1-1
2
1
1-2
3
2
2-1
4
3
3-1
5
3
3-2
6
3
3-3
For each row you fetch from the input table, use a for loop to insert multiple rows into the output table.
$res = $pdo->query("SELECT id, ticket_piece FROM Input_Table");
$insert_stmt = $pdo->prepare("INSERT INTO Output_Table (transaction_id, ticket_number) VALUES (:id, :ticket)");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$pieces = $row['ticket_piece'];
$id = $row['transaction_id'];
for ($i = 1; $i <= $pieces; $i++) {
$insert_stmt->execute([':id' => $id, ':ticket' => "$id-$i"]);
}
}
A solution in PHP or SQL will likely need to use a loop.
If this comes from the data model rather than business rules (and depending on other factors), a trigger might be a fairly simple option. The trigger body could have a WHILE or other loop to iterate over the ticket piece numbers, and CONCAT to combine the ID and piece number into a ticket number, inserting each in turn.
DELIMITER ;;
CREATE TRIGGER create_ticket_pieces
AFTER INSERT ON Input
FOR EACH ROW
BEGIN
DECLARE piece INT DEFAULT 1;
WHILE piece <= NEW.Ticket_Piece DO
INSERT INTO Output (Transaction_ID, Ticket_Number) Values (NEW.ID, Concat(NEW.ID, '-', piece));
SET piece := piece + 1;
END WHILE;
END;;
DELIMITER ;
An alternative some use is to pregenerate a table of numbers, then join with this table to generate rows:
INSERT INTO Output (Transaction_ID, Ticket_Number)
SELECT Input.ID, Concat(Input.ID, '-', Numbers.Number)
FROM Input
JOIN Numbers ON Numbers.Number <= Input.Ticket_Piece
WHERE ... -- select Input rows
It should be noted that by duplicating information (the transaction ID) in two different columns, the Output table isn't normalized. In particular, it violates 3rd normal form, due to a functional dependency of Transaction_ID on Ticket_Number. The way to resolve this is to leave the transaction ID out of the ticket number field (i.e. Output.Ticket_Number holds only the generated integer ≤ Input.Ticket_Piece). (See: "Third Normal Form: Composite PRIMARY KEY vs System-Generated Surrogate (IDENTITY)")
I want to insert a new dataset into a MySQL table tab with external data, but also with data from another table otherTab using the others' table primary key and another condition. However, it could be that the requested row simply does not exist (anymore) or the result set is empty due to a mismatch in the other supplied data. Then, the original INSERT should fail. All columns are forbidden to be NULL.
My first attempt was:
INSERT INTO tab (id, extid1, extid2, value)
SELECT 1,
(SELECT id FROM otherTab WHERE id = 12 AND data = 'TXT'),
(SELECT id FROM otherTab WHERE id = 34 AND data = 'JPG'),
1234
but the problem with it is that a returned empty result set is cast to the type of the column in tab, leading to a 0 as entry data.
The query shall be efficient and avoid unnecessary querying. This is how I achieve it with four subqueries:
INSERT INTO tab (id, extid1, extid2, value)
SELECT 1,
(SELECT id FROM otherTab WHERE id = 12 AND data = 'TXT'),
(SELECT id FROM otherTab WHERE id = 34 AND data = 'JPG'),
1234
WHERE EXISTS (SELECT id FROM otherTab WHERE id = 12 AND data = 'TXT')
AND EXISTS (SELECT id FROM otherTab WHERE id = 34 AND data = 'JPG')
I tried with other constructs, e.g. (SELECT IFNULL(SELECT id FROM otherTab WHERE id = 12 AND data = 'TXT', NULL)) to enforce NULL or even a string into the target column, but it also gets casted to a 0 or some value instead.
Here is the code for dbFiddle:
code
CREATE TABLE `tab` (
`id` int NOT NULL,
`seUuid4` binary(16) NOT NULL,
`rxUuid4` binary(16) NOT NULL,
`text` varchar(16)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `otherTab` (
`uuid4` binary(16) NOT NULL,
`lgUuid4` binary(16) NOT NULL,
`data` varchar(16)
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
ALTER TABLE `otherTab`
ADD PRIMARY KEY(`uuid4`);
ALTER TABLE `tab`
ADD CONSTRAINT `tab_ibfk_1` FOREIGN KEY (`rxUuid4`) REFERENCES `otherTab` (`uuid4`) ON DELETE RESTRICT ON UPDATE RESTRICT,
ADD CONSTRAINT `tab_ibfk_2` FOREIGN KEY (`seUuid4`) REFERENCES `otherTab` (`uuid4`) ON DELETE RESTRICT ON UPDATE RESTRICT;
INSERT INTO `otherTab` (uuid4, lgUuid4, data) VALUES
(UNHEX("22224444aaaa49c782408b2fe8c4dee0"), UNHEX("00001234aaaa4444aaaa432187654321"), "JPG"),
(UNHEX("11113333aaaa49c782408b2fe8c4dee0"), UNHEX("12340000bbbb6666bbbb432187654321"), "TXT");
INSERT INTO tab (id, seUuid4, rxUuid4, text)
SELECT
1,
(SELECT uuid4 FROM otherTab WHERE lgUuid4 = UNHEX('00001234aaaa4444aaaa432187654321') AND data = 'JPK' LIMIT 0,1),
(SELECT uuid4 FROM otherTab WHERE lgUuid4 = UNHEX('12340000bbbb6666bbbb432187654321') AND data = 'TXT' LIMIT 0,1),
'some text'
This interestingly works exactly as expected: Note the JPK instead of JPG. I verified my code and the PDO prepared statement fires out exactly the same command, but it gets inserted as INSERT INTO tab (id, seUuid4, rxUuid4) VALUES (1, 0x00000000000000000000000000000000, 0x00000000000000000000000000000000, 'datatext'); while the SQL client and phpMyadmin deliver the expected cannot insert null error message.
I could not find anything in the PDO options. If it helps, I use PDO with emulated prepared statements, but also tried without with no change.
PS: I posted already at dba.stackexchange.com/posts/276868
You could use a subquery:
INSERT INTO tab (id, extid1, extid2, value)
SELECT
FROM (
SELECT
1 id,
(SELECT id FROM otherTab WHERE id = 12 AND data = 'TXT') extid1,
(SELECT id FROM otherTab WHERE id = 34 AND data = 'JPG') extid2
1234 value
) t
WHERE extid1 IS NOT NULL and extid2 IS NOT NULL
Or, probably better yet, you can CROSS JOIN the two subqueries:
INSERT INTO tab (id, extid1, extid2, value)
SELECT 1, t1.id, t2.id, 1234
FROM (SELECT id FROM otherTab WHERE id = 12 AND data = 'TXT') t1,
CROSS JOIN (SELECT id FROM otherTab WHERE id = 34 AND data = 'JPG') t2
Actually, since you are reurning the same value that you are filtering on, two exists subqueries are probably sufficient:
INSERT INTO tab (id, extid1, extid2, value)
SELECT t.*
FROM (SELECT 1 id, 12 extid1, 34 extid2, 1234 value) t
WHERE EXISTS (SELECT 1 FROM otherTab t1 WHERE t1.id = t.extid1 AND t1.data = 'TXT')
AND EXISTS (SELECT 1 FROM otherTab t1 WHERE t1.id = t.extid2 AND t1.data = 'JPG')
I want make an update in one request (if possible) for make less mysql request..
I've a table with differents columns and a column "order" and I want change the value in the "order"
I can make a loop foreach in PHP, but each loop is a request like this:
$i = 1;
foreach($tables as $table){
$sql = 'UPDATE `table` SET `order` = '$i' WHERE `id` ='.$table['id'].';';
$i++;
}
Does it possible to make only one request ?
I've find this but it don't work
SET #pos := 0;
$sql ='UPDATE table SET order = ( SELECT #pos := #pos + 1 );';
Or I don't know how to make it work ..
(Posted on behalf of the OP).
I've understand that's not possible directly with MySQL query. So I'll make a loop for update each row, one by one.
This approach can solve your problem with a single query, but it will be tedious if we have more records
Let us have a table named order_details
CREATE TABLE `order_details` (
`order_id` int(11) NOT NULL,
`order_description` varchar(45) DEFAULT NULL,
`order_value` int(11) DEFAULT NULL,
PRIMARY KEY (`order_id`)
)
Select * from order_details
order_id order_description order_value
1 a 11
2 b 12
3 c 13
Now updating multiple rows in a single query
UPDATE test.order_details
SET order_value = CASE order_id
WHEN 1 THEN 21
WHEN 2 THEN 22
WHEN 3 THEN 23
END WHERE order_id IN (1,2,3)
You can verify it by executing a select query
Select * from order_details
order_id order_description order_value
1 a 21
2 b 22
3 c 23
Why not combine all the sql statements into 1 long string by keeping semi-colon (;) between each then send everything at once
foreach($tables as $table){
$sql .= 'UPDATE `table` SET `order` = '$i' WHERE `id` ='.$table['id'].';';
$i++;
}
mysqli_multi_query($sql); //do this after loop completes
Or did I misunderstand your request?
I want to execute a query where I can find one ID in a list of ID.
table user
id_user | name | id_site
-------------------------
1 | james | 1, 2, 3
1 | brad | 1, 3
1 | suko | 4, 5
and my query (doesn't work)
SELECT * FROM `user` WHERE 3 IN (`id_site`)
This query work (but doesn't do the job)
SELECT * FROM `user` WHERE 3 IN (1, 2, 3, 4, 6)
That's not how IN works. I can't be bothered to explain why, just read the docs
Try this:
SELECT * FROM `user` WHERE FIND_IN_SET(3,`id_site`)
Note that this requires your data to be 1,2,3, 1,3 and 4,5 (ie no spaces). If this is not an option, try:
SELECT * FROM `user` WHERE FIND_IN_SET(3,REPLACE(`id_site`,' ',''))
Alternatively, consider restructuring your database. Namely:
CREATE TABLE `user_site_links` (
`id_user` INT UNSIGNED NOT NULL,
`id_site` INT UNSIGNED NOT NULL,
PRIMARY KEY (`user_id`,`site_id`)
);
INSERT INTO `user_site_links` VALUES
(1,1), (1,2), (1,3),
(2,1), (2,3),
(3,4), (3,5);
SELECT * FROM `user` JOIN `user_site_links` USING (`id_user`) WHERE `id_site` = 3;
Try this: FIND_IN_SET(str,strlist)
NO! For relation databases
Your table doesn't comfort first normal form ("each attribute contains only atomic values, and the value of each attribute contains only a single value from that domain") of a database and you:
use string field to contain numbers
store multiple values in one field
To work with field like this you would have to use FIND_IN_SET() or store data like ,1,2,3, (note colons or semicolons or other separator in the beginning and in the end) and use LIKE "%,7,%" to work in every case. This way it's not possible to use indexes[1][2].
Use relation table to do this:
CREATE TABLE user_on_sites(
user_id INT,
site_id INT,
PRIMARY KEY (user_id, site_id),
INDEX (user_id),
INDEX (site_id)
);
And join tables:
SELECT u.id, u.name, uos.site_id
FROM user_on_sites AS uos
INNER JOIN user AS u ON uos.user_id = user.id
WHERE uos.site_id = 3;
This way you can search efficiently using indexes.
The problem is that you are searching within several lists.
You need something more like:
SELECT * FROM `user` WHERE id_site LIKE '%3%';
However, that will also select 33, 333 and 345 so you want some more advanced text parsing.
The WHERE IN clause is useful to replace many OR conditions.
For exemple
SELECT * FROM `user` WHERE id IN (1,2,3,4)
is cleaner than
SELECT * FROM `user` WHERE id=1 OR id=2 OR id=3 OR id=4
You're just trying to use it in a wrong way.
Correct way :
WHERE `field` IN (list_item1, list_item2 [, list_itemX])
I have a database with two tables. When a user posts an article, it will be inserted into both tables, (2 queries in one file)
I use post_id as foreign key, both tables post_id auto increment. Will foreign keys be messed up? For example if users A and B query the database at the same time.
Table 1
post_id user...
1 A
2 B
Table 2
post_id content...
1 A
2 B
First off you can't have auto increment on both tables.
Usually, what you do is insert in table 1, get the ID of the just inserted row.
Then you use this ID, to insert in table 2 which references table 1.
See: mysqli::$insert_id at
http://www.php.net/manual/en/mysqli.insert-id.php
Example:
$query = "INSERT INTO table1(user,whatever) VALUES ('A','something')";
$mysqli->query($query);
printf ("New Record has id %d.\n", $mysqli->insert_id);
$query = "INSERT INTO table2(post_id,content) VALUES ($mysqli->insert_id,'This is content')";
$mysqli->query($query);
You could also do this using a stored procedure based on: stackoverflow.com/a/1723325/1688441
DELIMITER //
CREATE PROCEDURE new_post_with_content(
user_id CHAR(5), content_text CHAR(100)
BEGIN
START TRANSACTION;
INSERT INTO table1 (user)
VALUES(user_id);
INSERT INTO table2 (post_id, content)
VALUES(LAST_INSERT_ID(), content_text);
COMMIT;
END//
DELIMITER ;
And you call it like so:
CALL new_engineer_with_task('A','This is the content');
Why not use table1 as user table and second with posts?
users
user_id(autoinc) username
1 A
2 B
3 C
posts
post_id(autoinc) user_id posts_text
1 2 text
2 1 other text