SQL how to make one query out of multiple ones - sql

I have a table that holds monthly data of billing records. so say Customer 1234 was billed in Jan/Feb and Customer 2345 was billing Jan/Feb/Mar. How can I group these to show me a concurrent monthly billing cycle. But also need to have non-concurrent billed months, so Customer 3456 was billed Feb/Apl/Jun/Aug
SELECT custName, month, billed, count(*) as Tally
FROM db_name
WHERE
GROUP BY
Results needed:
Customer 1234 was billed for 2 months Concurrent
Customer 2345 was billed for 3 months Concurrent
Customer 3456 was billed for 4 months Non-Concurrent
Any suggestions?

If the month is stored as a datetime field, you can use DATEDIFF to calculate the number of months between the first and the last bill. If the number of elapsed months equals the total number of bills, the bills are consecutive.
select
'Customer ' + custname + ' was billed for ' +
cast(count(*) as varchar) + ' months ' +
case
when datediff(month,min(billdate),max(billdate))+1 = count(*)
then 'Concurrent'
else 'Non-Concurrent'
end
from #billing
where billed = 1
group by custname
If you store the billing month as an integer, you can just subtract instead of using DATEDIFF. Replace the WHEN row with:
when max(billdate)-min(billdate)+1 = count(*)
But in that case I wonder how you distinguish between years.

If the months were all in a sequence, and we are limiting our search to a particular year then Min(month) + Count(times billed) - 1 should = Max(month).
declare #billing table(Custname varchar(10), month int, billed bit)
insert into #billing values (1234, 1, 1)
insert into #billing values (1234, 2, 1)
insert into #billing values (2345, 3, 1)
insert into #billing values (2345, 4, 1)
insert into #billing values (2345, 5, 1)
insert into #billing values (3456, 1, 1)
insert into #billing values (3456, 3, 1)
insert into #billing values (3456, 9, 1)
insert into #billing values (3456, 10, 1)
Select CustName, Count(1) as MonthsBilled,
Case
when Min(Month) + Count(1) - 1 = Max(Month)
then 1
else 0
end Concurrent
From #billing
where Billed = 1
Group by CustName
Cust Months Concurrent
1234 2 1
2345 3 1
3456 4 0

The suggestions here work based on an assumption that you will never bill a customer twice or more in the same month. If that isn't a safe assumption, you need a different approach. Let us know if that's the case.

how about:
SELECT custName, month, count(*) as tally
from billing
where billed = 1
group by custName, month

