Как попасть в составные строки агрегатного / группового запроса? - PullRequest
0 голосов
/ 09 марта 2011

Я пытаюсь реализовать интерфейс для отображения логического статуса позиций

Я застрял на правильном способе обновления базовых строк при работе с наборами строк, которые в совокупности.

Таблица

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

Строка сопоставляется с идентификаторами одной или нескольких строк, которые необходимо обновить в результате нажатия кнопки «Оплатить».

Моей первой мыслью было обновление следующим образом:

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

Но это разваливается (поправьте меня, если я ошибаюсь), в ту минуту, когда дополнительная строка для «Клиента А» вставляется между временем отображения интерфейса и временем, когда пользователь нажимает «Оплатить».

ВОПРОС: Поскольку это довольно простой сценарий, каков типичный способ его решения? Пока я могу думать только о том, чтобы переместить агрегацию / группировку в логику приложения.

Ответы [ 4 ]

2 голосов
/ 09 марта 2011

Вы можете создать временную таблицу для хранения 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 голос
/ 09 марта 2011

Вас беспокоит, что в промежутке времени, когда вы читаете данные и обновляете данные, они могут измениться?

Вам нужно будет начать транзакцию с уровнем изоляции REPEATABLE READ (я думаю).

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

0 голосов
/ 09 марта 2011

Я прокрутил свой собственный ответ на этот вопрос, и я не вижу в нем обратной стороны, однако я буду рад, если кто-нибудь на это скажет.

Что мне нравится, так это то, как я точно знаю, какие строкиобновление.

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, размещенном на сервере.

В любом случае я создал агрегат, который помещает список идентификаторов, разделенных запятыми, прямо в мой запрос.

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));
    }
}
0 голосов
/ 09 марта 2011

Ваша схема неверна. Вам нужна третья таблица ссылок, в которой хранится идентификатор клиента и имя клиента, а затем столбец 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
...