Is it possible to use SELECT DISTINCT and GROUP BY together? - sql

Is it possible to use SELECT DISTINCT and GROUP BY clause together?
I need DISTINCT to avoid duplicate values and make a single entry of the records, and then get the Total quantity of those duplicate values.
For example, I have columns like Item Name and Quantity and their records are (product A,5), (product A,7).
Since products are the same I want it as a single entry and then total its quantity. So, the output on my report would be like this: (product A,12)
Can DISTINCT and GROUP BY clause solve this together?

Based on your comment on Used_By_Already's answer, I think you need to use sum() over (...) construct, such as the following...
create table [dbo].[Purchases_Supplier] (
[Purchased From] nvarchar(50),
Address nvarchar(50),
[Transaction Month] nvarchar(6),
Category nvarchar(50),
Articles nvarchar(50),
Unit int,
[Unit Price] money,
Qty int,
Tin nvarchar(50),
Cashier nvarchar(50)
);
insert [dbo].[Purchases_Supplier] values
('Acme', '123 Street', '202003', 'Baubles', 'Product A', 1, 1.1, 5, 'Nickel', 'John'),
('Acme', '123 Street', '202003', 'Baubles', 'Product A', 1, 1.1, 7, 'Nickel', 'John'),
('Acme', '123 Street', '202003', 'Baubles', 'Product B', 1, 1.1, 9, 'Silver', 'Maria'),
('Acme', '123 Street', '202003', 'Baubles', 'Product B', 1, 1.1, 11, 'Silver', 'Maria');
declare #Supplier_Name nvarchar(50) = N'Acme',
#Month_Purc nvarchar(6) = '202003',
#Cat nvarchar(50) = 'Baubles';
select
Articles,
Qty,
Unit,
[Unit Price],
[Purchased From],
Address,
Tin,
Cashier,
-- NOTE: partition by has everything in the GROUP BY except [Qty]...
sum(Qty) over (partition by Articles, Unit, [Unit Price], [Purchased From], Address, Tin, Cashier) as Total
from dbo.Purchases_Supplier
where [Purchased From] = #Supplier_Name
and [Transaction Month] = #Month_Purc
and Category = #Cat
group by Articles, Qty, Unit, [Unit Price], [Purchased From], Address, Tin, Cashier;
Which yields the result:
Articles Qty Unit Unit Price Purchased From Address Tin Cashier Total
Product A 5 1 1.1000 Acme 123 Street Nickel John 12
Product A 7 1 1.1000 Acme 123 Street Nickel John 12
Product B 9 1 1.1000 Acme 123 Street Silver Maria 20
Product B 11 1 1.1000 Acme 123 Street Silver Maria 20

GROUP BY will result in distinct rows, it does this automatically, you do NOT also need SELECT DISTINCT to reduce the number of rows.
CREATE TABLE mytable(
product VARCHAR(1) NOT NULL
,quantity INTEGER NOT NULL
);
INSERT INTO mytable(product,quantity) VALUES ('a',7);
INSERT INTO mytable(product,quantity) VALUES ('a',5);
INSERT INTO mytable(product,quantity) VALUES ('b',17);
INSERT INTO mytable(product,quantity) VALUES ('b',15);
select product, sum(quantity) as qty
from mytable
group by product
product | qty
:------ | --:
a | 12
b | 32
db<>fiddle here
Note that there is a single row in that result, i.e. only one row for each distinct value in the column called product because we specified that column in the GROUP BY clause.
db<>fiddle here

Related

How to value cost of sales using FIFO in SQL Server

