14

(注: この質問はクエリのエスケープに関するものではなく、結果のエスケープに関するものです)

GROUP_CONCATを使用して、複数の行をコンマ区切りのリストに結合しています。たとえば、2 つの (例の) テーブルがあるとします。

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');

そして、投稿にコメントした各ユーザー名のリストとともに、すべての投稿を一覧表示したいと思います。

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

私に与えます:

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

これはうまく機能しますが、ユーザー名にコンマが含まれているとユーザーのリストが台無しになります。MySQL には、これらの文字をエスケープできる機能がありますか? (これはスキーマの例にすぎないため、ユーザー名には任意の文字を含めることができると想定してください)

4

10 に答える 10

46

実際、ascii control charactersデータベースのフィールドとレコードを分離するために特別に設計されたものがあります。

0x1F (31): unit (fields) separator

0x1E (30): record separator

0x1D (29): group separator

続きを読む: ASCII 文字について

それらをユーザー名に含めたり、データベース内の他のユーザーに含めたりすることはほとんどないnon-binary dataため、安全に使用できます。

GROUP_CONCAT(foo SEPARATOR 0x1D)

次に、必要なクライアント言語で分割しCHAR(0x1D)ます。

于 2012-03-07T11:51:09.503 に答える
15

ユーザー名に違法な文字が他にある場合は、あまり知られていない構文を使用して別の区切り文字を指定できます。

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

... パイプを許可しますか? または任意の文字?

おそらくバックスラッシュを使用して区切り文字をエスケープしますが、その前にバックスラッシュ自体をエスケープします。

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

この意志:

  1. 別のバックスラッシュでバックスラッシュをエスケープします
  2. バックスラッシュで区切り文字をエスケープする
  3. 結果を区切り文字で連結します

エスケープされていない結果を取得するには、同じことを逆の順序で実行します。

  1. バックスラッシュが前にない区切り文字で結果を分割します。実際には、少しトリッキーです。奇数個のブラックスラッシュが前にない場所で分割する必要があります。この正規表現は次のものと一致します。
    (?<!\\)(?:\\\\)*\|
  2. エスケープされたすべての区切り文字をリテラルに置き換えます。つまり、\| を置き換えます。| で
  3. すべての二重のバックスラッシュを単一のバックスラッシュに置き換えます。たとえば、\\ を \ に置き換えます。
于 2009-01-16T23:33:02.100 に答える
4

REPLACE()

例:

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

\,バックスラッシュ自体は魔法であり、単純になりますので、ダブルバックスラッシュを使用する必要があることに注意してください(バックスラッシュでコンマをエスケープする場合),

于 2009-01-16T23:14:05.757 に答える
4

\n は通常発生しないため、GROUP_CONCAT(name SEPARATOR '\n') をお勧めします。何もエスケープする必要がないため、これは少し単純かもしれませんが、予期しない問題が発生する可能性があります。もちろん、ニックによって提案されたエンコード/正規表現のデコードも素晴らしいです。

于 2009-06-18T20:43:28.530 に答える
1

アプリケーションでデコードを行う場合は、次を使用してくださいhex

SELECT GROUP_CONCAT(HEX(foo)) ...

または、長さをそれらに入れることもできます:

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

私もテストしたわけではありません:-D

于 2009-01-17T01:26:00.687 に答える
0

ジェイソンS:これはまさに私が扱っている問題です。私はPHPMVCフレームワークを使用しており、説明したように結果を処理していました(結果ごとに複数の行と結果をグループ化するコード)。ただし、モデルで実装する2つの関数に取り組んできました。1つは、オブジェクトを再作成するために必要なすべてのフィールドのリストを返し、もう1つは、最初の関数のフィールドを含む行を指定して、新しいオブジェクトをインスタンス化する関数です。これにより、データベースに行を要求し、モデルに必要なデータの内部を知らなくても、行をオブジェクトに簡単に戻すことができます。複数の行が1つのオブジェクトを表す場合、これはうまく機能しないため、GROUP_CONCATを使用してその問題を回避しようとしました。

于 2009-01-17T01:31:09.707 に答える
0

今のところ、どのキャラクターでも許可しています。パイプが表示される可能性は低いと思いますが、許可したいと思います。

とにかくアプリケーション入力から取り除くべき制御文字はどうですか? 私はあなたが必要だとは思わない。名前フィールドのタブまたは改行。

于 2009-01-18T01:30:55.793 に答える
0

SQL の世界の外でこれを後処理する方が良いかもしれない灰色の領域に入っています。

少なくとも私はそうします: GROUP BY の代わりに ORDER BY を使用し、結果をループしてグループ化をクライアント言語で行われるフィルターとして処理します。

  1. last_idNULLに初期化することから始めます
  2. 結果セットの次の行を取得します (それ以上の行がない場合は手順 6 に進みます)
  3. last_id行の ID が新しい出力行の開始と異なる場合:

    a. last_idNULL でない場合は、グループ化された行を出力します

    b. 新しいグループ化された行 = 入力行を設定しますが、名前を単一の要素配列として保存します

    c. last_id現在の ID の値に設定

  4. それ以外の場合 (id は と同じlast_id)、既存のグループ化された行に行名を追加します。

  5. 手順 2 に戻る
  6. それ以外の場合は終了します。NULL でない場合はlast_id、既存のグループ行を出力します。

次に、出力には配列として編成された名前が含まれ、それらをどのように処理/エスケープ/フォーマットするかを決定できます。

どの言語/システムを使用していますか? PHP? パール?ジャワ?

于 2009-01-17T01:18:38.580 に答える
0

いくつかの回答を拡張するために、@derobert の2 番目の提案を PHP に実装しましたが、うまく機能します。MySQL を次のように指定します。

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

次の関数を使用して分割しました。

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;
}

また、@Lemon Juice のセパレーターの 1 つを使用して、@ʞɔıu の提案も最初に実装しました。それは問題なく動作しましたが、その複雑さは別として、遅くなりました。主な問題は、PCRE が固定長の後読みのみを許可するため、提案された正規表現を使用して分割する必要があることです。そうしないと、文字列の末尾にある二重のバックスラッシュが失われます。したがって、次のような MySQL を指定すると (4 つの PHP バックスラッシュ => 2 つの MySQL バックスラッシュ => 1 つの実際のバックスラッシュに注意):

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

分割機能は次のとおりです。

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 );
}
于 2016-05-31T02:47:23.563 に答える
0

ニックが実際に言ったことは、強化されたものです-セパレーターも複数の文字にすることができます。

よく使っていた

GROUP_CONCAT(name SEPARATOR '"|"')

ユーザー名に「|」が含まれる可能性 かなり低いと思います。

于 2009-01-16T23:51:24.853 に答える