将迭代查询更改为基于关系集的查询

时间:2014-09-12 22:12:12

标签: sql sql-server tsql

SQL Fiddle

我正在尝试将迭代/游标查询(工作正常)更改为关系集查询以获得更好的性能。

我有什么:

表1

| ID | NAME |
|----|------|
|  1 |    A |
|  2 |    B |
|  3 |    C |


使用函数,我想将我的数据插入另一个表。以下函数是一个简化示例:

功能

CREATE FUNCTION fn_myExampleFunction
(
  @input nvarchar(50)
)

RETURNS @ret_table TABLE 
(
  output nvarchar(50)
)

AS

BEGIN
  IF @input = 'A'
    INSERT INTO @ret_table VALUES ('Alice')
  ELSE IF @input = 'B' 
    INSERT INTO @ret_table VALUES ('Bob')
  ELSE
    INSERT INTO @ret_table VALUES ('Foo'), ('Bar')
RETURN
END;


我期望的结果是在table2中插入数据,如下所示:

表2

| ID |  NAME |
|----|-------|
|  1 | Alice |
|  2 |   Bob |
|  3 |   Foo |
|  3 |   Bar |  


为了实现这一点,我尝试了一些CTE(公用表表达式)和关系查询,但没有一个按预期工作。到目前为止,我唯一可行的解​​决方案是迭代而非性能解决方案。

我目前的工作解决方案

BEGIN
  DECLARE 
    @ID int, 
    @i int = 0,
    @max int = (SELECT COUNT(name) FROM table1)

  WHILE ( @i < @max ) -- In this example, it will iterate 3 times
  BEGIN

    SET @i += 1

    -- Select table1.ID where row_number() = @i
    SET @ID =
          (SELECT 
            id 
          FROM
            (SELECT 
              id,
              ROW_NUMBER() OVER (ORDER BY id) as rn
            FROM 
              table1) rows
          WHERE
              rows.rn = @i
          )

    -- Insert into table2 one or more rows related with table1.ID
    INSERT INTO table2
      (id, name)
    SELECT
      @ID,
      fn_result.output
    FROM
      fn_myExampleFunction (
        (SELECT name FROM table1 WHERE id = @ID)
      ) fn_result

    END
END


目标是在不重复ID的情况下实现相同目标。

3 个答案:

答案 0 :(得分:2)

如果问题是关于如何以面向集合的方式应用函数,那么cross apply(或outer apply)就是你的朋友:

insert into table2 (
    id, name
) select
   t1.id,
   t2.output
from
   table1 t1
       cross apply
   fn_myExampleFunction(t1.name) t2

Example SQLFiddle

如果您的功能的非简化版本适合重写,其他解决方案可能会更快。

答案 1 :(得分:1)

这样的查询可以满足您的需求:

insert into table2(id, name)
    select id, (case when name = 'A' then 'Alice'
                     when name = 'B' then 'Bob'
                     when name = 'C' then 'Foo'
                end)
    from table1
    union all
    select id, 'Bar'
    from table1
    where name = 'C';

答案 2 :(得分:1)

为什么不将这些数据存储为表格?它是关系型的。在函数或存储过程中对其进行编码似乎不太理想。

无论如何,我希望以下内容为您提供有关如何改进代码的建议。我意识到你说你的函数比你的例子更复杂,但你仍然可以在函数内部使用这个想法。

INSERT dbo.table2 (ID, Name)
SELECT
   T1.ID,
   N.FullName
FROM
   dbo.table1 T1
   INNER JOIN (VALUES -- A "derived table" made up of only constants
      ('A', 'Alice'),
      ('B', 'Bob'),
      ('C', 'Foo'),
      ('C', 'Bar')
  ) N (ShortName, FullName)
      ON T1.Name = N.ShortName
;

但是,当然,如果它在真实的表中,那么它可以被渲染INNER JOIN dbo.NameTranslation N(然后更新它会更容易!)。

如果您的功能绝对不能被重写为关系(一次只能使用一个名称),那么您将使用CROSS APPLY

INSERT dbo.table2 (ID, Name)
SELECT
   T1.ID,
   N.OutputName
FROM
   dbo.table1 T1
   CROSS APPLY dbo.YourFunction(T1.Name) F
;

但是,对于大型行集,这不会很好。将函数重写为RETURNS TABLE在正确方向上的步骤(而不是RETURNS @variable TABLE (definition))的类型。