SQL Selecting & Counting From Another Table - sql

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.

Related

How to calculate rows count in where statement in sql?

I have two tables in SQL Server:
order (columns: order_id, payment_id)
payment (columns: payment_id, is_pay)
I want to get all orders with two more properties:
How many rows where is_pay is 1:
where payment_id = <...> payment.is_pay = 1
And the count of the rows (without the first filter)
select count(*)
from payment
where payment_id = <...>
So I wrote this query:
select
*,
(select count(1) from payment p
where p.payment_id = o.payment_id and p.is_pay = 1) as total
from
order o
The problem is how to calculate the rows without the is_pay = 1?
I mean the "some of many"
First aggregate in payment and then join to order:
SELECT o.*, p.total_pay, p.total
FROM [order] o
LEFT JOIN (
SELECT payment_id, SUM(is_pay) total_pay, COUNT(*) total
FROM payment
GROUP BY payment_id
) p ON p.payment_id = o.payment_id;
Change LEFT to INNER join if all orders have at least 1 payment.
Also, if is_pay's data type is BIT, change SUM(is_pay) to:
SUM(CASE WHEN is_pay = 1 THEN 1 ELSE 0 END)
Use a join with conditional aggregation:
SELECT
o.payment_id,
COUNT(CASE WHEN p.is_pay = 1 THEN 1 END) AS pay_cnt,
COUNT(p.payment_id) AS all_cnt
FROM "order" o
LEFT JOIN payment p
ON o.payment_id = p.payment_id
GROUP BY
o.payment_id;
You can use a lateral join (outer apply) for this:
select o.*, p.*
from orders o outer apply
(select count(*) as num_payments,
sum(case when is_pay = 1 then 1 else 0 end) as num_payments_1
from payments p
where p.payment_id = o.payment_id
) p;
Note: Assuming that is_pay only takes on the values of 0 and 1 (which seems reasonable given the name), you can simplify this to:
select o.*, p.*
from orders o outer apply
(select count(*) as num_payments,
sum(is_pay) as num_payments_1
from payments p
where p.payment_id = o.payment_id
) p;
If you are looking for counts per payment id then use this:
select
payment.payment_id,
count(*) as total,
count(case when payment.is_pay = 1 then 1 else 0) end as total_is_pay_orders
from orders
left join payment
on orders.payment_id = payment.payment_id
group by 1

SQL query to get three most recent records by customer

I have a table of orders and am looking to get the three most recent orders by customer id
customer orderID orderDate
1 234 2018-01-01
1 236 2017-02-01
3 256 20157-03-01
I was able to use row number () to identify the row number of each line in the table, but is there a way to get the three most recent orders by customer id? Some customers do have less than 3 orders while others have more than 10 orders so I wasn't able to specify by the row number.
Does anyone have recommendations for a different option?
Here is an interesting approach using apply (and assuming you have a customers table):
select o.*
from customers c cross apply
(select top 3 o.*
from orders o
where o.customerid = c.customerid
order by orderdate desc
) o;
You could use partition by;
select customerid, orderid,orderdate from (
select t.customerid, t.orderid,t.orderdate
,row_number() over (partition by t.customerid order by t.orderDate desc) as mostRecently
from samplecustomers t)
Records where mostRecently < 4
Use this query:
SELECT result.customer
, result.orderID
, result.orderDate
FROM
(
SELECT Temp.customer
, Temp.orderID
, Temp.orderDate
, ROW_NUMBER() OVER(PARTITION BY Temp.customer
ORDER BY Temp.orderDate DESC) AS MR
FROM YourTable AS Temp
) AS result
WHERE result.MR <= 3;
Try this:
SELECT *
FROM orders
WHERE customer = 1
ORDER BY orderDate ASC limit 3
This should solve the problem.
How about this:
select a.Customer, a.orderID, a.orderDate
from orders a
where a.orderID in
(
select top 3 b.orderID
from orders b
where b.Customer = a.Customer
order by b.orderDate desc
)
order by a.Customer, a.orderID, a.orderDate

Row value from another table

