Reduce number of repeating SUM functions in query - sql

How to reduce the number of SUM functions in my query?
SELECT P_NAME "Product name", (SUM(case when PR = 1 then QUANTITY end) -
SUM(case when PR = 2 then QUANTITY end)) "End balance",
CAST((SUM(case when PR = 1 then QUANTITY*PRICE end) - SUM(case when PR = 2 then
QUANTITY*PRICE end)) as decimal(13,2)) "End balance" FROM RPOD, DMS, DMZ
WHERE RPOD.KTOV=DMS.KTOV AND DMZ.NDM=DMS.DMZ_FK
GROUP BY P_NAME
ORDER BY P_NAME;

You can combine them:
SELECT P_NAME as "Product name",
SUM(case when PR = 1 then QUANTITY
when PR = 2 then - QUANTITY
end) as "End balance",
SUM(case when PR = 1 then QUANTITY * PRICE
when PR = 2 then - QUANTITY * PRICE
end) as "End balance/price",
FROM RPOD JOIN
DMS
ON RPOD.KTOV = DMS.KTOV JOIN
DMZ
ON DMZ.NDM = DMS.DMZ_FK
GROUP BY P_NAME
ORDER BY P_NAME;
Much more important aesthetics about SUM()s is learning to use proper, explicit JOIN syntax. Never use commas in the JOIN clause. Also, you should not give two columns in the result set the same name.

Related

SQL : hiding a calculated column

CREATE table orders
{
id integer,
product_id integer,
type VARCHAR(16)
}
SELECT
(SELECT COUNT(*) FROM orders) AS "Order Count",
-- I don't want total to show up
(SELECT COUNT(*) FROM orders WHERE product_id = 500) AS "total",
(SELECT COUNT(*) FROM orders WHERE product_id = 500 AND type = 'small') * 100 / "total" AS "% Small Sold",
(SELECT COUNT(*) FROM orders WHERE product_id = 500 AND type = 'medium') * 100 / "total" AS "% Medium Sold",
(SELECT COUNT(*) FROM orders WHERE product_id = 500 AND type = 'large') * 100 / "total" AS "% Large Sold"
FROM
orders
I have this SQL report. I have a number of columns and one of I'm creating to use to calculate my other columns, in this case "total". I don't want it to appear in the report though. Is there a way to code it in some other part of the query or mark it as hidden? I'm using Postgres.
You can use a common table expression (CTE) for your totals. Then select the fields you want to keep in your report from the CTE.
WITH totals AS (
SELECT
(SELECT COUNT(*) FROM orders) AS "Order Count",
-- I don't want total to show up
(SELECT COUNT(*) FROM orders WHERE product_id = 500) AS "total",
(SELECT COUNT(*) FROM orders WHERE product_id = 500 AND type = 'small') * 100 / "total" AS "% Small Sold",
(SELECT COUNT(*) FROM orders WHERE product_id = 500 AND type = 'medium') * 100 / "total" AS "% Medium Sold",
(SELECT COUNT(*) FROM orders WHERE product_id = 500 AND type = 'large') * 100 / "total" AS "% Large Sold"
FROM orders
)
SELECT
"Order Count",
"% Small Sold",
"% Medium Sold",
"% Large Sold"
FROM
totals
I'm not sure what you mean by hidden but I have to show you a better way to write this query
SELECT
COUNT(*) AS "Order Count",
-- I don't want total to show up
SUM(CASE WHEN PRODUCT_ID = 500 THEN 1 ELSE 0 END) AS "total",
SUM(CASE WHEN PRODUCT_ID = 500 AND type = 'small' THEN 1 ELSE 0 END) * 100 / SUM(CASE WHEN PRODUCT_ID = 500 THEN 1 ELSE 0 END) AS "% Small Soldl",
SUM(CASE WHEN PRODUCT_ID = 500 AND type = 'medium' THEN 1 ELSE 0 END) * 100 / SUM(CASE WHEN PRODUCT_ID = 500 THEN 1 ELSE 0 END) AS "% Medium Soldl",
SUM(CASE WHEN PRODUCT_ID = 500 AND type = 'large' THEN 1 ELSE 0 END) * 100 / SUM(CASE WHEN PRODUCT_ID = 500 THEN 1 ELSE 0 END) AS "% Large Soldl",
FROM orders
I expect you will see a significant increase in performance
Why don' t you filter first? It is decreasing your query performance.
Check this:
with maintab as (select case
when type is not Null then type
else 'total'
end type, count(*) cnt from orders
where product_id = 500 and type in ('small', 'medium', 'large')
group by rollup(type))
select type, cnt*100/(select cnt from maintab where type = 'total' ) percentage
from maintab
where type in ('small', 'medium', 'large');
If your table contains different values in product_id and type columns, of course you should go with this one:
with cnttab as (select count(*) cnt from orders), maintab as (select type, count(*) cnt from orders
where product_id = 500 and type in ('small', 'medium', 'large')
group by type)
select type, cnt*100/(select cnt from cnttab) percentage
from maintab;
You can compare explain plans.

