SQL Server 2016: Group by while updating the records - sql

I have a scenario wherein I need to determine the Margin Type for each Customer/Business combination against a product - meaning the Product with the highest Margin for a Customer /Business combination should be identified as 'High' Margin Type. All the other Product with the same combination should be identified as Low. So, I need a SQL to update the column MARGIN_TYPE accordingly.
I tried doing something like this, but do not understand how to group it by customer/business combination. Any help will be appreciated. Thank you!
UPDATE ORDER_TABLE
SET MARGIN_TYPE = 'High'
where MARGIN = (SELECT MARGIN FROM (SELECT MAX(MARGIN) FROM ORDER_TABLE) AS
MARGIN)

I think you just want an updatable CTE:
WITH CTE AS(
SELECT Customer,
Margin,
MarginType,
MAX(Margin) OVER (PARTITION BY Customer) AS MaxMargin
FROM dbo.YOurTable)
UPDATE CTE
SET MarginType = CASE Margin WHEN MaxMargin THEN 'High' ELSE 'Low' END;

With an updatebale CTE:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CUSTOMER, BUSINESS ORDER BY MARGIN DESC) rn
FROM ORDER_TABLE
)
UPDATE cte
SET MARGIN_TYPE = CASE WHEN rn = 1 THEN 'High' ELSE 'Low' END

Please use below query,
merge into ORDER_TABLE OT
using (select customer, business, product, margin, case when rnk = 1 then 'High' else
'Low' end as MARGIN_TYPE
select customer, business, product, margin, row_number() over (partition by customer,
business, product, margin order by margin desc) as rnk
from ORDER_TABLE
group by) QRY
on (OT.customer = QRY.customer and OT.business = QRY.business and OT.margin =
QRY.margin)
when matched then
update
set
OT.MARGIN_TYPE = QRY.MARGIN_TYPE;

UPDATE ORD
SET MARGIN_TYPE = case ORD.margin = m.margin then 'high' else 'low' end
from ORDER_TABLE ORD
left outer join
(SELECT customer,business,MAX(MARGIN) MARGIN
FROM ORDER_TABLE
group by customer,business) m
on(ORD.customer = m.customer and ORD.business = m.business)

Related

PostgreSQL - Find the most expensive and cheapest wine per region

Most Expensive And Cheapest Wine
I'm trying to solve this question from Stratascratch, following the hint was given on the platform.
Find the cheapest and the most expensive variety in each region. Output the region along with the corresponding most expensive and the cheapest variety.
Please help review my answer and would love to know the better way to solve this.
SELECT EX.region_1, EX.expensive_variety, CH.cheap_variety
FROM
(SELECT A.region_1, A.expensive_variety
FROM
(SELECT distinct region_1, variety AS expensive_variety, price,
ROW_NUMBER() OVER (PARTITION BY region_1 ORDER BY price desc) as
most_expensive
FROM winemag_p1
ORDER BY region_1 asc) A
WHERE A.most_expensive = 1) EX
INNER JOIN
(SELECT B.region_1, B.cheap_variety
FROM
(SELECT distinct region_1, variety as cheap_variety, price,
ROW_NUMBER() OVER (PARTITION BY region_1 ORDER BY price ASC) as cheapest
FROM winemag_p1
ORDER BY region_1 asc) B
WHERE B.cheapest = 1) CH
ON EX.region_1 = CH.region_1
Something like this, the MIN and MAX per region:
SELECT region
, MIN(price) AS cheapest
, MAX(price) AS most_expensive
FROM table_name
GROUP BY region;
You can find both in the same sub-query.
SELECT
B.region_1,
MAX(CASE WHEN cheapest = 1 then variety else '' end) cheapest_variety,
MAX(CASE WHEN cheapest = 1 then price else 0 end) cheapest_price,
MAX(CASE WHEN expensive = 1 then variety else '' end) expensive_variety,
MAX(CASE WHEN expensive = 1 then price else 0 end) expensive_price
FROM
(SELECT distinct region_1, variety as cheap_variety, price,
ROW_NUMBER() OVER (PARTITION BY region_1 ORDER BY price ASC) as cheapest,
ROW_NUMBER() OVER (PARTITION BY region_1 ORDER BY price DESC) as expensive
FROM winemag_p1
) B
WHERE cheapest = 1 OR expensive = 1
GROUP BY region_1
ORDER BY region_1;
``
You can use window functions or subqueries to get the highest and lowest prices per region. Then get all rows with these prices and aggregate per region.
For instance:
select
region_1,
min(price) as low_price,
string_agg(variety, ', ') filter (where price = min_price) as low_price_varieties,
max(price) as high_price,
string_agg(variety, ', ') filter (where price = max_price) as high_price_varieties
from
(
select
region_1, variety, price,
min(price) over (partition by region_1) as min_price,
max(price) over (partition by region_1) as max_price
from winemag_p1
) with_min_and_max
where price in (min_price, max_price)
group by region_1
order by region_1;
As to your own query: This is an okay query. Here are my remarks:
ORDER BY in a subquery only makes sense, when limiting the rows (with FETCH FIRST ROWS), because a query result is an unordered data set by definition.
Why DISTINCT? There are no duplicates to remove.
You don't handle ties. If there are two top wines with the same price in a region for instance, you pick one arbirarily.

