SQL LEFT JOIN 到许多类别

SQL LEFT JOIN to many categories

假设以下简单 scenario,其中产品行连接到一个主要类别、子类别和子类别。

DECLARE @PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));

INSERT @PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1', '10', '100'),
(2, 'NIKE CORTEZ', '1', '12', '104'),
(3, 'ADIDAS PANTS', '2', '27', '238'),
(4, 'PUMA REVOLUTION 5', '3', '35', '374'),
(5, 'SALOMON SHELTER CS', '4', '15', '135'),
(6, 'NIKE EBERNON LOW', '2', '14', '157');

DECLARE @CATS TABLE (ID int, DESCR varchar(100));

INSERT @CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');

DECLARE @SUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');

DECLARE @SUBSUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');


SELECT prod.ID,
    prod.DESCRIPTION,
    CONCAT(cat1.DESCR, ' > ', cat2.DESCR, ' > ', cat3.DESCR) AS CATEGORIES
FROM @PRODUCTS AS prod
LEFT JOIN @CATS AS cat1 ON cat1.ID = prod.CAT
LEFT JOIN @SUBCATS AS cat2 ON cat2.ID = prod.SUBCAT
LEFT JOIN @SUBSUBCATS AS cat3 ON cat3.ID = prod.SUBSUBCAT;

现在假设 @PRODUCTS table 上的外键不仅仅是它们各自 table 的索引。它们是多个类别、子类别和子类别的逗号分隔索引,例如 here.

DECLARE @PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));

INSERT @PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
(2, 'NIKE CORTEZ', '1, 5', '12, 15', '104, 374'),
(3, 'ADIDAS PANTS', '2, 6', '27, 35', '238, 374');

DECLARE @CATS TABLE (ID int, DESCR varchar(100));

INSERT @CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');

DECLARE @SUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');

DECLARE @SUBSUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');


SELECT prod.ID,
    prod.DESCRIPTION
    --CONCAT(cat1.DESCR, ' > ', cat2.DESCR, ' > ', cat3.DESCR) AS CATEGORIES
FROM @PRODUCTS AS prod
--LEFT JOIN @CATS AS cat1 ON cat1.ID = prod.CAT
--LEFT JOIN @SUBCATS AS cat2 ON cat2.ID = prod.SUBCAT
--LEFT JOIN @SUBSUBCATS AS cat3 ON cat3.ID = prod.SUBSUBCAT;

在这种情况下,我想实现以下目标:

  1. 能够检索猫、子猫、子子猫的各自名称,即。对于猫 '1, 2' 能够检索它们的名字(我试过 LEFT JOIN @CATS AS cat1 ON cat1.ID IN prod.CAT 但它不起作用)
  2. 创建相应猫、子猫、子子猫的三胞胎,即。对于

(检索到适当的名称后)创建管道分隔的类别 routes like name of cat 1 > name of subcat 12 > name of sub-subcat 239 | name of cat 2 > name of subcat 17 > name of sub-subcat 372

所以,对于 (1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),

这样的行

我想得到以下结果

ID DESCRIPTION CATEGORIES
1 NIKE MILLENIUM MEN > FOOTWEAR > RUNNING @ WOMEN > OUTERWEAR > FLEECE (I had to use @ as the delimiter of the two triplets because pipe messed with the table's columns)

如果用户愚蠢地存储了比子猫 ID 或子子猫 ID 多的猫 ID,查询应该只匹配具有相应位置匹配的猫 ID,即 for

它应该只创建一个三元组,例如 name of 1 > name of 12 > name of 239

STRING_SPLIT() 不承诺 return 特定顺序的值,因此在这种情况下它不起作用,因为顺序位置很重要。

使用 OPENJSON() 将字符串拆分为单独的行以确保值 return 以相同的顺序编辑。
OPENJSON() 也 return 是一个 key 字段,因此您可以在每个分组中加入行号。您需要一个 INNER JOIN,因为您的要求是该“列”中的所有值都必须存在。
使用 STUFF() 到 assemble 各种 cat>subcat>subsubcat 值。

DECLARE @PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));

INSERT @PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
(2, 'NIKE CORTEZ', '1, 5', '12, 15', '104, 374'),
(3, 'ADIDAS PANTS', '2, 6, 1', '27, 35, 10', '238, 374, 100'),
(4, 'JOE THE PLUMBER JEANS', '1, 5', '27', '238, 374');

DECLARE @CATS TABLE (ID int, DESCR varchar(100));

INSERT @CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');

DECLARE @SUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');

DECLARE @SUBSUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');

;
with prod as (
    SELECT p.ID,
        p.DESCRIPTION
        --CONCAT(cat1.DESCR, ' > ', cat2.DESCR, ' > ', cat3.DESCR) AS CATEGORIES
        , c.value as CatId
        , c.[key] as CatKey
        , sc.value as SubCatId
        , sc.[key] as SubCatKey
        , ssc.value as SubSubCatId
        , ssc.[key] as SubSubCatKey
    FROM @PRODUCTS p
      cross apply OPENJSON(CONCAT('["', REPLACE(cat, ', ', '","'), '"]')) c
      cross apply OPENJSON(CONCAT('["', REPLACE(subcat, ', ', '","'), '"]')) sc
      cross apply OPENJSON(CONCAT('["', REPLACE(subsubcat, ', ', '","'), '"]')) ssc
    where c.[key] = sc.[key]
      and c.[key] = ssc.[key]
)
, a as (
    select p.ID
    , p.DESCRIPTION
    , c.DESCR + ' > ' + sc.DESCR + ' > ' + ssc.DESCR as CATEGORIES
    , p.CatKey
    from prod p
      inner join @CATS c on c.ID = p.CatId
      inner join @SUBCATS sc on sc.ID = p.SubCatId
      inner join @SUBSUBCATS ssc on ssc.ID = p.SubSubCatId
)