SQL return results only if all rows match in join

I have the following tables
table name column names
----------- ------------------------
delivery_ service svc_name | svc_cost
product prod_id
service_prod svc_name | prod_id
order order_id
order_item order_id | prod_id
Now I want to calculate the total delivery service cost (svc_cost) of all items in an order. Of course, this total makes sense only if all items in the order are eligible for that delivery service.
For instance, product fresh tomatos only have express delivery service, whereas product dvd has both express and normal shipping as delivery service. Consequently, the delivery cost of an order with items fresh tomatos and dvd should only take express delivery service costs into account, since normal shipping is not eligible for the total of the order.
I'm not sure how I should translate this into SQL.
Any tips on where to start are welcome
Just an untested notepad scribble in anticipation of clarifications.
SELECT order_id, product_count
, CASE
WHEN strictly_express_cost = 0 THEN strictly_normal_cost + whatever_normal_cost
WHEN strictly_normal_cost = 0 THEN strictly_express_cost + whatever_express_cost
ELSE strictly_express_cost + strictly_normal_cost + whatever_normal_cost
END AS total_cost
FROM
(
SELECT order_id
, SUM(CASE WHEN express_cost > 0 AND normal_cost = 0 THEN express_cost ELSE 0 END) AS strictly_express_cost
, SUM(CASE WHEN express_cost = 0 AND normal_cost > 0 THEN normal_cost ELSE 0 END) AS strictly_normal_cost
, SUM(CASE WHEN express_cost > 0 AND normal_cost > 0 THEN express_cost ELSE 0 END) AS whatever_express_cost
, SUM(CASE WHEN express_cost > 0 AND normal_cost > 0 THEN normal_cost ELSE 0 END) AS whatever_normal_cost
, COUNT(DISTINCT prod_id) AS product_count
FROM
(
SELECT orditm.order_id, orditm.prod_id
, SUM(CASE WHEN svcprd.svc_name = 'express' THEN dlvsvc.svc_cost ELSE 0 END) AS express_cost
, SUM(CASE WHEN svcprd.svc_name = 'normal' THEN dlvsvc.svc_cost ELSE 0 END) AS normal_cost
FROM order_item AS orditm
LEFT JOIN service_prod AS svcprd
ON svcprd.prod_id = orditm.prod_id
LEFT JOIN delivery_service AS dlvsvc
ON dlvsvc.svc_name = svcprd.svc_name
GROUP BY orditm.order_id, orditm.prod_id
) q1
GROUP BY order_id
) q2

How to use count for a case statement

