“Unbekannte Spalte in 'Feldliste'”, wenn sich der Platzhalter der vorbereiteten Anweisung in der Unterabfrage befindet
Ich verwende PHP 5.5.9 und MySQL 5.5.44 mit mysqlnd 5.0.11-dev unter Ubuntu 14.04 LTS. Die folgende Anweisung kann nicht vorbereitet werden:
$db->prepare("SELECT nr.x FROM (SELECT ? AS x) AS nr")
Das ist trotz der Tatsache, dass die folgende Anweisung erfolgreich vorbereitet:
$db->prepare("SELECT nr.x FROM (SELECT '1337' AS x) AS nr")
Was verursacht diesen Unterschied?Das Handbuc sagt: "Parametermarken können nur dort verwendet werden, wo Datenwerte angezeigt werden sollen, nicht für SQL-Schlüsselwörter, Bezeichner usw." Aber das ist für einen Datenwert.
Not PDO ist schuldDas gleiche passiert im Standalone-Client:
mysql -uredacted -predacted redacted
-- Type 'help;' or '\h' for help.
SELECT nr.x FROM (SELECT '1337' AS x) AS nr;
-- x
-- 1337
-- 1 row in set (0.00 sec)
PREPARE workingstmt FROM 'SELECT nr.x FROM (SELECT ''1337'' AS x) AS nr';
-- Query OK, 0 rows affected (0.00 sec)
-- Statement prepared
DEALLOCATE PREPARE workingstmt;
-- Query OK, 0 rows affected (0.00 sec)
PREPARE brokenstmt FROM 'SELECT nr.x FROM (SELECT ? AS x) AS nr';
-- ERROR 1054 (42S22): Unknown column 'nr.x' in 'field list'
^D
-- Bye
Meine Motivatio Ich versuche, einer Tabelle eine Zeile mit einem automatisch inkrementierenden Primärschlüssel hinzuzufügen. In InnoDBs Standard-Auto-Increment-Sperrmodus, derdas Handbuc ruft "konsekutiv" auf, InnoDB überspringt einen Auto-Inkrement-Wert, wenn eine Zeile eingefügt wird, aber nicht, wie dies bei @ der Fall isINSERT IGNORE
oderON DUPLICATE KEY UPDATE
das läuft in eine bestehende Zeile, derenUNIQUE
-Werte stimmen mit denen der einzufügenden Zeile überein. (Diese werden im Handbuch als "Mixed-Mode-Inserts" bezeichnet.)
Alle paar Stunden importiere ich einen Feed von meinem Lieferanten. Dies hat ungefähr 200.000 Zeilen, und alle bis auf durchschnittlich 200 dieser Zeilen haben eindeutige Werte, die den Werten entsprechen, die bereits in der Tabelle vorhanden sind. Also, wenn ich @ verwenden würINSERT IGNORE
oderON DUPLICATE KEY UPDATE
Die ganze Zeit habe ich alle paar Stunden 199.800 IDs durchgebrannt. Also möchte ich nicht @ verwendINSERT IGNORE
oderON DUPLICATE KEY UPDATE
aus Angst, dass ich die 4,2 Milliarden Grenze von @ erschöpfen könnINTEGER UNSIGNED
mit wiederholten Einfügungen im Laufe der Zeit zu einer Tabelle mit dem gleichenUNIQUE
Schlüssel. Ich möchte die Spalte nicht auf @ umstellBIGINT
type, da 32-Bit-PHP keinen Typ mit der gleichen Semantik wie MySQL hatBIGINT
. Der Serveradministrator ist nicht bereit, auf 64-Bit-PHP zu wechseln oder @ zu änderinnodb_autoinc_lock_mode
für alle Benutzer des Servers.
So stattdessen habe ich beschlossen, @ zu versuchINSERT INTO ... SELECT
, Erstellen einer einzeiligen Tabelle mit den eindeutigen Schlüsselspalten in einer Unterabfrage und Verbinden mit der Haupttabelle, um bereits vorhandene eindeutige Schlüsselwerte abzulehnen. (Das Handbuch sagtINSERT INTO ... SELECT
ist eine "Masseneinfügung", die keine IDs brennt.) Die Absicht ist, so etwas zu tun:
INSERT INTO the_table
(uniquecol, othercol1, othercol2)
SELECT nr.uniquecol, :o1 AS othercol1, :o2 AS othercol2
FROM (
SELECT ? AS uniquecol
) AS nr
LEFT JOIN the_table ON nr.settlement_id = the_table.settlement_id
WHERE the_table.row_id IS NULL
Dies ist fehlgeschlagen und hat den PDO-Fehler verursacht:["42S22",1054,"Unknown column 'settlement_id' in 'field list'"]
<?php // MCVE follows
/* Connect to database */
$pdo_dsn = 'mysql:host=127.0.0.1;dbname=redacted';
$pdo_username = 'redacted';
$pdo_password = 'redacted';
$pdo_options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',];
$db = new PDO($pdo_dsn, $pdo_username, $pdo_password, $pdo_options);
$pdo_dsn = $pdo_username = $pdo_password = 'try harder';
// ensure that PDO doesn't convert everything to strings
// per http://stackoverflow.com/a/15592818/2738262
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
/* Create mock data with which to test the statements */
$prep_stmts = ["
CREATE TEMPORARY TABLE sotemp (
file_id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
settlement_id VARCHAR(30) NOT NULL,
num_lines INTEGER UNSIGNED NOT NULL DEFAULT 0,
, UNIQUE (settlement_id)
)
","
INSERT INTO sotemp (settlement_id, num_lines) VALUES
('15A1', 150),
('15A2', 273),
('15A3', 201)
"];
foreach ($prep_stmts as $stmt) $db->exec($stmt);
/* Now the tests */
$working_stmt = $db->prepare("
SELECT nr.settlement_id
FROM (
-- change this to either a value in sotemp or one not in sotemp
-- and re-run the test program
SELECT '15A3' AS settlement_id
) AS nr
LEFT JOIN sotemp ON nr.settlement_id = sotemp.settlement_id
WHERE sotemp.file_id IS NULL
");
if ($working_stmt) {
$working_stmt->execute();
$data = $working_stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Working: ".json_encode($data)."\n";
} else {
echo "Working statement failed: ".json_encode($db->errorInfo())."\n";
}
$broken_stmt = $db->prepare("
SELECT nr.settlement_id
FROM (
SELECT ? AS settlement_id
) AS nr
LEFT JOIN sotemp ON nr.settlement_id = sotemp.settlement_id
WHERE sotemp.file_id IS NULL
");
if ($broken_stmt) {
$broken_stmt->execute(['15A4']);
$data = $broken_stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Broken: ".json_encode($data)."\n";
} else {
echo "Broken statement failed: ".json_encode($db->errorInfo())."\n";
}
Was verursacht diesen Fehler? Und gibt es eine bessere Möglichkeit, eine Zeile nur einzufügen, wenn der Primärschlüssel nicht vorhanden ist, ohne dass die IDs für die automatische Inkrementierung erschöpft sind?