Изменение строкового значения с разделителями в запросе выбора sql

У меня есть столбец со строковым значением, имеющим разделители, как показано ниже, которые я использую в части выбора sql-запроса.

0040~0040~0040~0040~0040^00~00~00~01~05^100~001~010~011~015^00~00~00~01~05

отдельные головки разделены с помощью '^', как показано ниже.

Head1 = 0040~0040~0040~0040~0040

Head2 = 00~00~00~01~05

Head3 = 100~001~010~011~015

Head4 = 00~00~00~01~05

все 4 головы имеют одинаковое количество записей, разделенных '~' (записей не обязательно 5).

Мне нужно объединить первую запись из всех 4 голов в одну. Как показано ниже.

0040-00-100-00 с разделителями '-'

затем для второй записи и так далее для всех записей.

если количество записей равно 4, как и моя строка, отформатированный вывод должен выглядеть так (каждая запись разделена запятой):

0040-00-100-00,0040-00-001-00,0040-00-010-00,0040-01-011-01,
0040-05-015-05

Я хочу сделать это в запросе выбора, где я получаю значение этого столбца.

пример..

select x,y,z,(this is where I want this changes to be done.) from abc

person iamP    schedule 28.04.2016    source источник
comment
Есть ли в ваших строках столбец идентификатора?   -  person artm    schedule 28.04.2016
comment
да. Но это не требуется для решения этой проблемы.   -  person iamP    schedule 29.04.2016


Ответы (2)


Первый CTE — это просто выбор для создания фиктивных записей, вы можете игнорировать это. 2-й CTE является рекурсивным для разделения значений столбца на ^ для каждого идентификатора, 3-й CTE — для объединения разделов, разделенных -, с использованием XML для каждого идентификатора, последний CTE — для объединения всех их в одну строку, разделенную ,. Должен был быть столбец идентификатора для группировки, прежде чем объединять все. Он будет работать с n количеством строк.

;WITH myTable (id, MyColumn)
AS (SELECT 1, '0040~0040~0040~0040^00~00~00~01~05^100~001~010~011~015^00~00~00~01~05'
union ALL SELECT 2, 'b040~0040~0040~0040^b0~00~00~01~05^b00~001~010~011~015^b02~00~00~01~05'
union ALL SELECT 3, 'c040~0040~0040~0040^c0~00~00~01~05^c00~001~010~011~015^c02~00~00~01~05'
union ALL SELECT 4, 'd040~0040~0040~0040^d0~00~00~01~05^d00~001~010~011~015^d02~00~00~01~05'
),
RecursiveCTE (id, sub, startInd, endInd)
AS
(
    SELECT 
        id, 
        convert(varchar, SUBSTRING(MyColumn, 1, CHARINDEX('^', MyColumn ))) as sub
        , 0 AS startInd
        , CHARINDEX('^', MyColumn ) AS endInd
        from mytable
    UNION ALL
        select id,
        convert(varchar, SUBSTRING((SELECT MyColumn FROM myTable WHERE id = dr.id), endInd + 1, endInd - startInd)),
        endInd,
        CHARINDEX('^',  (SELECT MyColumn FROM myTable WHERE id = dr.id), endInd + 1) as endInd
    from RecursiveCTE dr
        where dr.endInd <> 0
),
PerIDSeperatedByDash AS (
select id,
STUFF((
select '-' + SUBSTRING(drinner.sub, 1, CHARINDEX('~', drinner.sub) - 1)
from RecursiveCTE drinner
WHERE (drinner.id = drouter.id)
FOR XML PATH ('')),1,1,'') AS DashSeperated
from RecursiveCTE drouter
group by id)
SELECT DISTINCT STUFF((
SELECT ',' + ds.DashSeperated
FROM PerIDSeperatedByDash ds
ORDER BY id
FOR XML PATH ('')),1,1,'') 
from PerIDSeperatedByDash 
person artm    schedule 29.04.2016
comment
Спасибо за ответ. Могу ли я использовать его в формате выбора, о котором я упоминал, просто каким-то образом заменив последний параметр выбора вашим кодом? - person iamP; 29.04.2016
comment
@iamP Я не думаю, что вы можете использовать CTE в качестве внутреннего выбора. Почему это должно быть в формате select * from (query here) ? - person artm; 29.04.2016