I'm using the below code to get the count of every case statements which has sum inside the case statements but I'm getting the error message.
SELECT
count(case when SUM(Orders.Sales)>10000 then 1 end) as High,
count(case when SUM(Orders.Sales)>5000 and SUM(Orders.Sales)<9999 then
SUM(Orders.Sales) end) as Medium,
count(case when SUM(Orders.Sales)<5000 then SUM(Orders.Sales) end) as Low
FROM Orders
INNER JOIN Returns ON Orders.[Order ID] = Returns.[Order ID]
OUTPUT
This is the required output which I'm supposed to be expected.
I feel that you should be doing the outer count rollups over a subquery which aggregates by order:
SELECT
COUNT(CASE WHEN sales < 5000 THEN 1 END) AS Low,
COUNT(CASE WHEN sales < 9999 THEN 1 END) AS Medium,
COUNT(CASE WHEN sales >= 9999 THEN 1 END) AS High
FROM
(
SELECT o.[Order ID], SUM(o.Sales) AS sales
FROM Orders o
INNER JOIN Returns r ON o.[Order ID] = r.[Order ID]
GROUP BY o.[Order ID]
) t;
That being said, I don't actually know what the purpose of joining Orders to the Returns table is. If you intend to only find sales amounts from orders which have been returned, then maybe this makes sense. Otherwise, maybe it doesn't make sense.

Complex SQL query with aggregate functions

I have 3 tables
Sales journal SALES:
DATE - Sale date
T_CODE - Product code
QUAN - Quantity sold
Product journal PRODUCTS:
CODE - Product code
NAME - Product name
Prices journal PRICES:
T_CODE - Product code
DATE - Date of change of price(i.e. the changed price is valid from that date onwards till the next change of price)
COST - The price of the product
I need to summarize total quantity sold and total value sold for first three months of 2018
I've tried to construct SQL query for this as follows:
SELECT PRODUCTS.NAME, PRODUCT.T_CODE
(SELECT SUM(SALES.QUAN) WHERE SALES.DATE BETWEEN '01.01.2018' AND '31.01.2018') AS JANUARY_QUANTITY
(SELECT SUM(SALES.QUAN)*PRICES.COST FROM SALES INNER JOIN PRICES ON PRICES.T_CODE = SALES.T_CODE) AS JANUARY_VALUE
(SELECT SUM(SALES.QUAN) WHERE SALES.DATE BETWEEN '01.02.2018' AND '28.02.2018') AS FEBRUARY_QUANTITY
(SELECT SUM(SALES.QUAN)*PRICES.COST FROM SALES INNER JOIN PRICES ON PRICES.T_CODE = SALES.T_CODE) AS FEBRUARY_VALUE
(SELECT SUM(SALES.QUAN) WHERE SALES.DATE BETWEEN '01.03.2018' AND '31.03.2018') AS MARCH_QUANTITY
(SELECT SUM(SALES.QUAN)*PRICES.COST FROM SALES INNER JOIN PRICES ON PRICES.T_CODE = SALES.T_CODE) AS MARCH_VALUE
LEFT JOIN PRODUCTS.CODE
GROUP BY ST.NAME;
Please help me to construct correct SQL query for this.
SELECT SUM(SALES.QUAN)*PRICES.COST FROM SALES INNER JOIN PRICES ON PRICES.T_CODE = SALES.T_CODE) AS JANUARY_VALUE
You are not getting the output that you are missing a JOIN condition on that matches the date of the sale with the date of the price. Also, you would need to move the multiplication into the aggregate function.
Putting the price inside the aggregate function allows the calculation to work correctly.
Also, I believe that your query could probably be simplified by using conditional aggregation, as follows :
SELECT
SUM(CASE WHEN S.DATE BETWEEN '01.01.2018' AND '31.01.2018' THEN S.QUAN ELSE 0 END) AS JANUARY_QUANTITY,
SUM(CASE WHEN S.DATE BETWEEN '01.01.2018' AND '31.01.2018' THEN S.QUAN * P.COST ELSE 0 END) AS JANUARY_VALUE,
SUM(CASE WHEN S.DATE BETWEEN '01.02.2018' AND '28.02.2018' THEN S.QUAN ELSE 0 END) AS FEBRUARY_QUANTITY,
SUM(CASE WHEN S.DATE BETWEEN '01.02.2018' AND '28.02.2018' THEN S.QUAN * P.COST ELSE 0 END) AS FEBRUARY_VALUE,
SUM(CASE WHEN S.DATE BETWEEN '01.03.2018' AND '31.03.2018' THEN S.QUAN ELSE 0 END) AS MARCH_QUANTITY,
SUM(CASE WHEN S.DATE BETWEEN '01.03.2018' AND '31.03.2018' THEN S.QUAN * P.COST ELSE 0 END) AS MARCH_VALUE
FROM
SALES S
LEFT JOIN PRICES P ON P.T_CODE = S.T_CODE AND P.DATE = S.DATE
With this technique, the table is scanned only once, and the results are then analyzed to feed the relevant columns in the resultset. This should efficiently replace your 6 subqueries.

