Все технические форумы на одном сайте Удобный поиск информации с популярных форумов в одном месте
Вопрос: Разбить строку на слова

Всем привет!

Есть массив строк, допустим 350. Каждая содержит около 100 слов.
Необходимо разить все строки на слова, и затем подсчитать как часто каждое слово повторяется.

Есть хорошая функция regexp_substr+connect by которая жУтко тормозить.
К примеру вот этот код вы уже сможете выполнить

create table t1
as
with t1 as
 (select '####<Mar 20, 2014 2:57:37 AM MSK> <Info> <Health> <vs-c06-as06-1182> <IBR_server1> <weblogic.GCMonitor> <<anonymous>> <> <> <1395269857320> <BEA-310002> <65% of the total memory in the server is free> ' as c1
    from dual
  connect by level <= 2)
SELECT to_char(trim(regexp_substr(c1, '[^ ]+', 1, LEVEL))) as c2
  FROM t1
CONNECT BY LEVEL <= regexp_count(c1, ' ') + 1


Есть ли какой ни будь другой способ разбить строку на подстроки?

Мне приходит в ум только заложиться на 1000 тыщ слов максимум в строке, и использовать instr или тот же regexp
Ответ:
Добрый Э - Эх
Skulll,

а вообще, после многократных обсуждений тут на форуме все пришли к выводу, что "размножать " строки нужно как минимум через коррелированный мультисет-подзапрос:
with t1 as
 (select level as lv, '####<Mar 20, 2014 2:57:37 AM MSK> <Info> <Health> <vs-c06-as06-1182> <IBR_server1> <weblogic.GCMonitor> <<anonymous>> <> <> <1395269857320> <BEA-310002> <65% of the total memory in the server is free> ' as c1
    from dual
 connect by level <=  100)

SELECT to_char(trim(regexp_substr(c1, '[^ ]+', 1, p.column_value))) as c2
  FROM t1
     , table(cast(multiset(select rownum
                             from dual
                          connect by level <= regexp_count(c1, ' ') + 1) as sys.odcinumberlist)
             ) p

(а если версия позволяет, то и вообще через APPLY)

Данный скрипт у меня почему-то разбивает только первую строку в таблице, а дальше идет тупо null. Почему?
Вопрос: Разбить строку

Приветствую!

Помогите, пожалуйста, решить следующую задачу...
Есть набор записей, в которых есть строки (длинные):
ID1, строка1
ID2, строка2
ID3, строка3

Необходимо разбить строки внутри записей так, чтобы получился рекордсет следующего вида:
ID1, номер строки, часть строки1
ID1, номер строки, часть строки1
ID1, номер строки, часть строки1
ID2, номер строки, часть строки2
ID2, номер строки, часть строки2
ID3, номер строки, часть строки3

И т.д... С одной строкой я решение нашел (через regexp_substr). Но что-то недопонимаю, как его прикрутить внутрь запроса...
Ответ:
env
Nikolay_Ch,

with t as (select 1 m,'строканачасти' l from dual union all
           select 2, 'другаястроканачасти' from dual)
select substr(l,(level-1)*:k,:k),'строка '||m,'часть '||level
from t
connect by level<=(length(l)/:k)+1 and prior m = m and prior dbms_random.value>0;


select substr(l,(level-1)*:k+1,:k),'строка '||m,'часть '||level
Вопрос: Разбить строку по символу в запросе

Привет!
Как можно разбить строку по символу в запросе?

Например есть строка 45/89, нужно получить два поля с значениями 45/89. Значения могут быть разными, например: 56/700, 5/100, 2/3 и т.д.
Ответ: wadman, точно. Спасибо всем!
Вопрос: Разбить строку на слова PL SQL