select DISTINCT ID
, DESCRIPTION
, replace(STUFF((SELECT distinct ' | ' + a2.CATEGORIES
            from a a2
            where a.ID = a2.ID
            FOR XML PATH(''))
        ,1,2,''), '>', '>') CATEGORIES
from a

那应该行得通,我把你的字符“>”换成“-”只是为了让数据更简单。

您的表格设计并不完美,但第一次尝试几乎不会完美。

select mainp.ID, mainp.DESCRIPTION, stuff(ppaths.metapaths, len(ppaths.metapaths),1,'') metalinks
from @PRODUCTS mainp
cross apply(
select
(select 
  c.DESCR + '-' + sc.DESCR + '-' + sbc.DESCR + '|'
from @PRODUCTS p    
    cross apply (select row_number() over(order by Value) id, Value from split(p.CAT, ','))cat_ids
    inner join @cats c on c.ID = cat_ids.Value
    cross apply (select row_number() over(order by Value) id, Value from split(p.SUBCAT, ','))subcat_ids
    inner join @SUBCATS sc on sc.ID = subcat_ids.Value
    and subcat_ids.id = subcat_ids.id
    cross apply (select row_number() over(order by Value) id, Value  from split(p.SUBSUBCAT, ','))subsubcat_ids
    inner join @SUBSUBCATS sbc on sbc.ID = subsubcat_ids.Value
    and subsubcat_ids.id = subcat_ids.id
where p.id = mainp.ID
for xml path('')) metapaths
) ppaths

link 拆分功能 https://desarrolladores.me/2014/03/sql-server-funcion-split-para-dividir-un-string/

由于对旧技术的更改,答案完全不同。我认为我原来的答案对使用当前 SQL 服务器版本的人来说仍然很好,所以我不想删除它。

我不记得我从哪里得到的函数。当我今天找到它时,它被命名为 split_delimiter。我更改了名称,添加了一些注释,并合并了一个长度超过一个字符的分隔符的功能。

CREATE FUNCTION [dbo].[udf_split_string](@delimited_string VARCHAR(8000), @delimiter varchar(10))
RETURNS TABLE AS
RETURN
WITH cte10(num) AS (    --  10 rows
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)                         
, cte100(num) AS (  --  100 rows
    SELECT 1 
    FROM   cte10 t1, cte10 t2
)
, cte10000(num) AS (    --  10000 rows
    SELECT 1 
    FROM   cte100 t1, cte100 t2
)
, cte1(num) AS (    --  1 row per character
    SELECT TOP (ISNULL(DATALENGTH(@delimited_string), 0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) 
    FROM   cte10000
)
, cte2(num) AS (    --  locations of strings
    SELECT  1
    UNION ALL
    SELECT t.num + len(replace(@delimiter, ' ', '_'))
    FROM   cte1 t
    WHERE  SUBSTRING(@delimited_string, t.num, len(replace(@delimiter, ' ', '_'))) = @delimiter
)
, cte3(num, [len]) AS (
    SELECT t.num
         , ISNULL(NULLIF(CHARINDEX(@delimiter, @delimited_string, t.num), 0) - t.num, 8000)
    FROM  cte2 t
)

SELECT [Key]   = ROW_NUMBER() OVER (ORDER BY t.num)
     , [Value] = SUBSTRING(@delimited_string, t.num, t.[len])
FROM cte3 t;
GO



DECLARE @PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));

INSERT @PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
(2, 'NIKE CORTEZ', '1, 5', '12, 15', '104, 374'),
(3, 'ADIDAS PANTS', '2, 6, 1', '27, 35, 10', '238, 374, 100'),
(4, 'JOE THE PLUMBER JEANS', '1, 5', '27', '238, 374');

DECLARE @CATS TABLE (ID int, DESCR varchar(100));

INSERT @CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');

DECLARE @SUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');

DECLARE @SUBSUBCATS TABLE (ID int, DESCR varchar(100));

INSERT @SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');

;
with prod as (
    SELECT p.ID,
        p.DESCRIPTION
        , c.value as CatId
        , c.[key] as CatKey
        , sc.value as SubCatId
        , sc.[key] as SubCatKey
        , ssc.value as SubSubCatId
        , ssc.[key] as SubSubCatKey
    FROM @PRODUCTS p
      cross apply dbo.udf_split_string(cat, ', ') c
      cross apply dbo.udf_split_string(subcat, ', ') sc
      cross apply dbo.udf_split_string(subsubcat, ', ') ssc
    where c.[key] = sc.[key]
      and c.[key] = ssc.[key]
)
, a as (
    select p.ID
    , p.DESCRIPTION
    , c.DESCR + ' > ' + sc.DESCR + ' > ' + ssc.DESCR as CATEGORIES
    , p.CatKey
    from prod p
      inner join @CATS c on c.ID = p.CatId
      inner join @SUBCATS sc on sc.ID = p.SubCatId
      inner join @SUBSUBCATS ssc on ssc.ID = p.SubSubCatId
)

select DISTINCT ID
, DESCRIPTION
, replace(STUFF((SELECT distinct ' | ' + a2.CATEGORIES
            from a a2
            where a.ID = a2.ID
            FOR XML PATH(''))
        ,1,2,''), '>', '>') CATEGORIES
from a