如何获取聚合/分组查询的组成行?

时间:2011-03-09 01:02:48

标签: c# sql-server tsql group-by

我正在尝试实现一个界面来翻转订单项的布尔状态

在使用聚合行集时,我坚持以正确的方式更新基础行。

表格

declare @item table(
    id int not null primary key,
    amount money not null,
    is_paid bit not null,
    client varchar(10) not null)
insert into @item values
    (1, 9.50, 0, 'Client A'), 
    (2, 11.50, 0, 'Client A'),
    (3, 20.00, 1, 'Client B')

查询

select sum(amount) as total_amount, is_paid, client
from @item
group by is_paid, client

结果

enter image description here

方案

现在说如果is_paid = 0,上面的结果都在一个带有“付费”按钮的网格中。

enter image description here

由于点击“付款”,行会映射到要更新的一行或多行的ID。

我的第一个想法就是这样更新:

update @item
set is_paid=1
where client='Client A'

但是,如果我错了,请分开(纠正我),在界面显示的时间和用户按下“付款”的时间之间插入“客户A”的附加行。

问题:由于这似乎是一个相当简单的场景,处理它的典型方法是什么?到目前为止,我唯一能想到的就是将聚合/分组移动到应用程序逻辑。

4 个答案:

答案 0 :(得分:2)

您可以在临时表中创建以存储client_id和item_id。然后通过将该表与项表连接来选择聚合。这样,当is_paid = 1时,您只能更新项表中具有临时表中相应记录的记录。例如:

// Assuming id in @item has been rename to item_id and client_id has been added to @item

declare @active table(
    client_id int not null,
    item_id int not null
);
insert into @active select client_id, item_id from @item;

select sum(@item.amount) as total_amount, @item.is_paid, @item.client_name
from @item inner join @active in @item.item_id = @active.item_id and @item.client_id = @active.client_id
group by @item.is_paid, @item.client_id
order by @item.client_name

update @item from @item inner join @active on @item.client_id = @active.client_id and @item.item_id = @active.item_id
set is_paid=1
where client='Client A'

或者,您可以向create_time添加@item列。这样,您只能更新在特定时间之前创建的那些。例如:

select sum(amount) as total_amount, is_paid, client, max(create_time) as last_time
from @item
group by is_paid, client

update @item
set is_paid=1
where client='Client A' and create_time <= last_time

答案 1 :(得分:1)

您担心在读取数据和更新数据之间可能会发生变化吗?

您需要使用REPEATABLE READ隔离级别启动事务(我认为)。

http://msdn.microsoft.com/en-us/library/aa259216(v=sql.80).aspx

答案 2 :(得分:0)

您的架构错了。您需要第三个链接表来存储客户端ID和客户端名称,然后是项目表中的clientID列。然后,您可以正确更新:

UPDATE @item SET is_paid = 1
WHERE @item.Clientid = (SELECT client.clientID from Client 
WHERE Client.ClientName = 'Client A') AND @item.ID IN
(1, 2) -- Your list of IDs checked to mark as paid in the grid

答案 3 :(得分:0)

我为这个推出了自己的答案,我没有看到它的缺点,但是我会欢迎任何人指出它。

我喜欢的是如何准确知道要更新的行。

declare @MyItem table(
    id int not null primary key,
    amount money not null,
    is_paid bit not null,
    client varchar(10) not null)
insert into @MyItem values
    (1, 9.50, 0, 'Client A'), 
    (2, 11.50, 0, 'Client A'),
    (3, 20.00, 1, 'Client B')
select dbo.SumKeys(id) as [IDs], sum(amount) as total_amount, is_paid, client
from @MyItem
group by is_paid, client

enter image description here

我不喜欢的是,我花了半天多的时间才能使这段代码正常工作,因为我正在与(对我而言)与sql server托管的clr编程有关的奇怪之处。

无论如何,我做了一个聚合,将逗号分隔的ID列表放入我的查询中。

using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
    Format.UserDefined,
    IsInvariantToDuplicates = true,
    IsInvariantToNulls = true,
    MaxByteSize = -1
    )]
public struct SumKeys : IBinarySerialize
{
    private readonly static char sep = ',';
    private SqlString result;
    public void Init()
    {
        result = string.Empty;
    }
    public void Accumulate(SqlInt32 value)
    {
        if (!value.IsNull && !Contains(value))
            this.Add(value);
    }
    private void Add(SqlInt32 value)
    {
        this.result += Wrap(value);
    }
    private void Add(string value)
    {
        Add(Convert.ToInt32(value));
    }
    private static string Wrap(SqlInt32 value)
    {
        return value.Value.ToString() + sep;
    }
    private bool Contains(SqlInt32 value)
    {
        return this.result.Value.Contains(Wrap(value));
    }
    public void Merge(SumKeys group)
    {
        foreach (var value in Items(group))
            if (!this.Contains(value))
                this.Add(value);
    }
    private static IEnumerable<SqlInt32> Items(SumKeys group)
    {
        foreach (var value in group.result.Value.Split(sep))
        {
            int i;
            if (Int32.TryParse(value, out i))
                yield return i;
        }
    }
    public SqlString Terminate()
    {
        return this.result.Value.TrimEnd(sep);
    }
    public void Read(System.IO.BinaryReader r)
    {
        this.result = r.ReadString();
    }
    public void Write(System.IO.BinaryWriter w)
    {
        w.Write(this.result.Value.TrimEnd(sep));
    }
}