Написать хранимую процедуру, которая принимает 1)- параметр – строку текста , состоящую из слов, а возвращает 2 параметра: 1)слово, имеющее максимальную длину и 2) количество символов в этом слове.
Ответ:
Сообщение от Grossmeister
В цикле отрезаешь по одному слову с пом. INSTR + SUBSTR
Ну ладно, раз уже появилась вторая абсолютно аналогичная тема, сделаем вас процедуру без регулярных выражений. Но тут удобнее немного в другом порядке. Самое первое действие - замена пробельных символов на пробелы. Это будет
SQL
1
v_txt := translate(v_txt, chr(8)||chr(9)||chr(10)||chr(13), ' '||' '||' '||' ');
Если какой-то пробельный символ забыл - легко добавить. Главное - понятно как. Специально пробелы через конкатенацию написал.
Вторым шагом неодиночные пробелы заменяем на одиночные. Это можно сделать так:
SQL
1
2
3
4
while instr(v_txt, ' '||' ') >0
loop
    v_txt := REPLACE(v_txt, ' '||' ', ' ');
END loop;
Третьим шагом обрезаем пробелы в начале и в конце. Ну это совсем просто
SQL
1
v_txt := TRIM(v_txt);
Теперь приступаем к поиску максимальной подстроки.
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IF instr(v_txt, ' ') =0 THEN
    p_ret := v_txt;
    p_len := LENGTH(v_txt);
ELSE
    p_ret := '';
    p_len := 0;
END IF;
while  instr(v_txt, ' ')>0
loop
    pos := instr(v_txt, ' ');
    v_ret := substr(v_txt, pos-1, pos-1);
    v_len := LENGTH(v_ret);
    IF v_len > p_len THEN
        p_ret :=v_ret;
    END IF;
    v_txt := substr(v_txt, pos+1);
END loop; 
Сейчас отлажу процедуру и помещу в следующем посте

Добавлено через 36 минут
В ранее приведенных кусках кода была парочка мелких ошибок и пропусков, которые вылезли при отладке. Процедура получилась такая
Кликните здесь для просмотра всего текста
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CREATE OR REPLACE
PROCEDURE maxword(p_txt    IN  varchar2,  
                        p_ret    OUT varchar2, 
                        p_len    OUT NUMBER) 
IS
    v_txt   varchar2(4000);
    v_len   NUMBER;
    pos     NUMBER;
    v_ret   varchar2(4000);
BEGIN
    v_txt := translate(p_txt, chr(8)||chr(9)||chr(10)||chr(13), ' '||' '||' '||' ');    --  все (неодиночные) пробельные символы заменяются пробелом
    while instr(v_txt, ' '||' ') >0
    loop
        v_txt := REPLACE(v_txt, ' '||' ', ' ');
    END loop;                                           --  парные пробелы заменяются на одиночные
    v_txt := TRIM(v_txt);                               --  убираются пробелы в начале и конце строки
    
    IF instr(v_txt, ' ') =0 THEN
        p_ret := v_txt;
        p_len := LENGTH(v_txt);
    ELSE
        p_ret := '';
        p_len := 0;
    END IF;                             -- присваиваются начальные значения
    while  instr(v_txt, ' ')>0
    loop
        pos := instr(v_txt, ' ');
        v_ret := substr(v_txt, 1, pos-1);
        v_len := LENGTH(v_ret);
        IF v_len > p_len THEN
            p_ret := v_ret;
            p_len := v_len;
        END IF;
        v_txt := substr(v_txt, pos+1, LENGTH(v_txt));
    END loop;                           -- ищется первое слово максимальной длины
END;

Ничего так получилось. Даже не очень длинно. Пример вызова процедуры я уже приводил выше
Вопрос: Разбить строку на подстроки в WITH

Возникли проблемы в СРМ с созданием отчёта (SSRS) при передачи multivalue параметров в хранимку. Если писать этот запрос в редакторе отчётов, всё работает, но когда я импортирую в хранимку - начинаются проблемы: SQL не распознаёт multivalue параметры.
код следующий:

T-SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT        stage.new_name AS stage_name, ISNULL(mission.new_gradename,'Задача без исполнителя') AS mission_grade,  ISNULL(mission.new_systemuseridname,'Задача без исполнителя') AS own_user_name, mission.new_name AS mission_name, mission.new_time AS mission_plan, 
                         SUM(billing.new_billed_time) AS mission_fact, mission.new_time - SUM(billing.new_billed_time) AS mission_remainder, billing_without_missions_set.billing_without_missions_time AS billing_without_mission, 
                         plan_by_grade.plan_by_grade_collumn AS plan_by_grade, fact_by_grade.fact_by_grade_collumn AS fact_by_grade, 
                         plan_by_grade.plan_by_grade_collumn - fact_by_grade.fact_by_grade_collumn AS remainder_by_grade
FROM            Filterednew_stage AS stage LEFT OUTER JOIN
                         Filterednew_mission AS mission ON stage.new_stageid = mission.new_stageid LEFT OUTER JOIN
                         Filterednew_billing AS billing ON stage.new_stageid = billing.new_stageid AND billing.new_missionid = mission.new_missionid LEFT OUTER JOIN
                             (SELECT        SUM(new_billed_time) AS billing_without_missions_time, new_stageid AS billing_without_missions_stage, new_systemuserid AS billing_without_missions_user
                               FROM            Filterednew_billing
                               WHERE        (new_missionid IS NULL)
                               GROUP BY new_stageid, new_systemuserid) AS billing_without_missions_set ON stage.new_stageid = billing_without_missions_set.billing_without_missions_stage AND 
                         mission.new_systemuserid = billing_without_missions_set.billing_without_missions_user LEFT OUTER JOIN
                             (SELECT        SUM(new_time) AS plan_by_grade_collumn, new_stageid AS plan_stage, new_grade AS plan_grade
                               FROM            Filterednew_mission AS mission
                               GROUP BY new_stageid, new_grade) AS plan_by_grade ON stage.new_stageid = plan_by_grade.plan_stage AND mission.new_grade = plan_by_grade.plan_grade LEFT OUTER JOIN
                             (SELECT        SUM(new_billed_time) AS fact_by_grade_collumn, new_stageid, new_grade
                               FROM            Filterednew_billing AS billing
                               GROUP BY new_stageid, new_grade) AS fact_by_grade ON stage.new_stageid = fact_by_grade.new_stageid AND mission.new_grade = fact_by_grade.new_grade
WHERE        (stage.new_stageid IN (@Stage,','))
GROUP BY stage.new_name, mission.new_gradename, mission.new_systemuseridname, mission.new_name, mission.new_time, billing_without_missions_set.billing_without_missions_time, 
                         plan_by_grade.plan_by_grade_collumn, fact_by_grade.fact_by_grade_collumn
ORDER BY stage_name, mission_grade, own_user_name
Параметр @Stage содержит список id, разделённых запятой. Мне надо каким-то образом разделить его. Думаю, что простейший способ - разбить через WITH

T-SQL
1
2
3
4
WITH id_list(stage_id) AS
(
   SELECT ...
)
И затем в запросе просто написать
T-SQL
1
stage.new_stageid IN (SELECT stage_id FROM id_list)
Кто-нибудь может подсказать, как сделать сплит в WITH?
Ответ:
Сообщение от Якобинец
В Гугле.
Ааа! В гугле! Как же я сразу не догадался!
Ну, не хотите говорить, - не говорите.
Собственно говоря, оно мне разве надо?!
Вопрос: Разбить строку на слова с условием длины слова

Добрый день.

Есть ли способ средствами языка mysql разбить результат SELECT str FROM table where id=1 на отдельные слова с условием длины слова (не учитывать слова короче 4-х символов)?
str - строка из трех-восьми слов разделенных пробелом.

Спасибо.
Ответ: Для выделения слова номер N можно использовать двукратный SUBCRTING_INDEX(). Именно для этого - подачи N - и требуется опорная таблица. Останется отсеять слова короче 4 символов, что тоже не самая сложная задача.
А почему бы не нормализовать исходные данные?
Вопрос: хеш строки

нужно создать короткий ключ для текстовых строк nvarchar(100)
придумал так: номер символа * его код.
допустим, строка: 'ABCD' получит такой хеш: 1*65 + 2*66 + 3*67 + 4*68 = 670

