Complex SQL query with aggregate functions - sql

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.

Related

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.

SQL - Dividing aggregated fields, very new to SQL

I have list of line items from invoices with a field that indicates if a line was delivered or picked up. I need to find a percentage of delivered items from the total number of lines.
SALES_NBR | Total | Deliveryrate
1 = Delivered 0 = picked up from FULFILLMENT.
SELECT SALES_NBR,
COUNT (ITEMS) as Total,
SUM (case when FULFILLMENT = '1' then 1 else 0 end) as delivered,
(SELECT delivered/total) as Deliveryrate
FROM Invoice_table
WHERE STORE IN '0123'
And SALE_DATE >='2020-02-01'
And SALE_DATE <='2020-02-07'
Group By SALES_NBR, Deliveryrate;
My query executes but never finishes for some reason. Is there any easier way to do this? Fulfillment field does not contain any NULL values.
Any help would be appreciated.
I need to find a percentage of delivered items from the total number of lines.
The simplest method is to use avg():
select SALES_NBR,
avg(fulfillment) as delivered_ratio
from Invoice_table
where STORE = '0123' and
SALE_DATE >='2020-02-01' and
SALE_DATE <='2020-02-07'
group by SALES_NBR;
I'm not sure if the group by sales_nbr is needed.
If you want to get a "nice" query, you can use subqueries like this:
select
qry.*,
qry.delivered/qry.total as Deliveryrate
from (
select
SALES_NBR,
count(ITEMS) as Total,
sum(case when FULFILLMENT = '1' then 1 else 0 end) as delivered
from Invoice_table
where STORE IN '0123'
and SALE_DATE >='2020-02-01'
and SALE_DATE <='2020-02-07'
group by SALES_NBR
) qry;
But I think this one, even being ugglier, could perform faster:
select
SALES_NBR,
count(ITEMS) as Total,
sum(case when FULFILLMENT = '1' then 1 else 0 end) as delivered,
sum(case when FULFILLMENT = '1' then 1 else 0 end)/count(ITEMS) as Deliveryrate
from Invoice_table
where STORE IN '0123'
and SALE_DATE >='2020-02-01'
and SALE_DATE <='2020-02-07'
group by SALES_NBR

Add dimensions to denormalized concatenated columns queried from temporary tables

