SQL-сервер, как изменить строку столбца с разделенными трубами на строки - PullRequest
0 голосов
/ 13 июня 2018

У меня есть 3 столбца в таблице, в которых 2 столбца имеют строку, разделенную '|'труба.Оба эти значения столбцов зависят друг от друга.

Например: у меня есть данные в таблице:

ID  product quantity
1   A|B|C   1|2|3
2   X|Y|Z   7|8|9

Я хотел бы изменить их на что-то вроде этого:

ID  product quantity
1   A   1
1   B   2
1   C   3
2   X   7
2   Y   8
2   Z   9

Как яя работаю с SSMS, у меня нет другого выбора, кроме SQL.Я пытаюсь использовать кросс-аппликацию, но я не получаю правильный результат.Для 1 строки я получаю 9 строк вместо 3. Может ли кто-нибудь предложить мне, какой метод я должен использовать?

Заранее спасибо !!JACK

Ответы [ 3 ]

0 голосов
/ 13 июня 2018

Данные испытаний

CREATE TABLE #t (ID INT,  product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #t VALUES 
(1   ,'A|B|C' ,  '1|2|3'),
(2   ,'X|Y|Z' ,  '7|8|9');

Запрос

WITH Products AS (
        SELECT    ID
                , Product_Split.a.value('.', 'VARCHAR(100)') Products
                , ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
        FROM (
              SELECT  ID
                     ,Cast ('<X>' 
                           + Replace(product, '|', '</X><X>') 
                           + '</X>' AS XML) AS Product_Data
                FROM #t
            ) AS t 
        CROSS APPLY Product_Data.nodes ('/X') AS Product_Split(a) 
),
 Quantities AS (
        SELECT    ID
                , Quantity_Split.a.value('.', 'VARCHAR(100)') Quantity
                , ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
        FROM (
                SELECT  ID
                        ,Cast ('<X>' 
                            + Replace(quantity, '|', '</X><X>') 
                            + '</X>' AS XML) AS Quantity_Data
                FROM #t
            ) AS t 
        CROSS APPLY Quantity_Data.nodes ('/X') AS Quantity_Split(a)
 )
 SELECT   t.ID
        , P.Products
        , Q.Quantity

 FROM #t t
 LEFT JOIN Products     P   ON t.ID = p.ID
 LEFT JOIN Quantities   Q   ON Q.ID = t.ID 
                            AND Q.rn = p.rn

Результирующий набор

╔════╦══════════╦══════════╗
║ ID ║ Products ║ Quantity ║
╠════╬══════════╬══════════╣
║  1 ║ A        ║        1 ║
║  1 ║ B        ║        2 ║
║  1 ║ C        ║        3 ║
║  2 ║ X        ║        7 ║
║  2 ║ Y        ║        8 ║
║  2 ║ Z        ║        9 ║
╚════╩══════════╩══════════╝
0 голосов
/ 13 июня 2018

расщепление строк легко, есть множество примеров.Самое сложное здесь - соединить фрагменты через их положение.Мое предложение использует возможности XML для нацеливания на элемент по его позиции:

DECLARE @tbl TABLE(ID INT,  product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO @tbl VALUES 
 (1   ,'A|B|C' ,  '1|2|3')
,(2   ,'X|Y|Z' ,  '7|8|9');

- это запрос

WITH CastedToXML AS
(
    SELECT *
         ,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
         ,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
    FROM @tbl
)
SELECT *
      ,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
      ,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int')) 
             ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) 
             FROM master..spt_values) AS Tally(Nmbr);

результат

+----+------+-------------------+--------------------+
| ID | Nmbr | ProductAtPosition | QuantityAtPosition |
+----+------+-------------------+--------------------+
| 1  | 1    | A                 | 1                  |
+----+------+-------------------+--------------------+
| 1  | 2    | B                 | 2                  |
+----+------+-------------------+--------------------+
| 1  | 3    | C                 | 3                  |
+----+------+-------------------+--------------------+
| 2  | 1    | X                 | 7                  |
+----+------+-------------------+--------------------+
| 2  | 2    | Y                 | 8                  |
+----+------+-------------------+--------------------+
| 2  | 3    | Z                 | 9                  |
+----+------+-------------------+--------------------+

Некоторое объяснение:

приведение к xml переводит ваш A|B|C в

<x>A</x>
<x>B</x>
<x>C</x>

Этот список объединяется с набором номеров, созданным на лету с использованием счетчика <x> как предел TOP.

Теперь легко выбрать <x> из вашего XML по позиции.

Попробуйте!

ОБНОВЛЕНИЕ:Неуникальные идентификаторы

DECLARE @tbl TABLE(ID INT,  product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO @tbl VALUES 
 (1   ,'A|B|C' ,  '1|2|3')
,(2   ,'X|Y|Z' ,  '7|8|9')
,(3   ,'a|b|c' ,  '7|8|9')
,(2   ,'D|e|f' ,  '7|8|9')
;
--This is the query

WITH CastedToXML AS
(
    SELECT *
         ,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RowIndex
         ,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
         ,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
    FROM @tbl
)
SELECT *
      ,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
      ,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int')) 
             ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) 
             FROM master..spt_values) AS Tally(Nmbr);
0 голосов
/ 13 июня 2018

Это довольно сложно, потому что вам нужно, чтобы значения совпадали.Следующее использует рекурсивный подход CTE:

with cte as (
      select id,
             left(product, charindex('|', product + '|') - 1) as product,
             left(quantity, charindex('|', quantity + '|') - 1) as quantity,
             substring(product, charindex('|', product + '|') + 1, 1000) as products,
             substring(quantity, charindex('|', quantity + '|') + 1, 1000) as quantities
     from t
     union all
      select id,
             left(products, charindex('|', products + '|') - 1) as product,
             left(quantities, charindex('|', quantities + '|') - 1) as quantity,
             substring(products, charindex('|', products + '|') + 1, 1000) as products,
             substring(quantities, charindex('|', quantities + '|') + 1, 1000) as quantities
     from cte
     where products <> '' and quantities <> ''
    )
select id, product, quantity
from cte;

Вот немного Rextester .

...