简体   繁体   English

帮助硬SQL查询根据每日总计更新到汇总表

[英]Help with hard sql query to update based on daily totals to summary table

The following are my sql server 2005 table structures: 以下是我的sql server 2005表结构:

Products (productID INT PK, ...) 产品(productID INT PK,...)

ProductBids (productID INT, userID INT, Created DATETIME) 产品出价(productID INT,userID INT,创建的DATETIME)

Users(UserID INT PK, TOTALBIDS INT) 用户(UserID INT PK,TOTALBIDS INT)

Each day a user can bid as many times as they want on all the products. 每天,用户可以对所有产品进行任意多次的出价。 There is a sql job that is run periodically, that counts the total bids a user has performed and puts the total in the TOTALBIDS field. 有一个sql作业会定期运行,该作业会计算用户执行的总出价,并将总出价放入TOTALBIDS字段中。

The catch is, our business rules require that we only count up to 10 bids for any given day. 问题是,我们的业务规则要求我们在任何一天最多只能计算10个出价。

So the update query has to group by day, and if the total bids for a user on the products goes over 10, we just use 10 for that day. 因此,更新查询必须按天分组,如果用户在产品上的总出价超过10,则当天只使用10。

eg day#1 bids 5 times in total day#2 bids 15 times in total day#3 bids 10 times 例如,第1天第1天总共出价5次#2第2天总共出价15次#3第10次出价

(assuming on 3 days in total) The bidCount for the user will be 5 + 10 + 10 = 25 (not 30). (假设总共3天),该用户的bidCount将为5 + 10 + 10 = 25(而不是30)。

Is this possible in a single query? 在单个查询中有可能吗?

You don't say what you want to do with the results, but you can certainly SELECT the user's earliest ten bids of each day: 您无需说出要如何处理结果,但可以确定每天选择用户最早的10个出价:

with ProductBidsRanked(productID, userID, Created, rk) as (
  select
    productID, userID, Created,
    row_number() over (
      partition by userID, dateadd(datediff(day,0,Created),0)
      order by Created
    )
)
  select productID, userID, Created
  from ProductBidsRanked
  where rk <= 10

Of course, if you only need the total, and want to replace the total with 10 when it exceeds 10, that's easier: 当然,如果您只需要总数,并且想在总数超过10时将其替换为10,那会更容易:

with PartialAgg(userID,countOr10) as (
  select
    userID,
    case when count(*) > 10 then 10 else count(*) end
  from ProductsBids
  group by userID, dateadd(datediff(day,0,Created),0)
)
  select
    userID, sum(countOr10) as BidsAdjusted
  from PartialAgg
  group by userID;

Response to comment: 对评论的回应:

You say you want to add it to the user's bidcount, but bidcount isn't a column name in any of your tables. 您说要将其添加到用户的出价计数中,但是出价计数不是任何表中的列名称。 Perhaps you meant TOTALBIDS, so for example, if the second query is the one that works for you, you could do something like this: 也许您的意思是TOTALBIDS,因此例如,如果第二个查询对您有效,则可以执行以下操作:

with PartialAgg(userID,countOr10) as (
  select
    userID,
    case when count(*) > 10 then 10 else count(*) end
  from ProductsBids
  group by userID, dateadd(datediff(day,0,Created),0)
), FullAgg(userID,BidsAdjusted) as (
  select
    userID, sum(countOr10) as BidsAdjusted
  from PartialAgg
  group by userID
)
  update users set
    TOTALBIDS = TOTALBIDS + BidsAdjusted
  from users join FullAgg
  on FullAgg.userID = users.userID

FYI, there's some SQL Server specific stuff here - ANSI doesn't allow UPDATE with a CTE, and I didn't confirm that T-SQL's quirky UPDATE .. FROM can be used in combination with a CTE. 仅供参考,这里有一些SQL Server特定的东西-ANSI不允许CTE进行UPDATE,而且我也没有确认T-SQL的古怪UPDATE .. FROM是否可以与CTE结合使用。

In any case, given that this seems like the kind of update you would run only infrequently, and never concurrently, it could be wisest to insert the results of my first suggestion (whichever serves your purpose) into a temporary table and base your update on that. 无论如何,鉴于这似乎是一种更新,您只会很少运行,而绝不会同时运行,因此将我的第一个建议的结果(以您的目的为准)插入临时表并将更新基于此是最明智的选择那。

CREATE TABLE dbo.ProductBids(ProductID INT, UserID INT, Created DATETIME);

CREATE TABLE dbo.Users(UserID INT, TotalBids INT);

INSERT dbo.Users(UserID) SELECT 1 UNION ALL SELECT 2;

INSERT dbo.ProductBids 
           SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()-1
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 1, GETDATE()
UNION ALL  SELECT 1, 2, GETDATE()
UNION ALL  SELECT 1, 2, GETDATE()
UNION ALL  SELECT 1, 2, GETDATE();

UPDATE u 
SET TotalBids = x.TotalBids
FROM
(
    SELECT
        UserID, 
        TotalBids = SUM(CASE WHEN c > 10 THEN 10 ELSE c END)
    FROM
    (
        SELECT
            UserID,
            c = COUNT(*)
        FROM
            dbo.ProductBids
        GROUP BY
            UserID,
            DATEADD(DAY, 0, DATEDIFF(DAY, 0, Created))  
    ) AS y
    GROUP BY UserID
) AS x
INNER JOIN dbo.Users AS u
ON x.UserID = u.UserID;

GO

SELECT UserID, TotalBids FROM dbo.Users;

GO

DROP TABLE dbo.Users, dbo.ProductBids;

However in general I frown upon storing this total, when you can derive the information from existing data. 但是,总的来说,当您可以从现有数据中获取信息时,我并不喜欢存储总数。 The problem is that the data in the Users table is only guaranteed to be accurate between the time you run the UPDATE statement and the next time any DML operation happens againt the ProductBids table. 问题在于,只能保证在您运行UPDATE语句的时间与下一次任何DML操作再次发生在ProductBids表之间的时间内,Users表中的数据是准确的。

I think you can use an aggregate + a case statement to do this. 我认为您可以使用汇总+案例声明来执行此操作。 Something like: 就像是:

declare @t table (a int, b int)

insert into @t values(1, 5)
insert into @t values(1, 15)
insert into @t values(1, 10)


select a, sum( case when b>10 then 10 else b end) 
from @t
group by a

The case statement is ensuring that you never add more than 10 if the value is greater than 10 case语句确保如果值大于10,则您永远不会添加超过10

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM