List Top Items *and* Whether Purchased (one SQL query) - sql

Here are the key fields I'm working with: (They come from a join on two other tables and there are also some extra fields like date I'll be filtering on, but that stuff I can handle.)
Customer Type (text), Item ID (number), Sales $s (number), Customer ID (number)
I want to answer two questions with one query, if possible:
1) For a given list of customer type(s), what were the top 25 items (by sum of sales)
2) Using the list of 25 items generated in step 1, did a given list of customer IDs purchase each of the specified items?
So my final result would look something like this:
(header) Item # | Customer Purchased?
(row 01) Item 1123 | Yes
(row 02) Item 2452 | Yes
(row 03) Item 3354 | No
...
(row 25) Item 2554 | No
The item numbers would be listed in decreasing sales volume (within the specified customer category/categories) and I'd be testing whether sum of sales > 0 to trip the Yes / No flag on customer(s) purchased.
Thanks!

Assuming you have your columns in a table #Orders, and the "list of customer Ids" in a table #CustomerIds:
create table #Orders (CustomerId int, CustomerType varchar(10), ItemId int, Sales decimal);
create table #MyCustomers (CustomerId int);
... you could try something like this:
declare #CustomerType varchar(10) = 'Ugly';
with MarkedOrders as (
select
o.ItemId,
o.Sales,
case when mc.CustomerId is not null then 1 else 0 end IsMyCustomer
from
Orders o
left join #MyCustomers mc
on mc.CustomerId = o.CustomerId
where
o.CustomerType = #CustomerType
)
select top 25
o.ItemId,
max(IsMyCustomer) IsPurchasedByMyCustomer
from MarkedOrders o
group by o.ItemId
order by sum(o.Sales) desc

Related

How to calculate the ratio of records based on values of two columns distinctly with the inner join of another table?

