SQL: Optimize query/ Reduce the size of the query - sql

SELECT p.Distributor,
SUM(r.SalesVolume) AS Sales,
CAST(( ( CAST(SUM(r.SalesVolume) AS DECIMAL(14, 4)) / (SELECT SUM(r.SalesVolume)
FROM RawData r
INNER JOIN Product p
ON r.ProductId = p.ProductId
WHERE p.Distributor IN( 'TF1', 'WARNER', 'GAUMONT', 'PATHE',
'STUDIOCANAL', 'M6SND', 'FRANCETV' )
AND p.VODEST IN ( 'EST' )
AND p.ContentFlavor IN ( 'HD' )) ) * 100 ) AS DECIMAL(20, 2)) AS MarketSharesVolume
FROM RawData r
INNER JOIN Product p
ON r.ProductId = p.ProductId
WHERE p.Distributor IN ( 'TF1', 'WARNER', 'GAUMONT', 'PATHE',
'STUDIOCANAL', 'M6SND', 'FRANCETV' )
AND p.VODEST IN ( 'EST' )
AND p.ContentFlavor IN ( 'HD' )
GROUP BY p.Distributor;
The above query doesnt look beautiful for sure. Basically if you notice the WHERE conditions in the sub query and the main query are similar. Is there a way I can combine them to reduce the size of this query. Also are there any other place where I could probably try and reduce the size of this query?
Looking for suggestions.

You can use SUM ... OVER() on the SUM to count up the grand total.
SELECT p.Distributor,
SUM(r.SalesVolume) AS Sales,
CAST(( ( CAST(SUM(r.SalesVolume) AS DECIMAL(14, 4)) / SUM(SUM(r.SalesVolume))
OVER() ) * 100 ) AS DECIMAL(20, 2)) AS MarketSharesVolume
FROM RawData r
INNER JOIN Product p
ON r.ProductId = p.ProductId
WHERE p.Distributor IN ( 'TF1', 'WARNER', 'GAUMONT', 'PATHE',
'STUDIOCANAL', 'M6SND', 'FRANCETV' )
AND p.VODEST IN ( 'EST' )
AND p.ContentFlavor IN ( 'HD' )
GROUP BY p.Distributor;

Related

Anyway to improve this SQL to avoid multiple Joins

I want to get sales result for 10 days for each product which is in my orders_summary table. Currently I'm joining orders_summary table 10 times to get sales for each day. Is there any better way to get this data?
Current sql:
SELECT P.ID,
P.SKU,
FIRST_DAY.ITEMS AS ITEMS_1,
FIRST_DAY.ORDERS AS ORDERS_1,
SECOND_DAY.ITEMS AS ITEMS_2,
SECOND_DAY.ORDERS AS ORDERS_2
FROM PRODUCTS AS P
LEFT JOIN
(SELECT SKU,
AMOUNT AS ITEMS,
ARRAY_LENGTH(LIST,
1) AS ORDERS
FROM ORDERS_SUMMARY
WHERE ORDER_DATE = TO_TIMESTAMP(1633158000000 / 1000.0)) AS FIRST_DAY ON P.SKU = FIRST_DAY.SKU
LEFT JOIN
(SELECT SKU,
AMOUNT AS ITEMS,
ARRAY_LENGTH(LIST,
1) AS ORDERS
FROM ORDERS_SUMMARY
WHERE ORDER_DATE = TO_TIMESTAMP(1633676400000 / 1000.0)) AS SECOND_DAY ON P.SKU = SECOND_DAY.SKU
...
result:
select main.sku, jsonb_populate_record(null::examples.table_fields, main.json_data)
from
(
select t2.sku, jsonb_object_agg(t2.itemNames, t2.items) || jsonb_object_agg(t2.orderNames, t2.orders) as json_data from
(
select
pr.sku,
'items' || tbl_dates.num::varchar as itemNames,
coalesce(sum(sOrd.amount), 0) as items,
'orders' || tbl_dates.num::varchar as orderNames,
coalesce(sum(sOrd.qty), 0) as orders
-- tbl_dates.dates
from products pr
inner join (
select tt.num, ('2021-01-01'::date + tt.num - 1) as dates
from (
select t.num from generate_series(1, 10, 1) AS t(num)
) tt
) tbl_dates on true
left join orders_summary sOrd on sOrd.sku = pr.sku and sOrd.order_date::date = tbl_dates.dates
group by pr.sku, tbl_dates.num, tbl_dates.dates
order by tbl_dates.num
) t2
group by t2.sku
) main;
I wrote simple select query, if you want to use a function then you can change this is '2021-01-01'::date to input variable and in this code generate_series(1, 10, 1) you can change 10 to the input variable