I have multiple queries but I would like to have only 1 query that runs it all

So I have a database and I would like to select the weight, problem is that I have different weights and I want to know if I can make it into 1 query. This here is 2 of the different weight Types I have and what I have done.
SELECT TOP (13) height, Sum(total weight) as 'total weight'
FROM dbo.combine
WHERE
FacilityType = 'male'
GROUP BY height
ORDER BY Sum(totalweight) DESC
SELECT TOP (13) height, Sum(total weight) as 'total weight'
FROM dbo.combine
WHERE
FacilityType = 'female'
GROUP BY height
ORDER BY Sum(totalweight) DESC
You can use GROUP BY and window functions:
SELECT FacilityType, WasteStreamType, tonnes_hh
FROM (SELECT FacilityType, WasteStreamType, Sum(TonnesFromHHSources) as tonnes_hh,
ROW_NUMBER() OVER (PARTITION BY FacilityType ORDER BY Sum(TonnesFromHHSources) DESC) as seqnum
FROM dbo.combine c
GROUP BY FacilityType, WasteStreamType
) fw
WHERE seqnum <= 13

How to split 10% of data set to “control” and 90% to “test” for each group in MS SQL server

Context:
I have a table which has RetailerCode, CustomerID,Segment like below
RetailerCode CID Segment
A6005 13SVC15 High
A6005 19VDE1F Low
A6005 1B3BD1F Medium
A6005 1B3HB48 Medium
A6005 1B3HB49 Low
A9006 1B3HB40 High
A9006 1B3HB41 High
A9006 1B3HB43 Low
A9006 1B3HB46 Medium
Here, I would like to divide the data set in to control and test as below,
For each RetailerCode, I have set of customers with each customer tagged to a segment. I need to divide in such a way that
For each retailer
10% of their High customers to control and remaining 90% of their high customers to test.
10% of their Medium customers to control and remaining 90% of their Medium customers to test.
10% of their Low customers to control and remaining 90% of their Low customers to test.
I tried below code and I know its wrong.
select RetailerCode, CID,Segment
(case when row_number() over (order by newid()) <= (select 0.1* count(*) from Table)
then 'control'
else 'test'
end) as group
from Table
group by RetailerCode, CID,Segment
Order by RetailerCode
Can someone please help me with it? Thanks in advance
You seem pretty close:
select RetailerCode, CID,Segment
(case when row_number() over (partition by segment order by newid()) <=
0.1 * count(*) over (partition by segment)
then 'control'
else 'test'
end) as group
from Table
Order by RetailerCode;
I don't see why a group by is needed.
percent_rank is based on rank & count:
select RetailerCode, CID,Segment
(case when percent_rank() over (partition by segment order by newid()) <= 0.1
then 'control'
else 'test'
end) as group
from Table
Order by RetailerCode
And ntile is based on row_number and count:
select RetailerCode, CID,Segment
(case when ntile(10) over (partition by segment order by newid()) = 1
then 'control'
else 'test'
end) as group
from Table
Order by RetailerCode

