有没有办法从 SQL 中的一组列中获取所有非空值?

Is there a way to get all non null value from a group of columns in SQL?

美好的一天,

请问,有没有更简单的方法获取一组不为空的列?

场景

我有一个从 excel 获取数据的上传器。该代码正在运行,但陷入僵局。我想修改我的代码,因为我正在做平均 RBAR(逐行痛苦)

代码

ALTER procedure [dbo].[saveProdPropSampDta]
@PartNo as nvarchar(20),
@PPFno as nvarchar(20),
@Dimension as nvarchar(30),
@ToUpdate as nvarchar(10),
@count as nvarchar(2),
@Judgement as nvarchar(2)

as
declare @TSQL as nvarchar(max)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
begin

--remove spaces from variable @dimension
set @Dimension = REPLACE(@Dimension, ' ','')



--ToUpdate is the value of from Excel

    set @TSQL = 'update ProductProperty set T'+@count+' = @ToUpdate where Partno = @Partno and PPFno = @PPFno and DName = @Dimension';

    exec sp_executesql @TSQL, N'@PartNo nvarchar(20), @PPFno nvarchar(20), @Dimension as nvarchar(30), @ToUpdate as nvarchar(10), @count as nvarchar(2)', @PartNo, @PPFno , @Dimension, @ToUpdate, @count;


    declare @cursor CURSOR
    declare @colname as nvarchar(30)
    declare @top as integer
    declare @query as nvarchar(MAX)
    declare @topass as nvarchar(MAX) = ''
    declare @retVal nvarchar(30)
    declare @categoryid NVARCHAR(MAX) 
        SET @cursor = CURSOR LOCAL FOR 
    (select [Name] from sys.columns where object_id = (select object_id from sys.tables where name = 'ProductProperty') and [Name] like 'T%' and [name] <> 'TEMP')order by [Name] asc

    --Gets column names from table from T1 -> T45

    OPEN @cursor

    FETCH NEXT


    FROM @cursor INTO @colname
    WHILE @@FETCH_STATUS = 0
    BEGIN

        SET @query = N'set @categoryid = (select distinct ' + @colname + ' from ProductProperty 
                    where PartNo = @PartNo  and PPFNo = @PPFno and DName = @Dimension)';
        --


        EXEC sp_executesql @query, 
                    N'@PartNo nvarchar(20), @PPFno nvarchar(20),@Dimension 
                    nvarchar(30), @categoryid NVARCHAR(MAX) OUTPUT', @PartNo, @PPFno,@Dimension, 
                    @categoryid OUTPUT

            if @categoryid is not null
                begin
                    set @topass = @topass + @colname + '+'
                end




    FETCH NEXT

    FROM @cursor INTO @colName
    END

    CLOSE @cursor
    DEALLOCATE @cursor


        set @topass = LEFT(@topass, LEN(@topass)-1)



        set @topass  =  N'set @categoryid = (Select cast(('+@topass+')/'+@count+' as decimal(18,3)) from ProductProperty where PartNo = @PartNo  and PPFNo = @PPFno and DName = @Dimension)';


    EXEC sp_executesql @topass, 
        N'@PartNo nvarchar(20), @PPFno nvarchar(20),@Dimension 
        nvarchar(30), @categoryid NVARCHAR(MAX) OUTPUT', @PartNo, @PPFno,@Dimension, 
        @categoryid OUTPUT

        set @topass = 'update ProductProperty set average = '+@categoryid+', Judgement = '''+@Judgement+''' where PartNo = @PartNo  and PPFNo = @PPFno and DName = @Dimension';




    EXEC sp_executesql @topass, 
        N'@PartNo nvarchar(20), @PPFno nvarchar(20),@Dimension 
        nvarchar(30)', @PartNo, @PPFno,@Dimension


end

这样的代码的原因

代码在VB.Net代码中每次调用时都会运行,它会从变量中获取数据到存储过程中。该代码实际上从非空的列中获取平均值

我想要什么 有没有类似

select ave(ifnull(T1.....T2,If Null dont include, ifnot null include)) from TABLE

我猜你需要这样的东西。

SELECT AVG(CASE WHEN T1 IS NOT NULL THEN T1 ELSE 0 END)
FROM TABLE

我可能会采用几种方法来重新设计您的解决方案。

如果您可以更改的内容有限,那么#2 可能是门票。但我个人的偏好是 #1 - 生成的代码更简单,如果您决定需要 T46,则无需进行任何更改。

方法编号 1:

您没有提供 table 的完整详细信息,因此我猜测您有一个构成主键的 ID 列,否则我们'我将在新解决方案中添加一个 :)

我还假设您接收 nvarchar 参数到您的存储过程是必要的而不是选择,并相应地声明数据类型。

是否无法重新设计您的数据模型?解决此问题的一种方法是将您的 "T1".."T45" 列移动到单独的 table 并将它们视为行。

您可以从 table 的:

Create Table ProductProperty 
(
    ID int identity, 
    Partno nvarchar(20), 
    PPFno nvarchar(20), 
    DName nvarchar(30),
    T1 decimal(18, 3),
    T2 decimal(18, 3),
    T3 decimal(18, 3),
--  ...
    T44 decimal(18, 3),
    T45 decimal(18, 3),
    average decimal(18, 3),
    Judgement nvarchar(2)
)

Create Table ProductProperty 
(
    ID int identity, 
    Partno nvarchar(20), 
    PPFno nvarchar(20), 
    DName nvarchar(30),
    average decimal(18, 3),
    Judgement nvarchar(2)
) -- You would probably want to put a unique index on (Partno, PPFno, DName)

Create Table ProductPropertyT
(
    ProductPropertyID int,
    TNo nvarchar(2),
    TValue decimal(18, 3)
)

现在您的存储过程只需要识别主键值(基于我对复合键 PartnoPPFnoDName 的推测),创建或更新记录ProductPropertyT table,并对 TValue 具有匹配 ProductPropertyId 值的记录进行简单平均:

alter procedure [dbo].[saveProdPropSampDta]
@PartNo as nvarchar(20),
@PPFno as nvarchar(20),
@Dimension as nvarchar(30),
@ToUpdate as nvarchar(10),
@count as nvarchar(2),
@Judgement as nvarchar(2)
as
begin

--remove spaces from variable @dimension
set @Dimension = REPLACE(@Dimension, ' ','')

    -- determine the PK
    declare @pk int
    select @pk = ID from ProductProperty where Partno = @Partno and PPFno = @PPFno and DName = @Dimension

    -- create / update the value in the ProductPropertyT table
    if exists (select * from ProductPropertyT where ProductPropertyID = @pk and TNo = @count)
    begin
        update ProductPropertyT 
        set TValue = cast(@ToUpdate as decimal(18, 3))
        where ProductPropertyID = @pk and TNo = @count
    end
    else
    begin 
        insert into ProductPropertyT (ProductPropertyID, TNo, TValue)
        values (@pk, @count, cast(@ToUpdate as decimal(18, 3)))
    end

    -- update the average and judgement in the ProductProperty table.
    update ProductProperty 
       set average = (select avg(TValue) from ProductPropertyT where ProductPropertyID = @pk)
          ,Judgement = @Judgement
     where ID = @pk
end

存储过程现在:更短;没有动态 sql;很容易理解。

方法编号 2:

如果您绝对必须拥有此数据模型,那么您可以使用 unpivot 从 T1...T45 列获取非空值并简单地对它们进行平均。

SELECT avg(TValue) 
from 
(   
    -- this will give you only the values for the current part/ppf/dimension
    select * from ProductProperty 
    where Partno = @Partno and PPFno = @PPFno and DName = @Dimension
) myrow
unpivot 
(
    TValue
    for TCol in (T1, T2, ... T44, T45)
) as x  

我将把它作为练习留给您,将其编织到更新语句中 - 但足以说明您的这部分过程不需要任何动态 sql。

希望对您有所帮助。