Put Condition to CASE expression (SQL) - 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

Related

SQL Selecting & Counting From Another Table

I have this query that works excellently and gives me the results I want, however, does anybody know how I can remove any rows that have 0 orders? I am sure it is something simple, I just can't get my head around it.
In other words, should it only show the top 2 rows?
SELECT customers.id, customers.companyname, customers.orgtype,
(SELECT COALESCE(SUM(invoicetotal), 0)
FROM invoice_summary
WHERE invoice_summary.cid = customers.ID
and invoice_summary.submitted between '2022-08-01' and '2022-08-31'
) AS total,
(SELECT COUNT(invoicenumber)
FROM invoice_summary
WHERE invoice_summary.cid = customers.ID
and invoice_summary.submitted between '2022-08-01' and '2022-08-31'
) AS orders
FROM customers WHERE customers.orgtype = 10
ORDER BY total DESC
ID
Company
Org
Total
Orders
1232
ACME 1
10
523.36
3
6554
ACME 2
10
411.03
2
1220
ACME 3
10
0.00
0
4334
ACME 4
10
0.00
0
You can use a CTE to keep the request simple :
WITH CTE_Orders AS (
SELECT customers.id, customers.companyname, customers.orgtype,
(SELECT COALESCE(SUM(invoicetotal), 0)
FROM invoice_summary
WHERE invoice_summary.cid = customers.ID
and invoice_summary.submitted between '2022-08-01' and '2022-08-31'
) AS total,
(SELECT COUNT(invoicenumber)
FROM invoice_summary
WHERE invoice_summary.cid = customers.ID
and invoice_summary.submitted between '2022-08-01' and '2022-08-31'
) AS orders
FROM customers WHERE customers.orgtype = 10
ORDER BY total DESC
)
SELECT * FROM CTE_Orders WHERE orders > 0
You will find aditionals informations about CTE on Microsoft documentation : https://learn.microsoft.com/fr-fr/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver16
You can do this by transforming your subquery to a CROSS APPLYof a pre-aggregated table
SELECT
c.id,
c.companyname,
c.orgtype,
ins.total,
ins.orders
FROM customers c
CROSS APPLY (
SELECT
COUNT(*) AS orders,
ISNULL(SUM(ins.invoicetotal), 0) AS total
FROM invoice_summary ins
WHERE ins.cid = c.ID
AND ins.submitted between '20220801' and '20220831'
GROUP BY () -- do not remove the GROUP BY
) ins
WHERE c.orgtype = 10
ORDER BY
ins.total DESC;
You can also do this with an INNER JOIN against it
SELECT
c.id,
c.companyname,
c.orgtype,
ins.total,
ins.orders
FROM customers c
INNER JOIN (
SELECT
ins.cid,
COUNT(*) AS orders,
ISNULL(SUM(ins.invoicetotal), 0) AS total
FROM invoice_summary ins
WHERE ins.submitted between '20220801' and '20220831'
GROUP BY ins.cid
) ins ON ins.cid = c.ID
WHERE c.orgtype = 10
ORDER BY
ins.total DESC;
Quick and dirty way would be to dump your results into a temp table, delete the records you don't want, then select what remains.
Add this to the end of your select before the FROM clause:
INTO #temptable
Then delete the records you don't want:
DELETE FROM #temptable WHERE [Orders] = 0
Then just select from the temp table.
There are other ways to do this, and you should read up on the downsides of temp tables before implementing this solution.

SQL: How to pull particular orders based on order details?

