How do I run this SQL command? - sql

Details of customers needed are here
Core -- Division_ID = 1
Valid Email -- email LIKE '%#%'
Opt-In: Yes -- Consent_Status_ID = 4
Consent Type Description: Consent to send marketing email -- Consent_Type_ID = 3
Has made a booking -- I assume this will be done anyways becasue i will only display bookings of customers with greater to or equal to 4 and revenue generated from them greater to or equal to 4
Confirmed booking -- Booking_Status_ID = 1
Total Revenue >= 20k
Total Direct Bookings >= 4
Maximum Revenue >= 20k
Details of customers end here
Tables look like this:
Customer
Customer_ID
Title
Initial
Firstname
email
Customer Consent
Customer_ID
consent_status_Id
consent_type_id
Household_ID
Booking
Customer_ID
Booking_ID
Total_Revenue
Customer_ID
Booking_Status_ID
Division_ID
Household
Household_ID
Surname
Query Start
Select distinct
c.Customer_ID,
c.Title,
c.Initial,
c.Firstname,
h.Surname,
c.email,
SUM(b.Total_Revenue) as 'Total Total Revenue',
COUNT(b.Customer_ID) as 'Number of Bookings'
FROM Customer c INNER JOIN Household h ON c.Household_ID=h.Household_ID
INNER JOIN Booking b ON c.Customer_ID=b.Customer_ID
INNER JOIN Customer_consent cc ON cc.Customer_ID=c.Customer_ID
where b.Division_ID = 1
and b.Booking_Status_ID = 1
and c.email LIKE '%#%'
and cc.consent_status_Id = 4
and cc.consent_type_id = 3
and (SELECT SUM(b.Total_Revenue) FROM Booking) >= 20000
and count(b.Customer_ID) >= 4
and MAX(b.Total_Revenue) >= 20000;
Query End
Error I came to was this:
An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.
What should my query be?
Thanks!

Your Query has aggregate functions in the select, so you'll need a group by clause any way:
FYI: Untested
Select distinct
c.Customer_ID,
c.Title,
c.Initial,
c.Firstname,
h.Surname,
c.email,
SUM(b.Total_Revenue) as 'Total Total Revenue',
COUNT(b.Customer_ID) as 'Number of Bookings'
FROM Customer c INNER JOIN Household h ON c.Household_ID=h.Household_ID
INNER JOIN Booking b ON c.Customer_ID=b.Customer_ID
INNER JOIN Customer_consent cc ON cc.Customer_ID=c.Customer_ID
where b.Division_ID = 1
and b.Booking_Status_ID = 1
and c.email LIKE '%#%'
and cc.consent_status_Id = 4
and cc.consent_type_id = 3
GROUP BY c.Customer_ID,
c.Title,
c.Initial,
c.Firstname,
h.Surname,
c.email,
HAVING
(SELECT SUM(b.Total_Revenue) FROM Booking) >= 20000
and count(b.Customer_ID) >= 4
and MAX(b.Total_Revenue) >= 20000;

Try something like this:
SELECT *
FROM (SELECT DISTINCT c.CUSTOMER_ID,
c.TITLE,
c.INITIAL,
c.FIRSTNAME,
h.SURNAME,
c.EMAIL,
Sum(b.TOTAL_REVENUE) AS [Total Total Revenue],
Count(b.CUSTOMER_ID) AS [Number of Bookings],
Max(b.TOTAL_REVENUE) AS MaxRevenue
FROM CUSTOMER c
INNER JOIN HOUSEHOLD h
ON c.HOUSEHOLD_ID = h.HOUSEHOLD_ID
INNER JOIN BOOKING b
ON c.CUSTOMER_ID = b.CUSTOMER_ID
INNER JOIN CUSTOMER_CONSENT cc
ON cc.CUSTOMER_ID = c.CUSTOMER_ID
WHERE b.DIVISION_ID = 1
AND b.BOOKING_STATUS_ID = 1
AND c.EMAIL LIKE '%#%'
AND cc.CONSENT_STATUS_ID = 4
AND cc.CONSENT_TYPE_ID = 3) T
WHERE [TOTAL TOTAL REVENUE] >= 20000
AND [NUMBER OF BOOKINGS] >= 4
AND MAXREVENUE >= 20000;

Related

Put Condition to CASE expression (SQL)

