Updating a column based on values in related rows - sql

I have a table like the following in SQL Server 2008
AMID TierLevel
-------- -------------
999 GOLD
1000 SILVER
1000 GOLD
1000 PLATINUM
1000 BRONZE
1001 GOLD
1001 SILVER
1002 SILVER
1003 GOLD
Now I want to Update this table like the following
AMID TierLevel
-------- -------------
999 GOLD
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1001 GOLD
1001 GOLD
1002 SILVER
1003 GOLD
Here the conditions are
I want unique Tier value for the same AMID, and the Tier values should be selected in priority base like
PLATINUM
GOLD
SILVER
BRONZE
It means if it is having the highest one present in its Tier values then select that. Like I shown in the second table, Platinum for 1000 and Gold for 1001..
please help me with this
Thanks,
Harry

DECLARE #amid TABLE (Amid INT, TierLevel VARCHAR(20));
INSERT #amid VALUES
(999 ,'GOLD'),
(1000,'SILVER'), (1000,'GOLD'), (1000,'PLATINUM'), (1000,'BRONZE'),
(1001,'GOLD'), (1001,'SILVER'),
(1002,'SILVER'), (1003,'GOLD');
;WITH [priority](r, n) AS
(
SELECT 1, 'PLATINUM'
UNION ALL SELECT 2, 'GOLD'
UNION ALL SELECT 3, 'SILVER'
UNION ALL SELECT 4, 'BRONZE'
),
per_amid(amid, h) AS
(
SELECT a.amid, MIN(p.r)
FROM #amid AS a
INNER JOIN [priority] AS p
ON a.TierLevel = p.n
GROUP BY a.amid
)
UPDATE a
SET TierLevel = p.n
FROM #amid AS a
INNER JOIN per_amid AS pa
ON a.Amid = pa.amid
INNER JOIN [priority] AS p
ON pa.h = p.r
-- added where clause to address question brought up on other answer
WHERE a.TierLevel <> p.n;
SELECT Amid, TierLevel FROM #amid;
Results:
Amid TierLevel
---- ---------
999 GOLD
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1001 GOLD
1001 GOLD
1002 SILVER
1003 GOLD

I would do this using a temporary table to store the rank of each Tier:
DECLARE #Rank TABLE (ID INT NOT NULL PRIMARY KEY, Name VARCHAR(10) NOT NULL)
INSERT #Rank VALUES (1, 'PLATINUM'), (2, 'GOLD'), (3, 'SILVER'), (4, 'BRONZE')
;WITH T AS
( SELECT AMID, TierLevel, MIN(ID) OVER(PARTITION BY AMID) [MinID]
FROM #T
INNER JOIN #Rank
ON Name = TierLevel
)
UPDATE T
SET TierLevel = Name
FROM T
INNER JOIN #Rank
ON ID = MinID
WHERE TierLevel <> Name
This was done using the following sample data:
CREATE TABLE #T (AMID INT, TierLevel VARCHAR(10))
INSERT #T VALUES
(999, 'GOLD'),
(1000, 'SILVER'),
(1000, 'GOLD'),
(1000, 'PLATINUM'),
(1000, 'BRONZE'),
(1001, 'GOLD'),
(1001, 'SILVER'),
(1002, 'SILVER'),
(1003, 'GOLD')

Related

How to insert data in a table from two not-connected tables SQL ORACLE?