I have a table that is having 2 duplicate rows (total of 3 rows), so I used the code below to get the duplicate value in the column
SELECT CustNo, COUNT(*) TotalCount
FROM Rental
GROUP BY CustNo
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC
So once I get the repeated value, I need to get the CustNo derived as duplicate from the customer table. How do I go about taking this value and using it in the select statment all in the same query.
I also have the select statement prepared like this.
Select * from Customer where CustNo = 'T0002';
Thanks.
Select * from Customer
where CustNo IN
(
SELECT CustNo
FROM Rental
GROUP BY CustNo
HAVING COUNT(*) > 1
)
You can use join:
SELECT c.*
FROM (SELECT CustNo, COUNT(*) TotalCount
FROM Rental
GROUP BY CustNo
HAVING COUNT(*) > 1
) cc JOIN
Customer c
on cc.CustNo = c.CustNo;
Select C.* from Customer C RIGHT JOIN (
SELECT CustNo
FROM Rental
GROUP BY CustNo
HAVING COUNT(*) > 1) D
ON C.CustNo = D.CustNo
You can also try this,
With tblDups as(
select CustNo,count(CustNo) as TotalCount from a_rental
Group by CustNo
Having count(CustNo) >1)
select b.* from a_rental b
inner join tblDups a on a.CustNo = b.Custno

select data and sum of a column for a distinct column in data

i have a table like this, (there is sum customers that each one has some payments):
customerID Payments InvoicCode
1 1000 112
1 250 456
2 100 342
1 20 232
2 500 654
3 300 230
what i want is like below (sum of a customer payments in each row):
customerID Payments InvoicCode SumPayment
1 1000 112 1270
1 250 456 1270
2 100 342 600
1 20 232 1270
2 500 654 600
3 300 230 300
It's not proper normal form to have wider-scoped data duplicated in multiple rows. Think about the impact of updating a payment or adding a new customer payment will have - you will have to update all the relevant totals for that customer.
It would be simpler to create a view/stored procedure that gives you the totals at runtime which you can call whenever you need them:
create view CustomerTotals as
select customerID
,sum(Payments) as SumPayment
from mytable
group by customerID
Then you would reference this with select * from CustomerTotals with output like:
customerID SumPayment
1 1270
2 600
3 300
Here it is:
SELECT t.customerID,
t.Payments,
t.InvoicCode,
aux.SumPayment
FROM tablename t
INNER JOIN
(SELECT customerID,
SUM(Payments) as SumPayment
FROM tablename
GROUP BY customerID) aux ON t.customerID = aux.customerID
Try this, (will mostly work on any rdbms)
SELECT a.*, b. totalPayment
FROM paymentsTable a
INNER JOIN
(
SELECT customerID, SUM(Payments) totalPayment
FROM paymentsTable
GROUP BY customerID
) b ON a.customerID = b.customerID
SQLFiddle Demo
You can create a view or try a select like this:
SELECT customerID,
Payments,
InvoicCode,
(SELECT SUM(Payments)
FROM Customer IC
WHERE IC.customerID = OC.customerID )
FROM Customer OC
Join the table to a summed version of itself:
select mytable.customerID, Payments, InvoicCode, SumPayment
from mytable
join (select customerID, sum(Payments) as SumPayment from mytable group by 1) x
on x.customerID = mytable.customerID
select t1.*,sumPay
from table t1,
(select customerID,sum(Payments) as sumPay
from table
group by customerID) t2
where t1.cutomerID=t2.customerID
You can use a sub-query to get the total sum and then join that to your table to add the other columns.
SELECT x2.customerID
, x2.payments
, x2.invoice
, x1.sumpayment
FROM
(
select customerID
,sum(Payments) as SumPayment
from yourtable
group by customerID
) x1
inner join yourtable x2
ON x1.customerID = x2.customerid
See SQL Fiddle with Demo
Assuming your dbms is MS Sql-Server, you can use a SUM(Payments)with OVER clause :
SELECT customerID, Payments,InvoicCode
,SumPayment=SUM(Payments)OVER(PARTITION BY customerID)
FROM t
SQL-Fiddle: http://sqlfiddle.com/#!3/2ac38/2/0
For MSSQL
SELECT Т1.*, Т2.SumPayment
FROM TableName T1 INNER JOIN
( SELECT customerId, SUM(Payments) SumPayment
FROM TableName
GROUP BY customerID
) T2 ON T1.customerID = T2.customerId
SqlFiddle:
SELECT
t.customerID as customerID,
t.Payments as Payments,
t.InvoicCode as InvoicCode,
total as SumPayment
FROM
theTable t,
(
SELECT customerId,
sum(Payments) as total
FROM theTable
GROUP BY customerId
) tmp
WHERE
tmp.customerId = t.customerId

Segment purchases based on new vs returning