но не придумал, как сделать короткую формулу всего этого. В частности, возможно ли средствами СКЛя разбить строку на строки? Т.е. чтобы строка 'ABCD' стала:
A
B
C
D
Ответ: Тупо и прямолинейно пронумеруйте свои строки.
Можно identity.

И не парьте отсутствующий мозг.
Вопрос: Разбить строку на записи через CTE

declare @d varchar(max)='11111,22,333,'

;with v(i, period) as (
	select CHARINDEX(',',@d), SUBSTRING(@d, 0, CHARINDEX(',',@d)) 
	union all
	select CHARINDEX(',', @d, i+1),SUBSTRING(@d, i+1, CHARINDEX(',',@d)-1) 
	from v 
	where i>0
)
select * from v where i>0
Если через запятую перечислены строки одинаковой длины, то все в порядке, а если разной, то получается ерунда.
Не могу понять, где я налажал.
Ответ: Antonariy, вариант с CTE
;
declare @delim  char(1)
select @delim = ';'
declare @tbl table (id int, value varchar(max))
insert into @tbl values (1, '11111;22;333;')
insert into @tbl values (2, '345;567;5643;')
;
with tdelim   as 
( select 
    t.id
   ,v.number 
from master.dbo.spt_values v 
 , @tbl t
where  substring( t.value , v.number ,1) = @delim
and v.type='P' 
and v.number <= len(t.value) and v.number > 0 
--- + учитываем начало 
union select id, 0 from  @tbl  
),
  tdelim1   as 
( select 
    d.id
   ,d.number  as start
   ,min( d1.number  ) as finish
from tdelim  d
inner join tdelim  d1
  on d1.id =d.id
  and d.number < d1.number
group by     d.id  ,d.number   
 )
select d.id
 ,d.start 
 , d.finish 
 ,substring( t.value , d.start+1  ,  d.finish -  d.start -1  ) as word
from tdelim1 d
  inner join @tbl t
    on t.id =d.id
order by d.id
Вопрос: Парсинг строки, замена символа подстроки

Доброго времени суток.

Помогите решить задачу:

Необходимо разбить строку вида 1308,"18402,6",2,"1,29,35,87,99",3,1
на колонки по разделителю - ,
НО значения в " необходимо считать одним целым.

На выходе нужно получить:

1308 "18402,6" 2 "1,29,35,87,99" 3 1


Была идея вначале заменить все запятые в подстроке на ~, получилось бы 1308,"18402~6",2,"1~29~35~87~99",3,1, а затем по разделителю распарить - regexp_substr(str2, '[^,]+', 1, 1) , regexp_substr(str2, '[^,]+', 1, 2), .......

Буду очень признателен за помощь.
Ответ: AmKad,

Супер.


сделал вот так:

with s as (
select '1,,1308,"18402,6","","",2,"1,29,35,87,99",3,1' str from dual
union all select '3,"18402,6","","",2,"1,29,35,87,99",3,1' str from dual
)
select
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 1), ' ,'),'"','') col_1,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 2), ' ,'),'"','') col_2,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 3), ' ,'),'"','') col_3,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 4), ' ,'),'"','') col_4,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 5), ' ,'),'"','') col_5,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 6), ' ,'),'"','') col_6,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 7), ' ,'),'"','') col_7,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 8), ' ,'),'"','') col_8,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 9), ' ,'),'"','') col_9,
replace(ltrim(regexp_substr(replace(str,',,',',"",'),'("[^"]*"|(,| |^)[^", ]+)', 1, 10), ' ,'),'"','') col_10
from s;

Спасибо за регулярку, я что-то долго тупил над ней)
Вопрос: Алгоритм наибольшей общей последовательности двух строк