I have four tables namely customers, orders, orderDetails and Products.
Customer table
cId cName
1 James
2 Adam
3 Ed
Order table
oId cId
1 1
2 2
3 3
OrderDetails table
oId odId pId Quantity
1 1 1 50
1 2 2 45
2 3 2 52
3 4 1 44
Products table
pId PName
1 Apple
2 Orange
I want the list of customers who have never ordered Oranges. I am able to pull records of customers whose order details don't have oranges. But in one of the case, James has ordered both apples and oranges. So, the query should not pull James. I can do this with a larger query. But I want this with a smaller query where something I'm missing.
SQL
SELECT c.cId, c.cName, p.PName, od.Quantity FROM customers c
LEFT JOIN orders o ON c.cId = o.cId
LEFT JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE od.pId != 2
I would do this using not exists:
with has_oranges as (
select o.*
from orders o join
orderlines ol
on o.oid = ol.oid
where ol.pid = 2
)
select c.*
from customers c
where not exists (select 1
from has_oranges ho
where ho.cid = c.cid
);
If you want customer information, I don't see what oid has to do with anything.
Notes:
The CTE determines who actually has oranges.
You don't need the products table, because you are using the pid.
Use NOT EXISTS
SELECT *
FROM Customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE p.pName = 'oranges' AND c.cId = o.cId
)
You want all customers that have never ordered oranges. So select all customer IDs that ordered oranges and only show customers that are not in this data set.
select *
from customers c
where cid not in
(
select cid
from orderdetails
where pid = (select pid from products where pname = 'Orange'
);
select * from CustomerTbl where Id in (select t1.Id from CustomerTbl t1
left join OrderTbl t2 on t1.Id = t2.CustomerId
left join OrderDetailTbl t3 on t3.OrderId = t2.Id
left join ProductTbl t4 on t4.Id = t3.ProductId
where t4.Id != 2)
This will return Customers who not ordered Oranges.
This is the sqlfiddle link : http://sqlfiddle.com/#!6/c908d/6
SQL Server 2008 introduced the EXCEPT and INTERSECT keywords for doing this sort of thing. I tend to find that the queries are clearer than when using CTEs.
Microsoft Documentation
select c.cId
from Customer c
except
select o.cId
from Orders o
join OrderDetail od on o.oId = od.oId
and od.pId = 2
cId
-----------
3
You can add the name to the result set by joining to the Customer table in the second half of the query:
select c.cId, c.cName
from Customer c
except
select o.cId, c.cName
from Orders o
join OrderDetail od on o.oId = od.oId
join Customer c on c.cId = o.cId
and od.pId = 2
cId cName
----------- --------------------
3 Ed
We have to eliminate users who took orange. So in the below query i have used sub query
Select C.Cname,OH.oid,PM.Pname,OD.Quantity from Customers C
inner join OrderHeader OH ON C.cid=OH.Cid
inner join OrderDetails OD on oh.oid=od.oid
inner join ProductMast PM on PM.pid=OD.pid where OH.oid not in (select oid
from OrderDetails where pid = 2)

Oracle SQL - Getting Max Date with several joins

I think I may be overthinking this, but I'm having an issue when trying to find a max date with several joins in an Oracle database as well as several where clauses. I've found many examples of simple max queries, but nothing specifically like this. The query works fine if I add a line in to find all records above a specific date (there are only a handful of results returned). However, I want to automatically get the most recent date for all records from the bill table. This database has an additional table where the actual bill amount is stored, so that adds another layer.
SELECT p.purchase_id, p.account_id, b.bill_date, bp.current_amount
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN Bills b ON bp.bill_id = b.bill_id
--NEED TO GET MOST RECENT DATE FROM BILL TABLE FOR EACH BILL
WHERE p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...);
I have tried doing subqueries with Max functions in them, but am not having any luck. Each query returns the same amount of records as the original query. How would I rearrange this query to still retrieve all of the necessary columns and where clauses while still limiting this to only the most recent purchase that was billed?
Try like below this
SELECT p.purchase_id, p.account_id, b.bill_date, bp.current_amount
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN ( SELECT bill_id, MAX(bill_date) bill_date
FROM Bills
GROUP BY bill_id
)b ON bp.bill_id = b.bill_id
WHERE p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...);
You may want to solve this problem using Rank() function,
SELECT p.purchase_id, p.account_id, , bp.current_amount, RANK() OVER ( partition by b.bill_id order by b.bill_date) as max_bill_date
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN Bills b ON bp.bill_id = b.bill_id
--NEED TO GET MOST RECENT DATE FROM BILL TABLE FOR EACH BILL
WHERE max_bill_date = 1
AND p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'`enter code here`
AND p.purchase_id IN ( ... list of purchases ...);
Do either of these work for you?
WITH data as (
SELECT
p.purchase_id, p.account_id, b.bill_date, bp.current_amount,
FROM
Purchases p
INNER JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
INNER JOIN Bills b ON b.bill_id = bp.bill_id
WHERE p.type <> 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...)
), most_recent as (
SELECT max(bill_date) as bill_date FROM data
)
SELECT *
FROM data
WHERE bill_date = (select bill_date from most_recent);
SELECT purchase_id, account_id, bill_date, current_amount
FROM (
SELECT
p.purchase_id, p.account_id, b.bill_date, bp.current_amount,
dense_rank() over (order by b.bill_date desc) as dr
FROM
Purchases p
INNER JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
INNER JOIN Bills b ON b.bill_id = bp.bill_id
WHERE p.type <> 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...)
) data
WHERE dr = 1;
I got this working pretty much right after posting, was a stupid mistake on my part. Data for the max function needed to be joined on the account_id, not the bill_id.

How do I run this SQL command?

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;

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