13


7

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

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

存在しない場合はCREATE TABLE IFコメント( `id` int(11)符号なしNOT NULL auto_increment、` post_id` int(11)符号なしNOT NULL、 `name` varchar(255)照合utf8_unicode_ci NOT NULL、` comment` varchar(255 )照合utf8_unicode_ci NULL以外、主キー( `id`)、キー` post_id`( `post_id`))ENGINE = MyISAMデフォルトCHARSET = utf8 COLLATE = utf8_unicode_ci AUTO_INCREMENT = 6;

「Comment」(「id」、「post_id」、「name」、「comment」)の値に挿入します(1、1、「bill」、「some comment」)、(2、1、「john」、別のコメント) ')、(3、2、' bill '、' blah ')、(4、3、' john '、' asdf ')、(5、4、' x '、' asdf ');

存在しない場合はCREATE TABLEを作成する `Post`(` id` int(11)NOT NULL auto_increment、 `title` varchar(255)collat​​e utf8_unicode_ci NOT NULL、プライマリキー(` id`))ENGINE = InnoDBデフォルトのCHARSET = utf8 COLLATE = utf8_unicode_ci AUTO_INCREMENT = 7。

INSERT INTO `Post`(` id`、 `title`)値(1、 'first post')、(2、 'second post')、(3、 'third post')、(4、 'four post') 、(5、 '5番目の投稿')、(6、 '6番目の投稿');

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

SELECT Post.idをpost_idとして、Post.titleをタイトルとして、GROUP_CONCAT(name)FROM Postから左へComment Comment.post_id = Post.idについてのコメント投稿GROUP BY Post.id

私にくれます:

id title GROUP_CONCAT(name)1最初の請求書、john 2 2番目の請求書3 3番目の投稿john 4 4番目の投稿x 5 5番目の投稿NULL 6 6番目の投稿NULL

ユーザー名にコンマが含まれていると、ユーザーの一覧が表示されなくなることを除けば、これは非常に便利です。 MySQLにはこれらの文字をエスケープさせる機能がありますか? (これはスキーマの例にすぎないため、ユーザー名には任意の文字を含めることができます)

10 Answer


38


実際には、データベースのフィールドとレコードを分離するために特別に設計された「ASCII制御文字」があります。

0x1F (31): unit (fields) separator

0x1E (30): record separator

0x1D (29): group separator

続きを読む:http://www.lammertbies.nl/comm/info/ascii-characters.html [ASCII文字について]

安全に使用できるように、あなたはそれらをユーザ名の中に決して持っていないでしょう、そしておそらくあなたのデータベースの中の他のどの「非バイナリデータ」の中にも決してないでしょう:

GROUP_CONCAT(foo SEPARATOR 0x1D)

それからあなたが望むどんなクライアント言語でも `CHAR(0x1D)`によって分割されます。


13


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

...GROUP_CONCAT(name SEPARATOR '|')...
  1. パイプを許可したいですか? または任意のキャラクター?

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

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

この意志:

  1. 他のバックスラッシュでバックスラッシュをエスケープする

  2. 円記号で区切り文字をエスケープする

  3. 結果を区切り文字で連結する

エスケープされていない結果を得るためには、逆の順序で同じことをしてください。

  1. 結果を区切り文字で分割します。先頭にaは付きません。 バックスラッシュ。 実のところ、それは少しトリッキーです、あなたはそれが先行していないところでそれを分割したいです。 この正規表現はそれにマッチするでしょう: (?

  2. エスケープ区切り文字をすべてリテラルに置き換えます。 \ |を置き換える と|

  3. すべての二重のバックスラッシュを単一のバックスラッシュに置き換えます。 \\を置き換える \で


4


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


3


例:

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

円記号自体は魔法であり、 `\、`は単に `、`になるので、二重円記号を使用する必要があることに注意してください(円記号でコンマをエスケープする場合)。


1


あなたのアプリケーションでデコードをするつもりなら、おそらく `hex`を使ってください。

SELECT GROUP_CONCAT(HEX(foo)) ...

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

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

私がテストしたのではない: - D


0


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

私はよく使った

GROUP_CONCAT(name SEPARATOR '"|"')

"|"を含むユーザー名の可能性かなり低いと思います。


0


あなたはそれがSQLの世界の外でこれを後処理することがより良いかもしれないその灰色の領域に入っています。

少なくともそれが私のやりたいことです。GROUP BYの代わりにORDER BYを実行し、結果をループ処理してクライアント言語で行われたフィルタとしてグループ化を処理します。

  1. `last_id`をNULLに初期化することから始めます

  2. 結果セットの次の行を取得します(行がこれ以上ない場合 ステップ6)

  3. 行のIDが「last_id」と異なる場合、新しい出力を開始します 行:+ a。 `last_id`がNULLでなければ、グループ化された行を出力します。 新しいグループ行=入力行を設定しますが、名前を単一の要素配列として格納します。 `last_id`を現在のIDの値に設定します

  4. そうでなければ(idは `last_id`と同じです)行名を追加します 既存のグループ化された行。

  5. ステップ2に戻る

  6. それ以外の場合は終了しました。 `last_id`がNULLでない場合、出力 既存のグループ行。

それからあなたの出力は配列として組織化された名前を含むことになって、あなたがそれらをどのように扱うか/ escape / formatしたいかを決めることができます。

どの言語/システムを使用していますか? PHP? Perl? Java?


0


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


0


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

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


0


いくつかの答えを拡大するために、@ derobertの 2番目の提案をPHPに実装しましたが、うまく機能します。 以下のようなMySQLがあるとします。

GROUP_CONCAT(CONCAT(LENGTH(フィールド)、 ':'、フィールド)SEPARATOR '')ASフィールド

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

function concat_split($ str){// PHPの愚かなマルチバイト文字列関数のオーバーロードを防ぐ必要があります。 static $ mb_overload_string = null; if(null === $ mb_overload_string){$ mb_overload_string = defined( 'MB_OVERLOAD_STRING')

$ ret = array(); for($ offset = 0; $ colon = strpos($ str、 ':'、$ offset); $ offset = $ colon 1 $ len){$ len = intval(substr($ str、$ offset、$ colon)) ; $ ret [] = substr($ str、$コロン1、$ len); }

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

$ retを返します。 }

私は最初、@ Lemon Juiceのセパレータの1つを使用して@ʞɔıuの提案も実装しました。 問題はありませんでしたが、複雑な問題は別として、遅くなりました。PCREは固定長の先読みしかできないため、推奨されている正規表現を使用して区切り記号を取得する必要があります。 そのため、MySQLには(注4 PHPバックスラッシュ⇒ 2 MySQLバックスラッシュ⇒ 1実際のバックスラッシュ)のように指定します。

GROUP_CONCAT(REPLACE(REPLACE(フィールド、 '\\\\'、 '\\\\\\\\')、CHAR(31)、CONCAT( '\\\\'、CHAR(31)))SEPARATOR 0x1f) ASフィールド

分割関数は次のとおりです。

関数concat_split($ str){$ ret = array(); // 4 PHPバックスラッシュ=> 2 PCREバックスラッシュ=> 1実際のバックスラッシュ。 $ strs = preg_split( '/(?