GROUP BY after CASE WHEN

I am trying to create a table from a join and summing some fields based on id. This part is working great. I am also trying to add an additional column and using a case when statement I want to populate it.
Here is the script
CREATE TABLE TABLE1
AS
SELECT ID, IDC, SUM(AMOUNT) PRICE, SUM(COST) COST, SUM(AMOUNT-COST) PROFIT,
CASE PROFIT
WHEN PROFIT < 1000 THEN 'Low'
WHEN PROFIT < 5000 THEN 'Medium'
ELSE 'High'
END AS PROFITLEVEL
FROM
(SELECT DISTINCT ID, IDC, AMOUNT, COST
FROM ORDER_ITEMS
LEFT JOIN ORDERS
ON ID = IDC)
GROUP BY ID, IDC;
This however returns a ORA-00905 : Missing keyword error.
Any help would be appreciated
You are using the CASE in a wrong way; besides, you try to use the alias PROFIT at the same level you define it.
You need to edit you CASE and use the expression that gives the PROFIT instead of the alias PROFIT:
CREATE TABLE TABLE1 AS
SELECT ID,
IDC,
SUM(AMOUNT) PRICE,
SUM(COST) COST,
SUM(AMOUNT - COST) PROFIT,
CASE
WHEN SUM(AMOUNT - COST) < 1000 THEN 'Low'
WHEN SUM(AMOUNT - COST) < 5000 THEN 'Medium'
ELSE 'High'
END AS PROFITLEVEL
FROM (SELECT DISTINCT ID,
IDC,
AMOUNT,
COST
FROM ORDER_ITEMS LEFT JOIN ORDERS ON ID = IDC)
GROUP BY ID, IDC;
The way you tried to use the CASE is useful if you need to check single values; for example:
select level,
case level
when 1 then 'one'
when 2 then 'two'
else 'other'
end
from dual
connect by level <=3

SQL query the largest and smallest amount

I have a table Sales with the following fields: code, amount, index, name.
I need to get the smallest and the largest amount for a given name, and the code for which the amount is the smallest and largest.
Can somebody help me in constructing a query?
If CTE and row_number() is available to you.
with S as
(
select Amount,
Code,
row_number() over(order by Amount asc) as rn1,
row_number() over(order by Amount desc) as rn2
from Sales
where Name = 'SomeName'
)
select SMin.Amount as MinAmount,
SMin.Code as MinCode,
SMax.Amount as MaxAmount,
SMax.Code as MaxCode
from S as SMin
cross join S as SMax
where SMin.rn1 = 1 and
SMax.rn2 = 1
To find min and max amount per name:
select
name
min(amount), max(amount)
from
sales
group by name
and to get both(min and max) and code in single query:
select *
from sales s
where
(amount = (select
max(s1.amount)
from sales s1
where s1.name = s.name)
or
amount = (select
min(s2.amount)
from sales s2
where s2.name = s.name)
)
Assuming this is Postgres, try the following:
select name,
amount,
code,
case when min_rank=max_rank then 'Minimum and Maximum'
when min_rank=1 then 'Minimum'
else 'Maximum'
end as min_or_max
from
(select s.*,
rank() over (partition by name order by amount) min_rank,
rank() over (partition by name order by amount desc) max_rank
from sales s) v
where 1 in (min_rank, max_rank)