Is there anyway that I can filter the customers whose total bets on in-play football is more than 50% of their total bets everywhere? This is what I have in mind.
There are 2 tables, where the sportsbook_bets contains all bets from all customers (a customer may place multiple bets).
Sample Data:
Customer table
customer_id
1
2
...
sportsbook_bet table
customer_id
sport_name
1
football
1
football
1
hockey
1
basketball
2
tennis
2
football
2
hockey
2
basketball
...
...
Based on the tables above, the query should return customer 1 since the total bets placed on football is at least 50% of the total bet everywhere; where customer 2 only have 25% (thus doesn't get queried).
SELECT c.customer_id
FROM customers c INNER JOIN sportsbook_bets s
ON c.customer_id=s.customer_id
WHERE 50 < ROUND((SUM(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 ELSE 0 END)/COUNT(s.bet_id))*100,0);
When performing a filter using an aggregate, need to use HAVING not WHERE.
Customers with >= 50% Bets on Football
SELECT c.customer_id
FROM customers c
INNER JOIN sportsbook_bets s
ON c.customer_id=s.customer_id
GROUP BY c.customer_id
HAVING COUNT(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 END)/1.0/COUNT(*) >= .50
If you want to see a row per bet for those the desired customers, could use this query instead:
WITH cte_CustomerBets AS (
SELECT
c.customer_id
,s.sports_name
/*Add any other columns you need as well*/
,COUNT(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 END) OVER (PARTITION BY CustomerID) AS TotalSportsBetsPerCustomer
,COUNT(*) OVER (PARTITION BY CustomerID) AS TotalBetsPerCustomer
FROM customers c
INNER JOIN sportsbook_bets s
ON c.customer_id=s.customer_id
GROUP BY c.customer_id
HAVING COUNT(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 END)/1.0/COUNT(*) >= .50
)
SELECT *
FROM cte_CustomerBets
WHERE TotalSportsBetsPerCustomer/1.0/TotalBetsPerCustomer >= .5
I'm going to assume that you need some other columns from your customer table, otherwise you don't need that table to get customer_id, as that column is already in sportsbook_bets table.
One solution is to use a subqueries:
select distinct
C.customer_id
From customers C
Join sportsbook_bets S On S.customer_id = C.customer_id
where (Select count(FB.bet_id) From sportsbook_bets FB where FB.customer_id = C.customer_id and FB.sport_name = 'football'and FB.in_play_yn = 'Y') >
((Select count(TB.bet_id) From sportsbook_bets TB where TB.customer_id = C.customer_id) / 50.0)
Other way that I like more it's to separate your calculations, for this you can use the APPLY operator:
Select distinct
C.customer_id
From customers C
Join sportsbook_bets S On S.customer_id = C.customer_id
Cross Apply
(
Select TotalBets = count(SB.bet_id)
From sportsbook_bets SB
where SB.customer_id = S.customer_id
) TB
Cross Apply
(
Select FootballBets = count(SB.bet_id)
From sportsbook_bets SB
where SB.customer_id = S.customer_id
and SB.sport_name = 'football'
and SB.in_play_yn = 'Y'
) FB
where FB.FootballBets > TB.TotalBets / 50.0
For readability, you can separate the two components -- total bets and in-play football bets -- into two queries. This may not perform as quickly as other solutions here, but it may be easier to understand and maintain.
WITH totalbets AS (
SELECT c.customer_id
, count(s.bet_id) as TotalBets
FROM customers c
INNER JOIN sportsbook_bets s ON c.customer_id = s.customer_id
GROUP BY c.customer_id
),
ipfbets AS (
SELECT customer_id
, count(bet_id) as IPFBets
FROM sportsbook_bets
WHERE sport_name = 'football'
and in_play_yn = 'Y'
GROUP BY customer_id
)
SELECT tb.customer_id
FROM totalbets tb
INNER JOIN ipfbets ipfb ON tb.customer_id = ipfb.customer_id
WHERE (1.0 * ipfb.IPFBets) / tb.TotalBets > 0.5

SQL Server : query with incorrect SUM result

My SQL Server query is supposed to get a count of each customer's number of orders, and the SUM of their reward points. For most customers the result is accurate (most people only have one or two orders). For a few people, the result is wildly off.
Here's the original query:
SELECT
c.email,
c.lastlogindate,
c.custenabled,
c.maillist,
d.GroupName,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
LEFT JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
LEFT JOIN discount_group d ON c.discount = d.id
)
LEFT JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, c.custenabled, c.maillist, c.lastlogindate, d.GroupName;
For one example, customerid 1234 has placed 21 orders, totaling 2724 points. This will report that he has placed 441 orders (21 * 21) valued at 57204 points (2724 * 21). The raw data is fine, but each order row is being duplicated by the amount of orders they placed (but not for most customers...)
If I change the query to this:
SELECT
o.orderid,
c.email,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
INNER JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
)
INNER JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, o.orderid;
The aggregate functions are calculated properly, but it will display one result for each order placed. So it will show "Customer 1234/21 orders/2724 points", 21 times.
I did remove the 'discount_group' join in the second query, but that was just to make it easier to read and change. That hasn't had any effect on results.
Here is a solution using common table expressions to aggregate your results.
Note: this will not show customers that have 0 orders or 0 rewards points. If you would like to show these, change the INNER JOINs to LEFT JOINs
WITH cteOrders AS
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
)
, cteRewards as
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
)
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN cteOrders o ON c.contactid = o.ocustomerid
INNER JOIN cteRewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;
Or using subqueries:
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
) o ON c.contactid = o.ocustomerid
INNER JOIN
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
) r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;