Avoid SQL Pivot returning duplicate rows

I have the following SQL script which returns duplciate values in PIVOT. How do I combine those duplicate records to one row.
Please check the below image for the results set.
SELECT *
FROM (SELECT X.stockcode,
X.description,
X.pack,
X.location,
X.lname,
X.qty,
Y.stockcode AS StockCode2,
y.periodname,
Y.months,
Y.saleqty
FROM (SELECT dbo.stock_items.stockcode,
dbo.stock_items.description,
dbo.stock_items.pack,
dbo.stock_loc_info.location,
dbo.stock_locations.lname,
dbo.stock_loc_info.qty
FROM dbo.stock_locations
INNER JOIN dbo.stock_loc_info
ON dbo.stock_locations.locno = dbo.stock_loc_info.location
LEFT OUTER JOIN dbo.stock_items
ON dbo.stock_loc_info.stockcode = dbo.stock_items.stockcode
WHERE ( dbo.stock_items.status = 's' )) AS X
LEFT OUTER JOIN (SELECT dbo.dr_invlines.stockcode,
( 12 + Datepart(month, Getdate()) - Datepart(month, dbo.dr_trans.transdate) ) % 12 + 1 AS Months,
Sum(dbo.dr_invlines.quantity) AS SaleQty,
dbo.period_status.periodname
FROM dbo.dr_trans
INNER JOIN dbo.period_status
ON dbo.dr_trans.period_seqno = dbo.period_status.seqno
LEFT OUTER JOIN dbo.stock_items AS STOCK_ITEMS_1
RIGHT OUTER JOIN dbo.dr_invlines
ON STOCK_ITEMS_1.stockcode = dbo.dr_invlines.stockcode
ON dbo.dr_trans.seqno = dbo.dr_invlines.hdr_seqno
WHERE ( STOCK_ITEMS_1.status = 'S' )
AND ( dbo.dr_trans.transtype IN ( 1, 2 ) )
AND ( dbo.dr_trans.transdate >= Dateadd(m, -6, Getdate()) )
GROUP BY dbo.dr_invlines.stockcode,
Datepart(month, dbo.dr_trans.transdate),
dbo.period_status.periodname) AS Y
ON X.stockcode = Y.stockcode) z
PIVOT (Sum(saleqty) FOR [months] IN ([1],[2],[3],[4],[5],[6])) AS pivoted
EDIT: I missed the root-cause of your issue being the inclusion of the periodname column causing the percieved duplication. I am leaving this in place as general solution showing CTE usage, because it could still be useful if you then want to do extra filtering/transformation of your pivot results
One way is to take the results of the pivot query and run it through a SELECT DISTINCT query.
An example of wrapping your pivot query as a CTE and using it to feed a SELECT DISTINCT below (please note: untested, but parses as valid in my SSMS)
WITH PivotResults_CTE (
stockcode,
description,
pack,
location,
lname,
qty,
StockCode2,
periodname,
months,
saleqty
)
AS (
SELECT *
FROM (
SELECT X.stockcode
,X.description
,X.pack
,X.location
,X.lname
,X.qty
,Y.stockcode AS StockCode2
,y.periodname
,Y.months
,Y.saleqty
FROM (
SELECT dbo.stock_items.stockcode
,dbo.stock_items.description
,dbo.stock_items.pack
,dbo.stock_loc_info.location
,dbo.stock_locations.lname
,dbo.stock_loc_info.qty
FROM dbo.stock_locations
INNER JOIN dbo.stock_loc_info ON dbo.stock_locations.locno = dbo.stock_loc_info.location
LEFT OUTER JOIN dbo.stock_items ON dbo.stock_loc_info.stockcode = dbo.stock_items.stockcode
WHERE (dbo.stock_items.STATUS = 's')
) AS X
LEFT OUTER JOIN (
SELECT dbo.dr_invlines.stockcode
,(12 + Datepart(month, Getdate()) - Datepart(month, dbo.dr_trans.transdate)) % 12 + 1 AS Months
,Sum(dbo.dr_invlines.quantity) AS SaleQty
,dbo.period_status.periodname
FROM dbo.dr_trans
INNER JOIN dbo.period_status ON dbo.dr_trans.period_seqno = dbo.period_status.seqno
LEFT OUTER JOIN dbo.stock_items AS STOCK_ITEMS_1
RIGHT OUTER JOIN dbo.dr_invlines ON STOCK_ITEMS_1.stockcode = dbo.dr_invlines.stockcode ON dbo.dr_trans.seqno = dbo.dr_invlines.hdr_seqno WHERE (STOCK_ITEMS_1.STATUS = 'S')
AND (
dbo.dr_trans.transtype IN (
1
,2
)
)
AND (dbo.dr_trans.transdate >= Dateadd(m, - 6, Getdate()))
GROUP BY dbo.dr_invlines.stockcode
,Datepart(month, dbo.dr_trans.transdate)
,dbo.period_status.periodname
) AS Y ON X.stockcode = Y.stockcode
) z
PIVOT(Sum(saleqty) FOR [months] IN (
[1]
,[2]
,[3]
,[4]
,[5]
,[6]
)) AS pivoted
)
SELECT DISTINCT *
FROM
PivotResults_CTE
;
Also note, your sql included in the above may look slightly different to your original but that is only because i ran it through a reformatter to ensure i understood the structure of it.
In other words, the basic CTE wrapper for your pivot query is:
WITH PivotResults_CTE (
Field1,
Field2,
...
)
AS (
YOUR_PIVOT_QUERY_HERE
)
SELECT DISTINCT *
FROM
PivotResults_CTE
;

