Frage

(NOTIZ:Bei dieser Frage geht es nicht darum, Abfragen zu entkommen, sondern darum, Ergebnissen zu entgehen.)

Ich benutze GROUP_CONCAT um mehrere Zeilen in einer durch Kommas getrennten Liste zusammenzufassen.Angenommen, ich habe die beiden (Beispiel-)Tabellen:

CREATE TABLE IF NOT EXISTS `Comment` (
`id` int(11) unsigned NOT NULL auto_increment,
`post_id` int(11) unsigned NOT NULL,
`name` varchar(255) collate utf8_unicode_ci NOT NULL,
`comment` varchar(255) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY  (`id`),
KEY `post_id` (`post_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=6 ;

INSERT INTO `Comment` (`id`, `post_id`, `name`, `comment`) VALUES
(1, 1, 'bill', 'some comment'),
(2, 1, 'john', 'another comment'),
(3, 2, 'bill', 'blah'),
(4, 3, 'john', 'asdf'),
(5, 4, 'x', 'asdf');


CREATE TABLE IF NOT EXISTS `Post` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=7 ;

INSERT INTO `Post` (`id`, `title`) VALUES
(1, 'first post'),
(2, 'second post'),
(3, 'third post'),
(4, 'fourth post'),
(5, 'fifth post'),
(6, 'sixth post');

Und ich möchte alle Beiträge zusammen mit einer Liste aller Benutzernamen auflisten, die den Beitrag kommentiert haben:

SELECT
Post.id as post_id, Post.title as title, GROUP_CONCAT(name) 
FROM Post 
LEFT JOIN Comment on Comment.post_id = Post.id
GROUP BY Post.id

gibt mir:

id  title   GROUP_CONCAT( name )
1   first post  bill,john
2   second post     bill
3   third post  john
4   fourth post     x
5   fifth post  NULL
6   sixth post  NULL

Das funktioniert großartig, außer dass die Liste der Benutzer ruiniert wird, wenn ein Benutzername ein Komma enthält.Verfügt MySQL über eine Funktion, mit der ich diese Zeichen maskieren kann?(Bitte gehen Sie davon aus, dass Benutzernamen beliebige Zeichen enthalten können, da dies nur ein Beispielschema ist.)

War es hilfreich?

Lösung

Wenn es ein anderes Zeichen ist, die in Benutzername illegal ist, können Sie ein anderes Trennzeichen mit einer wenig bekannten Syntax angeben:

...GROUP_CONCAT(name SEPARATOR '|')...

... Sie wollen Rohre ermöglichen? oder ein beliebiges Zeichen?

Entfliehen Sie das Trennzeichen, vielleicht mit umgekehrtem Schrägstrich, aber bevor diese Flucht zu tun Schrägstrichen selbst:

group_concat(replace(replace(name, '\\', '\\\\'), '|', '\\|') SEPARATOR '|')

Dies wird:

  1. entkommen keine Schrägstriche mit einem anderen Backslash
  2. entkommen das Trennzeichen mit einem Backslash
  3. verketten die Ergebnisse mit dem Trennzeichen

Um die unescaped Ergebnisse zu erhalten, tut das gleiche in umgekehrter Reihenfolge:

  1. teilen Sie die Ergebnisse durch das Trennzeichen, wo nicht ein Backslash. Eigentlich ist es ein wenig schwierig, wollen Sie es spalten, wo sie nicht durch eine ungerade Zahl von blackslashes voraus. Diese Regex passt auf, dass:
    (?<!\\)(?:\\\\)*\|
  2. ersetzen alle entkam Separator Zeichen mit Literalen, das heißt ersetzen \ | mit |
  3. ersetzen alle Doppelschrägstriche mit Sengschrägstriche, z.B. ersetzen \\ mit \

Andere Tipps

Tatsächlich gibt es welche ascii control characters speziell für die Trennung von Datenbankfeldern und Datensätzen entwickelt:

0x1F (31): unit (fields) separator

0x1E (30): record separator

0x1D (29): group separator

Mehr lesen: über ASCII-Zeichen

Sie werden sie niemals in Benutzernamen haben und höchstwahrscheinlich auch nie in einem anderen non-binary data in Ihrer Datenbank, damit sie sicher verwendet werden können:

GROUP_CONCAT(foo SEPARATOR 0x1D)

Dann geteilt durch CHAR(0x1D) in der von Ihnen gewünschten Kundensprache.

würde ich GROUP_CONCAT (Name SEPARATOR '\ n'), da \ vorschlagen n in der Regel nicht auf. Dies könnte ein wenig einfacher sein, da Sie nichts entkommen müssen, aber zu unerwarteten Problemen führen könnten. Die encodeing / regexp Decodierung Sachen wie durch Nick vorgeschlagen ist auch natürlich schön.

REPLACE()

Beispiel:

... GROUP_CONCAT(REPLACE(name, ',', '\\,')) 

Beachten Sie einen Doppelklick Backslash verwenden (wenn Sie das Komma mit Backslash), weil Backslash selbst Magie ist, und \, wird einfach ,.

Wenn Sie vorhaben, die Decodierung in der Anwendung zu tun, vielleicht auch nur hex verwenden:

SELECT GROUP_CONCAT(HEX(foo)) ...

oder Sie können auch die Länge in sie setzen:

SELECT GROUP_CONCAT(CONCAT(LENGTH(foo), ':', foo)) ...

Nicht, dass ich getestet entweder :-D

was nick wirklich gesagt, mit einer Verstärkung - der Separator auch mehr als ein Zeichen sein kann

.

Ich habe oft verwendet,

GROUP_CONCAT(name SEPARATOR '"|"')

Die Chancen eines Benutzernamens mit „|“ ziemlich niedrig ist, würde ich sagen.

Sie sind immer in diese Grauzone, wo es besser wäre, diese außerhalb der Welt der SQL nachzubearbeiten.

Mindestens das ist, was ich tun würde: Ich würde nur ORDER BY statt GROUP BY und eine Schleife durch die Ergebnisse, die die Gruppierung als Filter in der Client-Sprache getan zu behandeln:

  1. Start von last_id auf NULL initialisiert
  2. Fetch die nächste Zeile der Ergebnismenge (wenn es nicht mehr Reihen gehen zu Schritt 6)
  3. Wenn die ID der Zeile anders als last_id eine neue Ausgabezeile starten:

    a. wenn last_id nicht NULL ist dann Ausgabe der gruppierten Zeile

    b. Den neuen gruppierten row = die Eingabezeile, sondern speichert den Namen als ein einzelnes Element array

    c. Satz last_id auf den Wert der aktuellen ID

  4. Im anderen Fall (id ist die gleiche wie last_id) fügen Sie die Zeilennamen auf die bestehenden gruppierte Reihe.

  5. Gehen Sie zurück zu Schritt 2
  6. Ansonsten haben Sie fertig; wenn der last_id nicht NULL ist dann Ausgabe der vorhandene Gruppenzeile.

Dann die Ausgabe endet Namen wie als Array organisiert und können entscheiden, wie Sie behandeln möchten / Flucht / formatieren sie dann.

Welche Sprache / System verwenden Sie? PHP? Perl? Java?

Jason S: Das ist genau das Problem mit mir zu tun habe. Ich verwende einen PHP MVC-Framework und wurde die Verarbeitung der Ergebnisse, wie Sie beschreiben (mehrere Zeilen pro Ergebnis und Code-Gruppe die Ergebnisse zusammen). Allerdings habe ich auf zwei Funktionen gearbeitet für meine Modelle zu implementieren. Man gibt eine Liste aller notwendigen Felder benötigt, um das Objekt zu erstellen und das andere ist eine Funktion, die eine Zeile mit den Feldern von der ersten Funktion gegeben, instanziiert ein neues Objekt. Auf diese Weise können Sie mir eine Zeile aus der Datenbank anfordern und sie leicht in das Objekt zurückdrehen, ohne durch das Modell benötigt, um die Interna der Daten zu kennen. Das funktioniert nicht ganz so gut, wenn mehrere Zeilen ein Objekt darstellen, so dass ich versuche, GROUP_CONCAT zu verwenden um dieses Problem zu bekommen.

Im Moment erlaube ich jeden Charakter.Mir ist klar, dass ein Rohr wahrscheinlich nicht auftauchen wird, aber ich würde es gerne zulassen.

Wie wäre es mit einem Steuerzeichen, das Sie sowieso aus der Anwendungseingabe entfernen sollten?Ich bezweifle, dass Sie z. B. brauchen.ein Tabulator oder eine neue Zeile in einem Namensfeld.

Sie hierfür auf einige der Antworten zu erweitern, ich implementiert @derobert ‚s zweiten Vorschlag in PHP und es funktioniert gut . Angesichts MySQL wie:

GROUP_CONCAT(CONCAT(LENGTH(field), ':', field) SEPARATOR '') AS fields

Ich habe die folgende Funktion es aufzuspalten:

function concat_split( $str ) {
    // Need to guard against PHP's stupid multibyte string function overloading.
    static $mb_overload_string = null;
    if ( null === $mb_overload_string ) {
        $mb_overload_string = defined( 'MB_OVERLOAD_STRING' )
                && ( ini_get( 'mbstring.func_overload' ) & MB_OVERLOAD_STRING );
    }
    if ( $mb_overload_string ) {
        $mb_internal_encoding = mb_internal_encoding();
        mb_internal_encoding( '8bit' );
    }

    $ret = array();
    for ( $offset = 0; $colon = strpos( $str, ':', $offset ); $offset = $colon + 1 + $len ) {
        $len = intval( substr( $str, $offset, $colon ) );
        $ret[] = substr( $str, $colon + 1, $len );
    }

    if ( $mb_overload_string ) {
        mb_internal_encoding( $mb_internal_encoding );
    }

    return $ret;
}

ich anfangs auch @ ʞɔıu umgesetzt 's Vorschlag, von @Lemon Juice mit' s-Separatoren. Es funktionierte gut, aber abgesehen von seiner Komplikation war es langsamer, das Hauptproblem ist, dass PCRE nur feste Länge ermöglicht so den vorgeschlagene Lookbehind regex zu spalten die Begrenzungszeichen erfordert die Erfassung, sonst Schrägstriche am Ende der Saiten verdoppelt verloren. So gegeben MySQL wie (Anmerkung 4 PHP Schrägstriche => 2 MySQL Schrägstriche => 1 reale Backslash):

GROUP_CONCAT(REPLACE(REPLACE(field, '\\\\', '\\\\\\\\'),
    CHAR(31), CONCAT('\\\\', CHAR(31))) SEPARATOR 0x1f) AS fields

die Split-Funktion war:

function concat_split( $str ) {
    $ret = array();
    // 4 PHP backslashes => 2 PCRE backslashes => 1 real backslash.
    $strs = preg_split( '/(?<!\\\\)((?:\\\\\\\\)*+\x1f)/', $str, -1, PREG_SPLIT_DELIM_CAPTURE );
    // Need to add back any captured double backslashes.
    for ( $i = 0, $cnt = count( $strs ); $i < $cnt; $i += 2 ) {
        $ret[] = isset( $strs[ $i + 1 ] ) ? ( $strs[ $i ] . substr( $strs[ $i + 1 ], 0, -1 ) ) : $strs[ $i ];
    }
    return str_replace( array( "\\\x1f", "\\\\" ), array( "\x1f", "\\" ), $ret );
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top