Доброе время суток.
Подскажите, пожалуйста, как улучшить алгоритм поиска наибольшей общей последовательности двух строк. Строки будут представлены двумя таблицными переменными, содеращими символ и его номер в строке. Сделал по алгоритму, найденному в интернете. Но, как то на мой взгляд, будет медленно.
То что нужно в примере создать индексы в таблицных переменных - понимаю, но всё же хотелось бы получить совет по изменению самого алгоритма. Заранее спасибо.
-- таблицы входных предложений, для примера
Declare @colWord As Table(colId int, [Char] nvarchar(1));
Declare @rowWord As Table(rowId int, [Char] nvarchar(1));
-- таблица позиций пересечений букв предложений
Declare @charIntersects As Table ([Char] nvarchar(1), rowId int, colId int, grlobalId int);
-- таблица связей позиций
Declare @ParentChild As Table (Parent_Id int, Child_Id int);
-- таблица результатов последовательностей
Declare @Sequences As Table (pathId int, Sentence nvarchar(1024), itemCount int);

-- Две исходные таблицы предложений
Insert Into @colWord Values
(1, N'ч'), (2, N'е'), (3, N'г'), (4, N'о'), (5, N'-'), (6, N'т'), (7, N'м'), (8, N' '), (9, N'в');
Insert Into @rowWord Values
(1, N'в'), (2, N' '), (3, N'м'), (4, N'о'), (5, N'т'), (6, N'ч'), (7, N'е'), (8, N'г'), (9, N'о');

-- заполняем таблицу позиций пересечений букв предложений
-- и присваиваем уникальный индекс каждой позиции
Insert Into @charIntersects
Select
       TR.[Char], TR.rowId, TC.colId,
       Row_Number() Over (Order By TR.rowId, TC.colId) As globalId
From
       @rowWord TR Inner Join @colWord TC On (TR.[Char] = TC.[Char]);

-- заполняем таблицу связей, считая предком позицию у которой
-- и строковый и столбцовый индекс одновременно меньше
-- строкового и столбцового индекса дочерних позиций
Insert Into @ParentChild
Select
       TParent.grlobalId As Parent_Id, TChild.grlobalId As Child_Id
From @charIntersects TParent Inner Join @charIntersects TChild
       On (TParent.rowId < TChild.rowId And TParent.colId < TChild.colId);

-- собираем последовательности
With Joiner (Link_Id, Sentence) As
(
       -- инициализируем начало последовательностей
       -- позициями, которые не имеют предков
       Select Child_Id, 
             Cast(TForP.[Char] + TForC.[Char] as nvarchar(1024))
       From @ParentChild TPC
             Inner Join @charIntersects TForP On (TPC.Parent_Id = TForP.grlobalId)
             Inner Join @charIntersects TForC On (TPC.Child_Id = TForC.grlobalId)
       Where Parent_Id Not In (Select Child_Id From @ParentChild)
       Union All -- дописываем в хвост буквы дочерних
       Select TChild.Child_Id,
             Cast(TParent.Sentence + TForC.[Char] as nvarchar(1024))
       From @ParentChild TChild 
             Inner Join Joiner TParent On (TChild.Parent_Id = TParent.Link_Id)
             Inner Join @charIntersects TForC On (TForC.grlobalId = TChild.Child_Id)
) -- записываем в результат, нумеруя - меньший номер у наибольшей по длине последовательности
Insert Into @Sequences
Select Row_Number() Over (Order By Len(Sentence) Desc),
       Sentence, Len(Sentence) From Joiner;

-- выводим первый результат
Select Sentence From @Sequences T Where pathId = 1;
Ответ: Доброе время суток.
кролик-зануда
а то в одном сообщении вы добавляете односимвольные совпадения, в другом - игнорируете "!"

