SQL - combined SELECT queries and getting a % output - sql

I am using SQLiteStudio and I am trying to run the following query. However, it isn't returning a value. Can anyone help, please?
SELECT
(SELECT COUNT(t_record.LocationID)
FROM t_record,
t_location
WHERE t_record.LocationID = t_location.LocationID AND
Y_AXIS >= 0 AND
Goal_for = 1)
/
(SELECT COUNT(GoalID)
FROM t_record
WHERE t_record.Goal_for)
* 100
I have been asked to provide some of the data. Below I have included how I created my 3 tables and then some sample data for each section. Hopefully, this is enough but feel free to ask for more.
These are the 3 tables I have created
CREATE TABLE t_location (
LocationID INT PRIMARY KEY,
X_Axis INT NOT NULL,
Y_AXIS INT NOT NULL
);
CREATE TABLE t_method (
MethodID INT PRIMARY KEY,
Body_Part VARCHAR(45) NOT NULL
);
CREATE TABLE t_record (
GoalID INT PRIMARY KEY,
LocationID [INT FORIEIGN KEY] REFERENCES t_location (LocationID),
MethodID [INT FORIEIGN KEY] REFERENCES t_method (MethodID),
Time INT NOT NULL,
Goal_for BOOLEAN NOT NULL
Data for the method table
INSERT INTO t_method
VALUES (1,'Left Foot');
INSERT INTO t_method
VALUES (2,'Right Foot');
INSERT INTO t_method
VALUES (3,'Head');
Data for the location table - only included 3 locations.
INSERT INTO t_location
VALUES (1, 0, -1);
INSERT INTO t_location
VALUES (2, 0, 0);
INSERT INTO t_location
VALUES (3, 0, 1);
Data for the record table, it is very pland but I've varied the location.
INSERT INTO t_record
VALUES (1, 1, 1, 28, 1);
INSERT INTO t_record
VALUES (2, 1, 1, 6, 1);
INSERT INTO t_record
VALUES (3, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (4, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (5, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (6, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (7, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (8, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (9, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (10, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (11, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (12, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (1, 3, 1, 28, 1);
Thanks

Actually you had just a few syntax errors and correcting them plus adding something like * 1.0 to count() would do what you want (otherwise count() results are integer and in SQL server integer / integer would yield an integer which is 0 in your case).
This is your code, slightly modified to work:
SELECT
(SELECT COUNT(t_record.LocationID)
FROM t_record,
t_location
WHERE t_record.LocationID = t_location.LocationID AND
Y_AXIS >= 0 AND
Goal_for = 1) * 1.0
/
(SELECT COUNT(GoalID)
FROM t_record
WHERE t_record.Goal_for=1)
* 100;
(I agree with preventing old style joins etc but was not the problem here).
You could make it a little better:
SELECT
(SELECT COUNT(t_record.LocationID)
FROM t_record,
t_location
WHERE t_record.LocationID = t_location.LocationID AND
Y_AXIS >= 0 AND
Goal_for = 1) * 100.0
/
(SELECT COUNT(GoalID)
FROM t_record
WHERE t_record.Goal_for=1) as Pct;
While the above code works, I think there is a better and cleaner way:
SELECT SUM(case when Y_AXIS >= 0 then 1 end) * 100.0 / COUNT(*)
FROM t_record
inner join t_location ON t_record.LocationID = t_location.LocationID
where Goal_for = 1;
You can also check what is really going on there with adding those fields:
SELECT
SUM(case when Y_AXIS >= 0 then 1 end) as YAXIS_GTE_ZERO,
COUNT(*) as Total,
SUM(case when Y_AXIS >= 0 then 1 end) * 100.0 / COUNT(*) as Pct
FROM t_record
inner join t_location ON t_record.LocationID = t_location.LocationID
where Goal_for = 1;
And here is DBFiddle demo using your example data
EDIT: And this is for YAXIS >= 0 and YAXIS < 0 as in your comment:
SELECT
SUM(CASE WHEN Y_AXIS>=0 THEN 1 END) AS Opp_half_goals
, SUM(CASE WHEN Y_AXIS<0 THEN 1 END) AS Own_half_goals
, COUNT(*) AS Total
, SUM(CASE WHEN Y_AXIS>=0 THEN 1 END)* 100.0 / COUNT(*) AS Opp_half_Pct
, SUM(CASE WHEN Y_AXIS<0 THEN 1 END) * 100.0 / COUNT(*) AS Own_half_Pct
FROM t_record
INNER JOIN t_location ON t_record.LocationID=t_location.LocationID
WHERE Goal_for=1;

I Tried this; using 1.0 instead of 1 in the CASE WHEN expression, to avoid an integer division by integer, which would have resulted in a truncated integer as the result, namely 0:
SELECT
SUM(CASE WHEN y_axis >= 0 THEN 1.0 END)
/ COUNT(*)
* 100
AS result
FROM t_record
JOIN t_location USING(locationid)
;
-- out result
-- out ------------------------
-- out 84.6153846153846153800

Related

How can I calculate the percentage of occurrence of particular value in each column in SQL Server

I have a table with counts in each column (there are 20 columns), and I have calculated the percentage of 0's that appear in each column for the filtered list of employees.
Here are the simplified table definitions and data that I used to test. I have a table EmployeeNotificationCounts:
CREATE TABLE EmployeeNotificationCounts
(
EmployeeId INT NOT NULL,
EmployeeTypeId INT NOT NULL,
Notification1 INT NOT NULL,
Notification2 INT NOT NULL,
Notification3 INT NOT NULL,
Notification4 INT NOT NULL
)
INSERT INTO EmployeeNotificationCounts VALUES (1, 1, 15, 0, 0, 5)
INSERT INTO EmployeeNotificationCounts VALUES (2, 2, 1, 3, 0, 5)
INSERT INTO EmployeeNotificationCounts VALUES (3, 1, 15, 0, 0, 5)
INSERT INTO EmployeeNotificationCounts VALUES (4, 2, 3, 6, 0, 15)
INSERT INTO EmployeeNotificationCounts VALUES (5, 1, 15, 0, 0, 5)
INSERT INTO EmployeeNotificationCounts VALUES (6, 1, 15, 0, 0, 5)
INSERT INTO EmployeeNotificationCounts VALUES (7, 2, 18, 0, 0, 25)
INSERT INTO EmployeeNotificationCounts VALUES (8, 1, 15, 0, 0, 5)
INSERT INTO EmployeeNotificationCounts VALUES (9, 2, 0, 14, 0, 35)
INSERT INTO EmployeeNotificationCounts VALUES (10, 1, 15, 0, 0, 5)
I want to get a result like below in percentage for 0's in each notification for all employees of type = 2:
Notification1 Notificatio2 Notification3 Notification4
---------------------------------------------------------
10 70 100 0
I have a query that does this job
SELECT
((SELECT COUNT(*) FROM EmployeeNotificationCounts
WHERE Notification1 = 0 AND EmployeeTypeId = 2) * 100 / COUNT(*)) AS IncompleteChart0Percentage,
((SELECT COUNT(*) FROM EmployeeNotificationCounts
WHERE Notification2 = 0 AND EmployeeTypeId = 2) * 100 / COUNT(*)) AS IntComm0Percentage,
((SELECT COUNT(*) FROM EmployeeNotificationCounts
WHERE Notification3 = 0 AND EmployeeTypeId = 2) * 100 / COUNT(*)) AS PTComm0Percentage,
((SELECT COUNT(*) FROM EmployeeNotificationCounts
WHERE Notification4 = 0 AND EmployeeTypeId = 2) * 100 / COUNT(*)) AS PendingBilling0Percentage
FROM
EmployeeNotificationCounts ENC
WHERE
ENC.EmployeeTypeId = 2
I have this query that gets me the result as desired. But, can this be optimized for larger databases? In production, this table might have thousands of records. I haven't run this in production yet, just wanted to be sure if the percentage calculation statements can be written differently. Thanks for the help in advance.
Just another option
Select Notification1 = avg(case when Notification1 = 0 then 100.0 else 0 end)
,Notification2 = avg(case when Notification2 = 0 then 100.0 else 0 end)
,Notification3 = avg(case when Notification3 = 0 then 100.0 else 0 end)
,Notification4 = avg(case when Notification4 = 0 then 100.0 else 0 end)
From EmployeeNotificationCounts
Where ENC.EmployeeTypeId = 2
Returns
Notification1 Notification2 Notification3 Notification4
10.000000 70.000000 100.000000 0.000000
NOTE:
If you'd rather 70 as 0.7 for example, change 100.0 to 1.0
Another way of writing this in a slightly more compact way which you may prefer as there's many columns is to use iif
select
Avg(Iif(Notification1=0,100.0,0)) IncompleteChart0Percentage,
Avg(Iif(Notification2=0,100.0,0)) IntComm0Percentage,
Avg(Iif(Notification3=0,100.0,0)) PTComm0Percentage,
Avg(Iif(Notification4=0,100.0,0)) PendingBilling0Percentage
From EmployeeNotificationCounts
Where EmployeeTypeId = 2

SQL Query by using with

I have the following query...
------ create table
create table test222
(
sid bigint,
scode nvarchar(50),
parentid bigint,
sname nvarchar(50)
)
insert into test222 values (1, '11', 0, 'iam a boy')
insert into test222 values (2, '111', 1, 'boy')
insert into test222 values (3, '1111', 1, 'bo')
insert into test222 values (4, '11111', 3, 'girl')
insert into test222 values (5, '111111', 0, 'boyy')
insert into test222 values (6, '1111111', 5, 'gril')
insert into test222 values (7, '22', 0, 'body')
insert into test222 values (8, '222', 7, 'girll')
following is my code,,,
;WITH SInfo AS
(
SELECT
t.sId,
t.scode,
t.ParentId,
t.sName,
CONVERT(nvarchar(800), t.scode) AS Hierarchy,
t.ParentId as HParentId
FROM test222 as t
WHERE
t.sname like '%bo%'
UNION ALL
SELECT
si.sId,
si.scode,
si.ParentId,
si.sName,
CONVERT(nvarchar(800), TH.scode + '\' + si.Hierarchy),
th.parentid
FROM SInfo as si
INNER JOIN test222 TH
ON TH.sId = si.HParentId
)
Select t.sId, t.scode, t.ParentId, t.sName, t.Hierarchy
from SInfo as t
where
HParentId = 0 and
not exists (select 1 from SInfo as s
where
s.sid <> t.sid and
s.Hierarchy like t.Hierarchy + '%')
the op generated is shown below
5 111111 0 boyy 111111
7 22 0 body 22
3 1111 1 bo 11\1111
the third row is not correct
It should be
3 111111 1 bo 11\111\1111.
How can i do that???
All you need to do is change the parent id of the record - sid=3 to its chronological parent instead of its grand parent :-) . Check below.
Change
insert into #test222 values (3, '1111', 1, 'bo')
to
insert into #test222 values (3, '1111', 2, 'bo')
The reason you are not seeing the middle portion (record - sid:2) is because record - sid=2 and record - sid=3 essentially share the same "FROM" criteria. sname= '%bo%' (or rather LIKE '%bo%') and parentid=1. The record - sid=3 is the last record in the set with this shared "FROM" criteria and hence is the record being returned.
Order of SQL query execution (FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY)
Here is a link to recursive CTE queries
Hope this helps.

30/60/90 Day Sumation Query

I have the following query. It pulls invoice and time entries, and calculates the EHR (effective hourly rate) for each client, on a per month basis. What I need to get is:
company ,agreement ,lastMonthEHR,60dayEHR,90dayEHR,6MoEHR,12MoEHR,LifeEHR
CompanyA,AgreementB, 30.45, 27.76, 55.22, 30.75, 30.00, 25.00
EDIT:
I apologize for the format. I'll see if I can format it better. The following query returns monthly invoices, with EHR calculated.
SELECT a.AGR_Name, AGR_Type.AGR_Type_Desc, c.Company_Name, ap.InvoiceDate,ap.Revenue,ap.Hours,ap.EHR
FROM AGR_Header AS a INNER JOIN Company AS c ON a.Company_RecID = c.Company_RecID
LEFT JOIN AGR_Type ON a.AGR_Type_RecID = AGR_Type.AGR_Type_RecID
LEFT JOIN (
SELECT ar.AGR_Header_RecID,ar.Revenue,ac.InvoiceDate,ISNULL (ac.Hours, 0) AS Hours,
CASE
WHEN ac.Hours IS NULL THEN (ar.Revenue)
WHEN ac.Hours <= 1 THEN (ar.Revenue)
ELSE CAST (ar.Revenue / NULLIF (ac.Hours,0) as NUMERIC (9,2))
END AS 'EHR'
FROM (
SELECT ah.AGR_Header_RecID,
DATEADD(month,ai.Month_Nbr-1,dateadd(year,ai.Year_Nbr-2000,'2000-01-01')) as InvoiceDate,
CAST (ai.Monthly_Inv_Amt AS NUMERIC (9, 2)) AS Revenue
FROM
dbo.AGR_Header AS ah INNER JOIN
dbo.AGR_Invoice_Amt AS ai ON ah.AGR_Header_RecID = ai.AGR_Header_RecID
GROUP BY ah.AGR_Header_RecID, ai.Month_Nbr, ai.Year_Nbr) as ar
LEFT JOIN (
SELECT ah.AGR_Header_RecID,SUM(te.Hours_Actual) AS Hours, dateadd(month, datediff(month,0,te.Date_start),0) as InvoiceDate
FROM
dbo.Time_Entry AS te INNER JOIN
dbo.AGR_Header AS ah ON te.Agr_Header_RecID = ah.AGR_Header_RecID
WHERE (te.Agr_Header_RecID IS NOT NULL) AND (te.Agr_Hours IS NOT NULL)
GROUP BY ah.AGR_Header_RecID, dateadd(month, datediff(month,0,te.Date_Start),0)) AS ac ON ar.AGR_Header_RecID = ac.AGR_Header_RecID
AND ar.InvoiceDate = ac.InvoiceDate) AS ap ON ap.AGR_Header_RecID = a.AGR_Header_RecID
ORDER BY Company, Agreement, InvoiceDate
The SQL you posted is pretty complicated, but I think it can be simplified. I think the key is to get the invoice data into a format that is similar to the following:
DECLARE #invoice AS TABLE(
[ID] INT,
[CompanyID] INT,
[InvoiceDate] DATE,
[Hours] DECIMAL(9,2),
[Revenue] DECIMAL(9,2))
From there, the calculations are pretty simple, and they can be done using CASE WHEN statements with minimal subselects (I used one just for clarity, but even that one could be eliminated). Here's a full working example for SQL Server:
--Setup table and dummy data
DECLARE #invoice AS TABLE(
[ID] INT,
[CompanyID] INT,
[InvoiceDate] DATE,
[Hours] DECIMAL(9,2),
[Revenue] DECIMAL(9,2))
INSERT INTO #invoice VALUES(1, 1, '2013-01-01', 5, 100)
INSERT INTO #invoice VALUES(2, 1, '2013-02-01', 6, 100)
INSERT INTO #invoice VALUES(3, 1, '2013-03-01', 7, 100)
INSERT INTO #invoice VALUES(4, 1, '2013-04-01', 8, 100)
INSERT INTO #invoice VALUES(5, 1, '2013-05-01', 9, 100)
INSERT INTO #invoice VALUES(6, 1, '2013-06-01', 10, 100)
INSERT INTO #invoice VALUES(7, 1, '2013-07-01', 11, 100)
INSERT INTO #invoice VALUES(8, 1, '2013-08-01', 12, 100)
INSERT INTO #invoice VALUES(9, 2, '2013-04-01', 5, 100)
INSERT INTO #invoice VALUES(10, 2, '2013-05-01', 6, 100)
INSERT INTO #invoice VALUES(11, 2, '2013-06-01', 7, 100)
INSERT INTO #invoice VALUES(12, 2, '2013-07-01', 8, 100)
--Calculate last month start and end dates
--Hardcoded here for brevity
DECLARE #lastMonthStartDate AS DATETIME
DECLARE #lastMonthEndDate AS DATETIME
SET #lastMonthStartDate = '2013-08-01'
SET #lastMonthEndDate = '2013-09-01'
--Calculate EHRs for different time periods
SELECT
A.CompanyID,
CASE WHEN A.LastMonthHours = 0 THEN 0 ELSE A.LastMonthRevenue / A.LastMonthHours END as [LastMonthEHR],
CASE WHEN A.Last60DaysHours = 0 THEN 0 ELSE A.Last60DaysRevenue / A.Last60DaysHours END as [Last60DaysEHR],
CASE WHEN A.Last90DaysHours = 0 THEN 0 ELSE A.Last90DaysRevenue / A.Last90DaysHours END as [Last90DaysEHR]
FROM (
SELECT
[CompanyID],
SUM(CASE WHEN [InvoiceDate] >= #lastMonthStartDate AND [InvoiceDate] < #lastMonthEndDate THEN [Hours] ELSE 0 END) as [LastMonthHours],
SUM(CASE WHEN [InvoiceDate] >= #lastMonthStartDate AND [InvoiceDate] < #lastMonthEndDate THEN [Revenue] ELSE 0 END) as [LastMonthRevenue],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -60, GETDATE()) THEN [Hours] ELSE 0 END) as [Last60DaysHours],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -60, GETDATE()) THEN [Revenue] ELSE 0 END) as [Last60DaysRevenue],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -90, GETDATE()) THEN [Hours] ELSE 0 END) as [Last90DaysHours],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -90, GETDATE()) THEN [Revenue] ELSE 0 END) as [Last90DaysRevenue]
FROM #invoice
GROUP BY [CompanyID]
) A
I think this approach should work for you. I know I had to simply the problem to illustrate the way I would approach a query like this in my answer, so if you need me to expand on it please let me know.

Existing SQL Server 2008 script improvement

SQL Server 2008
I have two tables with OrderIds and ItemIds. I need a resulting table with each OrderId from forst table linked with OrderId from second table where the number of identical ItemIds is maximum.
I did a script that does this using two loops but if the number of OrderIds in those tables is big (~1000) it means the loop has to be run 1000x1000 times, which might be too long. Ca this be achieved in a better way?
See my below my already written script:
drop table #Match, #OrderRec, #OrderSent
create table #Match(
OrderIdRec int NULL,
OrderIdSent int NULL)
create table #OrderRec(
OrderIdRec int NOT NULL,
ItemId int NULL)
create table #OrderSent(
OrderIdSent int NOT NULL,
ItemId int NULL)
insert #OrderRec values (1, 1)
insert #OrderRec values (1, 5)
insert #OrderRec values (1, 7)
insert #OrderRec values (1, 4)
insert #OrderRec values (1, 15)
insert #OrderRec values (1, 10)
insert #OrderRec values (2, 21)
insert #OrderRec values (2, 15)
insert #OrderRec values (2, 21)
insert #OrderRec values (2, 26)
insert #OrderRec values (5, 4)
insert #OrderRec values (5, 3)
insert #OrderRec values (5, 12)
insert #OrderRec values (5, 1)
insert #OrderSent values (121, 1)
insert #OrderSent values (121, 2)
insert #OrderSent values (121, 5)
insert #OrderSent values (121, 10)
insert #OrderSent values (121, 9)
insert #OrderSent values (122, 6)
insert #OrderSent values (122, 7)
insert #OrderSent values (122, 9)
insert #OrderSent values (122, 11)
insert #OrderSent values (142, 1)
insert #OrderSent values (142, 12)
insert #OrderSent values (142, 4)
insert #OrderSent values (142, 11)
set nocount on
declare #OrderIdRec int,
#OrderIdSent int,
#cnt numeric(10),
#cnt_max numeric(10),
#OrderIdSentMax int
select #OrderIdRec = MIN(OrderIdRec)
from #OrderRec
while ISNULL(#OrderIdRec,0) > 0
begin
select #OrderIdSent = MIN(OrderIdSent)
from #OrderSent
set #cnt_max = 0
set #OrderIdSentMax = NULL
while ISNULL(#OrderIdSent,0) > 0
begin
set #cnt = 0
select #cnt = COUNT(*)
from #OrderRec r
inner join #OrderSent t
on t.ItemId = r.ItemId
where r.OrderIdRec = #OrderIdRec
and t.OrderIdSent = #OrderIdSent
if isnull(#cnt, 0) > #cnt_max
begin
set #cnt_max = #cnt
set #OrderIdSentMax = #OrderIdSent
end
select #OrderIdSent = MIN(OrderIdSent)
from #OrderSent
where OrderIdSent > #OrderIdSent
end
insert #Match(
OrderIdRec,
OrderIdSent)
values (#OrderIdRec, #OrderIdSentMax)
select #OrderIdRec = MIN(OrderIdRec)
from #OrderRec
where OrderIdRec > #OrderIdRec
end
select *
from #Match
order by OrderIdRec
The actual script starts with set nocount on, what is before is just to create a set of data to play with.
The result is:
OrderIdRec OrderIdSent
1 121
2 NULL
5 142
;WITH s AS
(
SELECT OrderIdRec, OrderIdSent,
rn = ROW_NUMBER() OVER (PARTITION BY OrderIdRec ORDER BY c DESC)
FROM
(
SELECT r.OrderIdRec, s.OrderIdSent,
c = COUNT(*) OVER (PARTITION BY r.OrderIdRec, s.OrderIdSent)
FROM #OrderRec AS r
INNER JOIN #OrderSent AS s
ON r.ItemId = s.ItemId
) AS s2
),
d AS (SELECT OrderIdRec FROM #OrderRec GROUP BY OrderIdRec)
SELECT d.OrderIdRec, s.OrderIdSent
FROM d LEFT OUTER JOIN s
ON d.OrderIdRec = s.OrderIdRec AND s.rn = 1
ORDER BY d.OrderIdRec;
The following query gets the counts for all pairs between the two tables:
select orec.OrderId, osent.OrderId, count(*) as cnt
from OrderRec orec join
OrderSent osent
on orec.itemId = osent.itemId
group by orec.OrderId, osent.OrderId;
The following gets the highest cnt value for each orec.OrderId:
select oo.*
from (select orec.OrderId, osent.OrderId, count(*) as cnt,
row_number() over (partition by orec.OrderId, osent.OrderId order by count(*) desc
) as seqnum
from OrderRec orec join
OrderSent osent
on orec.itemId = osent.itemId
group by orec.OrderId, osent.OrderId
) oo
where seqnum = 1;

Help with a query

Based on the following table
ID Effort Name
-------------------------
1 1 A
2 1 A
3 8 A
4 10 B
5 4 B
6 1 B
7 10 C
8 3 C
9 30 C
I want to check if the total effort against a name is less than 40 then add a row with effort = 40 - (Total Effort) for the name. The ID of the new row can be anything. If the total effort is greater than 40 then trucate the data for one of the rows to make it 40.
So after applying the logic above table will be
ID Effort Name
-------------------------
1 1 A
2 1 A
3 8 A
10 30 A
4 10 B
5 4 B
6 1 B
11 25 B
7 10 C
8 3 C
9 27 C
I was thinking of opening a cursor, keeping a counter of the total effort, and based on the logic insert existing and new rows in another temporary table.
I am not sure if this is an efficient way to deal with this. I would like to learn if there is a better way.
I think the first part could be done this way:
INSERT INTO tbl(Effort, Name)
SELECT 40 - SUM(Effort), Name
FROM tbl
GROUP BY Name
HAVING SUM(Effort) < 40)
The second part is harder. Perhaps you could do something like this instead?
INSERT INTO tbl(Effort, Name)
SELECT 40 - SUM(Effort), Name
FROM tbl
GROUP BY Name
HAVING SUM(Effort) <> 40)
What this does is, rather than making changes to your actual data, adds a row with a negative number for the Name if the total effort is > 40 hours, or a positive value if it is < 40 hours. This seems much safer for your data integrity than messing with the original values.
In SQL Server 2008, this may be done with a single MERGE statement:
DECLARE #efforts TABLE (id INT NOT NULL PRIMARY KEY, effort INT NOT NULL, name CHAR(1))
INSERT
INTO #efforts
VALUES (1, 1, 'A'),
(2, 1, 'A'),
(3, 8, 'A'),
(4, 10, 'B'),
(5, 4, 'B'),
(6, 1, 'B'),
(7, 10, 'C'),
(8, 3, 'C'),
(9, 30, 'C'),
(10, 60, 'C')
SELECT *
FROM #efforts
ORDER BY
name, id
;WITH total AS
( SELECT *
FROM #efforts e
UNION ALL
SELECT ROW_NUMBER() OVER(ORDER BY name) +
(
SELECT MAX(id)
FROM #efforts
),
40 - SUM(effort),
name
FROM #efforts
GROUP BY
name
HAVING SUM(effort) < 40
),
source AS
(
SELECT *,
(
SELECT SUM(effort)
FROM total ep
WHERE ep.name = e.name
AND ep.id <= e.id
) AS ce,
COALESCE(
(
SELECT SUM(effort)
FROM total ep
WHERE ep.name = e.name
AND ep.id < e.id
), 0) AS cp
FROM total e
)
MERGE
INTO #efforts e
USING source s
ON e.id = s.id
WHEN MATCHED AND 40 BETWEEN cp AND ce THEN
UPDATE
SET e.effort = s.effort + 40 - ce
WHEN MATCHED AND cp > 40 THEN
DELETE
WHEN NOT MATCHED BY TARGET THEN
INSERT (id, effort, name)
VALUES (id, effort, name);
SELECT *
FROM #efforts
ORDER BY
name, id
In SQL Server 2005, you'll need two statements (in one transaction):
DECLARE #efforts TABLE (id INT NOT NULL PRIMARY KEY, effort INT NOT NULL, name CHAR(1))
INSERT
INTO #efforts
VALUES (1, 1, 'A')
INSERT
INTO #efforts
VALUES (2, 1, 'A')
INSERT
INTO #efforts
VALUES (3, 8, 'A')
INSERT
INTO #efforts
VALUES (4, 10, 'B')
INSERT
INTO #efforts
VALUES (5, 4, 'B')
INSERT
INTO #efforts
VALUES (6, 1, 'B')
INSERT
INTO #efforts
VALUES (7, 10, 'C')
INSERT
INTO #efforts
VALUES (8, 3, 'C')
INSERT
INTO #efforts
VALUES (9, 30, 'C')
INSERT
INTO #efforts
VALUES (10, 60, 'C')
;WITH total AS
(
SELECT *,
COALESCE(
(
SELECT SUM(effort)
FROM #efforts ep
WHERE ep.name = e.name
AND ep.id <= e.id
), 0) AS cp
FROM #efforts e
)
DELETE
FROM total
WHERE cp > 40
INSERT
INTO #efforts
SELECT (
SELECT MAX(id)
FROM #efforts
) +
ROW_NUMBER() OVER (ORDER BY name),
40 - SUM(effort),
name
FROM #efforts
GROUP BY
name
HAVING SUM(effort) < 40
SELECT *
FROM #efforts
ORDER BY
name, id
This will give you the names that need modify:
SELECT Name, SUM(Effort)
FROM Table
GROUP BY Name
HAVING SUM(Effort) < 40
Select this into a temp table, Add a column for 40 - SUM, then create an insert statement from that. Much better than a cursor.
This will do the first part:
Insert Into dbo.Test (Name, Effort)
Select t.Name, 40 - SUM(t.Effort)
From dbo.Test t
Group By t.Name
Having SUM(t.Effort) < 40
And this will do the second part:
Update a
Set a.Effort = a.Effort - b.AmountToDeduct
From dbo.Test a
Join (
Select t.Name, (40 - SUM(t.Effort)) as 'AmountToDeduct'
From dbo.Test t
Group By t.Name
Having SUM(t.Effort) > 40
)b on a.Name = b.Name
Where a.ID = (Select MAX(c.ID)
From dbo.Test c
Where c.Name = a.Name
)