SQL Select Query - problem pivoting rows into columns

I have three tables in an SQL 2005 database, that I need to query and display on one row. The tables are:
MasterStock
StockID, Description
1, Plate
2, Bowl
ShopStock
ShopID, StockID, StockLevel
1,1,6
2,1,0
3,1,0
4,1,10
Sales
StockId, ShopId, SoldQuantity, transDate
1, 1, 1, 5/1/2011
1,2,1, 5/1/2011
I need to get them to show one row:
StockID, Description, 1 Sales, 1 Stock, 2 Sales, 2 Stock, 3 Sales,…
I have managed to get what somewhere with the query below:
SELECT MasterStock.StockID, MasterStock.Description,
SUM(CASE WHEN sales.shopid = 1 THEN sales.Soldquantity ELSE 0 END) AS [1 Sold],
MAX(CASE WHEN shopstock.shopid = 1 THEN shopstock.stockLevel ELSE 0 END) AS [1 Stock],
SUM(CASE WHEN sales.shopid = 2 THEN sales.Soldquantity ELSE 0 END) AS [2 Sold],
MAX(CASE WHEN shopstock.shopid = 2 THEN shopstock.stockLevel ELSE 0 END) AS [2 Stock],
SUM(CASE WHEN sales.shopid = 3 THEN sales.Soldquantity ELSE 0 END) AS [3 Sold],
MAX(CASE WHEN shopstock.shopid = 3 THEN shopstock.stockLevel ELSE 0 END) AS [3 Stock],
SUM(CASE WHEN sales.shopid = 4 THEN sales.Soldquantity ELSE 0 END) AS [4 Sold],
MAX(CASE WHEN shopstock.shopid = 4 THEN shopstock.stockLevel ELSE 0 END) AS [4 Stock]
FROM ShopStock INNER JOIN
Sales ON ShopStock.StockID = Sales.StockID AND ShopStock.shopID = Sales.ShopID
INNER JOIN MasterStock ON ShopStock.StockID = MasterStock.StockID
WHERE (sales.transdate > 1/1/2010)
GROUP BY MasterStock.StockID, MasterStock.Description
However, if there are no sales for the product it doesn’t show any stock levels. If I remove the shopID join on shopstock and sales it shows the stock levels, but reports inaccurate sales - multiplies by four (one for each shopstock record?).
I know I’m missing something here, but I’m not getting anywhere! Any help would be greatly received.
Two problems:
1) You need a LEFT OUTER JOIN between ShopStock and Sales, which will ensure that the query returns records from ShopStock even if there are no related entries in Sales. By definition, an INNER JOIN will not return records from either side of the join, if one of the sides is missing records.
2) You need to move your sales.transdate > 1/1/2010 condition to the inner join, rather than the WHERE clause. Conditions in the WHERE clause will be logically applied after any logic in the table joins. So even if you get your joins right, the where clause will filter out stock without sales because sales.transdate will appear null.
Something like this:
FROM ShopStock LEFT OUTER JOIN Sales
ON ShopStock.StockID = Sales.StockID
AND Sales.transdate > 1/1/2010
INNER JOIN // the rest of your joins here
I'm guessing you also want >= on your transdate filter as well, but that's just a hunch.
Good luck!