I want value the cost of goods sold using the FIFO method.
I know how many beers I sold. Based on my price I bought those beers at, what is the cost of those sales? So, my sales of 7 Peronis are valued at £1.70 -- based on the FIFO valuation method.
How do I calculate in SQL Server.
I am going to be working this out for many products and from many branches at the same time, so I would like to use a method that does not involve cursors (or any other types of loops).
-- SETUP
DROP TABLE IF EXISTS #Deliveries;
CREATE TABLE #Deliveries (DeliveryDate DATE, ProductCode VARCHAR(10), Quantity INT, Cost DECIMAL(6,2));
INSERT INTO #Deliveries (DeliveryDate, ProductCode, Quantity, Cost)
VALUES
('2020-11-23', 'PERONI', 2, 0.20), ('2020-11-24', 'PERONI', 4, 0.30), ('2020-11-25', 'PERONI', 7, 0.10),
('2020-11-23', 'BUDWEISER', 5, 0.20), ('2020-11-24', 'BUDWEISER', 5, 0.50), ('2020-11-25', 'BUDWEISER', 4, 0.80);
DROP TABLE IF EXISTS #StockResults;
CREATE TABLE #StockResults (ProductCode VARCHAR(10), SalesQty INT, CostOfSalesValue DECIMAL(6,2));
INSERT INTO #StockResults (ProductCode, SalesQty)
VALUES ('PERONI', 7), ('BUDWEISER', 4);
SELECT * FROM #Deliveries;
SELECT * FROM #StockResults;
-- DESIRED RESULT
/*
ProductCode SalesQty CostOfSalesValue
PERONI 7 1.70
BUDWEISER 4 0.80
*/
This is probably not very efficient but it shows you one way in which this can be achieved which should help you come up with your finished solution. I would imagine that there needs to be a lot more complexity built into this process to account for things like stock wastage, but I'll leave that up to you:
Query
-- SETUP
declare #Deliveries table (DeliveryDate date, ProductCode varchar(10), Quantity int, Cost decimal(6,2));
insert into #Deliveries (DeliveryDate, ProductCode, Quantity, Cost) values ('2020-11-23', 'PERONI', 2, 0.20), ('2020-11-24', 'PERONI', 4, 0.30), ('2020-11-25', 'PERONI', 7, 0.10),('2020-11-23', 'BUDWEISER', 5, 0.20), ('2020-11-24', 'BUDWEISER', 5, 0.50), ('2020-11-25', 'BUDWEISER', 4, 0.80);
declare #StockResults table (ProductCode varchar(10), SalesQty int);
insert into #StockResults (ProductCode, SalesQty) values ('PERONI', 7), ('BUDWEISER', 4);
-- QUERY
with r as
(
select d.ProductCode
,d.DeliveryDate
,d.Quantity
,d.Cost
,isnull(sum(d.Quantity) over (partition by d.ProductCode order by d.DeliveryDate rows between unbounded preceding and 1 preceding),0) as RunningQuantityStart
,sum(d.Quantity) over (partition by d.ProductCode order by d.DeliveryDate) as RunningQuantityEnd
from #Deliveries as d
)
select r.ProductCode
,s.SalesQty
,sum(case when r.RunningQuantityEnd >= s.SalesQty
then (s.SalesQty - r.RunningQuantityStart) * r.Cost
else (r.RunningQuantityEnd - r.RunningQuantityStart) * r.Cost
end
) as CostOfSalesValue
from r
join #StockResults as s
on r.ProductCode = s.ProductCode
and r.RunningQuantityStart < s.SalesQty
group by r.ProductCode
,s.SalesQty;
##Output
+-------------+----------+------------------+
| ProductCode | SalesQty | CostOfSalesValue |
+-------------+----------+------------------+
| BUDWEISER | 4 | 0.80 |
| PERONI | 7 | 1.70 |
+-------------+----------+------------------+
Below query might help you:
declare #maxQty int
select #maxQty = max(SalesQty) from #StockResults
;WITH AllNumbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number+1 FROM AllNumbers WHERE Number < #maxQty
)
select ProductCode, SalesQty, SUM(Cost) as CostOfSalesValue from
(
SELECT SR.ProductCode, SR.SalesQty, DLM.RN, DLM.Cost FROM #StockResults SR
outer apply
(
select ROW_NUMBER()OVER (order by DeliveryDate asc) as RN, Dl.Cost from #Deliveries Dl
inner join AllNumbers AL on AL.Number <= Dl.Quantity
where Dl.ProductCode = SR.ProductCode
) as DLM
) result
where RN <= SalesQty
group by ProductCode, SalesQty

I want to create a column that "anonymizes" the countries column for each product