I am working with a Data Base with 3 tables:
CREATE TABLE SALE_DETAIL
( S_SALE NUMBER,
S_NUMBER NUMBER,
S_ARTICLE VARCHAR2(30),
S_COUNT NUMBER
);
CREATE TABLE SALE
( SALE_ID NUMBER,
SALE_COMPANY VARCHAR2(30)
);
CREATE TABLE SELL
( SELL_ARTICLE VARCHAR2(30),
SELL_COUNT NUMBER
);
SALE and SELL are tables not connected to each other and they had some data and SALE_DETAIL is empty:
INSERT INTO SALE (SALE_ID, SALE_COMPANY) VALUES (1, 'Company1');
INSERT INTO SALE (SALE_ID, SALE_COMPANY) VALUES (2, 'Company1');
INSERT INTO SALE (SALE_ID, SALE_COMPANY) VALUES (3, 'Company2');
INSERT INTO SALE (SALE_ID, SALE_COMPANY) VALUES (4, 'Company3');
INSERT INTO SALE (SALE_ID, SALE_COMPANY) VALUES (5, 'Company2');
INSERT INTO SELL (SELL_ARTICLE,SELL_COUNT) VALUES ('ART1', 100);
INSERT INTO SELL (SELL_ARTICLE,SELL_COUNT) VALUES ('ART2', 50);
INSERT INTO SELL (SELL_ARTICLE,SELL_COUNT) VALUES ('ART1', 100);
INSERT INTO SELL (SELL_ARTICLE,SELL_COUNT) VALUES ('ART3', 200);
INSERT INTO SELL (SELL_ARTICLE,SELL_COUNT) VALUES ('ART3', 100);
I would like to fill SALE_DETAIL TABLE with data from SALE and SELL table which are not connected to each other.
S_SALE = SALE_ID --taken from SALE table
S_NUMBER = ROWID (sequential number starting from 1)
S_ARTICLE = SELL_ARTICLE -- taken from SELL table
S_COUNT = SELL_COUNT -- taken from SELL table
If SALE and SELL table were connected ...I tried this code
INSERT INTO SALE_DETAIL(S_SALE,S_NUMBER,S_ARTICLE,S_COUNT)
SELECT s.SALE_ID, ROWID, d.SELL_ARTICLE, d.SELL_COUNT FROM SALE s JOIN SELL d
ON (here I could code the connection beetween tha tables)
but How can I do it if they are not connected?
Join them virtually. How? For example, using row_number function, you can create a common column which is then used in join. Something like this:
SQL> insert into sale_detail (s_sale, s_number, s_article, s_count)
2 with
3 t_sale as
4 (select sale_id,
5 row_number() over (order by sale_id) rn
6 from sale),
7 t_sell as
8 (select sell_article,
9 sell_count,
10 row_number() over (order by rowid) rn
11 from sell)
12 select a.sale_id,
13 a.rn,
14 e.sell_article,
15 e.sell_count
16 from t_sale a join t_sell e on e.rn = a.rn;
5 rows created.
Result:
SQL> select * from sale_detail;
S_SALE S_NUMBER S_ARTICLE S_COUNT
---------- ---------- ------------------------------ ----------
1 1 ART1 100
2 2 ART2 50
3 3 ART1 100
4 4 ART3 200
5 5 ART3 100
SQL>
If number of rows in sale and sell tables isn't equal, then use outer join (you'll first have to find which table has more rows so that you'd know what to do).

SQL - Choosing a single record for an Id based on an attribute