Error With total Column which calculate the summation of Row values

I created a pivot query which calculate the sum of specific transaction and i want to add additional column which calculate the sum of all transaction for every ID, But my total column give me nulls only for all IDs, Any one can help with that?
this is my Query:
SELECT
AccountID
, OpeningBalance
, OpeningBalanceStatus
, ISNULL([CI], 0) AS CI
, ISNULL([CO], 0) AS CO
, ISNULL([SI], 0) AS SI
, ISNULL([CN], 0) AS CN
, ISNULL([PI], 0) AS PI
, ISNULL([JE], 0) AS JE
, ISNULL([NR], 0) AS NR
, [OpeningBalance]+[CI]+[CO]+[SI]+[CN]+[PI]+[JE]+[NR] AS TOTAL
FROM (SELECT
Accounting.AccDocumentDetails.AccountID
, Accounting.AccDocumentDetails.AmountStatus
, Accounting.AccAccounts.AccountType
, Accounting.AccDocumentHeader.CodeTypePart
, AccAccounts_1.OpeningBalance
, AccAccounts_1.OpeningBalanceStatus
, CASE
WHEN AmountStatus = 'd'
THEN (Amount * 1)
WHEN AmountStatus = 'C'
THEN (Amount * - 1)
END AS NewAmount FROM Accounting.AccDocumentDetails
INNER JOIN Accounting.AccAccounts
ON Accounting.AccDocumentDetails.AccountID = Accounting.AccAccounts.ID
INNER JOIN Accounting.AccChartOfAccounts
ON Accounting.AccChartOfAccounts.ID = Accounting.AccAccounts.ParentNode
INNER JOIN Accounting.AccDocumentHeader
ON Accounting.AccDocumentDetails.AccDocumentHeaderID = Accounting.AccDocumentHeader.ID
INNER JOIN Accounting.AccAccounts AS AccAccounts_1
ON Accounting.AccDocumentDetails.AccountID = AccAccounts_1.ID) AS PivotSelect PIVOT( SUM(NewAmount) FOR CodeTypePart IN ([ci],[CO],[SI],[CN],[PI],[JE],[NR]) ) AS PVT
and this is my output
enter image description here
One way to have the total column is you can use your query as sub query, then create the computation for the total on the outer query. Or you can use cte as well
WITH t_pvt AS (
SELECT AccountID
,OpeningBalance
,OpeningBalanceStatus
,ISNULL([CI], 0) AS CI
,ISNULL([CO], 0) AS CO
,ISNULL([SI], 0) AS SI
,ISNULL([CN], 0) AS CN
,ISNULL([PI], 0) AS PI
,ISNULL([JE], 0) AS JE
,ISNULL([NR], 0) AS NR
FROM (SELECT Accounting.AccDocumentDetails.AccountID
,Accounting.AccDocumentDetails.AmountStatus
,Accounting.AccAccounts.AccountType
,Accounting.AccDocumentHeader.CodeTypePart
,AccAccounts_1.OpeningBalance
,AccAccounts_1.OpeningBalanceStatus
,CASE WHEN AmountStatus = 'd'
THEN (Amount * 1)
WHEN AmountStatus = 'C'
THEN (Amount * - 1)
END AS NewAmount
FROM Accounting.AccDocumentDetails
INNER JOIN Accounting.AccAccounts
ON Accounting.AccDocumentDetails.AccountID = Accounting.AccAccounts.ID
INNER JOIN Accounting.AccChartOfAccounts
ON Accounting.AccChartOfAccounts.ID = Accounting.AccAccounts.ParentNode
INNER JOIN Accounting.AccDocumentHeader
ON Accounting.AccDocumentDetails.AccDocumentHeaderID = Accounting.AccDocumentHeader.ID
INNER JOIN Accounting.AccAccounts AS AccAccounts_1
ON Accounting.AccDocumentDetails.AccountID = AccAccounts_1.ID) AS PivotSelect
PIVOT (SUM(NewAmount)
FOR CodeTypePart IN ([ci],[CO],[SI],[CN],[PI],[JE],[NR])) AS PVT
)
SELECT *
,[OpeningBalance]+[CI]+[CO]+[SI]+[CN]+[PI]+[JE]+[NR] AS TOTAL
FROM t_pvt
With CTE
AS
( select column1 as c1 from tbl)
Select c1 from CTE
thats a simple example of CTE syntax and how you can use it