Each row in my table specifies a product and country that bought it. Like this:
Product 1, Country A
Product 1, Country B
Product 1, Country C
Product 2, Country A
Product 2, Country B
I want to add another column that basically counts up each country per product. Each time starting at one for a new product.
Product 1, Country A, Country 1
Product 1, Country B, Country 2
Product 1, Country C, Country 3
Product 2, Country A, Country 1
Product 2, Country B, Country 2
In the past this was done in a vba script simply running a loop comparing the product name with the product name from the previous row adding +1 if its the same and 1 if its not. I was wondering if there is a way to achieve this via SQL.
use row_number()
select *, row_number() over(partition by product order by country) as rn
from tablename
CREATE TABLE #Table1
([col1] varchar(9), [col2] varchar(9))
;
INSERT INTO #Table1
([col1], [col2])
VALUES
('Product 1', 'Country A'),
('Product 1', 'Country B'),
('Product 1', 'Country C'),
('Product 2', 'Country A'),
('Product 2', 'Country B')
;
select *, concat('Country',' ',row_number() over(partition by [col1] order by [col1])) as rn_column
from #Table1
output
col1 col2 rn_column
Product 1 Country A Country1
Product 1 Country B Country2
Product 1 Country C Country3
Product 2 Country A Country1
Product 2 Country B Country2

Find records that belong to the same identifier, check for multiple occurences of value in column

I have a table which links customer ID's to a sale ID. Multiple customers can be linked the same sale ID, however the first customer should be the Main customer with Type 'M'. All other customers should be type Other ('O').
Cust_ID Sale_ID Cust_Type
1 123 'M'
2 123 'O'
3 124 'M'
4 125 'M'
5 125 'O'
6 125 'O'
Sometimes multiple customers linked to the same Sale ID will be the Main ('M') customer - which is not correct:
Cust_ID Sale_ID Cust_Type
1 123 'M'
2 123 'M'
3 123 'O'
What I wish to be able to do is return a list of Customer ID's, Sale IDs and Customer Types where more than one of the customers in a sale ID are a main customer. I.e. Main ('M') occurs more than once across rows that have the same sale ID.
Any help is greatly appreciated!
So, the problem is that a sales_id can have more than one M value and you want to detect this. I would approach this by using a window function to count those values:
select t.*
from (select t.*,
sum(case when cust_type = 'M' then 1 else 0 end) over (partition by sales_id) as NumMs
from table t
) t
where NumMs > 1;
Actually, I would use the condition NumMs <> 1, because missing the main customer might also be important.
Is this what you mean? This can be achieved using a window function.
CREATE TABLE temp(
Cust_ID INT,
Sale_ID INT,
Cust_Type VARCHAR(1)
)
INSERT INTO temp VALUES
(1, 123, 'M'),
(2, 123, 'M'),
(3, 124, 'M'),
(4, 125, 'M'),
(5, 125, 'O'),
(6, 125, 'O');
WITH CTE AS(
SELECT *, cc = COUNT(*) OVER(PARTITION BY Sale_ID)
FROM temp
WHERE Cust_Type = 'M'
)
SELECT
Cust_ID,
Sale_ID,
Cust_Type
FROM CTE
WHERE cc > 1
DROP TABLE temp
How about this:
SELECT s.Cust_ID, s.Sale_ID, s.Cust_Type
FROM StackOverflow s INNER JOIN
(SELECT Sale_ID
FROM StackOverflow
WHERE Cust_Type = 'M'
GROUP BY Sale_ID
HAVING COUNT(*) > 1) as Multiples ON s.Sale_ID = Multiples.Sale_ID

Getting the daily sales report given the date