I have a shirt table. There are multiple records for each Id (multiple brands). The Material will be same for the Id. The materials have a ranking association with Brand.
Material Ranking:
Cotton: Jockey > Nike > Adidas
Any other Material: Adidas > Nike > Jockey
For my result, I want to select only 1 record per Id based on the above ranking.
If the Id has cotton, I want the Jockey record to be chosen if Jockey is present, or if Jockey isn't there Nike must be chosen, if Nike also isn't there Adidas must be chosen.
For any other material, the second ranking should be followed.
Shirt table:
Id
Brand
Material
Color
Cost
1
Nike
Cotton
Black
500
1
Jockey
Cotton
Blue
100
1
Adidas
Cotton
Red
1000
2
Jockey
Synthetic
Orange
20
2
Nike
Synthetic
Green
2
3
Nike
Cotton
Black
500
4
Nike
Wool
Black
600
4
Jockey
Wool
Blue
20000
4
Adidas
Wool
Red
1000
Result:
Id
Brand
Material
Color
Cost
1
Jockey
Cotton
Blue
100
2
Nike
Synthetic
Green
2
3
Nike
Cotton
Black
500
4
Adidas
Wool
Red
1000
Explanation:
Result table Records have been chosen as per the ranking.
Since Id 1 has cotton, the Jockey record has been chosen.
Since Id 2 does not have cotton, the Nike record has been chosen (as per the ranking)
Since Id 3 has cotton, but does not have a Jockey record, the next rank has been chosen.
Since Id 4 does not have cotton, the Adidas record has been chosen.
One way is using top(1) with ties
select top(1) with ties *
from tbl
order by row_number() over(partition by id
order by
case Material when 'Cotton' then
case Brand when 'Jockey' then 1
when 'Nike' then 2
when 'Adidas' then 3 end
else
case Brand when 'Adidas' then 1
when 'Nike' then 2
when 'Jockey' then 3 end
end)
Thinking in real life you would have more than 3 brands, a rankings table would be feasible to use.
DECLARE #Rankings TABLE
(
Material VARCHAR(10),
Brand VARCHAR(10),
Ranking INT
);
INSERT #Rankings
(
Material,
Brand,
Ranking
)
VALUES
('Cotton', 'Jockey', 1),
('Cotton', 'Nike', 2),
('Cotton', 'Adidas', 3),
(NULL, 'Adidas', 1),
(NULL, 'Nike', 2),
(NULL, 'Jockey', 3);
DECLARE #Items TABLE
(
Id INTEGER,
Brand VARCHAR(6),
Material VARCHAR(9),
Color VARCHAR(6),
Cost INTEGER
);
INSERT INTO #Items
(
Id,
Brand,
Material,
Color,
Cost
)
VALUES
(1, 'Nike', 'Cotton', 'Black', 500),
(1, 'Jockey', 'Cotton', 'Blue', 100),
(1, 'Adidas', 'Cotton', 'Red', 1000),
(2, 'Jockey', 'Synthetic', 'Orange', 20),
(2, 'Nike', 'Synthetic', 'Green', 2),
(3, 'Nike', 'Cotton', 'Black', 500),
(4, 'Nike', 'Wool', 'Black', 600),
(4, 'Jockey', 'Wool', 'Blue', 20000),
(4, 'Adidas', 'Wool', 'Red', 1000);
WITH ordered
AS (SELECT i.*,
ROW_NUMBER() OVER (PARTITION BY i.Id ORDER BY r.Ranking) orderId
FROM #Items i
INNER JOIN #Rankings r
ON i.Brand = r.Brand
AND
(
(
r.Material = i.Material
AND i.Material = 'Cotton'
)
OR
(
r.Material IS NULL
AND i.Material <> 'Cotton'
)
))
SELECT ordered.Id,
ordered.Brand,
ordered.Material,
ordered.Color,
ordered.Cost
FROM ordered
WHERE ordered.orderId = 1;
You can get the ranking over (Brand, Material) with a CASE expression, then you can compare this field with the best ranking, obtainable using the MIN window function. Hence retrieve the rows where ranking = best_ranking:
SELECT Id,
Brand,
Material,
Color,
Cost
FROM (SELECT tab.*,
CASE WHEN Brand = 'Jockey' AND Material = 'Cotton' THEN 1
WHEN Brand = 'Nike' THEN 2
WHEN Brand = 'Adidas' AND Material <> 'Cotton' THEN 1
ELSE 3
END AS ranking,
MIN(CASE WHEN Brand = 'Jockey' AND Material = 'Cotton' THEN 1
WHEN Brand = 'Nike' THEN 2
WHEN Brand = 'Adidas' AND Material <> 'Cotton' THEN 1
ELSE 3
END) OVER(PARTITION BY Id) AS best_ranking
FROM tab ) ranked_brands
WHERE ranking = best_ranking
Try it here.

Is there any way to make FULL OUTER JOIN table match with the correct record?

