Output two columns for 1 field for different date ranges? - sql

I have a SQL table "ITM_SLS" with the following fields:
ITEM
DESCRIPTION
TRANSACTION #
DATE
QTY SOLD
I want to be able to output QTY SOLD for a one month value and a year to date value so that the output would look like this:
ITEM, DESCRIPTION, QTY SOLD MONTH, QTY SOLD YEAR TO DATE
Is this possible?

You could calculate the total quantity sold using group by in a subquery. For example
select a.Item, a.Description, b.MonthQty, c.YearQty
from (
select distinct Item, Description from TheTable
) a
left join (
select Item, sum(Qty) as MonthQty
from TheTable
where datediff(m,Date,getdate()) <= 1
group by Item
) b on a.Item = b.Item
left join (
select Item, sum(Qty) as YearQty
from TheTable
where datediff(y,Date,getdate()) <= 1
group by Item
) c on a.Item = c.Item
The method to limit the subquery to a particular date range differs per DBMS, this example uses the SQL Server datediff function.

Assuming the "one month" is last month...
select item
, description
, sum (case when trunc(transaction_date, 'MM')
= trunc(add_months(sysdate, -1), 'MM')
then qty_sold
else 0
end) as sold_month
, sum(qty_sold) as sold_ytd
from itm_sls
where transaction_date >= trunc(sysdate, 'yyyy')
group by item, description
/

This will give you an idea of what you can do:
select
ITEM,
DESCRIPTION,
QTY SOLD as MONTH,
( select sum(QTY SOLD)
from ITM_SLS
where ITEM = I.ITEM
AND YEAR = i.YEAR
) as YEAR TO DATE
from ITM_SLS I

Related

Combine different results of "group by" queries in the same table

I need to make some comparation between 2 years: sales by product, sales by category, etc.
How can I have this in one table having 3 columns:
first column = product, category, etc
second column = sales in 2021
third column = sales in 2022
Sample of queries that must be combined in one single table as the one below
select product_code, sum(amount)
from product
where year = '2021'
group by product_code
select product_code, sum(amount)
from product
where year = '2022'
group by product_code
select category_code, sum(amount)
from category
where year = '2021'
group by category_code
select category_code, sum(amount)
from category
where year = '2022'
group by category_code
Please, see the final table
[1]: https://i.stack.imgur.com/smF7h.png
NOTE!
If for instance in 2021 there was no "product D", it will be 0 for "Sales_2021" or the "product A" is no longer present in 2022, it will be 0 for "Sales_2022".
Thank you
You need two things here:
Conditional aggregation (a CASE expression inside the aggregation function) in order to get 2021 and 2022 in one go.
A union of two intermediate result sets (product figures UNION ALL category figures).
And as any table - and a query result is again a table - is unordered, we need an ORDER BY at last to get products first and categories second and also the products ordered alphabetically and the categories, too.
The complete query:
select category_or_product, sales_2021, sales_2022
from
(
select
product_code as category_or_product,
sum(case when year = 2021 then amount else 0 end) as sales_2021,
sum(case when year = 2021 then amount else 0 end) as sales_2022,
1 as product_first
from product
group by product_code
union all
select
category_code as category_or_product,
sum(case when year = 2021 then amount else 0 end) as sales_2021,
sum(case when year = 2021 then amount else 0 end) as sales_2022,
2 as product_first
from category
group by category_code
) unioned
order by product_first, category_or_product;

Issues with postgreSQL subqueries

