3

我有两个表:MyOrders 和 MyDrivers。

在 MyOrders 表中,我有一个名为 Details 的列(数据类型 TEXT - 我知道,但我没有构建数据库......)

在 MyOrders.Details 中,有时会有逗号分隔的数值列表,这些数值对应于表 MyDrivers 的 ID 值。

目标是使用这些列表将 MyOrders 加入 MyDrivers。

例如:

CREATE TABLE MyOrders 
(
MyOrderID INT IDENTITY,
Details TEXT -- Wish it were NVarchar, but what can I do...
)
GO

CREATE TABLE MyDrivers
(
MyDriverID INT IDENTITY,
DriverName NVARCHAR(50)
)
GO

INSERT INTO MyOrders (Details) VALUES ('1,3,5,7,9')
INSERT INTO MyOrders (Details) VALUES ('2,4,6,8')
INSERT INTO MyOrders (Details) VALUES ('1,2,3,4')
INSERT INTO MyOrders (Details) VALUES ('4,5,6,7,8')
INSERT INTO MyOrders (Details) VALUES (NULL)
INSERT INTO MyOrders (Details) VALUES ('')
INSERT INTO MyOrders (Details) VALUES ('9')

INSERT INTO MyDrivers (DriverName) VALUES ('Alex')
INSERT INTO MyDrivers (DriverName) VALUES ('Bobby')
INSERT INTO MyDrivers (DriverName) VALUES ('Carl')
INSERT INTO MyDrivers (DriverName) VALUES ('Daryl')
INSERT INTO MyDrivers (DriverName) VALUES ('Ed')
INSERT INTO MyDrivers (DriverName) VALUES ('Frank')
INSERT INTO MyDrivers (DriverName) VALUES ('George')
INSERT INTO MyDrivers (DriverName) VALUES ('Hal')
INSERT INTO MyDrivers (DriverName) VALUES ('Ichabod')
INSERT INTO MyDrivers (DriverName) VALUES ('Justin Timberlake')

SELECT * FROM MyOrders O
INNER JOIN MyDrivers D
    ON D.MyDriverID = ...? substring()? patindex()?
WHERE O.MyOrderID = 1

此处所需的结果是,对于 MyOrderID 1,我将收到 5 行结果:在同一订单的详细信息列表中分配给该订单的五个驱动程序中的每一个都有一个。如果没有列表 (NULL, '', ' ', ' ') 那么我不希望返回任何行。**有时用户会删除此字段中的值并留下空格,所以我假设我必须使用 TRIM。但他们总是添加必要的逗号,所以至少有那个......

我不知道该怎么做;我在 SQL 方面还有很多东西要学。任何有用的提示/想法将不胜感激。

非常感谢您!

4

2 回答 2

2

你可以IN这样使用:

SELECT * 
FROM MyOrders O
INNER JOIN MyDrivers D
ON ',' + CAST(D.MyDriverID as varchar) +',' IN(','+ ISNULL(O.Details, '')  +',')
WHERE O.MyOrderID = 1

更新
实际上,你不能使用IN,但你可以使用LIKE. 这样做的原因是IN需要一个值列表,而不是用逗号分隔的单个字符串值。

SELECT MyOrderID, MyDriverID, DriverName
FROM MyOrders O
INNER JOIN MyDrivers D
ON ','+ cast(ISNULL(O.Details, '') as varchar(max))  +',' LIKE 
   '%,' + CAST(D.MyDriverID as varchar) +',%'
WHERE O.MyOrderID = 1

使用wewestthemenace他的回答中提供的小提琴,我已经测试了他建议的解决方案(拆分字符串)与我建议的解决方案(如)的性能。对于您的样本数据,使用like 似乎要快得多(不到一半的时间)(如果数据不同,结果可能会不同)。您可以在此链接中自行检查。

如果可能,我强烈建议更改数据库结构并创建另一个表来保存当前存储在 Details 列中的值。

于 2015-05-27T14:19:37.900 回答
2

首先,您需要一个拆分器函数来拆分逗号分隔的值。这是DelmitedSplitN4KJeff Moden 为目前最快的分离器之一编写的函数。阅读本文了解更多信息。

CREATE FUNCTION [dbo].[DelimitedSplitN4K](
    @pString NVARCHAR(4000), 
    @pDelimiter NCHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN

WITH E1(N) AS (
    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
),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
cteTally(N) AS(
    SELECT TOP (ISNULL(DATALENGTH(@pString)/2,0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (
    SELECT 1 UNION ALL 
    SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
),
cteLen(N1,L1) AS(
    SELECT s.N1,
        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,4000)
    FROM cteStart s
)
SELECT 
    ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
    Item       = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
;

然后,您要先拆分MyOrders.Details. 拆分后,您执行 aJOIN以达到所需的结果:

WITH CteSplitted AS(
    SELECT
        mo.MyOrderID,
        CAST(s.Item AS INT) AS DriverID
    FROM MyOrders mo
    CROSS APPLY dbo.DelimitedSplitN4K(CONVERT(NVARCHAR(4000), mo.Details), ',') s
)
SELECT * 
FROM CteSplitted cs
INNER JOIN MyDrivers d
    ON d.MyDriverID = cs.DriverID
WHERE cs.MyOrderID = 1

SQL小提琴

结果

| MyOrderID | DriverID | MyDriverID | DriverName |
|-----------|----------|------------|------------|
|         1 |        1 |          1 |       Alex |
|         1 |        3 |          3 |       Carl |
|         1 |        5 |          5 |         Ed |
|         1 |        7 |          7 |     George |
|         1 |        9 |          9 |    Ichabod |
于 2015-05-28T02:20:10.590 回答