I'm trying to join two table using FULL OUTER JOIN that two table have different row of data and the column between this two table are same.
Table 2 FULL OUTER JOIN Table 1
Table 1
id name Payment Amount
=== ======== =====================
1 Jack 10000
2 May 20000
3 Amy 30000
Table 2
id name Payment Amount AccountID
=== ======== ==================== ============
1 Jack 10000 000001
2 Amy 30000 000002
Output that show after execute
id T1name
T2name Payment Amount AccountID
=== ======== ======== ==================== ============
1 Jack Jack 10000 000001
2 May Amy 20000 000002
3 Amy 30000
Output that I expect
id T1name
T2name Payment Amount AccountID
=== ======== ======== ==================== ============
1 Jack Jack 10000 000001
2 May 20000
3 Amy Amy 30000 000002
The table is order by Payment amount.
CREATE TABLE #Table1
([id] varchar(2), [name] varchar(4), [Payment Amount] int)
INSERT INTO #Table1
([id], [name], [Payment Amount])
VALUES
('S1', 'Jack', 10000),
('S2', 'May', 20000),
('S3', 'Amy', 30000)
CREATE TABLE #Table2
([id] varchar(2), [name] varchar(4), [Payment Amount] int)
;
INSERT INTO #Table2
([id], [name], [Payment Amount])
VALUES
('X1', 'Jack', 10000),
('X2', 'Amy', 30000)
select A.id,A.name T1name ,isnull(B.name,'') T2name,A.[Payment Amount] from #Table1 A left join #Table2 B on A.name=B.name
and A.[Payment Amount]=B.[Payment Amount]
output
id T1name T2name Payment Amount
S1 Jack Jack 10000
S2 May 20000
S3 Amy Amy 30000
You should always JOIN with primary key(specifically keys) or with unique key always. otherwise you will get duplicate values. Name column may not be unique and you will get Cartesian product .In your case in order to get your desired results you should join on t1.name=t2.name

Line Number and attaching single employee

SQL Server 2000 so no ROW_NUMBER available ....
I need to attach employees to the free lines.
I have a dataset 1 that tells me the free lines per country and region combo.
Table A – available line numbers to use:
Country Region Line Number Employee
---------------------------------------------------
A 1 1 Null
A 1 2 Null
A 2 1 Null
Table B – what employees are available to fill missing line numbers:
Country Region Employee
----------------------------------------
A 1 Dave Smith
A 1 Johnny Cash
A 1 Peter Seller
A 2 David Donald
So required output is
Table C - attaching a single employee to each combo of country, region, line number:
Country Region Line Number Employee
-------------------------------------------------------------
A 1 1 Dave Smith
A 1 2 Johnny Cash
A 2 1 David Donald
I tried a lot of joins, including self joins, and cross joins in SQL Server 2000, but can't get the desired output.
This is my last attempt:
Select
A.Country, A.Region, A.Line Number,
B.Employee
From
Table_A A
Inner Join
Table_B B On A.Country = B.Country and A.Region = B.Region
You need an additional join key after country and region for the assignment. For this, you can use row_number():
select a.*, b.employee
from (select a.*,
row_number() over (partition by country, region order by linenumber) as seqnum
from table_a a
) a join
(select b.*
row_number() over (partition by country, region order by (select null) ) as seqnum
from b
) b
on b.country = a.country and b.region = a.region and b.seqnum = a.seqnum
Just pulling together all of the suggestions, answers, and comments.
--Setting up the tables as given:
CREATE TABLE #e (
Country char(1),
Region int,
LineNumber int,
Employee varchar(50));
INSERT #e
VALUES ('A', 1, 1,NULL)
,('A',1,2,NULL)
,('A',2,1,NULL);
CREATE TABLE #r (
Country char(1),
Region int,
Employee varchar(50));
INSERT #r
VALUES
('A', 1, 'Dave Smith')
,('A', 1, 'Johnny Cash')
,('A', 1, 'Peter Sellers')
,('A', 2, 'David Donald');
--Creating a temporary table with
--a line number to join on.
CREATE TABLE #T(
LineNumber int,
Country char(1),
Region int,
Employee varchar(50));
--Populate the temporary table
--with the line number data.
INSERT INTO #T
(
LineNumber,
Country,
Region,
Employee
)
SELECT
(SELECT
COUNT(*) AS Line
FROM #r AS R2
WHERE R2.Employee <= #r.Employee
AND R2.Region = #r.Region
) AS LineNumber,
Country,
Region,
Employee
FROM #r;
--Set up the final output.
SELECT
A.Country,
A.Region,
A.LineNumber,
B.Employee
FROM
#e A
INNER JOIN
#T B
ON A.Country = B.Country
AND A.Region = B.Region
AND A.LineNumber = B.LineNumber
ORDER BY
A.Country,
A.Region,
A.LineNumber;
--Clean up.
DROP TABLE #r;
DROP TABLE #T;
DROP TABLE #e;
Results:
+---------+--------+------------+--------------+
| Country | Region | LineNumber | Employee |
+---------+--------+------------+--------------+
| A | 1 | 1 | Dave Smith |
| A | 1 | 2 | Johnny Cash |
| A | 2 | 1 | David Donald |
+---------+--------+------------+--------------+

