How get previous values form last transaction using PK and FK - sql

I have this SQL Queries,
SELECT PaymentID, CustomerID, PaymentDate, Amount, Balance, Credit
FROM Payment
WHERE (PaymentDate = '2012-11-03')
I also like to print previous Balance and Credit form last transaction of the customer.
This is my try.
SELECT DISTINCT TOP 1 Balance, Credit, PaymentID
FROM Payment
WHERE CustomerID = '??' AND PaymentID < '??'
ORDER BY PaymentID DESC
As you can see this does nothing as Queries are not liked.
I think I have I have to use T-SQL or UNION but have no idea How to implement it.
This kind of Output I am trying to achieve.
Payment Table
PaymentID int
CustomerID varchar(50)
PaymentDate date
Amount decimal(18, 2)
Balance decimal(18, 2)
Credit decimal(18, 2)
Note:
This is for all the customers not only for single customer, it's like a sale Report.
There are can be more than one payment per customer in a day.

This might get you started
SELECT P1.paymentID, P1.CustomerID, P1.PaymentDate, max(PP.paymentID)
FROM Payment P1
Join Payment PP
on PP.paymentID < P1.paymentID
and PP.CustomerID = P1.CustomerID
and P1.PaymentDate = '2012-11-03'
GroupBy P1.paymentID, P1.CustomerID, P1.PaymentDate
not test but I think this will pull in the other data
select PC.*, PL.PaymentDate, PL.Amount, PL.Balance, PL.Credit
from
(SELECT P1.paymentID, P1.CustomerID, P1.PaymentDate, max(PP.paymentID) as PriorPID
FROM Payment P1
Join Payment PP
on PP.paymentID < P1.paymentID
and PP.CustomerID = P1.CustomerID
and P1.PaymentDate = '2012-11-03'
GroupBy P1.paymentID, P1.CustomerID, P1.PaymentDate) as PC
join Payment PL
on PL.paymentID = PC.PriorPID
and PL.CustomerID = PC.CustomerID

Related

Combining multiple queries

I want a table with all customers and their last charge transaction date and their last invoice date. I have the first two, but don't know how to add the last invoice date to the table. Here's what I have so far:
WITH
--Last customer transaction
cust_trans AS (
SELECT customer_id, created
FROM charges a
WHERE created = (
SELECT MAX(created) AS last_trans
FROM charges b
WHERE a.customer_id = b.customer_id)),
--All customers
all_cust AS (
SELECT customers.id AS customer, customers.email, CAST(customers.created AS DATE) AS join_date, ((1.0 * customers.account_balance)/100) AS balance
FROM customers),
--Last customer invoice
cust_inv AS (
SELECT customer_id, date
FROM invoices a
WHERE date = (
SELECT MAX(date) AS last_inv
FROM invoices b
WHERE a.customer_id = b.customer_id))
SELECT * FROM cust_trans
RIGHT JOIN all_cust ON all_cust.customer = cust_trans.customer_id
ORDER BY join_date;
This should get what you need. Notice each individual subquery is left-joined to the customer table, so you always START with the customer, and IF there is a corresponding record in each subquery for max charge date or max invoice date, it will be pulled in. Now, you may want to apply a COALESCE() for the max dates to prevent showing nulls, such as
COALESCE(maxCharges.LastChargeDate, '') AS LastChargeDate
but your call.
SELECT
c.id AS customer,
c.email,
CAST(c.created AS DATE) AS join_date,
((1.0 * c.account_balance) / 100) AS balance,
maxCharges.LastChargeDate,
maxInvoices.LastInvoiceDate
FROM
customers c
LEFT JOIN
(SELECT
customer_id,
MAX(created) LastChargeDate
FROM
charges
GROUP BY
customer_id) maxCharges ON c.id = maxCharges.customer_id
LEFT JOIN
(SELECT
customer_id,
MAX(date) LastInvoiceDate
FROM
invoices
GROUP BY
customer_id) maxInvoices ON c.id = maxInvoices.customer_id
ORDER BY
c.created