selecting customers based on account status

I need to generate a report of customers with the account status of COLLECTIONS and has more than 1 consecutive DECLINED charged.
Customer Table
=====================
CustomerID
FirstName
LastName
Email
Status (Collections)
Charge
=====================
ChargeID
CustomerID
DateCharged
Amount
ACK (Declined)
SELECT Customer.CustomerID, Customer.FirstName, Customer.LastName,
Customer.Status, Charge.ChargeID, Charge.Amount, Charge.DateCharged, Charge.ACK
FROM Customers
INNER JOIN Charge
ON Customer.CustomerID=Charge.CustomerID
AND Charge.ACK = 'Declined'
A customer has to have at least 2 DECLINED charges before they are sent to collections. How can I check to see if a customer has 2 declines?
Normally, I would say just do an aggregation with a having clause.
However, your query suggests that you also want the details on the declines. Instead, using a window function to calculate the total number of declines:
SELECT *
FROM (SELECT c.CustomerID, c.FirstName, c.LastName,
c.Status, ch.ChargeID, ch.Amount, ch.DateCharged, ch.ACK,
sum(case when ch.ACK = 'Declined' then 1 else 0 end) over
(partition by c.customerId) as NumDeclines
FROM Customers c INNER JOIN
Charge ch
ON c.CustomerID = ch.CustomerID
) cc
WHERE ACK = 'Declined' and NumDeclines >= 2
ORDER BY CustomerId, DateCharged;
If you just want the customers and not the detail, then do:
SELECT c.CustomerID, c.FirstName, c.LastName, c.Status
FROM Customers c INNER JOIN
Charge ch
ON c.CustomerID = ch.CustomerID
WHERE ACK = 'Declined'
GROUP BY c.CustomerID, c.FirstName, c.LastName, c.Statu
HAVING count(*) >= 2;
Per my comment, this is how I would accomplish the task at hand.
SELECT
*
FROM (
SELECT
Row_Number() OVER (PARTITION BY Customer.CustomerID ORDER BY Charge.DateCharged DESC) AS [Row],
Customer.CustomerID, Customer.FirstName, Customer.LastName,
Customer.Status, Charge.ChargeID, Charge.Amount, Charge.DateCharged, Charge.ACK
FROM Customers
INNER JOIN Charge ON Customer.CustomerID=Charge.CustomerID
) a
INNER JOIN (
SELECT
Row_Number() OVER (PARTITION BY Customer.CustomerID ORDER BY Charge.DateCharged DESC) AS [Row],
Customer.CustomerID, Customer.FirstName, Customer.LastName,
Customer.Status, Charge.ChargeID, Charge.Amount, Charge.DateCharged, Charge.ACK
FROM Customers
INNER JOIN Charge ON Customer.CustomerID=Charge.CustomerID
) b ON a.CustomerID=b.CustomerID
WHERE a.[Row]=b.[Row]-1
AND a.ACK = 'Declined'
AND b.ACK = 'Declined'
AND a.[Row] IN (1,2)
AND b.[Row] IN (1,2)

SQL Query SUM issue