Pro rata basis bonus distribution using setup table

CREATE TABLE #empInfo(emp_id INT, dept_id INT, salary INT)
CREATE TABLE #empBonus(dep_id INT, emp_id INT, bonus INT)
I have above two tables for Employee and Bonus, where I will allocate bonus for the employees in bonus table every year but in example we are going to do it for a year only so the year column is not given.
INSERT INTO #empInfo VALUES
(111, 100, 5000),
(112, 100, 4000),
(113, 100, 4000),
(114, 100, 3500),
(115, 100, 4500),
(116, 100, 3000),
(114, 200, 3500),
(115, 200, 4500),
(116, 200, 3000),
(114, 300, 3500),
(115, 300, 3500),
(116, 300, 3500)
INSERT INTO #empBonus VALUES
(100, 111, 1000),
(100, NULL, 4000),
(100, 111, 500),
(100, NULL, 4000),
(100, 113, 700),
(200, 114, 600),
(200, NULL, 1600),
(300, 116, 900)
Above, If employee id defined in empBonus table then the bonus should allocated for that employee and if null that means bonus for all employees whose are not listed in the empBonus and will get bonus according to their salary.
we can define bonus for multiple employees and it can be multiple for same employee, in this case we have to sum total bonus and perform operation accordingly. Same case is for NULL.
For example, Base on formula given below, I have done below calculation in the EXCEL for easy understanding and in SQL I am trying with OUTER APPLY but not getting what I want from single query ?
--Formula = bonus*salary/totSalary(of respective group or employee)
DeptID EmpID TotBonus Salary TotSalary Bonus
100 111 1500 5000 5000 1500.00000000000
100 112 8000 4000 15000 2133.33333333333
100 113 700 4000 4000 700.00000000000
100 114 8000 3500 15000 1866.66666666666
100 115 8000 4500 15000 2400.00000000000
100 116 8000 3000 15000 1600.00000000000
200 114 600 3500 3500 600.00000000000
200 115 1600 4500 7500 960.00000000000
200 116 1600 3000 7500 640.00000000000
300 114 0 3500 7000 0.00000000000
300 115 0 3500 7000 0.00000000000
300 116 900 3500 3500 900.00000000000
Any help will be appreciated, thanks in advance :)
Here is one way using FULL OUTER JOIN and SUM() OVER() Window aggregate
;WITH cte
AS (SELECT ei.emp_id,ei.dept_id, eb.dep_id,
bonus = COALESCE(bonus, Max(CASE WHEN eb.emp_id IS NULL THEN bonus END)
OVER( partition BY COALESCE(ei.dept_id, eb.dep_id) )),
salary = Cast(salary AS NUMERIC(22, 6)),
TotSalary= Iif(eb.emp_id IS NULL, Sum(CASE WHEN eb.emp_id IS NULL THEN salary END)
OVER(partition by ei.dept_id), salary)
FROM #empInfo ei
FULL OUTER JOIN (SELECT bonus= Sum(bonus),
dep_id,
emp_id
FROM #empBonus
GROUP BY dep_id,
emp_id) eb
ON ei.dept_id = eb.dep_id
AND eb.emp_id = ei.emp_id)
SELECT emp_id,
bonus,
salary,
TotSalary,
( bonus * salary ) / NULLIF(TotSalary, 0)
FROM cte
WHERE emp_id IS NOT NULL
Result:
+--------+-------+-------------+-----------+--------------------+
| emp_id | bonus | salary | TotSalary | Bonus Distribution |
+--------+-------+-------------+-----------+--------------------+
| 111 | 1500 | 5000.000000 | 5000 | 1500.00000000000 |
| 112 | 8000 | 4000.000000 | 19000 | 1684.21052631578 |
| 113 | 8000 | 4000.000000 | 19000 | 1684.21052631578 |
| 114 | 8000 | 3500.000000 | 19000 | 1473.68421052631 |
| 115 | 8000 | 4500.000000 | 19000 | 1894.73684210526 |
| 116 | 8000 | 3000.000000 | 19000 | 1263.15789473684 |
+--------+-------+-------------+-----------+--------------------+
Well, that was a good challenge for me.
First, create a cte is to calculate the TotSalary column:
;With cteTotalSalary as
(
-- select total salary for employees that are in the bonus table
SELECT e.emp_id, dept_id, Salary, Salary As TotSalary
FROM #empInfo e
INNER JOIN #empBonus b ON e.dept_id = b.dep_id AND e.emp_id = b.emp_id
UNION
-- select total salary for employees that are in NOT the bonus table
SELECT e.emp_id, dept_id, Salary, SUM(Salary) OVER(PARTITION BY dept_id) As TotSalary
FROM #empInfo e
WHERE EXISTS (
SELECT 1
FROM #empBonus b
WHERE e.dept_id = b.dep_id
AND b.emp_id IS NULL
)
AND NOT EXISTS
(
SELECT 1
FROM #empBonus b
WHERE e.dept_id = b.dep_id
AND e.emp_id = b.emp_id
)
)
Then, query this cte twice with a union to get both types of bonuses (employee bonus and department bonus)
-- Get the bonus of the employess that exists in the empBonus table
SELECT c.emp_id, dept_id, SUM(Bonus) OVER(PARTITION BY c.emp_id) as Bonus, Salary, TotSalary, CAST(SUM(CAST(Bonus as decimal)) OVER(PARTITION BY c.emp_id) as decimal) as [Bonus Distribution]
FROM cteTotalSalary c
INNER JOIN #empBonus b ON c.dept_id = b.dep_id AND c.emp_id = b.emp_id
UNION
-- Get the bonus of the employees that does not exists in the empBonus table
SELECT c.emp_id, dept_id, SUM(Bonus) OVER(PARTITION BY c.emp_id), Salary, TotSalary, SUM(CAST(Bonus as decimal) * Salary / TotSalary) OVER(PARTITION BY c.emp_id)
FROM cteTotalSalary c
INNER JOIN #empBonus b ON c.dept_id = b.dep_id AND b.emp_id IS NULL
AND NOT EXISTS (
SELECT 1
FROM #empBonus b
WHERE c.dept_id = b.dep_id
AND c.emp_id = b.emp_id
)
Results:
emp_id dept_id Bonus Salary TotSalary Bonus Distribution
111 100 1500 5000 5000 1500.000000000
112 100 8000 4000 19000 1684.210526314
113 100 8000 4000 19000 1684.210526314
114 100 8000 3500 19000 1473.684210526
115 100 8000 4500 19000 1894.736842104
116 100 8000 3000 19000 1263.157894736
You can see it in action here