I am querying two tables from an oracle database. POS.SNAPSHOT_SUMMARY_LOG contains daily snapshots of all reserved orders from today into the future. POS.SALES_SUMMARY_LOG contains final sales history until yesterday. I am trying to create a denormalized table that has a column with sales figures at different points in time (i.e. WINDOWS). The query below works well and is fast. I get a column for each sum for each window. See results for 2/26/18-3/26/18 windows_query. (The PREVIOUS_YEAR__DATE column is the most similar weekday from the previous year and not the same calendar date of the previous year)
However, I can't figure out how to group by the dimensions of PRODUCT_CATEGORY, CUSTOMER_CATEGORY, SALES_CHANNEL to the results so that the query runs quickly.
WITH SNAP AS( SELECT
TRUNC(SYSDATE,'DDD')-SNAPSHOT_DATE AS WINDOW, CURRENT_YEAR_DATE, PREVIOUS_YEAR__DATE, PRODUCT_CATEGORY, CUSTOMER_CATEGORY, SALES_CHANNEL, QUANTITY, REVENUE
FROM POS.SNAPSHOT_SUMMARY_LOG
WHERE (SNAPSHOT_DATE IN (TRUNC(SYSDATE,'DDD'), TRUNC(SYSDATE,'DDD')-7, TRUNC(SYSDATE,'DDD')-365,TRUNC(SYSDATE,'DDD')-372))),
HIST AS (SELECT CURRENT_YEAR_DATE, PREVIOUS_YEAR__DATE, PRODUCT_CATEGORY, CUSTOMER_CATEGORY, SALES_CHANNEL, QUANTITY, REVENUE
FROM POS.SALES_SUMMARY_LOG)
SELECT DISTINCT SNAP.CURRENT_YEAR_DATE, SNAP.PREVIOUS_YEAR__DATE,
CONCAT(SNAP0.REVENUE,HIST0.REVENUE)+0 AS REVENUE0,
CONCAT(SNAP7.REVENUE,HIST7.REVENUE)+0 AS REVENUE7,
CONCAT(SNAP365.REVENUE,HIST365.REVENUE)+0 AS REVENUE365,
CONCAT(SNAP372.REVENUE,HIST372.REVENUE)+0 AS REVENUE372,
CONCAT(SNAP0.QUANTITY,HIST0.QUANTITY)+0 AS QUANTITY0,
CONCAT(SNAP7.QUANTITY,HIST7.QUANTITY)+0 AS QUANTITY7,
CONCAT(SNAP365.QUANTITY,HIST365.QUANTITY)+0 AS QUANTITY365,
CONCAT(SNAP372.QUANTITY,HIST372.QUANTITY)+0 AS QUANTITY372
FROM SNAP
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM SNAP WHERE WINDOW=0 GROUP BY CURRENT_YEAR_DATE) SNAP0 ON SNAP.CURRENT_YEAR_DATE=SNAP0.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM SNAP WHERE WINDOW=7 GROUP BY CURRENT_YEAR_DATE) SNAP7 ON SNAP.CURRENT_YEAR_DATE=SNAP7.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM SNAP WHERE WINDOW=365 GROUP BY CURRENT_YEAR_DATE ) SNAP365 ON SNAP.PREVIOUS_YEAR_DATE=SNAP365.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM SNAP WHERE WINDOW=372 GROUP BY CURRENT_YEAR_DATE) SNAP372 ON SNAP.PREVIOUS_YEAR_DATE=SNAP372.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM HIST WHERE CURRENT_YEAR_DATE<TRUNC(SYSDATE,'DDD')-0 GROUP BY CURRENT_YEAR_DATE) HIST0 ON SNAP.CURRENT_YEAR_DATE=HIST0.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM HIST WHERE CURRENT_YEAR_DATE<TRUNC(SYSDATE,'DDD')-7 GROUP BY CURRENT_YEAR_DATE) HIST7 ON SNAP.CURRENT_YEAR_DATE=HIST7.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM HIST WHERE CURRENT_YEAR_DATE<TRUNC(SYSDATE,'DDD')-365 GROUP BY CURRENT_YEAR_DATE) HIST365 ON SNAP.PREVIOUS_YEAR_DATE=HIST365.CURRENT_YEAR_DATE
LEFT OUTER JOIN (SELECT CURRENT_YEAR_DATE, sum(REVENUE) AS REVENUE, sum(QUANTITY) AS QUANTITY FROM HIST WHERE CURRENT_YEAR_DATE<TRUNC(SYSDATE,'DDD')-372 GROUP BY CURRENT_YEAR_DATE) HIST372 ON SNAP.PREVIOUS_YEAR_DATE=HIST372.CURRENT_YEAR_DATE
WHERE SNAP.CURRENT_YEAR_DATE BETWEEN TRUNC(SysDate,'YEAR') AND ADD_MONTHS(TRUNC(TRUNC(SysDate,'YEAR'),'Y'),15)
I think you can rewrite your query using conditional aggregation, which ought to be a lot more performant than your current query.
E.g. I think you could rewrite it to be something like:
SELECT current_year_date,
previous_year__date,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 0 THEN revenue END) snap_revenue0,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 7 THEN revenue END) snap_revenue7,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 365 THEN revenue END) snap_revenue365,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 372 THEN revenue END) snap_revenue372,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 0 THEN revenue END) hist_revenue0,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 7 THEN revenue END) hist_revenue7,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 365 THEN revenue END) hist_revenue365,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 372 THEN revenue END) hist_revenue372,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 0 THEN quantity END) snap_quantity0,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 7 THEN quantity END) snap_quantity7,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 365 THEN quantity END) snap_quantity365,
SUM(CASE WHEN trunc(sysdate) - snapshot_date = 372 THEN quantity END) snap_quantity372,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 0 THEN quantity END) hist_quantity0,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 7 THEN quantity END) hist_quantity7,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 365 THEN quantity END) hist_quantity365,
SUM(CASE WHEN CURRENT_YEAR_DATE < (trunc(sysdate) - 372 THEN quantity END) hist_quantity372
FROM pos.snapshot_summary_log
GROUP BY current_year_date,
previous_year__date;
I don't quite understand what you mean by "add the dimensions of PRODUCT_CATEGORY, CUSTOMER_CATEGORY, SALES_CHANNEL"; if you mean you want the results grouped with those additional columns, then it's simply a matter of adding them into the above select column and group by lists, e.g.:
SELECT current_year_date,
previous_year__date,
product_category,
customer_category,
sales_channel,
<SUM columns>
FROM pos.snapshot_summary_log
GROUP BY current_year_date,
previous_year__date,
product_category,
customer_category,
sales_channel;
I've not done anything with the columns; you may want to throw an outer query around this that adds the snap and hist columns together, or maybe COALESCEs them, but I'll leave that up to you to decide.

Reduce number of repeating SUM functions in query

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.

SQL Query to compare 2 weeks

I've got to design a query in visual studio where I have 2 data sets.
basically it goes like this.
I want to compare this weeks call total to last week per country calling.
the only thing is last weeks calls may have come from 20 diff countries while this weeks might only have come from 15.
How can I make the query such that the 20 countries will show up for both while having "0" value in for countries that do not appear this week.
below is my query:
Select country,
Sum(Case When actstatus in (5,105) Then 1 Else 0 End) As TotalCalls,
Sum(Case When actstatus = 105 Then 1 Else 0 End) As FailedCalls
From termactivity(nolock)
INNER JOIN termconfig(NOLOCK) ON cfgterminalID = actterminalID
INNER JOIN Country (nolock) on country = cycode
Where actstatus in (5,105)
and (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
Group By country
order By country asc
When Act status = 105 it means the call was not completed and when it = 5 it means the call was successful. I am doing this to get a successful call % rate per week.
Thanks in Advance!
Apply the same logic as you did to total calls and failed calls as you did to the this week and last week.
SELECT country,
COUNT(CASE WHEN actTerminalDateTime < #StartDate THEN 1 END) [LastWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime < #StartDate THEN 1 END) [LastWeekFailedCalls],
COUNT(CASE WHEN actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekFailedCalls]
FROM termactivity (NOLOCK)
INNER JOIN termconfig (NOLOCK)
ON cfgterminalID = actterminalID
INNER JOIN Country (NOLOCK)
ON country = cycode
WHERE actstatus in (5,105)
AND actTerminalDateTime BETWEEN DATEADD(DAY, -7, #StartDate) AND #EndDate
GROUP BY country
ORDER BY country ASC
I've also tidied up your query slightly, for example there is no point in specifying
WHEN ActStatus IN (5, 105) ...
When your WHERE clause already limits all results to 5, 105, therefore this is a redundant predicate in your case expression
From what I understand, you want to perform separate queries for two weeks, and you want both queries to produce rows for all countries, regardless of whether all countries had any calls. To achieve this, you need to use LEFT OUTER JOINS. The below code should guarantee that every country found in the Country table has a row, even if both sums are 0.
SELECT country,
SUM(CASE WHEN actstatus IN (5,105) THEN 1 ELSE 0 END) AS TotalCalls,
SUM(CASE WHEN actstatus = 105 THEN 1 ELSE 0 END) AS FailedCalls
FROM Country (NOLOCK)
LEFT OUTER JOIN termconfig (NOLOCK) ON country = cycode
LEFT OUTER JOIN termactivity (NOLOCK) ON cfgterminalID = actterminalID
WHERE (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
GROUP BY country
ORDER BY country ASC
If this was not what you wanted, perhaps you need to clarify your question. Many others have assumed that you want to combine the results into a single query.