You left out some important information (like how Month is stored) and what database you're using, but here's a logical approach that you can start with:
CREATE VIEW CustomerBilledInMonth (CustName, Month, AmountBilled, ContinuousFlag) AS
SELECT CustName, Month, SUM(AmountBilled), 'Noncontinuous'
FROM BillingTable BT1
WHERE NOT EXISTS
(SELECT * FROM BillingTable BT2 WHERE BT2.CustName = BT1.CustName AND BT2.Month = BT1.Month - 1)
GROUP BY CustName, Month
UNION
SELECT CustName, Month, SUM(AmountBilled), 'Continuous'
FROM BillingTable BT1
WHERE EXISTS
(SELECT * FROM BillingTable BT2 WHERE BT2.CustName = BT1.CustName AND BT2.Month = BT1.Month - 1)
GROUP BY CustName, Month
Assuming that Month here is a consecutive integer field incremented by one from the first possible month in the system, this gives you with each customer's billing for each month summed up, and an additional flag containing 'Continuous' for those months that followed a month in which the customer was also billed and 'Noncontinuous' for those months that followed a month in which the customer was not billed.
Then:
SELECT CustName, LISTOF(Month), SUM(AmountBilled), MAX(ContinuousFlag)
FROM CustomerBilledInMonth GROUP BY CustName
will give you more or less what you want (where LISTOF is some kind of COALESCE type function dependent on the exact database you're using).

Related

How to spread annual amount and then add by month in SQL

Currently I'm working with a table that looks like this:
Month | Transaction | amount
2021-07-01| Annual Membership Fee| 45
2021-08-01| Annual Membership Fee| 145
2021-09-01| Annual Membership Fee| 2940
2021-10-01| Annual Membership Fee| 1545
the amount on that table is the total monthly amount (ex. I have 100 customers who paid $15 for the annual membership, so my total monthly amount would be $1500).
However what I would like to do (and I have no clue how) is divide the amount by 12 and spread it into the future in order to have a monthly revenue per month. As an example for 2021-09-01 I would get the following:
$2490/12 = $207.5 (dollars per month for the next 12 months)
in 2021-09-01 I would only get $207.5 for that specific month.
On 2021-10-01 I would get $1545/12 = $128.75 plus $207.5 from the previous month (total = $336.25 for 2021-10-01)
And the same operation would repeat onwards. The last period that I would collect my $207.5 from 2021-09-01 would be in 2022-08-01.
I was wondering if someone could give me an idea of how to perform this in a SQL query/CTE?
Assuming all the months you care about exist in your table, I would suggest something like:
SELECT
month,
(SELECT SUM(m2.amount/12) FROM mytable m2 WHERE m2.month BETWEEN ADD_MONTHS(m1.month, -11) AND m1.month) as monthlyamount
FROM mytable m1
GROUP BY month
ORDER BY month
For each month that exists in the table, this sums 1/12th of the current amount plus the previous 11 months (using the add_months function). I think that's what you want.
A few notes/thoughts:
I'm assuming (based on the column name) that all the dates in the month column end on the 1st, so we don't need to worry about matching days or having the group by return multiple rows for the same month.
You might want to round the SUMs I did, since in some cases dividing by 12 might give you more digits after the decimal than you want for money (although, in that case, you might also have to consider remainders).
If you really only have one transaction per month (like in your example), you don't need to do the group by.
If the months you care about don't exist in your table, then this won't work, but you could do the same thing generating a table of months. e.g. If you have an amount on 2020-01-01 but nothing in 2020-02-01, then this won't return a row for 2021-02-01.
CTE = set up dataset
CTE_2 = pro-rate dataset
FINAL SQL = select future_cal_month,sum(pro_rated_amount) from cte_2 group by 1
with cte as (
select '2021-07-01' cal_month,'Annual Membership Fee' transaction ,45 amount
union all select '2021-08-01' cal_month,'Annual Membership Fee' transaction ,145 amount
union all select '2021-09-01' cal_month,'Annual Membership Fee' transaction ,2940 amount
union all select '2021-10-01' cal_month,'Annual Membership Fee' transaction ,1545 amount)
, cte_2 as (
select
dateadd('month', row_number() over (partition by cal_month order by 1), cal_month) future_cal_month
,amount/12 pro_rated_amount
from
cte
,table(generator(rowcount => 12)) v)
select
future_cal_month
, sum(pro_rated_amount)
from
cte_2
group by
future_cal_month

Sum over N days excluding Weekends and Holidays

I have below table
AccountID
Date
Amount
123
07/02/2021
2000
123
07/09/2021
9000
123
07/15/2021
500
123
07/20/2021
500
123
07/28/2021
500
I am trying to create a test script to test data for just one month(July). I want to sum the amount over 5 days where 5 days does not count weekends and holidays. Since it is month of July the holiday falls on July 5th 2021(07/05/2021).
The output should look something like below
AccountID
Date
Amount
123
07/02/2021
11000
123
07/09/2021
9500
123
07/15/2021
1000
123
07/20/2021
500
123
07/28/2021
500
Below is the table create and data insert statements for reference :-
create table TRANSACTIONS (
AccountID int,
Date date,
Amount int
)
insert into TRANSACTIONS values (123, '07/02/2021', 2000)
insert into TRANSACTIONS values (123, '07/09/2021', 9000)
insert into TRANSACTIONS values (123, '07/15/2021', 500)
insert into TRANSACTIONS values (123, '07/20/2021', 500)
insert into TRANSACTIONS values (123, '07/28/2021', 500)
I was able to create script that could sum over 5 days with skipping weekends(Saturday and Sunday). I am not able to think how can I skip the holiday on July 5th, 2021. I am fine with hardcoding it since this is just for testing purposes. The code 'DATEPART(WEEKDAY, h2.Date) not in (1, 7)' skips Weekend and 'DATEADD(d, 6, h1.Date)' here I am adding 6 and not 5 even the sum should be for over 5 days because after reading some articles I figured that in skipping weekends the last day is not inclusive so used 6 instead of 5. This code adds perfectly over 5 days skipping weekends
SELECT AccountId, Date,
(
SELECT SUM(Amount)
FROM TRANSACTIONS h2
WHERE
h1.AccountID = h2.AccountID and
DATEPART(WEEKDAY, h2.Date) not in (1, 7) and
h2.Date between h1.Date AND DATEADD(d, 6, h1.Date)
) as SumAmount
FROM TRANSACTIONS h1
The only sane way to tackle this is to have a calendar table to represent holidays. The easiest approach is to store every date for the date range you're likely to need (eg 1970-2030) with the type of the date, perhaps and enum of WORKDAY, WEEKEND, HOLIDAY or whatever works, eg
CREATE TABLE CALENDAR (
Date DATE,
Day_type varchar(16)
);
-- insert rows for dates you care about
Depending on where you live, you may need to include a region column too (typically the country and/or state).
With such a table, you join to it:
SELECT
AccountId,
DATEADD(DAY, (DATEDIFF(DAY, 0, t.Date)/7)*7 + 7, 0) as Date,
SUM(Amount)
FROM TRANSACTIONS t
JOIN CALENDAR c on t.Date = c.Date
AND c.day_type = 'WORKDAY'
WHERE t.Date BETWEEN <your date range>
GROUP BY AccountId, DATEADD(DAY, (DATEDIFF(DAY, 0, t.Date)/7)*7 + 7, 0)

Get the ranges from the table on the basis of value

I have some specific transaction count value and I have to check if that count lies within what range. The ranges are specified in the below table. For example, if the count value is >= 1 AND less than 250001 from the range_start column then the count lies with range_id 1.
The tricky part is that if the transaction count on the first day of the month is greater than 1 and less than 31 and lies in range_id 3 then I have to divide the count into 3 bands for example if I have 30 transactions on the first day of the month then I will calculate the fees for the 10 transactions on the basis of range_id 1, and for the other 10 the fees would be calculated on the basis of range_id 2 and the remaining 10 transactions would be calculated on the range_id 3. Now on the second day of the month fees calculation would start from band 3 and it will keep moving to the next bands as the transaction volume would increase.
More Exaplanation:
Fees calculation is like this:
Total Transactions on first day of the month are:30
Auth Fees for First 10 transaction would be 10 * 0.1698 (range_id 1) = 1.698
Auth Fees the other 10 transactions would be 10 * 0.1536 (range_id 2)= 1.536
Auth Fees the other 10 transactions would be 10 * 0.1403 (range_id 3)= 1.403
Total Transactions on the second day of the month are : 20
Auth Fees for first 10 transactions would be 10 * 1.403 (range_id 4) = 14.03
Auth Fees for second 10 transactions would be 10 * 0.1036 (range_id 5) = 1.036
Total Transactions on the third day of the month are : 5
Auth Fees for 5 transactions would be 5 * 1.036 (range_id 6) = 5.18
I am saving the daily transaction count in a table, for example for the first day it would be 30,
for the second day it would be 50 and for the third day it would be 55 and on the first day of the month it would be reset to 0.
The fees calculation is daily and the transaction table will only have data for one day and at the end of the calculation, the transaction table is dropped, So I am keeping the total count for the previous business day in the table(gstl_daily_volume) as well in order to calculate the range_id next day.
I have achieved the band calculation with the below queries but the problem with this is that it can only calculate the fees accurately for one day and for the second day it starts again from the range_id 1 as it is not considering the volume of the previous day from gstl_daily_volume table. Please help me understand how can I continue on the second day from where I left off on first day of the month while considering the volume of the previous day.
declare #rangeTable TABLE
(
rangeId INT,
rangeStart INT,
rangeEnd INT NULL,
authFees decimal(8,4),
settlementFees decimal(8,4),
declinedFees decimal(8,4)
)
insert into #rangeTable
values
(1, 1, 11, 0.1698, 0.1359, 0.3284),
(2, 11, 21, 0.1536, 0.1536, 0.3280),
(3, 21, 31, 0.1403, 0.1330, 0.3278),
(4, 31, 41, 0.1203, 0.1320, 0.3276),
(5, 41, 51, 0.1036, 0.1310, 0.3274),
(6, 51, NULL, 0.0873, 0.1300, 0.3272)
declare #transactionsTable TABLE
(
transactionId INT IDENTITY(1,1),
transactionDate DateTime,
transactionAmount decimal(8,2)
)
insert into #transactionsTable
values
(N'2020-12-01', 500),
(N'2020-12-01', 501)
--- calculate per date total transaction fees
select
Datee = CAST(C.transactionDate AS DATE),
TotalSettlememtFee = SUM(C.settlementFees)
From
(
select
*
-- each transaction counts as 1
--,AuthFee = 1 * B.authFees
--,SettlememtFee = 1 * settlementFees
from
(
-- set a row number or transaction number per transaction which resets every month
select
rowNumber = ROW_NUMBER() OVER(PARTITION BY DATEPART(MONTH, transactionDate), DATEPART(YEAR, transactionDate) ORDER BY transactionDate),
*
from #transactionsTable tt
) A
-- cross apply to calculate which range for each transaction
CROSS APPLY
(
select
*
From #rangeTable rtt
where A.rowNumber >= rtt.rangeStart
AND A.rowNumber < rtt.rangeEnd
) B
) C
-- group by date to get the per date fees
group by CAST(C.transactionDate AS DATE)
Daily Volume table where The total count would be saved for each day:
GO
/****** Object: Table [dbo].[gstl_daily_volume] Script Date: 1/4/2021 5:50:08 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[gstl_daily_volume](
[business_date] [datetime] NULL,
[record_type] [varchar](50) NULL,
[total_count] [varchar](50) NULL,
[band_id] [bigint] NULL
) ON [PRIMARY]
GO
Please help, thanks in advance.
As the transactions table is a staging table, i.e. it only has data for current day - and gets empty end of the day.
I think if you add the previous days transaction count in your calculated row number this will work as expected.
DECLARE #previousDayTransactionCount INT;
-- TODO: set this to MAX from gstl_daily_volume for current month
--- calculate per date total transaction fees
select
Datee = CAST(C.transactionDate AS DATE),
TotalSettlememtFee = SUM(C.settlementFees)
From
(
select
*
-- each transaction counts as 1
--,AuthFee = 1 * B.authFees
--,SettlememtFee = 1 * settlementFees
from
(
-- set a row number or transaction number per transaction which resets every month
-- Then Add it here to current calculated rowNumber
select
rowNumber = #previousDayTransactionCount + (ROW_NUMBER() OVER(PARTITION BY DATEPART(MONTH, transactionDate), DATEPART(YEAR, transactionDate) ORDER BY transactionDate)),
*
from #transactionsTable tt
) A
-- cross apply to calculate which range for each transaction
CROSS APPLY
(
select
*
From #rangeTable rtt
where A.rowNumber >= rtt.rangeStart
AND A.rowNumber < rtt.rangeEnd
) B
) C
-- group by date to get the per date fees
group by CAST(C.transactionDate AS DATE)

How to write a database view that expands data into multiple rows?

I have a database table that contains collection data for product collected from a supplier and I need to produce an estimate of month-to-date production figures for that supplier using an Oracle SQL query. Each day can have multiple collections, and each collection can contain product produced across multiple days.
Here's an example of the raw collection data:
Date Volume ColectionNumber ProductionDays
2011-08-22 500 1 2
2011-08-22 200 2 2
2011-08-20 600 1 2
Creating a month-to-date estimate is tricky because the first day of the month may have a collection for two days worth of production. Only a portion of that collected volume is actually attributable to the current month.
How can I write a query to produce this estimate?
My gut feeling is that I should be able to create a database view that transforms the raw data into estimated daily production figures by summing collections on the same day and distributing collection volumes across the number of days they were produced on. This would allow me to write a simple query to find the month-to-date production figure.
Here's what the above collection data would look like after being transformed into estimated daily production figures:
Date VolumeEstimate
2011-08-22 350
2011-08-21 350
2011-08-20 300
2011-08-19 300
Am I on the right track? If so, how can this be implemented? I have absolutely no idea how to do this type of transformation in SQL. If not, what is a better approach?
Note: I cannot do this calculation in application code since that would require a significant code change which we can't afford.
try
CREATE TABLE TableA (ProdDate DATE, Volume NUMBER, CollectionNumber NUMBER, ProductionDays NUMBER);
INSERT INTO TableA VALUES (TO_DATE ('20110822', 'YYYYMMDD'), 500, 1, 2);
INSERT INTO TableA VALUES (TO_DATE ('20110822', 'YYYYMMDD'), 200, 2, 2);
INSERT INTO TableA VALUES (TO_DATE ('20110820', 'YYYYMMDD'), 600, 1, 2);
COMMIT;
CREATE VIEW DailyProdVolEst AS
SELECT DateList.TheDate, SUM (DateRangeSums.DailySum) VolumeEstimate FROM
(
SELECT ProdStart, ProdEnd, SUM (DailyProduction) DailySum
FROM
(
SELECT (ProdDate - ProductionDays + 1) ProdStart, ProdDate ProdEnd, CollectionNumber, VolumeSum/ProductionDays DailyProduction
FROM
(
Select ProdDate, CollectionNumber, ProductionDays, Sum (Volume) VolumeSum FROM TableA
GROUP BY ProdDate, CollectionNumber, ProductionDays
)
)
GROUP BY ProdStart, ProdEnd
) DateRangeSums,
(
SELECT A.MinD + MyList.L TheDate FROM
(SELECT MIN (ProdDate - ProductionDays + 1) MinD FROM TableA) A,
(SELECT LEVEL - 1 L FROM DUAL CONNECT BY LEVEL <= (SELECT Max (ProdDate) - MIN (ProdDate - ProductionDays + 1) + 1 FROM TableA)) MyList
) DateList
WHERE DateList.TheDate BETWEEN DateRangeSums.ProdStart AND DateRangeSums.ProdEnd
GROUP BY DateList.TheDate;
The view DailyProdVolEst gives you dynamically the result you described... though some "constraints" apply:
the combination of ProdDate and CollectionNumber should be unique.
the ProductionDays need to be > 0 for all rows
EDIT - as per comment requested:
How this query works:
It finds out what the smallest + biggest date in the table are, then builds rows with each row being a date in that range (DateList)... this is matched up against a list of rows containing the daily sum for unique combinations of ProdDate Start/End (DateRangeSums) and sums it up on the date level.
What do SUM (DateRangeSums.DailySum) and SUM (DailyProduction) do ?
Both sum things up - the SUM (DateRangeSums.DailySum) sums up in cases of partialy overlapping date ranges, and the SUM (DailyProduction) sums up within one date range if there are more than one CollectionNumber. Without SUM the GROUP BY wouldn't be needed.
I think a UNION query will do the trick for you. You aren't using the CollectionNumber field in your example, so I excluded it from the sample below.
Something similar to the below query should work (Disclaimer: My oracle db isn't accessible to me at the moment):
SELECT Date, SUM(Volume) VolumeEstimate
FROM
(SELECT Date, SUM(Volume / ProductionDays) Volume
FROM [Table]
GROUP BY Date
UNION
SELECT (Date - 1) Date, SUM(Volume / 2)
WHERE ProductionDays = 2
GROUP BY Date - 1)
GROUP BY Date
It sounds like what you want to do is sum up by day and then use a tally table to divide out the results.
Here's a runnable example with your data in T-SQL dialect:
DECLARE #tbl AS TABLE (
[Date] DATE
, Volume INT
, ColectionNumber INT
, ProductionDays INT);
INSERT INTO #tbl
VALUES ('2011-08-22', 500, 1, 2)
, ('2011-08-22', 200, 2, 2)
, ('2011-08-20', 600, 1, 2);
WITH Numbers AS (SELECT 1 AS N UNION ALL SELECT 2 AS N)
,AssignedVolumes AS (
SELECT t.*
, t.Volume / t.ProductionDays AS PerDay
, DATEADD(d, 1 - n.N, t.[Date]) AS AssignedDate
FROM #tbl AS t
INNER JOIN Numbers AS n
ON n.N <= t.ProductionDays
)
SELECT AssignedDate
, SUM(PerDay)
FROM AssignedVolumes
GROUP BY AssignedDate;​
I dummied up a simple numbers table with only 1 and 2 in it to perform the pivot. Typically you'll have a table with a million numbers in sequence.
For Oracle, the only thing you should need to change would be the DATEADD.

How to validate if there was 12 sequential payments

As example :
I have this scenario where we receive payments, a singular payment per family, and register those payments with it's amount in the DB.
The thing is that a family can move their loan from bank1 to bank2, only if they have 12 or more sequential payments.
As example if they have registered a payment for
oct, nov, dec, jan, feb, mar, apr, may, jun, jul, ago, and sept.
and feb didn't received any payment, the count will start over at march.
Coworkers are suggesting that the best approach is, in every payment registration count the total payments and register the total sequential payments in an int column called sequential.
as:
Payment Family Bank Date Sequential
---------------------------------------------------------
1200 2 1 10-22-2009 1
1200 2 1 11-22-2009 2
.
.
.
1200 2 1 08-22-2010 11
1200 2 1 09-22-2010 12
What I think, there must be an approach where the sequential column is needless, where if I want to validate if the last order by Date DESC 12 rows are sequential with only 1 month in difference.
any ideas?
Edited:
There will be million of rows in this table.
Also prefer to have only the dates in the tables and work with them at application level
Analytics!
Data:
create table payments
(amount number,
family number,
bank number,
payment_date date
);
insert into payments values (1200, 2, 1, date '2010-01-01');
insert into payments values (1200, 2, 1, date '2010-02-02');
insert into payments values (1200, 2, 1, date '2010-03-03');
insert into payments values (1200, 2, 1, date '2010-04-04');
insert into payments values (1200, 2, 1, date '2010-05-05');
insert into payments values (1200, 2, 1, date '2010-06-07');
insert into payments values (1200, 2, 1, date '2010-07-07');
--skip august
--insert into payments values (1200, 2, 1, date '2010-08-08');
insert into payments values (1200, 2, 1, date '2010-09-09');
insert into payments values (1200, 2, 1, date '2010-10-10');
insert into payments values (1200, 2, 1, date '2010-11-11');
--double pay november
insert into payments values (1200, 2, 1, date '2010-11-30');
insert into payments values (1200, 2, 1, date '2010-12-12');
Query:
select *
from (select family, bank,
trunc(payment_date, 'mon') as payment_month,
lead ( trunc(payment_date, 'mon'))
over ( partition by family
order by payment_date)
as next_payment_month
from payments
order by payment_date desc
)
-- eliminate multiple payments in month
where payment_month <> next_payment_month
-- find a gap
and add_months(payment_month, 1) <> (next_payment_month)
-- stop at the first gap
and rownum = 1
Results:
FAMILY BANK PAYMENT_M NEXT_PAYM
---------- ---------- --------- ---------
2 1 01-JUL-10 01-SEP-10
You can use the value in NEXT_PAYMENT_MONTH to perform whatever comparison you want at the application level.
SELECT trunc(MONTHS_BETWEEN(SYSDATE, DATE '2010-01-01')) FROM DUAL
gives you a number of months - that was what I meanty by using the value at the application level.
So this:
select trunc(
months_between(sysdate,
(select next_payment_date
from (select family, bank,
trunc(payment_date, 'mon') as payment_month,
lead ( trunc(payment_date, 'mon'))
over ( partition by family
order by payment_date)
as next_payment_month
from payments
where family = :family
order by payment_date desc
)
where payment_month <> next_payment_month
and add_months(payment_month, 1) <> (next_payment_month)
and rownum = 1
)
)
from dual
Gives you a number of months with successive payments since the last missed month.
To validate whether a single family have 12 sequential payments over the past twelve months, regardless of bank, use:
select sum(payment) total_paid,
count(*) total_payments,
count(distinct trunc(pay_date,'mon')) paid_months
from payment_table
where family = :family and pay_date between :start_date and :end_date;
total_payments indicates the number of payments made in the period, while paid_months indicates the number of separate months in which payments were made.
If you want to check whether they have already switched bank in the selected period, add a group by bank clause to the above query.
To list all families with 12 distinct months of payments within the period, use:
select family,
sum(payment) total_paid,
count(*) total_payments,
count(distinct trunc(pay_date,'mon')) paid_months
from payment_table
where pay_date between :start_date and :end_date
group by family
having count(distinct trunc(pay_date,'mon')) = 12;
If you want to restrict the results to families that have not already switched bank in the selected period, add a and count(distinct bank) = 1 condition to the having clause of the above query.
I suggest ensuring that the payment table has an index on family and pay_date.
I think a simple query will help, check this:
SELECT COUNT(*)
FROM payments p
WHERE p.Family = 2 AND p.Date between '01-01-2009' and '12-01-2009'
this way, you'll get the number of payments between any date with your current table structure.
How about this:
SELECT PT.Payment
, PT.Family
, PT.Bank
, PT.Date
, (SELECT COUNT(*) FROM PaymentTable T
WHERE DATEDIFF (d, T.Date, PT.Date) < 31) as IsSequential
FROM PaymentsTable PT
The above query will tell you for each payment if it's sequential (i.e. if there was a payment made the month before it)
Then you could run a query to determine if there are 12 sequential payments made for a specific month, or for a specific family.
Let's say you want to display all families that have at least 12 sequential payments:
SELECT ST.Family
, COUNT(ST.IsSequential) as NumberOfSequentialPayments
FROM
(SELECT PT.Payment
, PT.Family
, PT.Bank
, PT.Date
, (SELECT COUNT(*) FROM PaymentTable T
WHERE DATEDIFF (d, T.Date, PT.Date) < 31) as IsSequential
FROM PaymentsTable PT
) AS ST
WHERE NumberOfSequentialPayments >= 12
GROUP BY ST.Family
It is possible to do it as other have pointed out.
However, this is not a case when you have relational data, but you do things sequentially, which is a bad thing.
This is a case when a business rule is sequential in nature; in such cases having a sequential helper field might
simplify your queries
improve performance (if you talk about 100M records this sudenlly becomes almost highest rated factor and various denormalization ideas spring to mind)
make sense for other business rules (allow more functionality and flexibility)
Re last point: I think the most complete solution would require re-examining the business rules - you would probably discover that users would talk about 'missed payments', which suggest other tables, such as 'payment plan/schedule' and tied with other processes this might be really the right place to have either missed payment column or sequential value... This structure would also support flexibility in grace periods, prepaying, etc...