SQL Aggregate on Two tables - sql

Table A has millions of records from 2014, Using Oracle
ID Sales_Amount Sales_Date
1 10 20/11/2014
1 10 22/11/2014
1 10 22/12/2014
1 10 22/01/2015
1 10 22/02/2015
1 10 22/03/2015
1 10 22/04/2015
1 10 22/05/2015
1 10 22/06/2015
1 10 22/07/2015
1 10 22/08/2015
1 10 22/09/2015
1 10 22/10/2015
1 10 22/11/2015
Table B
ID ID_Date
1 22/11/2014
2 01/12/2014
I want sum of totals for 6 months as well as 1 year for ID 1 taking starting
date from Table B as 22/11/2014
Output Sales_Amount_6Months Sales_Amount_6Months
1 70 130
Shall I use add_months in this case?

Yes, you can use ADD_MONTHS() and conditional aggregation :
SELECT b.id,
SUM(CASE WHEN a.sales_date between b.id_date AND ADD_MONTHS(b.id_date,6) THEN a.sales_amount ELSE 0 END) as sales_6_month,
SUM(CASE WHEN a.sales_date between b.id_date AND ADD_MONTHS(b.id_date,12) THEN a.sales_amount ELSE 0 END) as sales_12_month
FROM TableB b
JOIN TableA a
ON(b.id = a.id)
GROUP BY b.id

Related

Update group of rows to delete a specific total amount from the sum of the rows SQL

In example I want to subtract the Subtracted value in TABLE 1 from the Sum Total of the ValueBefore in Table 2 grouped by the GroupID. Value subtracted from first row and if remainder then that is subtracted off the following row in group (and so on) until total is subtracted.
Is there a way to do this without iterating through rows to get the remainder?
My table 1:
TABLE 1 of subtracting value
GroupID Subtracted
1 32
2 30
My table 2:
ID GroupID ValueBefore Reduction ValueAfter
1 1 10 -10 0
2 1 15 -15 0
3 1 5 -5 0
4 1 5 -2 3
5 2 40 -30 10
6 2 30 0 30
I would use a cumulative sum and some relatively simple comparisons:
select t2.*,
(case when t2.running_value - t2.valuebefore < t1.subtracted
then 0
when t2.running_value < t1.subtracted
then t2.running_value - t1.subtracted
else t2.valuebefore
end) as reduction,
(case when t2.running_value < t1.subtracted then 0
else t2.running_value - t1.subtracted
end) as ValueAfter
from (select t2.*,
sum(valuebefore) over (partition by groupid order by id) as running_value
from t2
) t2 join
t1
on t2.groupid = t1.groupid;
One method is to use a LEFT JOIN in order to calculate the running total of ValueBefore.
This query:
SELECT t2.ID, t2.GroupID, t2.ValueBefore, r.ValueBefore AS rValueBefore
FROM Table2 AS t2
LEFT JOIN Table2 AS r
ON t2.GroupID = r.GroupID AND t2.ID >= r.ID
ORDER BY t2.ID, r.ID
produces this output:
ID GroupID ValueBefore rValueBefore
------------------------------------
1 1 10 10
2 1 15 10
2 1 15 15
3 1 5 10
3 1 5 15
3 1 5 5
4 1 5 10
4 1 5 15
4 1 5 5
4 1 5 5
5 2 40 40
6 2 30 40
6 2 30 30
You can now calculate the running total if you group by ID and SUM on rValueBefore:
SELECT t2.ID, t2.GroupID, t2.ValueBefore, SUM(r.ValueBefore) AS Total
FROM Table2 AS t2
LEFT JOIN Table2 AS r
ON t2.GroupID = r.GroupID AND t2.ID >= r.ID
GROUP BY t2.ID, t2.GroupID, t2.ValueBefore
Output:
ID GroupID ValueBefore Total
-----------------------------
1 1 10 10
2 1 15 25
3 1 5 30
4 1 5 35
5 2 40 40
6 2 30 70
You can now calculate ValueAfter using:
SELECT t.ID, t.GroupID, t.ValueBefore, Total,
Subtracted,
CASE
WHEN Total - t1.Subtracted < 0 THEN 0
ELSE Total - t1.Subtracted
END AS ValueAfter
FROM
(
SELECT t2.ID, t2.GroupID, t2.ValueBefore, SUM(r.ValueBefore) AS Total
FROM Table2 AS t2
LEFT JOIN Table2 AS r
ON t2.GroupID = r.GroupID AND t2.ID >= r.ID
GROUP BY t2.ID, t2.GroupID, t2.ValueBefore
) as t
JOIN Table1 AS t1 ON t.GroupID = t1.GroupID
ORDER BY ID
Output:
ID GroupID ValueBefore Total Subtracted ValueAfter
-------------------------------------------------------
1 1 10 10 32 0
2 1 15 25 32 0
3 1 5 30 32 0
4 1 5 35 32 3
5 2 40 40 30 10
6 2 30 70 30 40
Demo here