Can my query be made more efficient, and how about adding a GROUP BY clause?

I am trying to write a query that returns:
The total amount of transactions that occurred before a date range, for a particular customer.
The total amount of transactions that occurred within a date range, for a particular customer.
The total amount of payments that occurred before a date range, for a particular customer.
The total amount of payments that occurred within a date range, for a particular customer.
To that end, I've come up with the following query.
declare #StartDate DATE = '2016-08-01'
declare #EndDate DATE = '2016-08-31'
declare #BillingCategory INT = 0
select c.Id, c.Name, c.StartingBalance,
(select coalesce(sum(Amount), 0) from Transactions t where t.CustomerId = c.Id and t.[Date] < #StartDate) xDebits,
(select coalesce(sum(Amount), 0) from Transactions t where t.CustomerId = c.Id and t.[Date] >= #StartDate and t.[Data] <= #EndDate) Debits,
(select coalesce(sum(Amount), 0) from Payments p where p.CustomerId = c.Id and p.[Date] < #StartDate) xCredits,
(select coalesce(sum(Amount), 0) from Payments p where p.CustomerId = c.Id and p.[Date] >= #StartDate and p.[Date] <= #EndDate) Credits
from customers c
where c.BillingCategory in (0,1,2,3,4,5)
This query seems to give the results I want. I used the subqueries because I couldn't seem to figure out how to accomplish the same thing using JOINs. But I have a few questions.
Does this query retrieve the transaction and payment data for every single customer before filtering it according to my WHERE condition? If so, that seems like a big waste. Can that be improved?
I'd also like to add a GROUP BY to total each payment and transaction column by BillingCategory. But how can you add a GROUP BY clause here when the SELECTed columns are limited to aggregate functions if they are not in the GROUP BY clause?
The Transactions and Payments tables both have foreign keys to the Customers table.
Sample data (not real)
Customers:
Id Name BillingCategory
----- ------- ---------------
1 'ABC' 0
2 'DEF' 1
3 'GHI' 0
Transactions:
Id CustomerId Date Amount
----- ---------- ------------ ------
1 2 '2016-08-01' 124.90
2 2 '2016-08-04' 37.23
3 1 '2016-08-27' 450.02
Payments:
Id CustomerId Date Amount
----- ---------- ------------ ------
1 1 '2016-09-01' 50.00
2 1 '2016-09-23' 75.00
3 2 '2016-09-01' 100.00
You could build your sums seperately for Transactions and Payments in a CTE and then join them together:
WITH
CustomerTransactions AS
(
SELECT CustomerId,
SUM(CASE WHEN [Date] < #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS xDebits,
SUM(CASE WHEN [Date] >= #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS Debits
FROM Transactions
GROUP BY CustomerId
),
CustomerPayments AS,
(
SELECT CustomerId,
SUM(CASE WHEN [Date] < #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS xCredits,
SUM(CASE WHEN [Date] >= #StartDate THEN 1 ELSE 0 END * COALESCE(Amount, 0)) AS Credits
FROM Payments
GROUP BY CustomerId
)
SELECT C.Id, c.Name, c.StartingBalance,
COALESCE(T.xDebits, 0) AS xDebits,
COALESCE(T.Debits, 0) AS Debits,
COALESCE(P.xCredits, 0) AS xCredits,
COALESCE(P.Credits, 0) AS Credits
FROM Custormers C
LEFT OUTER JOIN CustomerTransactions T ON T.CustomerId = C.Id
LEFT OUTER JOIN CustomerPayments P ON P.CustomerId = C.Id
WHERE C.BillingCategory IN(0, 1, 2, 3, 4, 5);
You can do with sub-queries to be more efficient. Pre-query grouped by each customer for only those customers who qualify by the categories in question. These sub-queries will always result in an at-most, 1 record per customer so you don't get a Cartesian result. Get that for your debits and credits and re-join back to your master list of customers with a left-join in case one side or the other (debits/credits) may not exist.
declare #StartDate DATE = '2016-08-01'
declare #EndDate DATE = '2016-08-31'
declare #BillingCategory INT = 0
select
c.ID,
c.Name,
c.StartingBalance,
coalesce( AllDebits.xDebits, 0 ) DebitsPrior,
coalesce( AllDebits.Debits, 0 ) Debits
coalesce( AllCredits.xCredits, 0 ) CreditsPrior,
coalesce( AllCredits.Credits, 0 ) Credits
from
customers c
LEFT JOIN
( select t.CustomerID,
sum( case when t.[Date] < #StartDate then Amount else 0 end ) xDebits,
sum( case when t.[Date] >= #StartDate then Amount else 0 end ) Debits
from
customers c1
JOIN Transactions t
on c1.CustomerID = t.CustomerID
where
c1.BillingCategory in (0,1,2,3,4,5)
group by
t.CustomerID ) AllDebits
on c.CustomerID = AllDebits.CustomerID
LEFT JOIN
( select p.CustomerID,
sum( case when p.[Date] < #StartDate then Amount else 0 end ) xCredits,
sum( case when p.[Date] >= #StartDate then Amount else 0 end ) Credits
from
customers c1
JOIN Payments p
on c1.CustomerID = p.CustomerID
where
c1.BillingCategory in (0,1,2,3,4,5)
group by
p.CustomerID ) AllCredits
on c.CustomerID = AllCredits.CustomerID
where
c.BillingCategory in (0,1,2,3,4,5)
COMMENT ADDITION
With respect to Thomas's answer, yes they are close. My version also adds the join to the customer table for the specific billing category and here is why. I don't know the size of your database, how many customers, how many transactions. If you are dealing with a large amount that DOES have performance impact, Thomas's version is querying EVERY customer and EVERY transaction. My version is only querying the qualified customers by the billing category criteria you limited.
Again, not knowing data size, if you are dealing with 100k records may be no noticeable performance. If you are dealing with 100k CUSTOMERS, could be a totally different story.
#JonathanWood, correct, but my version has each internal subquery inclusive of the cus

How to sum over selected dates from table

Consider the following query which generates customerid and days on which they bought a particular product, clearly each customer will have different dates on which he/she bought an item. What I want to do is get total purchase made on those days that the customer bought that product.
I have the ff query.
Select customerid, eventdate
into #days
from table1
where product='chocolate'
now i want to sum all purchases made on just those days customer bought 'chocolate'.
so i have
select customerid, sum(purchases) purchases
into #pur
from table1 a
where eventdate in (select eventdate from #days where customerid=a.customerid)
group by customerid
but the above is taking to long to run so i cancelled it.
please assist with a better query.
Not very sure if this will work but try it out. If it doesnt then can you please provide sample data and expected output so that we can try it out in a better fashion
select customerid, sum(purchases) purchases
into #pur
from table1 a
inner join #days d
ON d.customerid = a.customerid
AND d.eventdate = a.eventdate
group by customerid
OR try this query
select customerid, sum(purchases) purchases
into #pur
from table1 a
inner join #days d
ON d.customerid = a.customerid
WHERE d.eventdate = a.eventdate
group by customerid
Hope this helps
The sum of all purchases by customer, counting only the dates on which he bought chocolate.
SELECT customerid, sum(purchases) purchases
FROM table1 a
WHERE eventdate IN
(SELECT eventdate
FROM table1
WHERE product = 'chocolate'
AND customerid = a.customerid)
GROUP BY customerid;
This gives sum of purchases for each customer AND for each of that day on which chocolate was purchased.
select customerid, eventdate ,sum(purchases) purchases
into #pur
from table1
where product='chocolate'
group by customerid,eventdate
If you want total purchase when chocolate was brought then do this
select customerid,sum(purchases) purchases
into #pur
from table1
where product='chocolate'
group by customerid
As per your clarification
select customerid, sum(purchases) purchases
into #pur
from table1 a
where eventdate in (select eventdate from table1 where product='chocolate')
group by customerid
I suggest you to apply indexing on eventdate column to improve query performance.
After thinking through carefully the following worked for me, and is faster.
--drop table #days
select customerid, eventdate
into #days
from table1
with(nolock, index(ix_eventdate))
WHERE EVENTDATE between 20140401 and 20140430
and product='chocolate'
--drop table #pur
select customerid, eventdate, purchases
into #pur
from table1
with(nolock, index(ix_eventdate))
where eventdate between 20140401 and 20140430
--drop table #first
select a.*, b.purchases
into #first from #days a
left join #pur b
on a.customerid=b.customerid
and a.EventDate =b.EventDate
--select * from #first
--drop table #purdays
select customerid, sum(purchases) revenue into #purdays from #first
group by customerid
order by customerid
select * from #purdays

How to select only customer data and sum of that at bottom of table as row using sql query?

i have sql query.
this query is providing me a lots of record of customer and that voucher and order subtotal but i want sum of debit and credit row and credit and debit column sum at bottom position of table.
so guide me how can i achieve this table using by query.
this is my sql query.
declare #Credit decimal(18,2)
declare #Debit decimal(18,2)
select #debit= SUM(sd.SubTotal)
from SalesOrderDetails sd
left join SalesOrder so
on sd.SalesOrderId = so.SalesOrderId
select #credit = SUM(Amount) from Vouchers
SELECT CustomerId,
OrderDate,
VoucherType,
VoucherNo,
SUM(Debit) as Debit,
SUM(Credit) as Credit
FROM (SELECT so.CustomerId,
so.OrderDate ,
'Sales' As VoucherType,
'' AS VoucherNo,
a.Total As Debit
, NULL As Credit
from SalesOrder so
inner join (
SELECT SalesOrderId,
sum(SubTotal) AS Total
FROM SalesOrderDetails
group by SalesOrderId
)as a
on so.SalesOrderId = a.SalesOrderId
UNION ALL
SELECT V.CustomerId
,v.VoucherDate As OrderDate
, 'Receipt' AS VoucherType
,v.VoucherNumber AS VoucherNo
,NULL AS Debit
,v.Amount AS Credit
from Vouchers v
) ledger
GROUP BY GROUPING SETS ((CustomerId),(OrderDate, VoucherType, VoucherNo), ())
i have attached my output image.
in my image i got this result but that is very messy process so give me a proper solution for this.
Add a HAVING Clause which happens after the grouping and aggregating?
....
GROUP BY GROUPING SETS ((CustomerId),(OrderDate, VoucherType, VoucherNo), ())
HAVING VoucherType Is Null

How to do a group by without having to pass all the columns from the select?

I have the following select, whose goal is to select all customers who had no sales since the day X, and also bringing the date of the last sale and the number of the sale:
select s.customerId, s.saleId, max (s.date) from sales s
group by s.customerId, s.saleId
having max(s.date) <= '05-16-2013'
This way it brings me the following:
19 | 300 | 26/09/2005
19 | 356 | 29/09/2005
27 | 842 | 10/05/2012
In another words, the first 2 lines are from the same customer (id 19), I wish to get only one record for each client, which would be the record with the max date, in the case, the second record from this list.
By that logic, I should take off s.saleId from the "group by" clause, but if I do, of course, I get the error:
Invalid expression in the select list (not contained in either an
aggregate function or the GROUP BY clause)
I'm using Firebird 1.5
How can I do this?
GROUP BY summarizes data by aggregating a group of rows, returning one row per group. You're using the aggregate function max(), which will return the maximum value from one column for a group of rows.
Let's look at some data. I renamed the column you called "date".
create table sales (
customerId integer not null,
saleId integer not null,
saledate date not null
);
insert into sales values
(1, 10, '2013-05-13'),
(1, 11, '2013-05-14'),
(1, 12, '2013-05-14'),
(1, 13, '2013-05-17'),
(2, 20, '2013-05-11'),
(2, 21, '2013-05-16'),
(2, 31, '2013-05-17'),
(2, 32, '2013-03-01'),
(3, 33, '2013-05-14'),
(3, 35, '2013-05-14');
You said
In another words, the first 2 lines are from the same customer(id 19), i wish he'd get only one record for each client, which would be the record with the max date, in the case, the second record from this list.
select s.customerId, max (s.saledate)
from sales s
where s.saledate <= '2013-05-16'
group by s.customerId
order by customerId;
customerId max
--
1 2013-05-14
2 2013-05-16
3 2013-05-14
What does that table mean? It means that the latest date on or before May 16 on which customer "1" bought something was May 14; the latest date on or before May 16 on which customer "2" bought something was May 16. If you use this derived table in joins, it will return predictable results with consistent meaning.
Now let's look at a slightly different query. MySQL permits this syntax, and returns the result set below.
select s.customerId, s.saleId, max(s.saledate) max_sale
from sales s
where s.saledate <= '2013-05-16'
group by s.customerId
order by customerId;
customerId saleId max_sale
--
1 10 2013-05-14
2 20 2013-05-16
3 33 2013-05-14
The sale with ID "10" didn't happen on May 14; it happened on May 13. This query has produced a falsehood. Joining this derived table with the table of sales transactions will compound the error.
That's why Firebird correctly raises an error. The solution is to drop saleId from the SELECT clause.
Now, having said all that, you can find the customers who have had no sales since May 16 like this.
select distinct customerId from sales
where customerID not in
(select customerId
from sales
where saledate >= '2013-05-16')
And you can get the right customerId and the "right" saleId like this. (I say "right" saleId, because there could be more than one on the day in question. I just chose the max.)
select sales.customerId, sales.saledate, max(saleId)
from sales
inner join (select customerId, max(saledate) max_date
from sales
where saledate < '2013-05-16'
group by customerId) max_dates
on sales.customerId = max_dates.customerId
and sales.saledate = max_dates.max_date
inner join (select distinct customerId
from sales
where customerID not in
(select customerId
from sales
where saledate >= '2013-05-16')) no_sales
on sales.customerId = no_sales.customerId
group by sales.customerId, sales.saledate
Personally, I find common table expressions make it easier for me to read SQL statements like that without getting lost in the SELECTs.
with no_sales as (
select distinct customerId
from sales
where customerID not in
(select customerId
from sales
where saledate >= '2013-05-16')
),
max_dates as (
select customerId, max(saledate) max_date
from sales
where saledate < '2013-05-16'
group by customerId
)
select sales.customerId, sales.saledate, max(saleId)
from sales
inner join max_dates
on sales.customerId = max_dates.customerId
and sales.saledate = max_dates.max_date
inner join no_sales
on sales.customerId = no_sales.customerId
group by sales.customerId, sales.saledate
then you can use following query ..
EDIT changes made after comment by likeitlikeit for only one row per CustomerID even when we will have one case where we have multiple saleID for customer with certain condition -
select x.customerID, max(x.saleID), max(x.x_date) from (
select s.customerId, s.saleId, max (s.date) x_date from sales s
group by s.customerId, s.saleId
having max(s.date) <= '05-16-2013'
and max(s.date) = ( select max(s1.date)
from sales s1
where s1.customeId = s.customerId))x
group by x.customerID
You can Try Maxing the s.saleId (Max(s.saleId)) and removing it from the Group By clause
A subquery should do the job, I can't test it right now but it seems ok:
SELECT s.customerId, s.saleId, subq.maxdate
FROM sales AS s
INNER JOIN (SELECT customerId, MAX(date) AS maxdate
FROM sales
GROUP BY customerId, saleId
HAVING MAX(s.date) <= '05-16-2013'
) AS subq
ON s.customerId = subq.customerId AND s.date = subq.maxdate