I have an aggregation problem that can probably best be described with some example data.
Below is a dataset with transports, identified by trp_no. Each such transport is loaded in a container. A container may hold multiple such transports, and in this example any transport may only be loaded in one container.
TRP_NO TRANSPORT_VOLUME COUNTRY CONTAINER_ID CONTAINER_MAX
------ ---------------- ------- ------------ -------------
1 10 SE A 80
2 20 SE A 80
3 30 SE A 80
The following keys (or functional dependencies) exists in the dataset:
trp_no -> {transport_volume, country, container_id}
container_id -> {container_max}
I want to calculate Filling Rate per Country, which is calculated as transported volume divided by the capacity. Translated into SQL, this becomes:
with sample_data as(
select 1 as trp_no, 10 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 2 as trp_no, 20 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 3 as trp_no, 30 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual
)
select country
,sum(transport_volume) / sum(container_max)
from sample_data
group
by country;
...which returns (10+20+30) / (80+80+80) = 25%. Which is not what I want, because all transports used the same container_id, and my query triple-counted the capacity.
The result I want is (10+20+30) / 80 = 75%.
So, I only want to sum container_max once for each container_id within the group.
Any ideas on how to fix the query?
This uses Rachcha's bigger sample set, which I think is necessary to really test this problem.
with sample_data as(
select 1 as trp_no, 10 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 2 as trp_no, 20 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 3 as trp_no, 30 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 4 as trp_no, 10 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 5 as trp_no, 20 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 6 as trp_no, 30 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 7 as trp_no, 10 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual union all
select 8 as trp_no, 15 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual union all
select 9 as trp_no, 20 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual
),
country_container_sum as
(
select country, sum(container_max) sum_container_max
from
(
select distinct country, container_id, container_max
from sample_data
)
group by country
),
country_transport_volume_sum as
(
select country, sum(transport_volume) sum_transport_volume
from sample_data
group by country
)
select country, sum_transport_volume / sum_container_max rate
from country_container_sum
join country_transport_volume_sum using (country);
Results:
COUNTRY RATE
------- ----
SE 0.666666666666667
AU 0.9
I added a little more sample data for illustrating a minor fix in the query that solved it-
with sample_data as(
select 1 as trp_no, 10 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 2 as trp_no, 20 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 3 as trp_no, 30 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 4 as trp_no, 10 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 5 as trp_no, 20 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 6 as trp_no, 30 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 7 as trp_no, 10 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual union all
select 8 as trp_no, 15 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual union all
select 9 as trp_no, 20 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual
)
select country
,sum(transport_volume / container_max) -- Note the change here
from sample_data
group
by country;
OUTPUT:
COUNTRY SUM(TRANSPORT_VOLUME/CONTAINER_MAX)
------- -----------------------------------
SE 1.35
AU .9
EDIT:
As I see your sample data, I think you need a bit of normalization in your database. The columns for a container and columns for a transport trip should reside in separate tables like this:\
TABLE CONTAINER (
container_id VARCHAR2 / INTEGER,
container_max INTEGER,
country VARCHAR2
)
TABLE trip (
trp_no INTEGER,
transport_volume INTEGER,
container_id VARCHAR2 / INTEGER REFERENCES container.container_id
)
EDIT 2:
If you want to specifically sum up the transport volumes according to the containers' capacities, you can use something like the following query (with the same sample data table sample_data from above):
select d.country,
(select sum(t.transport_volume)
from sample_data t
where t.country = d.country) /
(select sum(c.container_max)
from ( select country, container_max
from sample_data
group by container_id, country, container_max
) c
where c.country = d.country) as col1
from sample_data d
group by d.country;
OUTPUT:
COUNTRY COL1
------- -----------
SE 0.666666667
AU 0.9
This approach, while other ways are simpler, uses analytic functions. I only edit with this approach because, while jonearle's response gives you the correct output, you responded saying that you wanted an approach that uses analytic functions. This approach uses analytic functions.
However, you cannot use aggregate functions nor the group by clause with analytic functions (the idea itself doesn't make sense), without adding a second layer to the query. Depending on what other similar queries you want to run, this might be easier for you as a template query, however it's hard to tell without knowing what other similar queries you're running.
with sample_data as(
select 1 as trp_no, 10 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 2 as trp_no, 20 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 3 as trp_no, 30 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 4 as trp_no, 10 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 5 as trp_no, 20 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 6 as trp_no, 30 as transport_volume, 'SE' as country, 'B' as container_id, 100 as container_max from dual union all
select 7 as trp_no, 10 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual union all
select 8 as trp_no, 15 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual union all
select 9 as trp_no, 20 as transport_volume, 'AU' as country, 'C' as container_id, 50 as container_max from dual
)
, sub as(
select x.*, sum(x.cont_mx_n) over (partition by country order by country, container_id, trp_no) as cont_mx
from(
select country
, container_id
, trp_no
, sum(transport_volume) over (partition by country order by country, container_id, trp_no) as transp_vol
, case when lead(container_id,1) over (partition by country order by country, container_id, trp_no) = container_id
then null
else container_max end as cont_mx_n
, row_number() over (partition by country order by country, container_id, trp_no) as maxchk
from sample_data
order by country, container_id, trp_no) x)
select country, transp_vol / cont_mx as rate
from sub y
where y.maxchk = (select max(x.maxchk) from sub x where x.country = y.country);
Result of the above is:
AU 0.9
SE 0.666666666666667
I tried this:
with sample_data as(
select 1 as trp_no, 10 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 2 as trp_no, 20 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual union all
select 3 as trp_no, 30 as transport_volume, 'SE' as country, 'A' as container_id, 80 as container_max from dual
)
select country
,sum(transport_volume) / container_max
from sample_data
group
by country, container_max;
The result was the expected.
ps: some nice guy remembered us about also grouping container_id, which won't affect the result in this case, but might be needed in other cases :-)
Related
I want to get summary numbers from the cust_detail table if a specific invoice_code appears in invoice_detail.
In this example, I'd like to report cust_detail summaries only for batches 10 and 20 because they are the ones with invoice_code='9999'. But the duplication in the invoice_detail table is skewing my numbers.
with
invoice_detail as
(
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '20' as invoice_batch, '1111' as invoice_code from dual union all
select '30' as invoice_batch, '9999' as invoice_code from dual
),
cust_detail as
(
select '1' as cust_id, '10' as invoice_batch, 40 as points_paid, 30 as points_earned, 30 as points_delivered from dual union all
select '1' as cust_id, '20' as invoice_batch, 10 as points_paid, 10 as points_earned, 10 as points_delivered from dual union all
select '1' as cust_id, '30' as invoice_batch, 20 as points_paid, 15 as points_earned, 5 as points_delivered from dual
)
select cust_id,
sum(points_paid) over (partition by c.invoice_batch
order by cust_id) batch_total
from cust_detail c
inner join invoice_detail i on c.invoice_batch=i.invoice_batch
where i.invoice_code = '9999';
Desired results:
CUST_ID PAID EARNED DELIVERED TOT_PAID TOT_EARNED TOT_DELIVERED
--------- ------ -------- ----------- ---------- ------------ ---------------
1 40 30 30 60 45 40
1 20 15 5 60 45 40
You can remove duplications from invoice_detail with distinct before join:
with invoice_detail as
(
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '10' as invoice_batch, '9999' as invoice_code from dual union all
select '20' as invoice_batch, '1111' as invoice_code from dual union all
select '30' as invoice_batch, '9999' as invoice_code from dual
),
cust_detail as
(
select '1' as cust_id, '10' as invoice_batch, 40 as points_paid, 30 as points_earned, 30 as points_delivered from dual union all
select '1' as cust_id, '20' as invoice_batch, 10 as points_paid, 10 as points_earned, 10 as points_delivered from dual union all
select '1' as cust_id, '30' as invoice_batch, 20 as points_paid, 15 as points_earned, 5 as points_delivered from dual
)
select cust_id
,points_paid
,points_earned
,points_delivered
,sum(points_paid) over (partition by c.cust_id) as tot_paid
,sum(points_earned) over (partition by c.cust_id) as tot_earned
,sum(points_delivered) over (partition by c.cust_id) as tot_delivered
from cust_detail c
join (select distinct * from invoice_detail) i
on c.invoice_batch=i.invoice_batch
where i.invoice_code = '9999';
Note that summaries include batches 10 and 30 because batch 20 with invoice_code='1111'.
SQL Fiddle
I am not sure what your desired results have to do with your query. But, I would expect your query to look more like this:
select cust_id,
sum(points_paid) over (partition by cust_id) as batch_total
from cust_detail c inner join
invoice_detail i
on c.invoice_batch=i.invoice_batch
where i.invoice_code = '9999' ;
Business Rules: Get total cust_points for customer only if total(trans_amount) for a trans_code grouping > 0.
For customer #1, the summary at date_code level (code 10) is > 0 so cust_points total = 70.
For customer #2 only code 20 group totals > 0 so total only 75 total cust_points
Here's my query:
with customers as
(select '1' as cust_id, 10 as date_code, 30 as cust_points from dual union all
select '1' as cust_id, 10 as date_code, 40 as cust_points from dual union all
select '1' as cust_id, 20 as date_code, 22 as cust_points from dual union all --These points should not total because trans_amount sum for code 20 is less than 0
select '1' as cust_id, 40 as date_code, 33 as cust_points from dual union all -- These points should not total because there is not trans_amounts > 0 for date_code
select '2' as cust_id, 10 as date_code, 20 as cust_points from dual union all
select '2' as cust_id, 20 as date_code, 65 as cust_points from dual union all
select '2' as cust_id, 20 as date_code, 10 as cust_points from dual
),
transactions_row as
(
select '1' as cust_id, '10' as trans_code, 10.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -15.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -20.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -10.00 as trans_amount from dual union all
select '1' as cust_id, '30' as trans_code, 30.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -20.00 as trans_amount from dual union all
select '2' as cust_id, '10' as trans_code, -50.00 as trans_amount from dual union all
select '2' as cust_id, '20' as trans_code, 20.00 as trans_amount from dual
)
select cust_id,
sum(cust_points)
from customers
where cust_id in
(
select cust_id
from (
select cust_id, trans_code, sum(trans_amount)
from transactions_row
group by cust_id, trans_code
having sum(trans_amount) > 0
)
)
group by cust_id
Desired Results
CUST_ID CUST_POINTS
1 70 /* (30 because total trans_amount for tran_code(10) > 0 +
40 because total trans_amount for tran_code(10) > 0) */
2 75 /* Do not include the 20 points because total trans_amt for 10 < 0 */
Here's one way using exists:
select cust_id,
sum(cust_points)
from customers c
where exists (
select 1
from transactions_row tr
where tr.trans_code = c.date_code
and tr.cust_id = c.cust_id
group by tr.trans_code, tr.cust_id
having sum(tr.trans_amount) > 0
)
group by cust_id
SQL Fiddle Demo
I need to calculate the price change of an item (both in cost and % change) over the last three years.
The table has four fields:
SKU_no, Date_updated, Price, Active_flag
When the Active_flag field is A, the item is active, when I it is inactive. Some items haven't changed prices in years so they won't have three years of entries with an inactive flag.
Sample table
SKU_NO Update_date Price Active_flag
30 1/1/1999 40.8 I
33 1/1/2014 70.59 A
33 1/1/2013 67.23 I
33 1/1/2012 60.03 I
33 1/1/2011 55.08 I
33 1/1/2010 55.08 I
34 1/1/2009 51 A
36 1/1/2014 70.59 A
36 1/1/2013 67.23 I
36 1/1/2012 60.03 I
38 1/1/2002 43.32 A
38 1/1/2001 43.32 I
38 4/8/2000 43.32 I
38 1/1/1999 43.32 I
39 1/1/2014 73.08 A
39 1/1/2013 69.6 I
39 1/1/2012 62.13 I
39 1/1/2011 57 I
39 1/1/2010 57 I
39 1/1/2009 52.8 I
This is the first query I wrote. I'm not too familiar with complex calculations
select
s.VENDOR,
s.FISCAL_YEAR,
s.FISCAL_MONTH_NO,
s.FISCAL_YEAR||'_'||FISCAL_MONTH_NO as PERIOD,
CASE WHEN S.COST_USED_FLAG IN ('CONTRACT') THEN 'CONTRACT' ELSE 'NON-CONTRACT' END AS CONTRACT_TYPE,
CASE WHEN ((s.FISCAL_YEAR = 2014 AND FISCAL_MONTH_NO <=9) OR (FISCAL_YEAR = 2013 AND FISCAL_MONTH_NO >=10)) THEN 'CP_1'
WHEN ((s.FISCAL_YEAR = 2013 AND FISCAL_MONTH_NO <= 9) OR (FISCAL_YEAR = 2012 AND FISCAL_MONTH_NO >=10)) THEN 'CP_2'
WHEN ((s.FISCAL_YEAR = 2012 AND FISCAL_MONTH_NO <= 9) OR (FISCAL_YEAR = 2011 AND FISCAL_MONTH_NO >=10)) THEN 'CP_3'
ELSE 'NULL' END CAGR_PERIODS,
CASE WHEN s.MARKET IN ('PO', 'SC', 'OC') THEN 'PC' ELSE 'EC' END AS MARKET_TYPE,
s.MARKET,
s.COST_PLUS_FLAG,
s.COST_USED_FLAG,
LPAD(S.PC_ITEM_NO,6,'0') AS NEW_ITEM_NO,
s.PC_ITEM_NO,
i.ITEM_NO,
i.VEND_CAT_NUM,
i.DESCRIPTION,
s.PC_PROD_CAT,
s.PC_PROD_SUBCAT,
i.SELL_UOM,
i.QTY_PER_SELL_UOM,
i.PRIMARY_UOM,
i.HEAD_CONV_FACT,
SUM(s.QTY_EACH) AS QUANTITY_SOLD,
SUM(s.EXT_GROSS_COGS) AS TOTAL_COGS,
SUM(s.EXT_GROSS_COGS)/ SUM(s.QTY_EACH) as NET_SALES,
SUM(s.EXT_SALES)/ SUM(s.QTY_EACH) as ASP,
SUM(s.EXT_SALES) AS TOTAL_SALES,
SUM(S.EXT_SALES) - SUM(S.EXT_GROSS_COGS) as GROSS_PROFIT
from SIXSIGMA.CIA_ALL_SALES_TREND_DATA s
INNER JOIN MGMSH.ITEM i
ON S.PC_ITEM_NO = I.ITEM_NO
WHERE S.VENDOR = 'BD' AND
(S.EXT_SALES IS NOT NULL AND S.FISCAL_YEAR IN ('2013','2012','2011'))
GROUP BY
s.VENDOR,
s.FISCAL_YEAR,
s.FISCAL_MONTH_NO,
s.FISCAL_YEAR||'_'||FISCAL_MONTH_NO,
CASE WHEN s.MARKET IN ('PO', 'SC', 'OC') THEN 'PC' ELSE 'EC' END,
CASE WHEN S.COST_USED_FLAG IN ('CONTRACT') THEN 'CONTRACT' ELSE 'NON-CONTRACT' END,
CASE WHEN ((s.FISCAL_YEAR = 2014 AND FISCAL_MONTH_NO <=9) OR (FISCAL_YEAR = 2013 AND FISCAL_MONTH_NO >=10)) THEN 'CP_1'
WHEN ((s.FISCAL_YEAR = 2013 AND FISCAL_MONTH_NO <= 9) OR (FISCAL_YEAR = 2012 AND FISCAL_MONTH_NO >=10)) THEN 'CP_2'
WHEN ((s.FISCAL_YEAR = 2012 AND FISCAL_MONTH_NO <= 9) OR (FISCAL_YEAR = 2011 AND FISCAL_MONTH_NO >=10)) THEN 'CP_3'
ELSE 'NULL' END,
s.MARKET,
s.COST_USED_FLAG,
s.COST_PLUS_FLAG,
s.PC_ITEM_NO,
s.PC_PROD_CAT,
i.SELL_UOM,
i.QTY_PER_SELL_UOM,
i.PRIMARY_UOM,
i.HEAD_CONV_FACT,
i.DESCRIPTION,
i.VEND_CAT_NUM,
s.PC_PROD_SUBCAT,
i.ITEM_NO
ORDER BY s.PC_ITEM_NO,s.FISCAL_YEAR, s.FISCAL_MONTH_NO
There are several ways to approach this, but I would recommend a windowing function such as LAG or LEAD. With these functions, you can reference neighboring rows. For example:
lead(column, offset, default) over (partition by some_column order by column)
And in the example below:
lead(price, 1, price) over (partition by sku_no order by update_date desc)
Here is a working example with sample data:
with sample_data as (
select '30' sku_no, to_date('1/1/1999','DD/MM/YYYY') update_date, 40.8 price, 'I' active_flag from dual union all
select '33', to_date('1/1/2014','DD/MM/YYYY'), 70.59, 'A' from dual union all
select '33', to_date('1/1/2013','DD/MM/YYYY'), 67.23, 'I' from dual union all
select '33', to_date('1/1/2012','DD/MM/YYYY'), 60.03, 'I' from dual union all
select '33', to_date('1/1/2011','DD/MM/YYYY'), 55.08, 'I' from dual union all
select '33', to_date('1/1/2010','DD/MM/YYYY'), 55.08, 'I' from dual union all
select '34', to_date('1/1/2009','DD/MM/YYYY'), 51 , 'A' from dual union all
select '36', to_date('1/1/2014','DD/MM/YYYY'), 70.59, 'A' from dual union all
select '36', to_date('1/1/2013','DD/MM/YYYY'), 67.23, 'I' from dual union all
select '36', to_date('1/1/2012','DD/MM/YYYY'), 60.03, 'I' from dual union all
select '38', to_date('1/1/2002','DD/MM/YYYY'), 43.32, 'A' from dual union all
select '38', to_date('1/1/2001','DD/MM/YYYY'), 43.32, 'I' from dual union all
select '38', to_date('4/8/2000','DD/MM/YYYY'), 43.32, 'I' from dual union all
select '38', to_date('1/1/1999','DD/MM/YYYY'), 43.32, 'I' from dual union all
select '39', to_date('1/1/2014','DD/MM/YYYY'), 73.08, 'A' from dual union all
select '39', to_date('1/1/2013','DD/MM/YYYY'), 69.6 , 'I' from dual union all
select '39', to_date('1/1/2012','DD/MM/YYYY'), 62.13, 'I' from dual union all
select '39', to_date('1/1/2011','DD/MM/YYYY'), 57 , 'I' from dual union all
select '39', to_date('1/1/2010','DD/MM/YYYY'), 57 , 'I' from dual union all
select '39', to_date('1/1/2009','DD/MM/YYYY'), 52.8 , 'I' from dual)
select
sku_no,
update_date,
price,
lead(price,1, price) over (partition by sku_no order by update_date desc) prior_price, -- Showing the offset
price - lead(price,1, price) over (partition by sku_no order by update_date desc) price_difference, -- Calculate the difference
round((price - lead(price,1, price) over (partition by sku_no order by update_date desc)) * 100 /price, 2) percent_change -- Calculate the percentage
from sample_data
where update_date >= add_months(trunc(sysdate,'YYYY'),-36); -- You said in the last three years
You can also use LAG with a different order by sort. If you want to calculate the difference from three years prior, I would suggest using the KEEP function.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I have a table as below.
DATE_WORKED COUNTRY
1-Nov-13 United Kingdom
4-Nov-13 United Kingdom
5-Nov-13 India
6-Nov-13 India
7-Nov-13 India
8-Nov-13 United Kingdom
11-Nov-13 United Kingdom
12-Nov-13 India
13-Nov-13 India
14-Nov-13 India
15-Nov-13 United Kingdom
18-Nov-13 United Kingdom
19-Nov-13 India
20-Nov-13 India
21-Nov-13 India
22-Nov-13 United Kingdom
25-Nov-13 United Kingdom
26-Nov-13 India
27-Nov-13 India
28-Nov-13 India
29-Nov-13 United Kingdom
I am looking to find the start_date and end date for each stay in a country.
COUNTRY START_DATE END_Date
United Kingdom 1-Nov-13 4-Nov-13
India 5-Nov-13 7-Nov-13
United Kingdom 8-Nov-13 11-Nov-13
India 12-Nov-13 14-Nov-13
United Kingdom 15-Nov-13 18-Nov-13
India 19-Nov-13 21-Nov-13
United Kingdom 22-Nov-13 25-Nov-13
India 26-Nov-13 28-Nov-13
United Kingdom 29-Nov-13
Please help me with an SQL query to achieve this.
Thanks in advance.
Using Tabibitosan:
SQL> create table mytable (date_worked,country)
2 as
3 select to_date('1-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
4 select to_date('4-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
5 select to_date('5-Nov-13','dd-Mon-yy'), 'India' from dual union all
6 select to_date('6-Nov-13','dd-Mon-yy'), 'India' from dual union all
7 select to_date('7-Nov-13','dd-Mon-yy'), 'India' from dual union all
8 select to_date('8-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
9 select to_date('11-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
10 select to_date('12-Nov-13','dd-Mon-yy'), 'India' from dual union all
11 select to_date('13-Nov-13','dd-Mon-yy'), 'India' from dual union all
12 select to_date('14-Nov-13','dd-Mon-yy'), 'India' from dual union all
13 select to_date('15-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
14 select to_date('18-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
15 select to_date('19-Nov-13','dd-Mon-yy'), 'India' from dual union all
16 select to_date('20-Nov-13','dd-Mon-yy'), 'India' from dual union all
17 select to_date('21-Nov-13','dd-Mon-yy'), 'India' from dual union all
18 select to_date('22-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
19 select to_date('25-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
20 select to_date('26-Nov-13','dd-Mon-yy'), 'India' from dual union all
21 select to_date('27-Nov-13','dd-Mon-yy'), 'India' from dual union all
22 select to_date('28-Nov-13','dd-Mon-yy'), 'India' from dual union all
23 select to_date('29-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual
24 /
Table created.
SQL> with tabibitosan as
2 ( select row_number() over (order by date_worked)
3 - row_number() over (partition by country order by date_worked) grp
4 , date_worked
5 , country
6 from mytable
7 )
8 select country
9 , min(date_worked) start_date
10 , max(date_worked) end_date
11 from tabibitosan
12 group by country
13 , grp
14 order by start_date
15 /
COUNTRY START_DATE END_DATE
-------------- ------------------- -------------------
United Kingdom 01-11-2013 00:00:00 04-11-2013 00:00:00
India 05-11-2013 00:00:00 07-11-2013 00:00:00
United Kingdom 08-11-2013 00:00:00 11-11-2013 00:00:00
India 12-11-2013 00:00:00 14-11-2013 00:00:00
United Kingdom 15-11-2013 00:00:00 18-11-2013 00:00:00
India 19-11-2013 00:00:00 21-11-2013 00:00:00
United Kingdom 22-11-2013 00:00:00 25-11-2013 00:00:00
India 26-11-2013 00:00:00 28-11-2013 00:00:00
United Kingdom 29-11-2013 00:00:00 29-11-2013 00:00:00
9 rows selected.
Somewhat more complicated than #RobVanWijk's answer:
with v_data as (
select to_date('2013-11-01', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-04', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-05', 'YYYY-MM-DD') as date_worked, 'India' as country from dual union all
select to_date('2013-11-06', 'YYYY-MM-DD') as date_worked, 'India' as country from dual union all
select to_date('2013-11-07', 'YYYY-MM-DD') as date_worked, 'India' as country from dual union all
select to_date('2013-11-08', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-11', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-12', 'YYYY-MM-DD') as date_worked, 'India' as country from dual
)
select country, start_day, end_day from (
select
v3.*,
row_number() over (partition by start_day, end_day order by date_worked) as rn
from (
select
v2.*,
max(case when is_first_day = 1 then date_worked else null end) over (Partition by null order by date_worked) as start_day,
min(case when is_last_day = 1 then date_worked else null end) over (Partition by null order by date_worked desc) as end_day
from (
select
v1.*,
(case when country <> nvl(country_next_day, 'n/a') then 1 else 0 end) is_last_day,
(case when country <> nvl(country_prev_day, 'n/a') then 1 else 0 end) is_first_day
from (
select
date_worked,
country,
lead(country) over (order by date_worked) as country_next_day,
lag(country) over (order by date_worked) as country_prev_day
from v_data
) v1
) v2
order by date_worked
) v3
) v4 where rn=1
Explanation:
for each workday, get the successor and the predecessor using the lag() and lead() analytic functions (v1)
for each workday, decide whether it is the start or end of a group by comparing its country to the previous and next countries (v2)
for each group, compute the start and end day (v3)
for each workday, compute its ordering inside its group (v4)
return only workdays with ordering 1
Try this query:
select country,min(date_worked) as start_date,max(date_worked) as end_date
from (select country,date_worked,
Row_Number() over(order by date_worked)
-Row_Number() over(partition by country order by date_worked) as disTance
from YourTable)
group by disTance,country order by min(date_worked);
Say, I have the following data:
select 1 id, date '2007-01-16' date_created, 5 sales, 'Bob' name from dual union all
select 2 id, date '2007-04-16' date_created, 2 sales, 'Bob' name from dual union all
select 3 id, date '2007-05-16' date_created, 6 sales, 'Bob' name from dual union all
select 4 id, date '2007-05-21' date_created, 4 sales, 'Bob' name from dual union all
select 5 id, date '2013-07-16' date_created, 24 sales, 'Bob' name from dual union all
select 6 id, date '2007-01-17' date_created, 15 sales, 'Ann' name from dual union all
select 7 id, date '2007-04-17' date_created, 12 sales, 'Ann' name from dual union all
select 8 id, date '2007-05-17' date_created, 16 sales, 'Ann' name from dual union all
select 9 id, date '2007-05-22' date_created, 14 sales, 'Ann' name from dual union all
select 10 id, date '2013-07-17' date_created, 34 sales, 'Ann' name from dual
I want to get results like the following:
Name Total_cumulative_sales Total_sales_current_month
Bob 41 24
Ann 91 34
In this table, for Bob, his total sales is 41 starting from the beginning. And for this month which is July, his sales for this entire month is 24. Same goes for Ann.
How do I write an SQL to get this result?
Try this way:
select name, sum(sales) as Total_cumulative_sales ,
sum(
case trunc(to_date(date_created), 'MM')
when trunc(sysdate, 'MM') then sales
else 0
end
) as Total_sales_current_month
from tab
group by name
SQL Fiddle Demo
More information
Trunc
Case Statement
SELECT Name,
SUM(Sales) Total_sales,
SUM(CASE WHEN MONTH(date_created) = MONTH(GetDate()) AND YEAR(date_created) = YEAR(GetDate()) THEN Sales END) Total_sales_current_month
GROUP BY Name
Should work, but there's probably a more elegant way to specify "in the current month".
This should work for sales over a number of years. It will get the cumulative sales over any number of years. It won't produce a record if there are no sales in the latest month.
WITH sales AS
(select 1 id, date '2007-01-16' date_created, 5 sales, 'Bob' sales_name from dual union all
select 2 id, date '2007-04-16' date_created, 2 sales, 'Bob' sales_name from dual union all
select 3 id, date '2007-05-16' date_created, 6 sales, 'Bob' sales_name from dual union all
select 4 id, date '2007-05-21' date_created, 4 sales, 'Bob' sales_name from dual union all
select 5 id, date '2013-07-16' date_created, 24 sales, 'Bob' sales_name from dual union all
select 6 id, date '2007-01-17' date_created, 15 sales, 'Ann' sales_name from dual union all
select 7 id, date '2007-04-17' date_created, 12 sales, 'Ann' sales_name from dual union all
select 8 id, date '2007-05-17' date_created, 16 sales, 'Ann' sales_name from dual union all
select 9 id, date '2007-05-22' date_created, 14 sales, 'Ann' sales_name from dual union all
select 10 id, date '2013-07-17' date_created, 34 sales, 'Ann' sales_name from dual)
SELECT sales_name
,total_sales
,monthly_sales
,mon
FROM (SELECT sales_name
,SUM(sales) OVER (PARTITION BY sales_name ORDER BY mon) total_sales
,SUM(sales) OVER (PARTITION BY sales_name,mon ORDER BY mon) monthly_sales
,mon
,max_mon
FROM ( SELECT sales_name
,sum(sales) sales
,mon
,max_mon
FROM (SELECT sales_name
,to_number(to_char(date_created,'YYYYMM')) mon
,sales
,MAX(to_number(to_char(date_created,'YYYYMM'))) OVER (PARTITION BY sales_name) max_mon
FROM sales
ORDER BY 2)
GROUP BY sales_name
,max_mon
,mon
)
)
WHERE max_mon = mon
;