Потому что рассматриваются два алгоритма.
1-ый – наибольшая общая последовательность символов (LCS). Символы в строках идут в том же порядке в обеих строках, но индексное расстояние между символами произвольное.
2-ой – наибольшая общая подстрока. Здесь более жёсткое ограничение – расстояние между соседними символами должно быть равно 1.
В первом приближении задачу можно описать так. Имеется эталонная строка. Её «повредили»: 5-10% символов случайным образом заменили, 5-10% удалили. Разбили на 3-4 приблизительно равные части и их случайным образом переставили.
На входе несколько таких «повреждённых» строк. Требуется определить, какая из них исходная эталонная строка.
Предварительная гипотеза: найти все LCS между «повреждённой» и «эталонной» строками и по нему определить меру сходства.
Несколько переделал алгоритм поиска LCS первого сообщения.
Declare @MaxRow int, @curRow int = 1;
-- таблицы входных предложений, для примера
Declare @colSentence As Table(colId int, [Char] nvarchar(1));
Declare @rowSentence As Table(rowId int, [Char] nvarchar(1));
-- таблица позиций пересечений букв предложений
Declare @charIntersect As Table ([Char] nvarchar(1), colId int, rankRow int, Index idx_rank (rankRow));
-- таблица результатов последовательностей
Declare @Sequences As Table 
	([Sequence] nvarchar(1024), charCount int, rankRow int, colId int,
	 Primary Key (rankRow Desc, colId Desc), Index idx_needed (charCount Desc, rankRow Desc, colId Desc));

-- Две исходные таблицы предложений
Insert Into @colSentence Values
(1, N'_'), (2, N'a'), (3, N'b'), (4, N'_'), (5, N'c'), (6, N'_'), (7, N'd'), (8, N'e'), (9, N'e');
Insert Into @rowSentence Values
(1, N'd'), (2, N'a'), (3, N'e'), (4, N' '), (5, N'b'), (6, N' '), (7, N'c'), (8, N'd'), (9, N'e');

-- заполняем таблицу позиций пересечений букв предложений, создавая сквозную нумерацию 
-- строк (какие-то символы могу и не совпадать и существующий номер будет пропущен).
Insert Into @charIntersect
Select
       TR.[Char], TC.colId,
	   Dense_Rank() Over (Order By TR.rowId) As rankId
From @rowSentence TR Inner Join @colSentence TC On (TR.[Char] = TC.[Char]);

-- Формируем опорный элемент
Insert Into @Sequences Values (N'', 0, 0, 0);
-- число строк
Select @MaxRow = Max(rankRow) From @charIntersect;
-- выбираем для каждого символа строки начальную последовательность
-- максимальной длины (по возможности ближайшую по строке и столбцу)
While (@curRow <= @MaxRow)
begin
	Insert Into @Sequences
	Select
		(Select Top(1) TSec.[Sequence] From @Sequences TSec 
		 Where TChar.rankRow > TSec.rankRow And TChar.colId > TSec.colId
		 Order By TSec.charCount Desc, TSec.rankRow Desc, TSec.colId Desc
		) + TChar.[Char] As [Sequence],
		(Select Top(1) TSec.[charCount] From @Sequences TSec 
		 Where TChar.rankRow > TSec.rankRow And TChar.colId > TSec.colId
		 Order By TSec.charCount Desc, TSec.rankRow Desc, TSec.colId Desc
		) + 1 As [charCount],
		TChar.rankRow, TChar.colId
	From @charIntersect TChar 
	Where TChar.rankRow = @curRow
	Set @curRow = @curRow + 1;
end;
Select Top(1) * From @Sequences Order By charCount Desc;

Подскажите, пожалуйста, насколько правильно настроил индексы для табличных переменных? Интересует, будет ли в цикле while поиск и вставка в @Sequences выполняться за Log(N)? И как лучше это сделать? И можно ли избавиться от while (цикл над таблицами для выборки вставки выглядит не совсем правильно с точки зрения концепции баз данных)?
По идее должно по скорости выполняться в лучшем случае (все символы в строках уникальные и строки равны) – за N*Log(N), а в худшем (в обеих строках один и тот же символ) – за N^2*Log(N). В первой реализации было. Лучшее – N^2, худшее – за N^3.
Для поиска всех последовательностей, похоже, придётся делать внешний цикл, для поиска LCS замены найденных символов на заведомо несуществующий, и поиск новой LCS, пока не получим пустую. Похоже сложность будет N^2*Log(N).