I'm trying to write a query that can select a particular date and count how many of those customers have placed orders previously and how many are new. For simplicity, here is the table layout:
id (auto) | cust_id | purchase_date
-----------------------------------
1 | 1 | 2010-11-15
2 | 2 | 2010-11-15
3 | 3 | 2010-11-14
4 | 1 | 2010-11-13
5 | 3 | 2010-11-12
I was trying to select orders by a date and then join any previous orders on the same user_id from previous dates, then count how many had orders, vs how many didnt. This was my failed attempt:
SELECT SUM(
CASE WHEN id IS NULL
THEN 1
ELSE 0
END ) AS new, SUM(
CASE WHEN id IS NOT NULL
THEN 1
ELSE 0
END ) AS returning
FROM (
SELECT o1 . *
FROM orders AS o
LEFT JOIN orders AS o1 ON ( o1.user_id = o.user_id
AND DATE( o1.created ) = "2010-11-15" )
WHERE DATE( o.created ) < "2010-11-15"
GROUP BY o.user_id
) AS t
Given a reference data (2010-11-15), then we are interested in the number of distinct customers who placed an order on that date (A), and we are interested in how many of those have placed an order previously (B), and how many did not (C). And clearly, A = B + C.
Q1: Count of orders placed on reference date
SELECT COUNT(DISTINCT Cust_ID)
FROM Orders
WHERE Purchase_Date = '2010-11-15';
Q2: List of customers placing order on reference date
SELECT DISTINCT Cust_ID
FROM Orders
WHERE Purchase_Date = '2010-11-15';
Q3: List of customers who placed an order on reference date who had ordered before
SELECT DISTINCT o1.Cust_ID
FROM Orders AS o1
JOIN (SELECT DISTINCT o2.Cust_ID
FROM Orders AS o2
WHERE o2.Purchase_Date = '2010-11-15') AS c1
ON o1.Cust_ID = c1.Cust_ID
WHERE o1.Purchase_Date < '2010-11-15';
Q4: Count of customers who placed an order on reference data who had ordered before
SELECT COUNT(DISTINCT o1.Cust_ID)
FROM Orders AS o1
JOIN (SELECT DISTINCT o2.Cust_ID
FROM Orders AS o2
WHERE o2.Purchase_Date = '2010-11-15') AS c1
ON o1.Cust_ID = c1.Cust_ID
WHERE o1.Purchase_Date < '2010-11-15';
Q5: Combining Q1 and Q4
There are several ways to do the combining. One is to use Q1 and Q4 as (complicated) expressions in the select-list; another is to use them as tables in the FROM clause which don't need a join between them because each is a single-row, single-column table that can be joined in a Cartesian product. Another would be a UNION, where each row is tagged with what it calculates.
SELECT (SELECT COUNT(DISTINCT Cust_ID)
FROM Orders
WHERE Purchase_Date = '2010-11-15') AS Total_Customers,
(SELECT COUNT(DISTINCT o1.Cust_ID)
FROM Orders AS o1
JOIN (SELECT DISTINCT o2.Cust_ID
FROM Orders AS o2
WHERE o2.Purchase_Date = '2010-11-15') AS c1
ON o1.Cust_ID = c1.Cust_ID
WHERE o1.Purchase_Date < '2010-11-15') AS Returning_Customers
FROM Dual;
(I'm blithely assuming MySQL has a DUAL table - similar to Oracle's. If not, it is trivial to create a table with a single column containing a single row of data. Update 2: bashing the MySQL 5.5 Manual shows that 'FROM Dual' is supported but not needed; MySQL is happy without a FROM clause.)
Update 1: added qualifier 'o1.Cust_ID' in key locations to avoid 'ambiguous column name' as indicated in the comment.
How about
SELECT * FROM
(SELECT * FROM
(SELECT CUST_ID, COUNT(*) AS ORDER_COUNT, 1 AS OLD_CUSTOMER, 0 AS NEW_CUSTOMER
FROM ORDERS
GROUP BY CUST_ID
HAVING ORDER_COUNT > 1)
UNION ALL
(SELECT CUST_ID, COUNT(*) AS ORDER_COUNT, 0 AS OLD_CUSTOMER, 1 AS NEW_CUSTOMER
FROM ORDERS
GROUP BY CUST_ID
HAVING ORDER_COUNT = 1)) G
INNER JOIN
(SELECT CUST_ID, ORDER_DATE
FROM ORDERS) O
USING (CUST_ID)
WHERE ORDER_DATE = [date of interest] AND
OLD_CUSTOMER = [0 or 1, depending on what you want] AND
NEW_CUSTOMER = [0 or 1, depending on what you want]
Not sure if that'll do the whole thing, but it might provide a starting point.
Share and enjoy.
select count(distinct o1.cust_id) as repeat_count,
count(distinct o.cust_id)-count(distinct o1.cust_id) as new_count
from orders o
left join (select cust_id
from orders
where purchase_date < "2010-11-15"
group by cust_id) o1
on o.cust_id = o1.cust_id
where o.purchase_date = "2010-11-15"