Интересное решение простой задачи

ForJest

- свежая кровь
Интересное решение простой задачи(SUM для строк или агрегатный CONCAT)

Предложите пожалуйста хорошее решение задачи (комменты что это криво не принимаются, т.к. это подзадача другой).
Есть Таблица ID INT, ID_CRIT, Name CHAR(20) допустим. Нужно конкатенировать все Name для каждого ID_CRIT и запихать результат во временную таблицу, сохранив ID_CRIT.
Как запихивать писать не обязательно, но факт тот что данные должны быть в мускуле :)
Конкатенировать можно, разделяя любым символом, например запятой :)
Ограничения для задачи - гонять Name из MySQL в PHP и обратно нельзя. Количество операций над таблицей не ограничено.
 

Crazy

Developer
У тебя есть уверенность, что принимающее поле не переполнится?
 

ForJest

- свежая кровь
Дык - как оно может переполнится? Ну сделаю его TEXT. Если я буду делать SELECT INSERT, то муська тогда сама определится с размером поля.

Я уже придумал решение одно, но не считаю его оптимальным.
SELECT CONCAT(IF(id=2, name, ''), ',', IF(id=10, name, ''), ',', IF(id=34, name, ''.....
Но получается что придется тупо перечислить все номера записей по сути, которые интересуют.
Меня еще интересует вопрос - нельзя ли это решить с помощью серверных переменных?
При моем решении экономия получится только на том, что не придется гонять данные потенциально имеющие большой размер. Да и сама задача важна не сама по себе, а способ решения подобного рода задач.
 

.des.

Поставил пиво кому надо ;-)
Ну раз количество запросов не ограничено и главное чтобы не передавать ничего в пхп, то можно сделать следующее.
Извращение еще то.. но правда и задача такая :)))

PHP:
CREATE TEMPORARY TABLE joined (id_crit INT DEFAULT NULL, name text);
// Получаем первый уникальный id_crit для выборки
SELECT @nid:=MIN(id_crit) FROM t;
[ЦВЕТОМ=red]Следующий блок повторить N раз.
N равно количеству уникальных id_crit в таблице
Например SELECT DISTINCT(id_crit) FROM T; [/ЦВЕТОМ]
PHP:
SELECT name FROM t WHERE id_crit=@nid INTO OUTFILE 'names' LINES TERMINATED BY ',';
LOAD DATA INFILE 'names' INTO joined (name);
UPDATE joined SET id_crit=@nid WHERE ISNULL(id_crit);

// Получаем следующий уникальный id_crit для выборки
SELECT @nid:=MIN(id_crit) FROM t WHERE id_crit>@nid;

// здесь надо не забыть удалить файл 'names' средствами php
Вроде так. :(
неудобств выше крыши.
1. Надо иметь привилегии на селект оутфайл
2. После каждого цикла файл надо удалять средствами php.
3. Наверняка будет медленно :( вот здесь не хватает возможности SELECT INTO MEMORY :)
4. Весьма громоздко.
5.Если ID_cr
Но это вроде все неудобства. Остально (сколько его там остального осталось) вроде в порядке. :)
 

.des.

Поставил пиво кому надо ;-)
А теперь более сложные, но и более красивые решения.
Сложные в том, что нельзя обойтись стандартными средствами MySQL - точнее нельзя обойтись чтобы это было просто.. :(
(или по крайней мере я не знаю как :()
но можно расширить MySQL
То есть как было бы хорошо применить SUM к строкам :)
нет ничего проще. Пишем
PHP:
SELECT id_cnt,STRSUM(name) FROM ... GROUP BY 1;
Хм.. разочарую :( нет такой агрегатной функции STRSUM
Но ее можно создать. Читать здесь:
http://www.mysql.com/doc/en/Adding_UDF.html

После написания и компиляции просто подключаем .so с этой функцией
PHP:
CREATE AGGREGATE FUNCTION STRSUM RETURNS STRING SONAME 'strsum.so';
Лично я не разбирался можно ли то же самое проделать под винды.. но думаю что это и особо не нужно.
Вот больший интерес у меня вызвал следующий факт.
http://www.mysql.com/doc/en/UDF_return_values.html

The return value of the main function xxx() is the function value, for long long and double functions. A string functions should return a pointer to the result and store the length of the string in the length arguments.

Set these to the contents and length of the return value. For example:

memcpy(result, "result string", 13);
*length = 13;
The result buffer that is passed to the calc function is 255 byte big. If your result fits in this, you don't have to worry about memory allocation for results.
То есть.. отводится 255 байт по дефалту :( а нужно будет больше.. :( вот как определить сколько потребуется разбираться тому, кому все же потребуется писать функцию :)

//////////////////////////////////////////////////////////
Вот я тут подумал :) мы с тобой ForJest думали думали.. а сейчас придет si или еще кто нибудь.. проснувшийся, отдохнувший и ошарашит нас простым до ужаса решением данной задачи :)
 

ForJest

- свежая кровь
_des_ к сожалению оба варианта не подходят в общем случае... Хотя как пища для размышлений очень полезны :)).
Спасибо тебе за кропотливое внимание к этой задаче :)
Я придумал следующий вариант - он мне кажется оптимальным по всем параметрам - таки использование серверных переменных решает многие пробемы, без написание хранимых процедур (камешек в сторону PG :))
Я решил не использовать min() для получения следующего id_crit, хотя это было бы еще меньшим участием PHP В процессе :) Но решил пожертвовать этим ради снижения нагрузки.

PHP:
$db-> exec_query("CREATE TEMPORARY TABLE joined (id_crit INT PRIMARY KEY, name text)");

$db-> exec_query("SELECT DISTINCT id_crit FROM tmp_name");
$ids = array();
while ($db-> get_data())
{
     $ids[] = $db-> result["id_crit"];
}

foreach ($ids as $id_crit)
{
     $db-> exec_query("SET @link_name = ''");
     $db-> exec_query("SELECT @link_name:=CONCAT(@link_name, ', ', name) FROM tmp_name WHERE id_name=$id_name ORDER BY name");
     $db-> exec_query("INSERT INTO tmp VALUES ($id_name, SUBSTRING(@link_name, 2))");
}
Итого имеем N запросов все равно. Но таким образом мы избавляемя от излишнего бесполезного обмена данными между PHP и мускулом.
 

.des.

Поставил пиво кому надо ;-)
GROUP_CONCAT(expr)
Full syntax:
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] [,col ...]]
[SEPARATOR str_val])
This function was added in MySQL version 4.1. It returns a string result with the concatenated values from a group:
:)

http://www.mysql.com/doc/en/GROUP-BY-Functions.html

Таки ведь сделали :)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Автор оригинала: .des.
Таки ведь сделали :)
Код:
create function group_concat(text,text) returns text as 'select $1 || $2' language 'SQL'; 

create aggregate group_concat( sfunc = group_concat, basetype = text, stype = text, initcond = '' );
а когда понадобится конкатенация через запятую, будем опять ждать пока ООО Мыскль почешется? :D
Топик для размышлений о пользе ХП в народном хозяйстве: http://www.sql.ru/forum/actualthread.aspx?bid=7&tid=44660
Edit: ах, да, слово "SEPARATOR" не разглядел. ООО Мыскль почесался.
 
Сверху