Given a date, the table displays the items sold on that date.
The table groups the category of the items and show the total sales value for each category. At the end, the report shows the total sales value for the day(s). Something like this:
ID Category Price Units Total Value
----------------------------------------------------
2244 class 10.50 10 105.00
2555 class 5.00 5 25.00
3455 class 20.00 1 20.00
Total 16 150.00
1255 pop 20.00 5 100.00
5666 pop 10.00 10 100.00
Total 15 200,00
1244 rock 2.50 20 50.00
8844 rock 5.00 50 250.00
Total 70 300.00
----------------------------------------------
Total Daily Sales 101 650.00
DBMS: SQL Server 2012
Bolded: primary keys
Item (upc, title*, type, category, company, year, price, stock)
PurchaseItem (receiptId, upc, quantity)
Order (receiptId, date, cid, card#, expiryDate, expectedDate, deliveredDate)
Rough work of what I have so far..
SELECT I.upc, I.category, I.price, P.quantity, P.quantity*I.price AS totalValue, SUM(totalValue), SUM(P.quantity) AS totalUnits, O.date
FROM Item I, Order O
JOIN (SELECT P.quantity
FROM PurchaseItem P, Item I
WHERE I.upc = P.upc)
ON I.upc = P.upc
WHERE O.date = ({$date}) AND O.receiptId = P.receiptId
GROUP BY I.upc, I.category, I.price, P.quantity, totalValue, O.date
Alright, this isn't right and I'm kind of stuck. Need some help!
I want it so it produces the total value of items from one category then in the end, it will add up the total value of the items from all categories.
SAMPLE TABLES
Item(2568, Beatles, rock, Music Inc, 1998, 50.50, 5000)
PurchaseItem (5300, 2568, 2)
Order (5300, 10/09/2014, ...Not important..) cid is customerId and card# is credit card number.
SSRS or a similar reporting package can usually handles this for you natively. If this has to be done in SQl script then a quick solution would be a cursor/while loop. pseudo code would look like this;
create tempsales table;
get distinct list of categories;
for each category
Begin
insert into tempsale (...)
sales where category = #category
group by category, item
insert into tempsales (...) -- use NULL value for item or perhaps a value of 'TOTAL'
Sales where category = #category
group by category
when last category
insert into tempsales (...) -- use NULL value for item AND Category or perhaps a value of 'TOTAL'
total with no group
end
select from tempsales;
See if this helps:
CREATE SAMPLE DATA
use tempdb;
create table Item(
upc int,
category varchar(100),
price decimal(8,2)
)
create table PurchaseItem(
receiptId int,
upc int,
quantity int
)
create table [Order](
receiptId int,
[date] date
)
insert into Item values
(2244, 'class', 10.50),
(2555, 'class', 5.0),
(3455, 'class', 20.0),
(1255, 'pop', 20.0),
(5666, 'pop', 10.0),
(1244, 'rock', 2.50),
(8844, 'rock', 5.0)
insert into PurchaseItem values
(5300, 2244, 10),
(5300, 2555, 5),
(5300, 3455, 1),
(5300, 1255, 5),
(5300, 5666, 10),
(5300, 1244, 20),
(5300, 8844, 50)
insert into [Order] values(5300,'20140910')
SOLUTION
;with cte as(
select
i.upc as Id,
i.category as Category,
i.price as Price,
p.quantity as Units,
price * quantity as TotalValue
from [Order] o
inner join PurchaseItem p
on p.receiptId = o.receiptId
inner join Item i
on i.upc = p.upc
)
select
Id,
case
when grouping(Id) = 1 then 'Total'
else Category
end as Category,
Price,
sum(Units) as Units,
sum(TotalValue) as TotalValue
from cte
group by
grouping sets(Category, (Category, Id, Price, Units, TotalValue))
union all
select
null,
'Total Daily Sales',
null,
sum(Units),
sum(Totalvalue)
from cte
DROP SAMPLE DATA
drop table item
drop table PurchaseItem
drop table [Order]

SQL: Fetching total shares of a customer through multiple tables

I have 3 tables customer(cid, name, phone) and transactions (cid (reference), fundid, date, shares) and fund (fundid, fund_name).
I am trying to write an sql query that would get me the total number of shares for each customer for each fund.
Here are the sample inserts:
INSERT INTO CUSTOMER(1, 'Alex', '123456678');
INSERT INTO CUSTOMER(2, 'Bill', '6323450236');
INSERT INTO CUSTOMER(3, 'Marie', '8568289912');
INSERT INTO FUND (1, 'Docotel');
INSERT INTO FUND (2, 'Armen');
INSERT INTO FUND (3, 'TD');
INSERT INTO TRANSACTIONS(1, 2, '2010-2-12', 234); (means shares bought)
INSERT INTO TRANSACTIONS(3, 1, '2010-4-2', 192);
INSERT INTO TRANSACTIONS(1, 2, '2010-4-22', -45); (the '-' means shares sold)
INSERT INTO TRANSACTIONS(1, 3, '2010-4-26', 220);
INSERT INTO TRANSACTIONS(3, 2, '2010-7-21', 170);
I want the sql result to look something like this:
Name| Fund_Name | Total_Shares |
Alex Docotel 189
Alex TD 220
Marie Docotel 192
Marie Armen 170
Thanks
Try this:
SELECT customer.name, fund.fund_name, T1.total_shares
FROM
(
SELECT cid, fundid, SUM(shares) AS total_shares
FROM transactions
GROUP BY cid, fundid
) T1
JOIN customer ON T1.cid = customer.cid
JOIN fund ON T1.fundid = fund.fundid
ORDER BY customer.name