The query is getting slow when a CASE statement in Having a clause in PostgreSQL

Below query is getting slower as I have used the case statement in having clause and the data is very huge, so below query is taking time for fetching the such huge data for overcoming this I need some alternatiive solution instead of the Having clause with case statement.
Explanation about the query,
I need to fetch the data by some types with condtions like I have three types
Flat Amount(1), Flat amount of rent(2) and percentage of rent(3) on that basis I need to do the SUM of the charges with condtions like if type is 2 then fetch the record with charge type is 2 and charge code is 3 like that. Please look into the below query and suggest me alternative solution is there,
SELECT
n.id,
ro.id,
n.notice_amount,
n.notice_type_id,
SUM ( c.charge_amount ) AS charge_amount
FROM
notification n
JOIN notification_doc_groups ndg ON ( ndg.notice_id = n.id AND ndg.is_published = TRUE )
JOIN properties p ON ( p.id = ndg.property_id )
JOIN customer_details cd ON ( cd.notification_id = n.id )
JOIN rent_out ro ON ( ro.property_id = p.id )
JOIN charges c ON ( c.rent_out_id = ro.id )
WHERE
( ( n.notice_type_id = 1
AND c.charge_amount > 0
AND c.charge_type_id = 2 )
OR ( n.notice_type_id = 2
AND c.charge_type_id = 2
AND c.charge_code = 3 )
OR ( n.notice_type_id = 3
AND c.scheduled_id IS NOT NULL ) )
GROUP BY
n.id,
ro.id,
n.notice_type_id,
n.notice_amount
HAVING
CASE
WHEN n.notice_type_id = 1 THEN ( ( sum ( c.charge_amount ) >= n.notice_amount ) )
WHEN n.notice_type_id = 2 THEN sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 ) -- or( )
WHEN n.notice_type_id = 3 THEN sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 )
END;
Thanks
You can separate out the three different cases and use a union to join them. It shouldn't make a difference in the results but might make it easier for the optimizer. If nothing else you'll be able to run each separate notice type and see if one is causing the slowdown.
I am a bit confused about the condition on notice types 2 and 3 though:
sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 )
It looks like you don't need to check the charge amount at all, you can just find entries where the notice_amount is 100 or less.
SELECT
n.id,
ro.id,
n.notice_amount,
n.notice_type_id,
SUM ( c.charge_amount ) AS charge_amount
FROM
notification n
JOIN notification_doc_groups ndg ON ( ndg.notice_id = n.id AND ndg.is_published = TRUE )
JOIN properties p ON ( p.id = ndg.property_id )
JOIN rent_out ro ON ( ro.property_id = p.id )
JOIN charges c ON ( c.rent_out_id = ro.id )
WHERE
n.notice_type_id = 3 AND c.scheduled_id IS NOT NULL
GROUP BY
n.id,
ro.id,
n.notice_type_id,
n.notice_amount
HAVING
sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 )
UNION ALL
SELECT
n.id,
ro.id,
n.notice_amount,
n.notice_type_id,
SUM ( c.charge_amount ) AS charge_amount
FROM
notification n
JOIN notification_doc_groups ndg ON ( ndg.notice_id = n.id AND ndg.is_published = TRUE )
JOIN properties p ON ( p.id = ndg.property_id )
JOIN rent_out ro ON ( ro.property_id = p.id )
JOIN charges c ON ( c.rent_out_id = ro.id )
WHERE
n.notice_type_id = 2 AND c.charge_type_id = 2
GROUP BY
n.id,
ro.id,
n.notice_type_id,
n.notice_amount
HAVING
sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 )
UNION ALL
SELECT
n.id,
ro.id,
n.notice_amount,
n.notice_type_id,
SUM ( c.charge_amount ) AS charge_amount
FROM
notification n
JOIN notification_doc_groups ndg ON ( ndg.notice_id = n.id AND ndg.is_published = TRUE )
JOIN properties p ON ( p.id = ndg.property_id )
JOIN rent_out ro ON ( ro.property_id = p.id )
JOIN charges c ON ( c.rent_out_id = ro.id )
WHERE
n.notice_type_id = 1 AND c.charge_amount > 0 AND c.charge_type_id = 2
GROUP BY
n.id,
ro.id,
n.notice_type_id,
n.notice_amount
HAVING
sum ( c.charge_amount ) >= n.notice_amount
Can you try this:
HAVING (n.notice_type_id = 1 AND sum ( c.charge_amount ) >= n.notice_amount)
OR ( n.notice_type_id IN (2,3) AND n.notice_amount / 100 >=1)
My guess it that the planner can't optimize for the case so try an array:
having (array[
sum ( c.charge_amount ) >= n.notice_amount,
sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 ),
sum ( c.charge_amount ) >= sum ( c.charge_amount ) * ( n.notice_amount / 100 )
]::bool[])[n.notice_type_id]

