Count only when the next row is different from the previous row - sql

I have a table with 12 registers. I want to count the row just if the column "AREA_OPERATIVA" is different from the previous row (ordering by date asc).
For example, from row 1 to row 2 it shouldn't count anything because both have same area 'CROSS' but between rows 2 and 3, it should count (or sum 1, I don't care) since 'CROSS' and 'UTRDANIOS' are different. So the final count should be 3 for the whole table.
Is it possible to do this via query or do I need to make a script with a cursor for this purpose?
I've tried this:
SELECT a.creclama,
sum (CASE WHEN b.area_operativa NOT LIKE a.area_operativa THEN 1 ELSE 0 END) AS increment
FROM TR_ASGAREOPE a
INNER JOIN TR_ASGAREOPE b ON a.creclama = b.creclama
and a.cdistribuidora = b.cdistribuidora
and a.secuencia = b.secuencia
WHERE a.creclama = 10008354
group by a.creclama;
But is counting the full 12 rows.
EDIT:
Finally I could resolve this by the next query:
select sum (
CASE WHEN (comparacion.area_operativa not like comparacion.siguiente_fila THEN 1 ELSE 0 END) AS incremento
from (
select creclama,
area_operativa,
lead(area_operativa) over (order by fmodifica) as siguiente_fila
from TR_ASGAREOPE
where creclama = 10008354
order by fmodifica
);
Hope it is useful for someone in the future, it really got me stuck for a day. Thank you all guys.

You could try using analytic functions like lead or lag, for example
SELECT CRECLAMA,
CASE WHEN AREA_OPERATIVA <> NEXTROW THEN 1 ELSE 0 END AS INCREMENT
FROM (
SELECT CRECLAMA,
AREA_OPERATIVA,
LEAD(AREA_OPERATIVA) OVER (PARTITION BY 1 ORDER BY CRECLAMA) AS NEXTROW
FROM TR_ASGAREOPE
)

Here's an approach using LEAD:
WITH TR_ASGAREOPE(CRECLAMA, AREA_OPERATIVA, DATE_FIELD) AS
(SELECT 10008354, 'CROSS', DATE '2019-01-01' FROM DUAL UNION ALL
SELECT 10008354, 'CROSS', DATE '2019-01-02' FROM DUAL UNION ALL -- 1
SELECT 10008354, 'UTRDANIOS', DATE '2019-01-03' FROM DUAL UNION ALL -- 2
SELECT 10008354, 'EXP263', DATE '2019-01-04' FROM DUAL UNION ALL -- 3
SELECT 10008354, 'EXP6', DATE '2019-01-05' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-06' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-07' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-08' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-09' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-10' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-11' FROM DUAL UNION ALL
SELECT 10008354, 'EXP6', DATE '2019-01-12' FROM DUAL UNION ALL
SELECT 12345678, 'AREA49', DATE '2019-02-01' FROM DUAL UNION ALL
SELECT 12345678, 'AREA49', DATE '2019-02-02' FROM DUAL UNION ALL -- 1
SELECT 12345678, 'AREA50', DATE '2019-02-03' FROM DUAL UNION ALL
SELECT 12345678, 'AREA50', DATE '2019-02-04' FROM DUAL UNION ALL -- 2
SELECT 12345678, 'AREA52', DATE '2019-02-05' FROM DUAL UNION ALL
SELECT 12345678, 'AREA52', DATE '2019-02-06' FROM DUAL UNION ALL
SELECT 12345678, 'AREA52', DATE '2019-02-07' FROM DUAL UNION ALL -- 3
SELECT 12345678, 'AREA53', DATE '2019-02-08' FROM DUAL UNION ALL -- 4
SELECT 12345678, 'AREA52', DATE '2019-02-09' FROM DUAL UNION ALL -- 5
SELECT 12345678, 'AREA53', DATE '2019-02-10' FROM DUAL),
cteData AS (SELECT CRECLAMA,
LEAD(CRECLAMA) OVER (ORDER BY DATE_FIELD) AS NEXT_CRECLAMA,
AREA_OPERATIVA,
LEAD(AREA_OPERATIVA) OVER (ORDER BY DATE_FIELD) AS NEXT_AREA_OPERATIVA
FROM TR_ASGAREOPE)
SELECT CRECLAMA, COUNT(*)
FROM cteData
WHERE CRECLAMA = NEXT_CRECLAMA AND
AREA_OPERATIVA <> NEXT_AREA_OPERATIVA
GROUP BY CRECLAMA
ORDER BY CRECLAMA;
I added data for another CRECLAMA value to show how it would work.
Results:
CRECLAMA COUNT(*)
10008354 3
12345678 5
dbfiddle here

You can use lag() analytic function :
with t as
(
select a.*,
lag(a.area_operativa,1,a.area_operativa) over (order by a."date") as lg
from asgareope a
where a.creclama = 10008354
)
select t.creclama, sum(case when lg = area_operativa then 0 else 1 end) as "increment"
from t
group by t.creclama

I think you can simple use COUNT(DISTINCT ...) for your problem, if the AREA_OPERATIVA could not return to previously used value:
SELECT CRECLAMA, COUNT(DISTINCT AREA_OPERATIVA)
FROM TR_ASGAREOPE
GROUP BY CRECLAMA

Related

How can I use the LAG FUNCTION to show revenue this year vs last year in Snowflake with?

I would like to show the current revenue vs last's year's revenue in the same row per region per type. Example:For 2022-04-01, US, Type 1 --> REVENUE: 2456, REVENUE_LAST_YEAR: 4000
2021-04-01, US, Type 1 --> REVENUE: 4000, REVENUE_LAST_YEAR: 0
For some reason, the Lag formula in Snowflake is showing wrong values. Could someone please help ?
WITH
indata(dt,region,type,revenue) AS (
SELECT DATE '2021-04-01','US','Type 1',4000 UNION ALL SELECT DATE '2021-05-01','Europe','Type 2',5777
UNION ALL SELECT DATE '2021-06-01','US','Type 1',45433 UNION ALL SELECT DATE '2021-07-01','Europe','Type 2',8955
UNION ALL SELECT DATE '2021-08-01','US','Type 1',45777 UNION ALL SELECT DATE '2021-09-01','Asia','Type 1',7533
UNION ALL SELECT DATE '2021-10-01','US','Type 1',8866 UNION ALL SELECT DATE '2021-11-01','Asia','Type 2',5534
UNION ALL SELECT DATE '2021-12-01','US','Type 2',4000 UNION ALL SELECT DATE '2022-01-01','Asia','Type 1',7244
UNION ALL SELECT DATE '2022-02-01','US','Type 1',6678 UNION ALL SELECT DATE '2022-03-01','Asia','Type 1',5654
UNION ALL SELECT DATE '2022-04-01','US','Type 1',2456 UNION ALL SELECT DATE '2022-05-01','Asia','Type 1',4525
UNION ALL SELECT DATE '2022-06-01','US','Type 1',6677 UNION ALL SELECT DATE '2022-07-01','Asia','Type 1',6654
UNION ALL SELECT DATE '2022-08-01','US','Type 1',6677 UNION ALL SELECT DATE '2022-09-01','Asia','Type 2',5754
UNION ALL SELECT DATE '2022-10-01','US','Type 1',7744 UNION ALL SELECT DATE '2022-11-01','Asia','Type 2',5644
UNION ALL SELECT DATE '2022-12-01','Europe','Type 2',6775 UNION ALL SELECT DATE '2023-01-01','Asia','Type 2',6777
UNION ALL SELECT DATE '2023-02-01','Europe','Type 2',7755
)
SELECT indata.*,
lag(REVENUE, 1, 0) over (partition by region,type,revenue order by year(dt)) REVENUE_last_year
FROM indata
order by year(dt)
Partitioning by region, type and month-day:
SELECT indata.*,
LAG(REVENUE, 1, 0) over (partition by region,type, TO_VARCHAR(dt, 'mmdd')
order by dt) AS REVENUE_last_year
FROM indata
ORDER BY dt;
Output:

Rolling sum previous 12 months grouped by 2 dimensions (SQL- Snowflake)

I have the following table structure available in the C:
I am struggling in Snowflake with a query that should show me the the sum of previous 12 months for every distinct month in the table split into three dimensions .
The way reporting date 01.08.2022 for region='US' and type=1 is calculated: it is the sum of the past 12 months' row of "revenue_12_months" = 4000+ 45433+45777+ 8866+ 4000+ 6678+ 2456+ 6677+ 6677+ 7744+ 6775 + 7755
WITH
indata(dt,region,type,revenue) AS (
SELECT DATE '2021-04-01','US','Type 1',4000 UNION ALL SELECT DATE '2021-05-01','Europe','Type 2',5777
UNION ALL SELECT DATE '2021-05-01','US','Type 1',45433 UNION ALL SELECT DATE '2021-07-01','Europe','Type 2',8955
UNION ALL SELECT DATE '2021-06-01','US','Type 1',45777 UNION ALL SELECT DATE '2021-09-01','Asia','Type 1',7533
UNION ALL SELECT DATE '2021-07-01','US','Type 1',8866 UNION ALL SELECT DATE '2021-11-01','Asia','Type 2',5534
UNION ALL SELECT DATE '2021-08-01','US','Type 1',4000 UNION ALL SELECT DATE '2022-01-01','Asia','Type 1',7244
UNION ALL SELECT DATE '2021-09-01','US','Type 1',6678 UNION ALL SELECT DATE '2022-03-01','Asia','Type 1',5654
UNION ALL SELECT DATE '2021-10-01','US','Type 1',2456 UNION ALL SELECT DATE '2022-05-01','Asia','Type 1',4525
UNION ALL SELECT DATE '2021-11-01','US','Type 1',6677 UNION ALL SELECT DATE '2022-07-01','Asia','Type 1',6654
UNION ALL SELECT DATE '2021-12-01','US','Type 1',6677 UNION ALL SELECT DATE '2022-09-01','Asia','Type 2',5754
UNION ALL SELECT DATE '2022-01-01','US','Type 1',7744 UNION ALL SELECT DATE '2022-11-01','Asia','Type 2',5644
UNION ALL SELECT DATE '2022-02-01','US','Type 1',6775 UNION ALL SELECT DATE '2023-01-01','Asia','Type 2',6777
UNION ALL SELECT DATE '2022-03-01','US','Type 1',7755
)
select dt,region,type, SUM(revenue) OVER (ORDER BY dt,region,type ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) revenue_12_months
from indata
You want it per region and type, so that needs to part of the partition by clause
sum(revenue) over (partition by region, type order by dt rows between 11 preceding and current row)
could it be that you simply forgot the GROUP BY part in your window function?
WITH
indata(dt,region,type,revenue) AS (
SELECT DATE '2021-04-01','US','Type 1',4000 UNION ALL SELECT DATE '2021-05-01','Europe','Type 2',5777
UNION ALL SELECT DATE '2021-05-01','US','Type 1',45433 UNION ALL SELECT DATE '2021-07-01','Europe','Type 2',8955
UNION ALL SELECT DATE '2021-06-01','US','Type 1',45777 UNION ALL SELECT DATE '2021-09-01','Asia','Type 1',7533
UNION ALL SELECT DATE '2021-07-01','US','Type 1',8866 UNION ALL SELECT DATE '2021-11-01','Asia','Type 2',5534
UNION ALL SELECT DATE '2021-08-01','US','Type 1',4000 UNION ALL SELECT DATE '2022-01-01','Asia','Type 1',7244
UNION ALL SELECT DATE '2021-09-01','US','Type 1',6678 UNION ALL SELECT DATE '2022-03-01','Asia','Type 1',5654
UNION ALL SELECT DATE '2021-10-01','US','Type 1',2456 UNION ALL SELECT DATE '2022-05-01','Asia','Type 1',4525
UNION ALL SELECT DATE '2021-11-01','US','Type 1',6677 UNION ALL SELECT DATE '2022-07-01','Asia','Type 1',6654
UNION ALL SELECT DATE '2021-12-01','US','Type 1',6677 UNION ALL SELECT DATE '2022-09-01','Asia','Type 2',5754
UNION ALL SELECT DATE '2022-01-01','US','Type 1',7744 UNION ALL SELECT DATE '2022-11-01','Asia','Type 2',5644
UNION ALL SELECT DATE '2022-02-01','US','Type 1',6775 UNION ALL SELECT DATE '2023-01-01','Asia','Type 2',6777
UNION ALL SELECT DATE '2022-03-01','US','Type 1',7755
)
select dt,region,type, SUM(revenue) OVER (PARTITION BY region, type ORDER BY dt,region,type ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) revenue_12_months
from indata
ORDER BY REGION, TYPE, DT;
Best regards,
TK

PL/SQL distinct date for loop

I want to use for loop for date in my table which only cares years and months, not days.
CURSOR ret_cur is SELECT orderdate FROM Orders WHERE status
= 'DELAYED';
ret_rec ret_cur%ROWTYPE;
I currently have
insert into Orders(OrderId, CustomerId, RetailerId, ProductId, Count,
UnitPrice, OrderDate, Status) values (2,2,1,10,45,60,
to_date('20180102','YYYYMMDD'),'DELIVERED');
this data type in my orders table. (its an example for format)
I want to use DISTINCT to iterate through orderdate based on YYYY-MM. (dont care Day)
I have tried select distinct to_char(orderdate, 'YYYY-MM') but I seems to not work.
for example, if i have 20180103, 20180104, 20180105 , it should be one iteration since they all have same years and months.
To select days without time you could tunc(sysdate). For months we have to group by a char-value:
select to_char(mydatecol,'yyyymm'), count(*) from
(
select sysdate mydatecol from dual UNION ALL -- Fake-Table with some dates
select sysdate - 1 mydatecol from dual UNION ALL
select sysdate - 2 mydatecol from dual UNION ALL
select sysdate - 3 mydatecol from dual UNION ALL
select sysdate - 4 mydatecol from dual UNION ALL
select sysdate - 30 mydatecol from dual UNION ALL
select sysdate - 31 mydatecol from dual UNION ALL
select sysdate - 32 mydatecol from dual UNION ALL
select sysdate - 33 mydatecol from dual
)
group by to_char(mydatecol,'yyyymm')
Result:
201809 3
201810 6
I think you'd like to have such a collation as below :
with Orders
(
OrderId, CustomerId, RetailerId, ProductId,
Count, UnitPrice, OrderDate, Status
) as
(
select 2,2,1,10,45,60, to_date('20180102','YYYYMMDD'),'DELIVERED' from dual
)
select o.*
from Orders o
where to_char(OrderDate,'yyyy-mm')
= to_char(to_date('&myDate','yyyymmdd'),'yyyy-mm');
-- for "myDate" substitution variable use 20180103 or 20180104 or 20180105 .. etc.
The best way todo this kind of query is to truncate the date value:
SELECT CustomerId, trunc(OrderDate,'MM') OrderMonth
, sum(Count) totalCount
, sum(Count*UnitPrice) totalPrice
FROM Orders
GROUP BY CustomerId, trunc(OrderDate,'MM')
for example...

How to count days between 2 rows multiple scenarios

I would like to count days from table like this:
[]]1
I have 2 columns with status and date, I need to count how many days one row had status with 1 and 3, for example: first row has staus 1 second row has status 2 the difference between those two rows is 3 days, the same eight and nine row. All those calculation I neet to do in sql.
This will work for sql-server 2012. I was unable to find any documentation that would prevent this syntax in express.
DECLARE #t table(Status int, Date datetime)
INSERT #t
VALUES
(1,'2018-02-15'),
(2,'2018-02-18'),
(3,'2018-02-20'),
(2,'2018-02-23')
;WITH CTE as
(
SELECT Date, lead(Date)over(order by date) NextDate, Status
FROM #t
)
SELECT sum(DateDiff(day, Date, NextDate))
FROM CTE
WHERE Status in (1,3)
You can do that if you have analytic (aka window) functions available (Oracle, SQL Server, Postgresql, MySQL). The basic idea would be (using Oracle SQL dialect):
select
sum(trunc(next_date) - trunc(dat))
from (
select
status,
dat,
lead(dat) over (order by dat asc) next_date
from
( -- This is a "fake" data table
select 1 as status, to_date('15/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 2 as status, to_date('18/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 3 as status, to_date('20/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 2 as status, to_date('23/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 4 as status, to_date('24/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 7 as status, to_date('27/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 9 as status, to_date('28/2/2018' ,'dd/mm/yyyy') dat from dual
union all
select 3 as status, to_date('1/3/2018' ,'dd/mm/yyyy') dat from dual
union all
select 14 as status, to_date('4/3/2018' ,'dd/mm/yyyy') dat from dual
union all
select 15 as status, to_date('7/3/2018' ,'dd/mm/yyyy') dat from dual
union all
select 1 as status, to_date('10/3/2018' ,'dd/mm/yyyy') dat from dual
union all
select 2 as status, to_date('14/3/2018' ,'dd/mm/yyyy') dat from dual
) d
) d2
where
status in (1, 3);
Other dialects may be slightly different but the idea is the same.

How to convert partial dates in Oracle SQL

I have 2 date columns in 2 diff tables that I need to compare, both varchar2 type. Both columns have partial and full dates based on the data.
T1:
ID Partial_date1
1 19-DEC-2016
2 06-MAY-2015
3 2016
4
5 AUG-2016
6 16-NOV-2015 00:00
7 01-JAN-2016
T2:
ID Partial_date2
1 09-JAN-2016
2 2016
3 SEP-2015
4
5 23-MAR-2016 00:00
6 15-MAY-2015
7
I want to search for all records that have full dates (as it is not possible to convert partial dates), to select only the records with full dates, I have used length >10. Here is the SQL I wrote but does not seem to be working.
select t1.id from t1, t2
where t1.id =t2.id
and length(t1.partial_date1)>10
and length(t2.partial_date2)>10
and to_date(t1.partial_date1,'DD-MON-YYYY') > to_date(t2.partial_date2,'DD-MON-YYYY')
I either get an error - ORA-01830: date format picture ends before converting entire input string
or literal does not match format string.
What am I doing wrong? how do I get the right results?
It seems you consider a date complete when the string starts with DD-MMM-YYYY. You can use REGEXP_LIKE to find such rows:
where regexp_like(partial_date, '^[[:digit:]]{2}-[[:upper:]]{3}-[[:digit:]]{4}')
(You may want to adjust the pattern according to your needs, e.g. replace [[:upper:]] with [[:alpha:]].)
In order to convert a date containing a textual month you should use TO_DATE with a language parameter:
to_date(partial_date, 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=AMERICAN')
A possible query:
select tt1.id
from
(
select
id,
to_date(substr(partial_date1, 1, 11), 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=AMERICAN')
as dt
from t1
where regexp_like(partial_date1, '^[[:digit:]]{2}-[[:upper:]]{3}-[[:digit:]]{4}')
) tt1
join
(
select
id,
to_date(substr(partial_date2, 1, 11), 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=AMERICAN')
as dt
from t2
where regexp_like(partial_date2, '^[[:digit:]]{2}-[[:upper:]]{3}-[[:digit:]]{4}')
) tt2 on tt2.id = tt1.id and tt2.dt < tt1.dt;
However, keep in mind that you are still dealing with strings. Conversion on "dates" like these would fail and crash your query:
30-FEB-2017
01-YAN-2017
99-XXX-9999
So depending on the data quality your best bet may be to write a PL/SQL function in order to catch conversion errors.
When you write condition in where clause oracle may use it as access predicate. And it function to_date(t1.partial_date1,'DD-MON-YYYY') is used to any row.
That's why you get an error.
I see two ways:
First way is use a subquery to get a shrunk dataset, substr only 10 symbols and then convert it
with t1(id,partial_date1) as
(
select 1,'19-DEC-2016' from dual union all
select 2,'06-MAY-2015' from dual union all
select 3, '2016' from dual union all
select 4,'' from dual union all
select 5,'AUG-2016' from dual union all
select 6,'16-NOV-2015 00:00' from dual union all
select 7, '01-JAN-2016' from dual
), t2(id,partial_date2) as
(
select 1,'09-JAN-2016' from dual union all
select 2,'2016' from dual union all
select 3,'SEP-2015' from dual union all
select 4,'' from dual union all
select 5,'23-MAR-2016 00:00' from dual union all
select 6,'15-MAY-2015' from dual union all
select 7,'' from dual
)
select *
from
(select
t1.id,
partial_date1,
partial_date2
from
t1, t2
where
t1.id =t2.id
and length(t1.partial_date1) > 10
and length(t2.partial_date2) > 10
and rownum > 0)
where
to_date(substr(partial_date1,1,10),'DD-MON-YYYY') > to_date(substr(partial_date2,1,10),'DD-MON-YYYY');
/
The second way is to explicitly convert any format to properly one
with t1(id,partial_date1) as (
select 1,'19-DEC-2016' from dual union all
select 2,'06-MAY-2015' from dual union all
select 3, '2016' from dual union all
select 4,'' from dual union all
select 5,'AUG-2016' from dual union all
select 6,'16-NOV-2015 00:00' from dual union all
select 7, '01-JAN-2016' from dual)
,t2(id,partial_date2) as (
select 1,'09-JAN-2016' from dual union all
select 2,'2016' from dual union all
select 3,'SEP-2015' from dual union all
select 4,'' from dual union all
select 5,'23-MAR-2016 00:00' from dual union all
select 6,'15-MAY-2015' from dual union all
select 7,'' from dual)
select * from(
select t1.id,
case
when regexp_like(t1.partial_date1,'\d{1,2}-\w{3}-\d{4} \d{1,2}:\d{2}') then to_date(t1.partial_date1,'dd-MON-yyyy HH24:MI')
when regexp_like(t1.partial_date1,'\d{1,2}-\w{3}-\d{4}') then to_date(t1.partial_date1,'dd-MON-yyyy')
when regexp_like(t1.partial_date1,'\w{3}-\d{4}') then to_date(t1.partial_date1,'MON-yyyy')
when regexp_like(t1.partial_date1,'\d{4}') then to_date(t1.partial_date1,'yyyy')
end as pd1,
case
when regexp_like(t2.partial_date2,'\d{1,2}-\w{3}-\d{4} \d{1,2}:\d{2}') then to_date(t2.partial_date2,'dd-MON-yyyy HH24:MI')
when regexp_like(t2.partial_date2,'\d{1,2}-\w{3}-\d{4}') then to_date(t2.partial_date2,'dd-MON-yyyy')
when regexp_like(t2.partial_date2,'\w{3}-\d{4}') then to_date(t2.partial_date2,'MON-yyyy')
when regexp_like(t2.partial_date2,'\d{4}') then to_date(t2.partial_date2,'yyyy')
end as pd2
from t1,t2
where t1.id = t2.id)
where pd1 > pd2
/
case
when PurchaseDate = '0' then NULL
when right(PurchaseDate, 4) = '0000' then convert(date, left(PurchaseDate,4) + '1231', 112)
when RIGHT(PurchaseDate, 2) = '00' then DATEADD(day,-1,DATEADD(month,cast(left(RIGHT(Purchasedate,4),2) AS INT),DATEADD(year,cast(LEFT(Purchasedate, 4) AS int)-1900,0)))
else convert(date, ltrim(rtrim(cast(PurchaseDate as varchar(50)))), 112)
end
Apologize. This is from my phone. This does a few things. It assumes different formats plus it counters for missing days and months.
Hope this helps.