I am writing a SQL query where i want to list down and calculate some data every thing looks to work fine but the only problem I am having is when i SUM values in a given column the final result is a overall total but i want this total only to be for a month of October.
The scenario is as followed i need to calculate a total amount of deposits made by costomers over the month of October with other information that works well its only the sum issue.
SQL:
SELECT customers.id AS 'Customer ID',
customers.firstname AS
'Customer First Name',
customers.lastname AS 'Customer Last Name'
,
users.firstname AS
'Employee First Name',
users.lastname AS 'Employee Last Name'
,
positions.currency AS 'Currency',
Sum(customer_deposits.amount) AS 'Total Deposits',
Sum(positions.status) AS 'Total Bets',
Sum(customer_total_statistics.totalbonusdeposits) AS 'Total Bonuses',
positions.date AS 'Date'
FROM customers
LEFT JOIN customer_deposits
ON customers.id = customer_deposits.customerid
LEFT JOIN users
ON customers.employeeinchargeid = users.id
LEFT JOIN positions
ON customers.id = positions.customerid
LEFT JOIN customer_total_statistics
ON customers.id = customer_total_statistics.customerid
WHERE customer_deposits.status = 'approved'
AND positions.status = 'won'
AND positions.date BETWEEN '2013-10-01' AND '2013-11-01'
AND customers.isdemo = '0'
GROUP BY customers.id
ORDER BY customers.id
Well, you don't filter your customer_deposits at all, so the sum over customer_deposits.amount will of course give you the sum of all the deposits. You'll have to add another where to filter customer_deposits by date as well, not just positions.
It looks like you have multiple 1-n relationships so your joins are creating cartesian products. So if you had a customer with 2 deposits and 2 positions:
Positions customer_deposits
CustomerID | Amount CustomerID | Amount
1 | 10 1 | 30
1 | 20 1 | 40
When you do this join:
SELECT c.ID, cd.Amount AS DepositAmount, p.Amount AS PositionAmount
FROM Customers c
INNER JOIN customer_deposits cd
ON c.ID = cd.CustomerID
INNER JOIN Positions p
ON c.ID = p.CustomerID
You end up with 4 rows with all combinations of deposit amount and position amount:
ID DepositAmount PositionAmount
1 10 30
1 10 40
1 20 30
1 20 40
So if you sum this up you end up with incorrect sums because rows are duplicated. You need to move your SUMs to subqueries:
SELECT customers.id AS 'Customer ID',
customers.firstname AS 'Customer First Name',
customers.lastname AS 'Customer Last Name',
users.firstname AS 'Employee First Name',
users.lastname AS 'Employee Last Name',
positions.currency AS 'Currency',
cd.amount AS 'Total Deposits',
P.status AS 'Total Bets',
cs.totalbonusdeposits AS 'Total Bonuses',
positions.date AS 'Date'
FROM customers
INNER JOIN
( SELECT CustomerID, SUM(Amount) AS Amount
FROM customer_deposits
WHERE customer_deposits.status = 'approved'
GROUP BY CustomerID
) cd
ON customers.id = cd.customerid
LEFT JOIN users
ON customers.employeeinchargeid = users.id
INNER JOIN
( SELECT CustomerID, Date, SUM(Status) AS Status
FROM positions
WHERE positions.status = 'won'
AND positions.date BETWEEN '2013-10-01' AND '2013-11-01'
GROUP BY CustomerID, Date
) P
ON customers.id = positions.customerid
LEFT JOIN
( SELECT CustomerID, SUM(totalbonusdeposits) AS totalbonusdeposits
FROM customer_total_statistics
GROUP BY CustomerID
) cs
ON customers.id = customer_total_statistics.customerid
WHERE customers.isdemo = '0';
Note, you have used fields in the where clause for left joined tables which effectively turns them into an inner join, e.g.
positions.status = 'won'
If there is no match in positions, the status will be NULL, and NULL = 'won' evaluates to NULL (not true) so it will exclude all rows where there is no match in position. As such I have changed your LEFT JOIN's to INNER.

How to calculate a total of values from other tables in a column of the select statement

I have three tables:
CustOrder: id, CreateDate, Status
DenominationOrder: id, DenID, OrderID
Denomination: id, amount
I want to create a view based upon all these tables but there should be an additional column i.e. Total should be there which can calculate the sum of the amount of each order.
e.g.
order 1 total denominations 3, total amount = 250+250+250=750
order 2 total denominations 2, total amount = 250+250=500
Is it possible?
I try to guess your table relations (and data too, you did not provide any sample):
SELECT co.id,
COUNT(do.DenID) AS `Total denominations`,
SUM(d.amount) AS `Total amount`
FROM CustOrder co
INNER JOIN DenominationOrder do ON co.id = do.OrderId
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY co.id
Try this:
SELECT o.CreateDate, COUNT(o.id), SUM(d.amount) AS 'Total Amount'
FROM CustOrder o
INNER JOIN DenominationOrder do ON o.id = do.OrderID
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY o.CreateDate
DEMO
Another way to do this, by using CTE, like this:
;WITH CustomersTotalOrders
AS
(
SELECT o.id, SUM(d.amount) AS 'TotalAmount'
FROM CustOrder o
INNER JOIN DenominationOrder do ON o.id = do.OrderID
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY o.id
)
SELECT o.id, COUNT(ot.id) AS 'Orders Count', ot.TotalAmount
FROM CustOrder o
INNER JOIN CustomersTotalOrders ot on o.id = ot.id
INNER JOIN DenominationOrder do ON ot.id = do.OrderID
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY o.id, ot.TotalAmount
This will give you:
id | Orders Count | Total Amount
-------+---------------+-------------
1 3 750
2 2 500
DEMO using CTE