SQL Server: select the largest order total from multiple customers with multiple orders, and there are multiple items on each order

I am really stuck on a problem and could use a little help. Here is the problem statement:
"Write the query that will show all the customers, the total of all orders, a count of orders made, the average total of each order, average number of items per order (with decimal points), the largest order total and the smallest order total for each customer. Show every customer even if a customer didn't make an order."
These are the tables:
the lovely tables
I've gotten this far, and I'm hung up on the max order total. I was thinking of a subquery for the highest and lowest order totals but I can't make it work.
SELECT
TC.intCustomerID
,TC.strLastName + ',' + ' ' + TC.strFirstName AS strCustomerName
,ISNULL(SUM( TCOI.intQuantity * TI.monPrice), 0) AS monOrderTotals
,COUNT(DISTINCT TCO.intOrderIndex) AS intNumberOfOrders
,ISNULL(SUM(TCOI.intQuantity * TI.monPrice) / COUNT(DISTINCT TCO.intOrderIndex), 0) AS monAverageOrderTotals
,(SELECT MAX(TCOI.intQuantity * TI.monPrice)
FROM TItems AS TI, TCustomerOrderItems AS TCOI
WHERE TI.intItemID = TCOI.intItemID
-- Cross-query join with two columns
-- AND TC.intCustomerID = TCOI.intCustomerID
-- AND TCO.intOrderIndex = TCOI.intOrderIndex
----GROUP BY
-- TCOI.intCustomerID
--,TCOI.intOrderIndex
) AS monMostExpensiveOrder
FROM
TCustomers AS TC
LEFT OUTER JOIN
TCustomerOrders AS TCO ON (TC.intCustomerID = TCO.intCustomerID)
LEFT OUTER JOIN
TCustomerOrderItems AS TCOI ON (TCO.intOrderIndex = TCOI.intOrderIndex)
LEFT OUTER JOIN
TItems AS TI ON (TCOI.intItemID = TI.intItemID)
GROUP BY
TC.intCustomerID
,TC.strLastName
,TC.strFirstName
Any insight would be greatly appreciated.
For me, using a common table expression goes a long way towards making code easier to read and write when you are working with derived tables (selecting from subqueries).
I think this should cover what you are trying to do, but I was not sure which way you wanted to count average items per order (by number of distinct items or the quantity of items):
with cte as (
select
tc.intCustomerId
, tc.strFirstName
, tc.strLastName
, tcoi.intOrderIndex
, TotalPrice = isnull(sum(tcoi.intQuantity * ti.monPrice), 0 )
, ItemCount = count(*)
, TotalItemQuantity = sum(tcoi.intQuantity)
from TCustomers tc
left join tCustomerOrderItems as tcoi
on tc.intCustomerId = tcoi.intCustomerId
left join tItems as ti
on ti.intItemID = tcoi.intItemID
)
select
intCustomerId
, Name = isnull(strLastName+', ') + isnull(strFirstName,'')
, countOrders = count(intOrderIndex)
, sumTotalPrice = sum(TotalPrice)
, minTotalPrice = min(TotalPrice)
, maxTotalPrice = max(TotalPrice)
, avgTotalPrice = avg(TotalPrice)
, avgItemCount = (sum(ItemCount)+.0)/nullif(count(intOrderIndex),0)
, avgItemQuant = (sum(TotalItemQuantity)+.0)/nullif(count(intOrderIndex),0)
from cte
group by
intCustomerId
, strFirstName
, strLastName
To take out the cte part, you would just move the query into the from.
select
intCustomerId
, Name = isnull(strLastName+', ') + isnull(strFirstName,'')
, countOrders = count(intOrderIndex)
, sumTotalPrice = sum(TotalPrice)
, minTotalPrice = min(TotalPrice)
, maxTotalPrice = max(TotalPrice)
, avgTotalPrice = avg(TotalPrice)
, avgItemCount = (sum(ItemCount)+.0)/nullif(count(intOrderIndex),0)
, avgItemQuant = (sum(TotalItemQuantity)+.0)/nullif(count(intOrderIndex),0)
from (
select
tc.intCustomerId
, tc.strFirstName
, tc.strLastName
, tcoi.intOrderIndex
, TotalPrice = isnull(sum(tcoi.intQuantity * ti.monPrice), 0 )
, ItemCount = count(*)
, TotalItemQuantity = sum(tcoi.intQuantity)
from TCustomers tc
left join tCustomerOrderItems as tcoi
on tc.intCustomerId = tcoi.intCustomerId
left join tItems as ti
on ti.intItemID = tcoi.intItemID
) as cte
group by
intCustomerId
, strFirstName
, strLastName
You will first need to calculate totals per order and per customer.
I will say that the schema is deficient in not storing order totals, since item price is likely to change, and TCustomerOrders likely includes historical orders. Prefixing tables and column names is also not recommended.
WITH CustomerOrders AS
(
SELECT
oi.intCustomerID as CustomerID,
oi.intOrderIndex as OrderID,
SUM(oi.intQuantity * i.monPrice) as SalesAmount,
COUNT(DISTINCT oi.intItemID) as DistinctItemCount,
SUM(oi.intQuantity) as ItemCount
FROM TCustomerOrderItems as oi
INNER JOIN TItems as i on oi.intItemID = i.intItemID
GROUP BY oi.intCustomerID, oi.intOrderIndex
),
CustomerSales AS
(
SELECT
co.CustomerID,
SUM(co.SalesAmount) as TotalSalesAmount,
COUNT(*) as OrderCount,
AVG(co.SalesAmount) as AvgOrderSalesAmount,
-- If item count should be distinct SKU's, use DistinctItemCount
-- Cast to numeric or another non-integer type to get fractional averages
AVG(CAST(co.ItemCount as numeric(14,4)) as AvgItemCount,
MIN(co.SalesAmount) as SmallestOrderSalesAmount,
MAX(co.SalesAmount) as LargestOrderSalesAmount
FROM CustomerOrders co
GROUP BY co.CustomerID
)
SELECT
c.intCustomerID as CustomerID,
c.strFirstName as CustomerFirstName,
c.strLastName as CustomerLastName,
COALESCE(cs.TotalSalesAmount, 0) as TotalSalesAmount,
COALESCE(cs.OrderCount, 0) as OrderCount,
COALESCE(cs.AvgOrderSalesAmount, 0) as AvgOrderSalesAmount,
COALESCE(cs.AvgItemCount, 0) as AvgItemCount,
COALESCE(cs.SmallestOrderSalesAmount, 0) as SmallestOrderSalesAmount,
COALESCE(cs.LargestOrderSalesAmount, 0) as LargestOrderSalesAmount
FROM TCustomers c
LEFT OUTER JOIN CustomerSales cs on c.intCustomerID = cs.CustomerID;