I have the following chunk of code, in which in trying to count the sales of beef, chicken and pork in each month of the last year (i also need to determine the market share of the meats each month)
SELECT
CAST(EXTRACT('MONTH' FROM TO_TIMESTAMP(FULLDATE, 'YYYY-MM-DD')) AS INT) AS month
FROM purchases_2020
JOIN categories ON purchases_2020.purchaseid = categories.purchase_id
(
SELECT
COUNT (purchaseid) AS total_sales
FROM purchases_2020
JOIN categories ON purchases_2020.purchaseid = categories.purchase_id
WHERE category = 'whole milk' OR category = 'yogurt' OR category = 'domestic eggs'
GROUP BY month
) a
GROUP BY month
ORDER BY month
The expected result is the following image
EDIT to add the exact error message
but in getting this error message
syntax error at or near "SELECT"
LINE 6: SELECT
^
[SQL: SELECT
CAST(EXTRACT('MONTH' FROM TO_TIMESTAMP(FULLDATE, 'YYYY-MM-DD')) AS INT) AS month
FROM purchases_2020
JOIN categories ON purchases_2020.purchaseid = categories.purchase_id
(
SELECT
COUNT (purchaseid) AS total_sales
FROM purchases_2020
JOIN categories ON purchases_2020.purchaseid = categories.purchase_id
WHERE category = 'whole milk' OR category = 'yogurt' OR category = 'domestic eggs'
GROUP BY month
) a
GROUP BY month
ORDER BY month
This is the data schema i'm working with.
EDIT
I'm aware i can query the total_sales like this:
SELECT
CAST(EXTRACT('MONTH' FROM TO_TIMESTAMP(FULLDATE, 'YYYY-MM-DD')) AS INT) AS month,
COUNT (purchaseid) AS total_sales
FROM purchases_2020
JOIN categories ON purchases_2020.purchaseid = categories.purchase_id
WHERE category = 'beef' OR category = 'pork' OR category = 'chicken'
GROUP BY month
ORDER BY month
But doing it like this locks me out of doing of writting the market_share formula on the select statement because of the WHERE statement no being inside a subquery.
This query should give you the count of sales by month and category. I can't test it because I don't have datas.
SELECT
c.category,
EXTRACT('MONTH' FROM FULLDATE) AS month,
count(purchaseid) AS total_sales
FROM purchases_2020 p JOIN categories c ON p.purchaseid = c.purchase_id
WHERE category in ('beef','pork','chicken')
GROUP BY month,c.category
ORDER BY month,c.category;

Grouping multiple selects within a SQL query

I have a table Supplier with two columns, TotalStock and Date. I'm trying to write a single query that will give me stock totals by week / month / year for a list of suppliers.
So results will look like this..
SUPPLIER WEEK MONTH YEAR
SupplierA 50 100 2000
SupplierB 60 150 2500
SupplierC 15 25 200
So far I've been playing around with multiple selects but I can't get any further than this:
SELECT Supplier,
(
SELECT Sum(TotalStock)
FROM StockBreakdown
WHERE Date >= '2014-5-12'
GROUP BY Supplier
) AS StockThisWeek,
(
SELECT Sum(TotalStock)
FROM StockBreakdown
WHERE Date >= '2014-5-1'
GROUP BY Supplier
) AS StockThisMonth,
(
SELECT Sum(TotalStock)
FROM StockBreakdown
WHERE Date >= '2014-1-1'
GROUP BY Supplier
) AS StockThisYear
This query throws an error as each individual grouping returns multiple results. I feel that I'm close to the solution but can't work out where to go
You don't have to use subqueries to achieve what you want :
SELECT Supplier
, SUM(CASE WHEN Date >= CAST('2014-05-12' as DATE) THEN TotalStock END) AS StockThisWeek
, SUM(CASE WHEN Date >= CAST('2014-05-01' as DATE) THEN TotalStock END) AS StockThisMonth
, SUM(CASE WHEN Date >= CAST('2014-01-01' as DATE) THEN TotalStock END) AS StockThisYear
FROM StockBreakdown
GROUP BY Supplier
You may need to make the selects for the columns return only a single result. You could try this (not tested currently):
SELECT Supplier,
(
SELECT TOP 1 StockThisWeek FROM
(
SELECT Supplier, Sum(TotalStock) AS StockThisWeek
FROM StockBreakdown
WHERE Date >= '2014-5-12'
GROUP BY Supplier
) tmp1
WHERE tmp1.Supplier = Supplier
) AS StockThisWeek,
(
SELECT TOP 1 StockThisMonth FROM
(
SELECT Supplier, Sum(TotalStock) AS StockThisMonth
FROM StockBreakdown
WHERE Date >= '2014-5-1'
GROUP BY Supplier
) tmp2
WHERE tmp2.Supplier = Supplier
) AS StockThisMonth,
...
This selects the supplier and then tries to create two columns StockThisWeek and StockThisMonth by selecting the first entry from the select you created before. As through the GROUP BY there should only be one entry per supplier, so you don't lose and data.

In Oracle SQL, how do you query the proportion of records of a certain value?

Say, you have a query like
SELECT COUNT(*), date FROM ORDERS GROUP BY date ORDER BY date
but you also want to have a third "phantom/dummy field", where it basically tells you the fraction of orders each day that are of a particular type (lets say "Utensils" and "Perishables").
I should say that there is an additional column in the ORDERS table that has the type of the order:
order_type
The third dummy column should do something like take the count of orders on a date that have the "Utensils" or the "Perishables" type (not XOR), then divide by the total count of orders of that day, and then round to 2 decimal points, and append a percentage sign.
The last few formatting things, aren't really important...all I really need to know is how to apply the logic in valid PLSQL syntax.
Example output
4030 2012-02-02 34.43%
4953 2012-02-03 16.66%
You can do something like
SELECT COUNT(*),
dt,
round( SUM( CASE WHEN order_type = 'Utensils'
THEN 1
ELSE 0
END) * 100 / COUNT(*),2) fraction_of_utensils_orders
FROM ORDERS
GROUP BY dt
ORDER BY st
If you find it easier to follow, you could also
SELECT COUNT(*),
dt,
round( COUNT( CASE WHEN order_type = 'Utensils'
THEN 1
ELSE NULL
END) * 100/ COUNT(*), 2) fraction_of_utensils_orders
FROM ORDERS
GROUP BY dt
ORDER BY st
To Add sum of orders of same type to query:
select
o.*,
(
select count(o2.OrderType)
from ORDERS o2
where o2.OrderType = o.OrderType
) as NumberOfOrdersOfThisType
from ORDERS o
To Add fraction of orders of same type to query:
(Check variable definition to make sure it is PL/SQL)
declare totalCount number
select count(*)
into totalCount
from ORDERS
select
o.*,
(
select count(o2.OrderType)
from ORDERS o2
where o2.OrderType = o.OrderType
) / totalCount as FractionOfOrdersOfThisType
from ORDERS o

How can you use SQL to return values for a specified date or closest date < specified date?

I've written an SQL statement to return a list of prices based on a date parameter, but I am finding that on some dates, the price is missing. I am looking for a way to modify this statement to return the price on the date specified, but if that is not available return the price for the most recent price available before the date specified.
Select date, grp, id, price
From
price_table
Where
date In ('12/31/2009', '11/30/2009') And
grp In ('Group1')
For example, in the I would like to be able to re-write the statement above to return all of the records below, showing appropriate parameter dates for all records. Assume this is a subset of a table with daily prices and the values below are the last prices for the months noted.
12/31/2009 Group1 1111 100
12/31/2009 Group1 2222 99
12/29/2009 Group1 3333 98
11/30/2009 Group1 1111 100
11/28/2009 Group1 2222 99
11/30/2009 Group1 3333 98
UPDATE:
Thanks to some help from srgerg below, I have been able to create a statement that works for one date at a time, but I would still like to find a way to pass multiple dates to the query.
Select p1.date, p1.grp, p1.id, p1.price
From
price_table As p1 Inner Join
(Select Max(p2.date) As maxdt, id
From
price_table As p2
Where
p2.date <= '12/31/2009'
Group By
p2.id) As p On p.maxdt = p1.date And p1.id = p.id
Where grp in ('Group1')
You could try something like this:
SELECT date, grp, id
, (SELECT TOP 1 price
FROM price_table P2
WHERE P1.id = P2.id
AND P1.grp = P2.grp
AND P2.date <= P1.date
ORDER BY P2.date DESC)
FROM price_table P1
WHERE P1.date IN ('12/31/2009', '11/30/2009')
AND P1.grp IN ('Group1')
Edit To get the last record for each month, group and id you could try this:
SELECT date, grp, id, price
FROM price_table P1
WHERE P1.date = (SELECT MAX(date)
FROM price_table P2
WHERE P1.grp = P2.grp
AND P1.id = P2.id
AND YEAR(P1.date) = YEAR(P2.date)
AND MONTH(P1.date) = MONTH(P2.date))
AND P1.grp In ('Group1')
Here's my approach to solving to this problem:
Associate every search date with a date in the table.
Use search dates as (additional) group terms.
Rank the dates in descending order, partitioning them by group terms.
Select those with the desired group term values and rank = 1.
The script:
WITH price_grouped AS (
SELECT
date, grp, id, price,
dategrp = CASE
WHEN date <= '11/30/2009' THEN '11/30/2009'
WHEN date <= '12/31/2009' THEN '12/31/2009'
/* extend the list of dates here */
END
FROM price_table
),
price_ranked AS (
SELECT
date, grp, id, price, dategrp,
rank = RANK() OVER (PARTITION BY grp, dategrp ORDER BY date DESC)
FROM price_grouped
)
SELECT date, grp, id, price
FROM price_ranked
WHERE grp IN ('Group1')
AND rank = 1
The above solution may seem not very handy because of the necessity to repeat each search date twice. An alternative to that might be to define the search date list as a separate CTE and, accordingly, assign the dates in a different way:
WITH search_dates (Date) AS (
SELECT '11/30/2009' UNION ALL
SELECT '12/31/2009'
/* extend the list of dates here */
),
price_grouped AS (
SELECT
p.date, p.grp, p.id, p.price,
dategrp = MIN(d.Date)
FROM price_table p
INNER JOIN search_dates d ON p.date <= d.Date
GROUP BY
p.date, p.grp, p.id, p.price
),
price_ranked AS (
SELECT
date, grp, id, price, dategrp,
rank = RANK() OVER (PARTITION BY grp, dategrp ORDER BY date DESC)
FROM price_grouped
)
SELECT date, grp, id, price
FROM price_ranked
WHERE grp IN ('Group1')
AND rank = 1
But take into account that the former solution will most probably be more performant.