Sum Amount, display full resultset where Groups of Column Values Match in Same Table Oracle SQL

I need to get the sum of all TOTAL fields where the ID, RECNO and DRCR fields have the same value, while also displaying these fields in the result set.
eg
ID RECNO SECRECNO DRCR TOTAL
1 9 5 D 25
1 9 12 D 22
1 9 6 C 33
1 9 5 D 50
1 8 2 D 12
1 8 2 C 23
2 9 5 D 100
So the results of the query should be
ID RECNO SECRECNO DRCR SUM(TOTAL)
1 9 5 D 75
1 9 12 D 22
1 9 6 C 33
1 8 2 D 12
1 8 2 C 23
2 9 5 D 100
This query will give the results set, without the TOTAL:
select distinct t1.recno, t1.secrecno
from table t1, table t2
where t1.recno = t2.recno and t.id = '1' and t1.drcr = 'D'
But I can't see how to SUM the TOTAL of these results.
How to do this?
select t1.id,
t1.recno,
t1.secrecno,
t1.drcr,
SUM( TOTAL )
from table t1
INNER JOIN
table t2
ON ( t1.recno = t2.recno )
WHERE t1.id = '1'
AND t1.drcr = 'D'
GROUP BY
t1.id,
t1.recno,
t1.secrecno,
t1.drcr

Update table records with accumulated result

Lets say I have a table Tbl (Represents simple timelogs for work made on different customers)
Five columns
Id: int
TimeUse: float
IdCustomer: int
Created: DateTime
TimeCalc: float
I have a number of records in this table, (TimeCalc is initialized to value = 0)
What I want my SQL to do is:
when TimeUse for all foregoing records on a specific customer accumulates to a value < 10 then the value in TimeCalc should be 0
when TimeUse for all foregoing records on a specific customer accumulates to a value >= 10 then the value in TimeCalc should be = TimeUse for the record...
I have messed around with Case routines with subqueries, but can't get it working.
BEFORE
Id TimeUse IdCustomer Created TimeCalc
1 2 1 14/09/09 0
2 5 2 14/09/10 0
3 2 1 14/09/11 0
4 5 2 14/09/12 0
5 4 1 14/09/13 0
6 2 2 14/09/14 0
7 4 1 14/09/15 0
8 1 1 14/09/16 0
9 3 2 14/09/17 0
10 2 1 14/09/18 0
11 4 2 14/09/19 0
AFTER
Id TimeUse IdCustomer Created TimeCalc
1 2 1 14/09/09 0
2 5 2 14/09/10 0
3 2 1 14/09/11 0
4 5 2 14/09/12 0
5 4 1 14/09/13 0
6 2 2 14/09/14 2
7 4 1 14/09/15 0
8 1 1 14/09/16 1
9 3 2 14/09/17 3
10 2 1 14/09/18 2
11 4 2 14/09/19 4
Can this be solved in an SQL update?
In SQL Server 2012+, you can do this with a cumulative sum:
select Id, TimeUse, IdCustomer, Created,
(case when sum(timeuse) over (partition by idcustomer order by id) < 10 then 0
else timeuse
end) as timecalc
from table t;
You can do the same thing in earlier versions using outer apply or a subquery.
If you want an update, just use a CTE:
with toupdate as (
select t.*,
(case when sum(timeuse) over (partition by idcustomer order by id) < 10 then 0
else timeuse
end) as new_timecalc
from table t
)
update toupdate
set timecalc = new_timecalc;
EDIT:
The following will work in any version of SQL Server:
with toupdate as (
select t.*,
(case when (select sum(t2.timeuse)
from table t2
where t2.idcustomer = t.idcustomer and
t2.id <= t.id
) < 10 then 0
else timeuse
end) as new_timecalc
from table t
)
update toupdate
set timecalc = new_timecalc;