I'm developing a new analyst feature for an internal tool my company will (hopefully if I do well) use.
For simplicity sake, let's say we have
CREATE TABLE Products (
ProductID varchar,
Description varchar,
....
);
and
CREATE TABLE Orders (
ProductID varchar,
Bought date,
Returned date,
....
);
The tables would look something like this:
Products
ProductID
Description
SPO00
Sports product 1
SPO01
Sports product 2
SPO02
Sports product 3
ELE00
Electronics product 1
ELE02
Electronics product 2
Orders
ProductID
Bought
Returned
ELE00
2021-01-05
2021-01-07
SPO00
2021-01-01
NULL
SPO00
2021-01-05
2021-01-08
SPO00
2021-01-08
NULL
SPO01
2021-01-10
NULL
SPO01
2021-01-15
NULL
SPO02
2021-01-18
2021-01-20
I'd like to make a request to our DB and retrieve the description of specific products, and the percentage of bought products that are eventually returned.
I'd would also like to add specific parameters to the query, for example select only orders from beginning of the year as well as only the products from a specific department, for example.
So, it would looks something like this:
Description
ratio returned
Sports product 1
0.33
Sports product 2
0.00
Sports product 3
1.0
So, the products table might have product lines of electronics and sports and ProductID would be ELE00-ELE05 and SPO00-SPO03, respectively.
The above table is grabbing all products that have ProductID with SPO prefix and getting that specific products bought and returned ratio.
I've only been able to get the specific products, but the returned ratio is the same for each row. I think because its not doing the ratio calculation for each distinct product. I think its doing one overall ratio calculation and displaying that for each product.
Here is the query I've tried.
SELECT DISTINCT Product.Description, (CAST((SELECT DISTINCT COUNT(*) FROM Orders WHERE(ProductID like 'SPO%' AND Returned > '2021-01-01') AS FLOAT)) / (CAST((SELECT DISTINCT COUNT(*) FROM Orders WHERE (ProductID like 'SPO%' AND Bought > '2021-01-01') AS FLOAT)) AS returnedRatio
FROM Product INNER JOIN
Orders ON Orders.ProductID = Product.ProductID
I'm thinking I might need to do a nested query to get the ratios for each product and then get the description?
All help would be greatly appreciated because I've never done very complex queries so I'm still learning.
Does this work for you?
I used a case expression inside the count() function to count the number of returned products.
The * 1.0 turns the integer division into a decimal division without explicitly casting.
Sample data
CREATE TABLE Products (
ProductID nvarchar(5),
Description nvarchar(50)
);
insert into Products (ProductId, Description) values
('SPO00', 'Sports product 1'),
('SPO01', 'Sports product 2'),
('SPO02', 'Sports product 3'),
('ELE00', 'Electronics product 1'),
('ELE02', 'Electronics product 2');
CREATE TABLE Orders (
ProductID nvarchar(5),
Bought date,
Returned date
);
insert into Orders (ProductID, Bought, Returned) values
('ELE00', '2021-01-05', '2021-01-07'),
('SPO00', '2021-01-01', NULL),
('SPO00', '2021-01-05', '2021-01-08'),
('SPO00', '2021-01-08', NULL),
('SPO01', '2021-01-10', NULL),
('SPO01', '2021-01-15', NULL),
('SPO02', '2021-01-18', '2021-01-20');
Solution
select p.Description,
count(case when o.Returned is not null then 1 end) as ReturnCount,
count(1) TotalCount,
count(case when o.Returned is not null then 1 end) * 1.0 / count(1) as ReturnRatio
from Products p
join Orders o
on o.ProductID = p.ProductID
where p.ProductID like 'SPO%'
and o.Bought >= '2021-01-01'
group by p.Description;
Result
Description ReturnCount TotalCount ReturnRatio
---------------- ----------- ---------- --------------
Sports product 1 1 3 0.333333333333
Sports product 2 0 2 0
Sports product 3 1 1 1
Fiddle to see things in action.

Search data in sql on behalf of list of parameters ,

Hi All i am unable to create a query in sql i want to get all employee who's contain the product which i passed in parameter
Ex, i if passed product id 11,12 then its should be return only DeliveryBoyId1 and 2 and if passed the product id 11 then its should be return only DeliveryBoyId 1,2,3 and if i passed productid 11,12,16 then its should return 0 record because there is no delivery boy assigned the product id 11,12,16
I don't know what your table is called so I'm calling it dbo.Delivery. Try this out:
;with CTE as (
select distinct DeliveryBoyId --get all the boys who delivered product 11
from dbo.Delivery
where ProductId = 11
union all --combine the above results with the below
select distinct DeliveryBoyId --get all the boys who delivered product 12
from dbo.Delivery
where ProductId = 12
)
select DeliveryBoyId
from CTE
group by DeliveryBoyId
having count(1) = 2 --get all the boys who appear twice in the above table, once for each product
If you want to match additional products, you can add to the CTE section for other product IDs.
If you want a single solution for any number of products, you can use this instead (which may be a little less readable but more concise and easier to maintain):
select DeliveryBoyId
from (
select distinct DeliveryBoyId, ProductId
from dbo.Delivery
where ProductId in (11, 12)
) s
group by DeliveryBoyId
having count(1) = 2 --number matches number of products you need to match

how to find rows not exist or different in another table

I have two tables.
One is inventorytable with columns PO No,index,item name,amount,date,
PO Index Item amount date unit price
PO01 1 pen 20 1/2/2018 10
PO01 2 pad 15 1/3/2018 10
PO02 1 book 30 2/5/2018 10
Another table is paymentrecord with PO No,index,item name, amount, value.
PO Index Item amount
PO01 1 pen 10
PO01 2 pad 15
How to find inventory not paid or not fully paid?
PO Index Item amount unpaid value
PO01 1 pen 10 100
PO02 1 book 30 3000
I tried following sql
select *
from inventory
where not exists (select inventory.PONo,inventory.index
from inventory,payment
where inventory.PO=payment.PO )
but it returns nothing
Use an outer join to link payments to inventory. Filter on payment.amount being less than the calculated value of the inventory items, unit_price * amount:
select i.po_no
, i.Index
, i.Item
, i.amount
, ((i.unit_price * i.amount) - nvl(p.amount, 0) as unpaid_value
from inventory i
left join payment p
on p.po_no = i.po_no
and p.index = i.index
where (i.unit_price * i.amount) > nvl(p.amount, 0)
/
Note: the names of things are inconsistent across all your examples. So you may need to tweak this solution to make it run on your schema.
I guess you will need both, PO and Index, to uniquely identify record.
Try out below.
select * from inventory
where not exists
(select 1 from inventory,payment where inventory.PO=payment.PO and inventory.Index=payment.Index)
You can use below query
select i.ipo, i.iindex, i.iitem,
(i.iamount - coalesce(p.pamount,0)) as Tamount
from payment p right outer join invent i
on (i.ipo = p.ppo and i.iindex = p.pindex and i.iitem = p.pitem)
where i.iamount != coalesce(p.pamount,0)
This is result in
ipo iindex iitem Tamount
PO01 1 pen 10
PO02 1 book 30
Table structure followed is
create table invent
(
iPO varchar(4),
iIndex int,
iItem varchar(10),
iamount int,
idate date,
iunit_price int);
create table payment
(
pPO varchar(4),
pIndex int,
pItem varchar(10),
pamount int);
Hope following query helps. Use same column name instead of *.
select * from inventory
Except
select * from payment

How to group this query so it behaves correctly?

I have a table called Stock and another called Listed, within the Stock Table is a Status Code that indicates when something is at the front of the queue of items of stock - I want to be able to find the most recently added item and set this to be the "front of queue" status.
For example to get all the items listed and then order them by the one most recently listed
I would use this query:
SELECT SKU FROM Stock
INNER JOIN Listed
ON Listed.ListingID = Stock.ListingID
WHERE Stock.StatusCode = 2
ORDER BY Listed.ListDate
However I want to find all the items in my Stock table which need to be at the front of the queue - ie. have a StatusCode of 1 where those items have no SKU with a StatusCode of 1
e.g. I have a few items with various ProductCodes in the Stock table but can have StatusCodes of 1s and 2s - where the 1 indicates the first item in the queue, and 2 indicates the rest of the items with the same ProductCode.
How do I write my query to set all those items which need a StatusCode of 1 where anything with a given ProductCode has nothing with a status code of 1?
I want to set the most recently added Stock item listed with a Status Code of 1 as I have to reset them all to 2 as part of a maintainence process and need to restore the "front-of-queue" item.
Most Recently Added: ListDate
StatusCode: 1 (Front of Queue), 2 (Other Items in Stock of same Product Code)
Here is some sample Data
Stock Table
SKU ProductCode StatusCode
1 111111 1
2 111111 2
3 222222 1
4 222222 2
5 333333 2
6 333333 2
Listed Table
ListID SKU ListDate
01 1 01/01/2009
02 2 02/01/2009
03 3 03/01/2009
04 4 04/01/2009
05 5 05/01/2009
06 6 06/01/2009
In the Stock Table SKU 6 with the ProductCode 333333 has two items with the same StatusCode, I want to set the one with the most recent ListDate from the Listed Table
to StatusCode 1. This would apply to all other cases of this where I need the most
recently added item to have this StatusCode
UPDATE S1
SET S1.StatusCode = 1
FROM Stock S1
LEFT JOIN Stock S2
ON (S1.ProductCode = S2.ProductCode
AND S2.StatusCode = 1)
JOIN Listed L1
ON (S1.SKU = L1.SKU)
WHERE S2.StatusCode IS NULL
AND L1.ListDate =
( SELECT MIN(L2.ListDate)
FROM Listed L2
WHERE L1.SKU = L2.SKU )
Sometimes you say you want to "find" such items (that I guess would be a SELECT) and sometimes you say you want to "set" their status code -- I've taken the latter operation because it seems a better match for the problem you describe, whence the UPDATE.
Also, it's not clear what you want to do when multiple otherwise-satisfactory items have identical dates and thus it's impossible to uniquely define the latest one; maybe other consraints in your situation make that impossible? Here I'm setting all of their status codes, of course it would also be possible to set none of them or a somewhat arbitrarily chosen one (by ordering on some other criteria?).
This is a variation of pick-a-winner... it's pick-all-losers.
Here's the gist. There are several records with a common value, but one record is special - it is the winner. The rest of the records with that common value are losers.
For example, this query picks a winner (per name) from Customers by using the lowest id. It does this by defining what a winner is in the subquery.
SELECT *
FROM Customers c1
WHERE
(
SELECT Min(CustomerID)
FROM Customers c2
WHERE c2.Name = c1.Name
GROUP BY c2.Name
) = c1.CustomerID
Then picking the losers is a simple change:
SELECT *
FROM Customers c1
WHERE
(
SELECT Min(CustomerID)
FROM Customers c2
WHERE c2.Name = c1.Name
GROUP BY c2.Name
) != c1.CustomerID
This is a variation on a common theme. A similar application to this type of query is used to deal with duplicate rows. In this senario you might want to delete all but one rows of a set.
This query solves you problem:
DECLARE #Stock AS TABLE (SKU Bigint,ProductCode Bigint,StatusCode Bigint)
INSERT INTO #Stock VALUES (1,111111,1)
INSERT INTO #Stock VALUES (2,111111,2)
INSERT INTO #Stock VALUES (3,222222,1)
INSERT INTO #Stock VALUES (4,222222,2)
INSERT INTO #Stock VALUES (5,333333,2)
INSERT INTO #Stock VALUES (6,333333,2)
DECLARE #Listed AS TABLE (ListID Bigint,SKU Bigint,ListDate DateTime)
INSERT INTO #Listed VALUES (1,1,'01/01/2009')
INSERT INTO #Listed VALUES (2,2,'02/01/2009')
INSERT INTO #Listed VALUES ( 3,3,'03/01/2009')
INSERT INTO #Listed VALUES ( 4,4,'04/01/2009')
INSERT INTO #Listed VALUES ( 5,5,'05/01/2009')
INSERT INTO #Listed VALUES ( 6,6,'06/01/2009')
UPDATE #Stock
SET StatusCode = 1
FROM #Stock AS T1
INNER JOIN #Listed AS T2 ON T1.SKU = T2.SKU
WHERE
T1.SKU IN
(SELECT TOP 1 T3.SKU FROM #Stock AS T3
INNER JOIN #Listed AS T4 ON T3.SKU = T4.SKU
AND T3.ProductCode = T1.ProductCode ORDER BY ListDate)
AND ProductCode IN
(SELECT DISTINCT ProductCode
FROM #Stock AS S1
WHERE 1 NOT IN (SELECT DISTINCT StatusCode FROM #Stock AS S2 WHERE S2.ProductCode = S1.ProductCode))

"CSS-like" fallback cascading data in SQL Server 2005

I'm trying to implement a price fallback system in SQL server. I'd like to have a set of increasingly specific prices (eg: by region, store, warehouse, etc.) for a product, that may or may not be defined, and be able to select the most specific prices (ie: the one with most parameters defined) in a report.
For example, I might have the following data:
Region
--------
1
2
Store
--------
1
2
3
Product | Region | Store | Price
--------------------------------
Foo | NULL | NULL | 1.0
Foo | 1 | NULL | 2.0
Foo | 1 | 1 | 2.5
Foo | 1 | 2 | 2.3
So if I wanted to know the price for product Foo...
in Region 1, Store 1 = 2.5
in Region 1, Store 3 = 2.0 (Store 3 is not defined explicitly in the data, so the result comes from the NULL store for Region 1)
in Region 2, Store 4 = 1.0 (Region 2 is not defined explicitly in the data, so the result comes from the NULL region)
For the sake of simplicity, I can assume that the Store is always more specific than the region, and a store can only exist in one region.
A schema for this data would be something like this:
CREATE TABLE Prices(
ID int IDENTITY(1,1) NOT NULL,
Product int NOT NULL,
Region int NULL,
Store int NULL,
Price money NOT NULL,
CONSTRAINT PK_Prices PRIMARY KEY CLUSTERED (ID ASC),
CONSTRAINT IX_Prices UNIQUE NONCLUSTERED (Product ASC, Region ASC, Store ASC)
)
Aside from crunching this data in code, how can I query this table for a list of effective prices for every product, based on (Region, Store)?
try this:
EDIT for all products, each listed once, even where given region and store do not exist...
CREATE PROCEDURE GetPrice
#Region int = null
,#Store int = null
AS
SELECT
Product
,Region
,Store
,Price
FROM (SELECT
Product
,Region AS Region
,Store As Store
,Price
,Row_Number() OVER(PARTITION BY Product ORDER BY SortBy,Product,Region,Store,Price) AS RowNumber
FROM (SELECT 1 AS SortBy,* FROM Prices WHERE (Region = #Region OR #Region IS NULL) AND (Store = #Store OR #Store IS NULL)
UNION
SELECT 2 AS SortBy,* FROM Prices WHERE (Region = #Region OR #Region IS NULL)
UNION
SELECT 3 AS SortBy,* FROM Prices
) Prices
) dt
WHERE RowNumber=1
ORDER BY Product
GO
Wow, you guys are trying way too hard. Here's the easy way: use the COALESCE command, which takes the first non-null value.
SELECT COALESCE(st.Price, rg.Price, gn.Price) AS Price
FROM dbo.Prices gn /*General price*/
LEFT OUTER JOIN dbo.Prices rg /*Regional price*/
ON rg.Product = #Product AND rg.Region = #Region AND rg.Store IS NULL
LEFT OUTER JOIN dbo.Prices st /*Store price*/
ON rg.Product = #Product AND rg.Region = #Region AND rg.Store = #Store
WHERE gn.Product = #Product
AND gn.Region IS NULL AND gn.Store IS NULL
That way, you'll get the store price if that's not null, or the regional price if that's not null, or the general price if all else fails.
I think your data model might be a little screwy. Is the prices table being populated just for the report? I might review my tables, but if i had to use this setup, I would do something like this:
For product prices in a specified region (NOT STORE). Use this select in a sproc that accepts region as a parameter. If I did it right, it will return the region price, if there is one, and the product price, if there isn't.
Select Product, Price from dbo.prices where (Region = #region) or (region is NULL and product not in (select product from prices where region = #Region))
For product prices in a specified store. Use this select in a sproc that accepts store as a parameter. You will also need to pass in region or figure it out from the store. If I did it right, it will return the store price, if there is one, then he region price, then the product price.
Select product, price from dbo.prices where (store = #store) or (store is NULL AND region = #Region and product not in (select product from prices where store = #store) or (store is NULL and region is NULL and product not in (select product from prices where region = #Region))
To get prices from multiple regions or stores, just execute one of the above sprocs multiple times (in a loop or cursor).
I guess this must work (didn't try it, though):
select top 1 price
from prices
where product = #product
and isnull(region, #region) = #region
and isnull(store, #store) = #store
order by region desc, store desc
A product must have least 1 Prices record. The ORDER BY .. DESC sorts original NULL values last.