Проблема, которую вы представили, не является легко решаемой, ограничивая себя внутренней частью оператора select. Хотя вы сказали, что нет, но если бы вы знали, сколько «головок» будет в каждой строке, вы могли бы настроить один, сложный и очень длинный оператор выбора (с вашим «строковым значением, имеющим разделители», называемым фубар):

SELECT x,y,z, SUBSTR(foobar, 1, INSTR(foobar, '~') - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^') + 1, INSTR(foobar, '~', INSTR(foobar, '^')) - INSTR(foobar, '^') - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 2) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 2)) - INSTR(foobar, '^', 1, 2) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 3) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 3)) - INSTR(foobar, '^', 1, 3) - 1) || ',' || SUBSTR(foobar, INSTR(foobar, '~') + 1, INSTR(foobar, '~', 1, 2) - INSTR(foobar, '~', 1, 1) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '~', INSTR(foobar, '^', 1, 1)) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 1)) - INSTR(foobar, '^', 1, 1) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '~', INSTR(foobar, '^', 1, 2)) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 2)) - INSTR(foobar, '^', 1, 2) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '~', INSTR(foobar, '^', 1, 3)) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 3)) - INSTR(foobar, '^', 1, 3) - 1) 
FROM abc

И это получает только первые две «записи», однако он получит каждую «голову», независимо от длины, и его можно расширить, чтобы включить больше записей, но это не сработало бы, если бы было только две записи, а оператор выбора был расширен для пяти записей, что нецелесообразно, поскольку вы сказали, что в строке может быть любое количество записей. Есть способ добиться этого с помощью оператора CASE, но он очень длинный и очень сложный. Вот пример запроса, который может быть тем, что вы ищете:

SELECT x, y, z,
  CASE (LENGTH(SUBSTR(foobar, 1, INSTR(foobar, '^')))-LENGTH(REPLACE(SUBSTR(foobar, 1, INSTR(foobar, '^')), '~', '')) + 1)
    WHEN 1 THEN SUBSTR(foobar, 1, INSTR(foobar, '^') - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 1) + 1, INSTR(foobar, '^', 1, 2) - INSTR(foobar, '^', 1, 1) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 2) + 1, INSTR(foobar, '^', 1, 3) - INSTR(foobar, '^', 1, 2) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 3) + 1, LENGTH(foobar) - INSTR(foobar, '^', 1, 3))
    WHEN 2 THEN SUBSTR(foobar, 1, INSTR(foobar, '~') - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^') + 1, INSTR(foobar, '~', INSTR(foobar, '^')) - INSTR(foobar, '^') - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 2) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 2)) - INSTR(foobar, '^', 1, 2) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '^', 1, 3) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 3)) - INSTR(foobar, '^', 1, 3) - 1) || ',' || SUBSTR(foobar, INSTR(foobar, '~') + 1, INSTR(foobar, '^', 1, 1) - INSTR(foobar, '~', 1, 1) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '~', INSTR(foobar, '^', 1, 1)) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 1), 1) - INSTR(foobar, '^', 1, 1) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '~', INSTR(foobar, '^', 1, 2)) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 2)) - INSTR(foobar, '^', 1, 2) - 1) || '-' || SUBSTR(foobar, INSTR(foobar, '~', INSTR(foobar, '^', 1, 3)) + 1, INSTR(foobar, '~', INSTR(foobar, '^', 1, 3)) - INSTR(foobar, '^', 1, 3) - 1) 
    ELSE 'Too many entries: ' || (LENGTH(SUBSTR(foobar, 1, INSTR(foobar, '^')))-LENGTH(REPLACE(SUBSTR(foobar, 1, INSTR(foobar, '^')), '~', '')) + 1) || ' entries given of 2 maximum'
  END AS SAMPLE_OUTPUT
FROM abc;

Приведенный выше запрос (если его расширить до двух записей) упорядочит ваши «головы». Он очень длинный и сложный, но если вы знаете, что длина каждого заголовка будет постоянной (например, всегда иметь длину 4 на запись в «Head1»), тогда код можно сократить, с дополнительным преимуществом более быстрого выполнения. , если значения жестко запрограммированы без INSTR. В приведенном ниже примере предполагается, что длина головы равна 4, 2, 3 и 2, как в ваших примерах данных:

SELECT x, y, z,
  CASE (LENGTH(SUBSTR(foobar, 1, INSTR(foobar, '^')))-LENGTH(REPLACE(SUBSTR(foobar, 1, INSTR(foobar, '^')), '~', '')) + 1)
    WHEN 1 THEN REPLACE(foobar, '^', '-')
    WHEN 2 THEN SUBSTR(foobar, 1, 4) || '-' || SUBSTR(foobar, 11, 2) || '-' || SUBSTR(foobar, 17, 3) || '-' || SUBSTR(foobar, 25, 2) || ',' || SUBSTR(foobar, 6, 4) || '-' || SUBSTR(foobar, 14, 2) || '-' || SUBSTR(foobar, 21, 3) || '-' || SUBSTR(foobar, 28, 2) 
    WHEN 3 THEN SUBSTR(foobar, 1, 4) || '-' || SUBSTR(foobar, 16, 2) || '-' || SUBSTR(foobar, 25, 3) || '-' || SUBSTR(foobar, 37, 2) || ',' || SUBSTR(foobar, 6, 4) || '-' || SUBSTR(foobar, 19, 2) || '-' || SUBSTR(foobar, 29, 3) || '-' || SUBSTR(foobar, 40, 2) || ',' || SUBSTR(foobar, 11, 4) || '-' || SUBSTR(foobar, 22, 2) || '-' || SUBSTR(foobar, 33, 3) || '-' || SUBSTR(foobar, 43, 2)
    ELSE 'Too many entries: ' || (LENGTH(SUBSTR(foobar, 1, INSTR(foobar, '^')))-LENGTH(REPLACE(SUBSTR(foobar, 1, INSTR(foobar, '^')), '~', '')) + 1) || ' entries given of 3 maximum'
  END AS SAMPLE_OUTPUT
FROM abc;

И последнее замечание: я рекомендую использовать псевдоним столбца для оператора case по причинам, которые станут очевидными, если вы попытаетесь протестировать код без него.

person Scott Mikutsky    schedule 29.04.2016
comment
Да, это отлично работает, если есть 2 записи. Поскольку длина моих записей не фиксирована, мне придется найти другой способ решить эту проблему. Спасибо за ваш ответ. :) - person iamP; 29.04.2016
comment
@iamP Второй запрос можно расширить, чтобы вместить больше записей. Пример выше поддерживает только две записи, но можно добавить больше. Если хотите, я расширим запрос, чтобы он поддерживал больше записей. - person Scott Mikutsky; 29.04.2016
comment
Записи могут принимать любое количество. Я предполагаю, что это будет намного длиннее, когда число будет 5+ или даже 4+. ваш код намного легче понять. Есть ли способ, которым я могу просто каким-то образом повторить случай 1 для любого количества записей, чтобы сделать его коротким? - person iamP; 29.04.2016
comment
@iamP Если длина записей в каждой голове постоянна (например, это всегда 4 числа, как в Head1), то запрос можно существенно сократить и, вероятно, он будет выполняться быстрее, потому что вместо включения всех функций INSTR в оператор CASE, вы можете жестко закодировать значения после определения количества записей. Я добавлю пример этого в свой ответ. - person Scott Mikutsky; 29.04.2016