Week based count

I have a requirement to retrieve the data in the below fashion
Weeks delay_count
0 6
1 0
2 3
3 4
4 0
5 1
6 0
7 0
8 0
9 0
10 2
11 0
12 0
13 0
14 0
15 3
Here weeks is the hard coded column from 0 to 15 and delay_count is the derived column. I have a column delay_weeks. Based on the values in this column I need to populate the values in the delay_count column (derived column)
delay_weeks column values are below.
blank
blank
blank
2
10
5
blank
3
2
10
2
3
3
3
0
0
15
22
29
Conditions:
When delay_weeks is blank or 0 then count in the delay_count column should be 1
When delay_weeks is 3 then in the delay_count column the count should be 1 under week 3
When delay_weeks is 10 then in the delay_count column the count should be 1 under week 10
When delay_weeks is greater than or equal to 15 then in the delay_count column the count should be 1 under week 15.
I wrote code like below
SELECT "Weeks", a."delay_count"
FROM (SELECT LEVEL AS "Weeks"
FROM DUAL
CONNECT BY LEVEL <= 15) m,
(SELECT VALUE, COUNT (VALUE) AS "delay_numbers"
FROM (SELECT CASE
WHEN attr11.VALUE >= 15
THEN '15'
ELSE attr11.VALUE
END
VALUE
FROM docs,
(SELECT object_id, VALUE, attribute_type_id
FROM ATTRIBUTES
WHERE attribute_type_id =
(SELECT attribute_type_id
FROM attribute_types
WHERE name_display_code =
'ATTRIBUTE_TYPE.DELAY IN WEEKS')) attr11
WHERE docs.obj_id = attr11.object_id(+)
GROUP BY VALUE) a
WHERE m."Weeks" = a.VALUE(+)
select
weeks,
nvl(cnt, 0) as delay_count
from
(select level-1 as weeks from dual connect by level < 17)
left join (
select
nvl(least(attr11.value, 15), 0) as weeks,
count(0) as cnt
from
DOCS
left join (
ATTRIBUTES attr11
join ATTRIBUTE_TYPES atr_tp using(attribute_type_id)
)
on atr_tp.name_display_code = 'ATTRIBUTE_TYPE.DELAY IN WEEKS'
and docs.obj_id = attr11.object_id
group by nvl(least(attr11.value, 15), 0)
) using(weeks)
order by 1
Reverse-engineering the relevant parts of the table definitions, I think this gives you what you want:
select t.weeks, count(delay) as delay_count
from (select level - 1 as weeks from dual connect by level <= 16) t
left join (
select case when a.value is null then 0
when to_number(a.value) > 15 then 15
else to_number(a.value) end as delay
from docs d
left join (
select a.object_id, a.value
from attributes a
join attribute_types at on at.attribute_type_id = a.attribute_type_id
where at.name_display_code = 'ATTRIBUTE_TYPE.DELAY IN WEEKS'
) a on a.object_id = d.obj_id
) delays on delays.delay = t.weeks
group by t.weeks
order by t.weeks;
With what I think is matching data I get:
WEEKS DELAY_COUNT
---------- -----------
0 6
1 0
2 3
3 4
4 0
5 1
6 0
7 0
8 0
9 0
10 2
11 0
12 0
13 0
14 0
15 3
But obviously since you haven't given the real table structures I'm guessing a bit on the relationships. Obligatory SQL Fiddle.

How to group sums by weekday in MySQL?

I have a table like this:
id | timestamp | type
-----------------------
1 2010-11-20 A
2 2010-11-20 A
3 2010-11-20 B
4 2010-11-21 A
5 2010-11-21 C
6 2010-11-27 B
and I need to count the rows for each type, grouped by weekday; like this:
weekday | A | B | C
--------------------------
5 2 2 0 -- the B column equals 2 because nov 20 and nov 27 are saturday
6 1 0 1
What would be the simplest solution for this?
I don't mind using views, variables, subqueries, etc.
Use:
SELECT WEEKDAY(t.timestamp) AS weekday,
SUM(CASE WHEN t.type = 'A' THEN 1 ELSE 0 END) AS a,
SUM(CASE WHEN t.type = 'B' THEN 1 ELSE 0 END) AS b,
SUM(CASE WHEN t.type = 'C' THEN 1 ELSE 0 END) AS c
FROM YOUR_TABLE t
GROUP BY WEEKDAY(t.timestamp)