Assume our company has multiple marketing campaigns for one specific product, which might boost the sales of it in certain way. The results of the campaign are shown in the following data table:
Date CampaignID QtySold
2017-01-05 1 20
2017-01-18 2 35
2017-01-23 1 15
…
For modeling purposes, the desired output table looks like this:
CampaignID JanQtySold FebQtySold … DecQtySold
1 55 30
2 45 20
…
N
I have tried to get the month of each transaction for each campaign, and then group by campaignID and month.
select
campaignid,
strftime('%m',date) as Month,
sum(qtysold) as Sum_Qty
from campaign
group by campaignid, month
;
The return should include a unique CampaignID but I don't know how to proceed.
I believe that you want something like :-
select
campaignid,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 1
)
,0) AS JanQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 2
)
,0) As FebQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 3
)
,0) As MarQtySold
/* .............. and so on ..........*/
from campaign
group by campaignid
;
That is introducing more groups is not really what you want for more derived columns (introducing more grouping components will introduce more rows, so you only want the 1 grouping component i.e. the campaign id). Rather you want to use sub-queries to generate the data for the derived columns.
Note for brevity only 3 months (Jan-Mar) have been shown, the other months is just a matter of copying one of the months and then amending the test value and the column name respectively.
Example :-
Using :-
DROP TABLE IF EXISTS campaign;
CREATE TABLE IF NOT EXISTS campaign (Date TEXT, CampaignID INTEGER, QtySold INTEGER);
INSERT INTO campaign VALUES
('2017-01-05',1,20),('2017-01-23',1,15),('2017-02-01',1,5),
('2017-01-18',2,35)
;
select
campaignid,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 1
)
,0) AS JanQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 2
)
,0) As FebQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 3
)
,0) As MarQtySold
from campaign
group by campaignid
;
results in :-
You need conditional aggregation with a CASE statement inside SUM():
select
CampaignID,
sum(case strftime('%m',Date) when '01' then QtySold else 0 end) as JanQtySold,
sum(case strftime('%m',Date) when '02' then QtySold else 0 end) as FebQtySold,
sum(case strftime('%m',Date) when '03' then QtySold else 0 end) as MarQtySold,
sum(case strftime('%m',Date) when '04' then QtySold else 0 end) as AprQtySold,
sum(case strftime('%m',Date) when '05' then QtySold else 0 end) as MayQtySold,
sum(case strftime('%m',Date) when '06' then QtySold else 0 end) as JunQtySold,
sum(case strftime('%m',Date) when '07' then QtySold else 0 end) as JulQtySold,
sum(case strftime('%m',Date) when '08' then QtySold else 0 end) as AugQtySold,
sum(case strftime('%m',Date) when '09' then QtySold else 0 end) as SepQtySold,
sum(case strftime('%m',Date) when '10' then QtySold else 0 end) as OctQtySold,
sum(case strftime('%m',Date) when '11' then QtySold else 0 end) as NovQtySold,
sum(case strftime('%m',Date) when '12' then QtySold else 0 end) as DecQtySold
from campaign
where strftime('%Y',Date) = '2017'
group by CampaignID
I added the condition:
where strftime('%Y',Date) = '2017'
just in case there are rows in the table for multiple years.
If you don't want zeros in the results just remove all else 0 from the case statements.
See the demo.
Just to make it more efficient, you can a use a CTE:
with cte as (
select
CampaignID,
strftime('%Y',Date) as year,
strftime('%m',Date) as month,
QtySold
from campaign
)
select
CampaignID,
sum(case month when '01' then QtySold else 0 end) as JanQtySold,
sum(case month when '02' then QtySold else 0 end) as FebQtySold,
sum(case month when '03' then QtySold else 0 end) as MarQtySold,
sum(case month when '04' then QtySold else 0 end) as AprQtySold,
sum(case month when '05' then QtySold else 0 end) as MayQtySold,
sum(case month when '06' then QtySold else 0 end) as JunQtySold,
sum(case month when '07' then QtySold else 0 end) as JulQtySold,
sum(case month when '08' then QtySold else 0 end) as AugQtySold,
sum(case month when '09' then QtySold else 0 end) as SepQtySold,
sum(case month when '10' then QtySold else 0 end) as OctQtySold,
sum(case month when '11' then QtySold else 0 end) as NovQtySold,
sum(case month when '12' then QtySold else 0 end) as DecQtySold
from cte
where year = '2017'
group by CampaignID
See the demo.
Results:
| CampaignID | JanQtySold | FebQtySold | MarQtySold | AprQtySold | MayQtySold | JunQtySold | JulQtySold | AugQtySold | SepQtySold | OctQtySold | NovQtySold | DecQtySold |
| ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- |
| 1 | 35 | 65 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 35 | 75 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Related
I have datas in a table with schema:
Id INTEGER,
date DATETIME,
value REAL
id is primary key, and I have an index on date column to speed up querying values within a specific date range.
What should I do if I need N equal date ranges between specific start and end dates, and query aggregated datas for each date range?
For example:
Start date: 2015-01-01
End date: 2019-12-31
N: 5
In this case equal date intervals should be:
2015-01-01 ~ 2015-12-31
2016-01-01 ~ 2016-12-31
2017-01-01 ~ 2017-12-31
2018-01-01 ~ 2018-12-31
2019-01-01 ~ 2019-12-31
And the query should aggregate all values (AVG) in between those intervals, so I would like to have 5 total rows after the execution.
Maybe something with CTE?
There are 2 ways to do it.
They both use recursive ctes but return different results.
The 1st one with NTILE():
with
dates as (select '2015-01-01' mindate, '2019-12-31' maxdate),
alldates as (
select mindate date from dates
union all
select date(a.date, '1 day')
from alldates a cross join dates d
where a.date < d.maxdate
),
groups as (
select *, ntile(5) over (order by date) grp
from alldates
),
cte as (
select min(date) date1, max(date) date2
from groups
group by grp
)
select * from cte;
Results:
| date1 | date2 |
| ---------- | ---------- |
| 2015-01-01 | 2016-01-01 |
| 2016-01-02 | 2016-12-31 |
| 2017-01-01 | 2017-12-31 |
| 2018-01-01 | 2018-12-31 |
| 2019-01-01 | 2019-12-31 |
And the 2nd builds the groups with math:
with
dates as (select '2015-01-01' mindate, '2019-12-31' maxdate),
cte1 as (
select mindate date from dates
union all
select date(
c.date,
((strftime('%s', d.maxdate) - strftime('%s', d.mindate)) / 5) || ' second'
)
from cte1 c inner join dates d
on c.date < d.maxdate
),
cte2 as (
select date date1, lead(date) over (order by date) date2
from cte1
),
cte as (
select date1,
case
when date2 = (select maxdate from dates) then date2
else date(date2, '-1 day')
end date2
from cte2
where date2 is not null
)
select * from cte
Results:
| date1 | date2 |
| ---------- | ---------- |
| 2015-01-01 | 2015-12-31 |
| 2016-01-01 | 2016-12-30 |
| 2016-12-31 | 2017-12-30 |
| 2017-12-31 | 2018-12-30 |
| 2018-12-31 | 2019-12-31 |
In both cases you can get the averages by joining the table to the cte:
select c.date1, c.date2, avg(t.value) avg_value
from cte c inner join tablename t
on t.date between c.date1 and c.date2
group by c.date1, c.date2
I am sure first time this question is been asked in this group.
Using sqlite i would like to do pivot display with total count in each row bottom. And i am able to do pivot table but total count is failed to do so .
CURRENT SQL QUERY:
SELECT
number as no,
outl as name,
(CASE WHEN week = "WEEK1" THEN sunday ELSE 0 END) AS WK1S,
(CASE WHEN week = "WEEK1" THEN monday ELSE 0 END) AS WK1M,
(CASE WHEN week = "WEEK1" THEN tuesday ELSE 0 END) AS WK1T,
(CASE WHEN week = "WEEK1" THEN wednesday ELSE 0 END) AS WK1W,
(CASE WHEN week = "WEEK1" THEN thursday ELSE 0 END) AS WK1T,
(CASE WHEN week = "WEEK1" THEN saturday ELSE 0 END) AS WK1SA,
(CASE WHEN week = "WEEK2" THEN sunday ELSE 0 END) AS WK2S,
(CASE WHEN week = "WEEK2" THEN monday ELSE 0 END) AS WK21M,
(CASE WHEN week = "WEEK3" THEN sunday ELSE 0 END) AS WK3S,
(CASE WHEN week = "WEEK3" THEN monday ELSE 0 END) AS WK3M,
(CASE WHEN week = "WEEK3" THEN tuesday ELSE 0 END) AS WK3T,
(CASE WHEN week = "WEEK3" THEN wednesday ELSE 0 END) AS WK3W,
(CASE WHEN week = "WEEK3" THEN thursday ELSE 0 END) AS WK3T,
(CASE WHEN week = "WEEK3" THEN saturday ELSE 0 END) AS WK3SA
FROM labels51 group by number
CURRENT OUTPUT
Expected: on bottom i need total count of each row like case when WK1S = sunday then count(WK1S) and case when WK1M = monday then count (WK1M) .... ..
try this
SELECT
number as no,
outl as name,
(CASE WHEN week = "WEEK1" THEN sunday ELSE 0 END) AS WK1S,
(CASE WHEN week = "WEEK1" THEN monday ELSE 0 END) AS WK1M,
(CASE WHEN week = "WEEK1" THEN tuesday ELSE 0 END) AS WK1T,
(CASE WHEN week = "WEEK1" THEN wednesday ELSE 0 END) AS WK1W,
(CASE WHEN week = "WEEK1" THEN thursday ELSE 0 END) AS WK1T,
(CASE WHEN week = "WEEK1" THEN saturday ELSE 0 END) AS WK1SA,
(CASE WHEN week = "WEEK2" THEN sunday ELSE 0 END) AS WK2S,
(CASE WHEN week = "WEEK2" THEN monday ELSE 0 END) AS WK21M,
(CASE WHEN week = "WEEK3" THEN sunday ELSE 0 END) AS WK3S,
(CASE WHEN week = "WEEK3" THEN monday ELSE 0 END) AS WK3M,
(CASE WHEN week = "WEEK3" THEN tuesday ELSE 0 END) AS WK3T,
(CASE WHEN week = "WEEK3" THEN wednesday ELSE 0 END) AS WK3W,
(CASE WHEN week = "WEEK3" THEN thursday ELSE 0 END) AS WK3T,
(CASE WHEN week = "WEEK3" THEN saturday ELSE 0 END) AS WK3SA
FROM labels51
UNION all
SELECT "GRAND TOTAL",
NULL ,
COUNT(CASE WHEN week = "WEEK1" AND sunday LIKE "%sunday%" THEN 1 END) AS WK1S,
COUNT(CASE WHEN week = "WEEK1" AND monday LIKE "%monday%" THEN 1 END) AS WK1M,
COUNT(CASE WHEN week = "WEEK1" AND tuesday LIKE "%tuesday%" THEN 1 END) AS WK1T,
COUNT(CASE WHEN week = "WEEK1" AND wednesday LIKE "%wednesday%" THEN 1 END) AS WK1W,
COUNT(CASE WHEN week = "WEEK1" AND thursday LIKE "%thursday%" THEN 1 END) AS WK1T,
COUNT(CASE WHEN week = "WEEK1" AND saturday LIKE "%saturday%" THEN 1 END) AS WK1SA,
COUNT(CASE WHEN week = "WEEK2" AND sunday LIKE "%sunday%" THEN 1 END) AS WK2S,
COUNT(CASE WHEN week = "WEEK2" AND monday LIKE "%monday%" THEN 1 END) AS WK2M,
COUNT(CASE WHEN week = "WEEK1" AND sunday LIKE "%sunday%" THEN 1 END) AS WK3S,
COUNT(CASE WHEN week = "WEEK3" AND monday LIKE "%monday%" THEN 1 END) AS WK3M,
COUNT(CASE WHEN week = "WEEK3" AND tuesday LIKE "%tuesday%" THEN 1 END) AS WK3T,
COUNT(CASE WHEN week = "WEEK3" AND wednesday LIKE "%wednesday%" THEN 1 END) AS WK3W,
COUNT(CASE WHEN week = "WEEK3" AND thursday LIKE "%thursday%" THEN 1 END) AS WK3T,
COUNT(CASE WHEN week = "WEEK3" AND saturday LIKE "%saturday%" THEN 1 END) AS WK3SA
FROM labels51
I need compare value from 1 column with previous value from 2 column. For example, I have table:
id | create_date | end_date
1 | 2016-12-31 | 2017-01-25
2 | 2017-01-26 | 2017-05-21
3 | 2017-05-22 | 2017-08-26
4 | 2017-09-01 | 2017-09-02
I need to compare create_date for id = 2 with end_date for id = 1
and compare create_date for id = 3 with end_date for id = 2 etc.
Result: show me id which has create_date (id = n) <> end_date (id = n-1) + interval '1' day
Should I use lag() function? How I can compare it? Which function I should use and how?
Thank you
Teradata doesn't have lag/lead, but you can still get the same functionality:
select
id,
create_date,
end_date,
max(end_date) over (order by id between 1 preceding and 1 preceding) as prev_end_date
...
qualify
create_date <> prev_end_date + INTERVAL '1' day;
How would one create my expected results. Any help would be appreciated, Thanks in advance!
Master Calendar:
SELECT DISTINCT
--CA.CALENDAR_DATE,
TO_CHAR(CALENDAR_DATE,'MM/DD/YYYY') AS CALENDAR_DATE
TO_CHAR(NEXT_DAY(CALENDAR_DATE, 'Monday') - 7, 'MM/DD/YY-') ||
TO_CHAR(NEXT_DAY(CALENDAR_DATE, 'Monday') - 1, 'MM/DD/YY') AS WEEK_OF_YEAR,
ROW_NUMBER () OVER ( ORDER BY CALENDAR_DATE) AS MasterCalendar_RNK
FROM CALENDAR CA
WHERE 1=1
--AND CA.CALENDAR_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE), -12) AND TRUNC(SYSDATE)
--AND CA.CALENDAR_DATE BETWEEN TRUNC(SYSDATE) -5 AND TRUNC(SYSDATE)
ORDER BY TO_DATE(CALENDAR_DATE,'MM/DD/YYYY') DESC
Input:
Member StartDate EndDate
A 1/31/17
B 2/1/17 2/15/17
Expected Results:
Member StartDate EndDate Week_Of_Year Active
A 1/31/17 1/30/17-2/5/17 1
A 1/31/17 2/6/17-2/12/17 1
A 1/31/17 2/13/17-2/19/17 1
B 2/1/17 2/15/17 1/30/17/2/5/17 1
B 2/1/17 2/15/17 2/6/17-2/12/17 1
B 2/1/17 2/15/17 2/13/17-2/19/17 1
I'm making a daily report that will produce a value for the next 7 days as such:
select
a.itemnumber
,sum(case when a.activitydate = trunc(sysdate) then a.qtyordered else 0 end) as Today
,sum(case when a.activitydate = trunc(sysdate) + 1 then a.qtyordered else 0 end) as Tomorrow
,sum(case when a.activitydate = trunc(sysdate) + 2 then a.qtyordered else 0 end) as Day3
,sum(case when a.activitydate = trunc(sysdate) + 3 then a.qtyordered else 0 end) as Day4
,sum(case when a.activitydate = trunc(sysdate) + 4 then a.qtyordered else 0 end) as Day5
,sum(case when a.activitydate = trunc(sysdate) + 5 then a.qtyordered else 0 end) as Day6
,sum(case when a.activitydate = trunc(sysdate) + 6 then a.qtyordered else 0 end) as Day7
,a.balanceonhand BOH
from
mytable a
where
a.itemnumber between 14000 and 15000
These aliases are more ambiguous than I would like, and especially if the user compares reports from one day to another, these mean nothing. Using PL/SQL Developer, can I make an alias that will produce a variable date? I tried the following code:
select
a.itemnumber
,sum(case when a.activitydate = trunc(sysdate) then a.qtyordered else 0 end) as sysdate
,sum(case when a.activitydate = trunc(sysdate) + 1 then a.qtyordered else 0 end) as sysdate + 1
,sum(case when a.activitydate = trunc(sysdate) + 2 then a.qtyordered else 0 end) as sysdate + 2
,sum(case when a.activitydate = trunc(sysdate) + 3 then a.qtyordered else 0 end) as sysdate + 3
,sum(case when a.activitydate = trunc(sysdate) + 4 then a.qtyordered else 0 end) as sysdate + 4
,sum(case when a.activitydate = trunc(sysdate) + 5 then a.qtyordered else 0 end) as sysdate + 5
,sum(case when a.activitydate = trunc(sysdate) + 6 then a.qtyordered else 0 end) as sysdate + 6
,a.balanceonhand BOH
from
mytable a
where
a.itemnumber between 14000 and 15000
group by
a.itemnumber
,a.balanceonhand
But it says the FROM keyword not found where expected. Is this possible?
It would be easier if you use a row generator to get your seven dates (or indeed any number of dates). This will provide the data in row based format.
From there I suggest PIVOT is a good way to reformat the output into columns rather than rows. I've provided an 'offset' column in the dategen query so that this can be added to the startdate which, in this case, is a sqlplus variable but I'd suggest using a bind variable in the production query. I'd strongly recommend against using SYSDATE as this makes things very hard to test. It's easier when you can change the dates covered by the report to suit available test data.
1 WITH
2 dategen
3 as
4 (
5 SELECT TO_DATE('&startdate','DD-MON-YYYY')+LEVEL mydate, LEVEL myoffset
6 FROM dual
7 CONNECT BY LEVEL <=7)
8 SELECT itemnumber,balanceonhand, activitydate, today, tomorrow, day3, day4, day5, day6, day7
9 FROM (
10 SELECT *
11 FROM mytable t
12 JOIN dategen d ON (d.mydate = t.activitydate)
13 PIVOT (
14 SUM(qtyordered)
15 FOR myoffset IN (0 as today,1 as tomorrow,2 day3,3 as day4, 4 as day5, 5 as day6, 6 as day7)
16* ))
SQL> /
Enter value for startdate: 01-JAN-2015
old 5: SELECT TO_DATE('&startdate','DD-MON-YYYY')+(level-1) mydate, LEVEL myoffset
new 5: SELECT TO_DATE('01-JAN-2015','DD-MON-YYYY')+(level-1) mydate, LEVEL myoffset
ITEMNUMBER BALANCEONHAND ACTIVITYDATE TODAY TOMORROW DAY3 DAY4 DAY5 DAY6 DAY7
120 100 02-JAN-15 10
140 100 04-JAN-15 10
100 100 02-JAN-15 10
3 rows selected.
SQL> select * from mytable;
ITEMNUMBER BALANCEONHAND ACTIVITYDATE QTYORDERED
---------- ------------- ------------------ ----------
100 100 02-JAN-15 10
120 100 02-JAN-15 10
140 100 04-JAN-15 10
3 rows selected.