简体   繁体   中英

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

The following are my sql server 2005 table structures:

Products (productID INT PK, ...)

ProductBids (productID INT, userID INT, Created DATETIME)

Users(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.

The catch is, our business rules require that we only count up to 10 bids for any given day.

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.

eg day#1 bids 5 times in total day#2 bids 15 times in total day#3 bids 10 times

(assuming on 3 days in total) The bidCount for the user will be 5 + 10 + 10 = 25 (not 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:

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:

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:

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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