ADD Specific values in SQL Column determined by other Column - sql

I have a Database that determines different values based on a label.
Where the label determines whether it's an exempted value or not.
For instance, 2 = non exempted and 3 = exempted. If I run a query my results look something like this
|Name |ExemptionStatus |Total Value|
|X |2 |100 |
|X |3 |200 |
My Query is
SELECT NAME, EXEMPTIONSTATUS
SUM(TOTAL_VALUE) AS 'TOTAL VALUE'
FROM ORDER_ACCOUNT JOIN ACCOUNT_INVOICE
WHERE ORDER_ACCOUNT.DATE BETWEEN 'M/D/YEAR' AND 'M/D/YEAR'
GROUP BY NAME, EXEMPTIONSTATUS
ORDER BY NAME ASC
How can I get my query to create a new column for the values, for example:
|Name |NON EXEMPT VALUE|EXEMPT VALUE|
|X |100 |200 |
I just don't know how how I would sort it whether it's in my Where clause or not.

Use a CASE statement within a SUM to only total NON EXEMPT, then EXEMPT, and select them as separate columns. Similar to the following (might need to add TOTAL_VALUE to the GROUP BY, or remove EXEMPTIONSTATUS)
SELECT
NAME
,SUM(CASE WHEN EXEMPTIONSTATUS = 2 THEN TOTAL_VALUE ELSE 0 END) AS 'NON EXEMPT VALUE'
,SUM(CASE WHEN EXEMPTIONSTATUS = 3 THEN TOTAL_VALUE ELSE 0 END) AS 'EXEMPT VALUE'
FROM ORDER_ACCOUNT JOIN ACCOUNT_INVOICE
WHERE ORDER_ACCOUNT.DATE BETWEEN 'M/D/YEAR' AND 'M/D/YEAR'
GROUP BY NAME, EXEMPTIONSTATUS
ORDER BY NAME ASC
EDIT: New code below adds new columns to your existing table. you will need to replace the #Test with your tables, but I believe this will get you what you're looking for.
SELECT
NAME,
EXEMPTIONSTATUS
,[TOTAL_VALUE]
,(SELECT SUM(CASE WHEN EXEMPTIONSTATUS = 2 THEN TOTAL_VALUE ELSE 0 END) FROM #Test t WHERE t.NAME = NAME) 'NON EXEMPT VALUE'
,(SELECT SUM(CASE WHEN EXEMPTIONSTATUS = 3 THEN TOTAL_VALUE ELSE 0 END) FROM #Test t WHERE t.NAME = NAME) 'EXEMPT VALUE'
FROM #Test
This gives me the following output
| NAME | EXEMPTIONSTATUS | TOTAL_VALUE | NON EXEMPT VALUE | EXEMPT VALUE |
| X | 2 | 100 | 100 | 200 |
| X | 3 | 200 | 100 | 200 |

Let's say your table structure is like this:
CREATE TABLE tab(ID int, Name nvarchar(20), ExemptionStatus int, TotalValue int);
INSERT INTO tab(ID, Name, ExemptionStatus, TotalValue) values (1, 'X', 2, 100);
INSERT INTO tab(ID, Name, ExemptionStatus, TotalValue) values (2, 'X', 3, 200);
So your data looks like this:
ID Name ExemptionStatus TotalValue
1 X 2 100
2 X 3 200
Then the query you'd use is:
SELECT NotExempted.Name,
NotExempted.NonExemptValue,
Exempted.ExemptValue
FROM (SELECT Name,
CASE
WHEN ExemptionStatus = 2 THEN TotalValue
END
AS 'NonExemptValue'
FROM #tab
) NotExempted
INNER JOIN (SELECT Name,
CASE
WHEN ExemptionStatus = 3 THEN TotalValue
END
AS 'ExemptValue'
FROM #tab
) Exempted ON NotExempted.Name = Exempted.Name
WHERE NotExempted.NonExemptValue IS NOT NULL
AND Exempted.ExemptValue IS NOT NULL
GROUP BY NotExempted.Name,
NotExempted.NonExemptValue,
Exempted.ExemptValue
You result will look like this :
Name NonExemptValue ExemptValue
X 100 200
You can see this here -> http://sqlfiddle.com/#!9/8902d3/2
Now, let's say you have data like this :
CREATE TABLE #tab(ID int, Name nvarchar(20), ExemptionStatus int, TotalValue int)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (1, 'X', 2, 100)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (2, 'X', 3, 200)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (3, 'X', 2, 1000)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (4, 'X', 3, 2000)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (5, 'X', 2, 1045)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (6, 'X', 3, 2045)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (7, 'X', 2, 1034)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (8, 'X', 3, 2023)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (9, 'X', 2, 1023)
INSERT INTO #tab(ID, Name, ExemptionStatus, TotalValue) values (10, 'X', 3, 2076)
which looks like this:
ID Name ExemptionStatus TotalValue
1 X 2 100
2 X 3 200
3 X 2 1000
4 X 3 2000
5 X 2 1045
6 X 3 2045
7 X 2 1034
8 X 3 2023
9 X 2 1023
10 X 3 2076
If you need to sum the total value up, then you can use the following query (which is a slight modification of the query above):
SELECT NotExempted.Name,
NotExempted.NonExemptValue,
Exempted.ExemptValue
FROM (SELECT Name,
CASE
WHEN ExemptionStatus = 2 THEN (SELECT SUM(TotalValue) FROM #tab WHERE ExemptionStatus = 2)
END
AS 'NonExemptValue'
FROM #tab
) NotExempted
INNER JOIN (SELECT Name,
CASE
WHEN ExemptionStatus = 3 THEN (SELECT SUM(TotalValue) FROM #tab WHERE ExemptionStatus = 3)
END
AS 'ExemptValue'
FROM #tab
) Exempted ON NotExempted.Name = Exempted.Name
WHERE NotExempted.NonExemptValue IS NOT NULL
AND Exempted.ExemptValue IS NOT NULL
GROUP BY NotExempted.Name,
NotExempted.NonExemptValue,
Exempted.ExemptValue
Your result will look like this :
Name NonExemptValue ExemptValue
X 4202 8344
You can see this here -> http://sqlfiddle.com/#!9/02c76/3
Hope this helps!!!

Related

Remove duplicate value in different categories in same table SQL but keep the first category value

Let's say I have a table with id and category like the table below
D_id | D_category
-----------------
1 | A
2 | A
3 | A
1 | B
2 | B
4 | B
5 | B
1 | C
2 | C
4 | C
5 | C
6 | C
Hence the rules are like this
values in category A should not be appear in category B and category C
values in category B should not be appear in category C
The end result should be like this
D_id | D_category
-----------------
1 | A
2 | A
3 | A
4 | B
5 | B
6 | C
I will provide a solution that works but its not an ideal solution can anyone help me to provide a better solution in case there are more categories meaning that if there are more category then it should follow the rules the values in previous categories should not appear in any other categories
DECLARE #A TABLE(
D_id INT NOT NULL,
D_category VARCHAR(MAX));
INSERT INTO #A(D_id,D_category)
VALUES (1, 'A'),
(2, 'A'),
(3, 'A'),
(1, 'B'),
(2, 'B'),
(4, 'B'),
(5, 'B'),
(1, 'C'),
(2, 'C'),
(4, 'C'),
(5, 'C'),
(6, 'C')
DELETE t
FROM #A t
WHERE t.D_category = 'B' AND EXISTS (SELECT 1 FROM #A t2 WHERE t2.D_category = 'A' and t.D_id = t2.D_id)
DELETE t
FROM #A t
WHERE t.D_category = 'C' AND EXISTS (SELECT 1 FROM #A t2 WHERE t2.D_category = 'B' and t.D_id = t2.D_id)
DELETE t
FROM #A t
WHERE t.D_category = 'C' AND EXISTS (SELECT 1 FROM #A t2 WHERE t2.D_category = 'A' and t.D_id = t2.D_id)
select * from #A
Just check that the specified record doesn't exist earlier in the sequence.
select *
from #A A1
where not exists (
select 1
from #A A2
where A2.D_id = A1.D_id
and A2.D_category < A1.D_category
)
or just make use of row_number()
select *
from
(
select *, r = row_number() over (partition by D_id order by D_category)
from #A
) a
where a.r = 1
Delete using the join syntax:
delete a
from my_table a
join my_table b on a.D_id = b.D_id
and a.D_category > b.D_category
See live demo.

I want to output a list of every Case_Number with Code 1 that has a higher UniqueID value than its Code 2 counterpart's UniqueID value

I have a table which looks something like this:
Case_Number | Code | UniqueID
a 1 1372
a 2 1352
a 3 1325
b 1 1642
b 2 1651
b 3 1623
c 1 1743
c 2 1739
c 3 1720
... ... ...
From this database I want to output a list of every Case_Number where the UniqueID value of Code 1 is higher than the UniqueID value of Code 2 (But ignoring the UniqueID value of Code 2, or any other Code x that might be in the table). Meaning that if the UniqueID value of Code 2 is higher than Code 1, which is the case with Case_Number b in the example above, it should not show up in the list.
So, querying the above table would result in this:
Case_Number | Code | UniqueID
a 1 1372
c 1 1743
Hmmm . . . You seem to want:
select t.*
from t
where t.code = 1 and
t.uniqueid > (select max(t2.uniqueid)
from t t2
where t2.case_number = t.case_number and t2.code = 2
);
The max() in the subquery is simply to handle the case where there is more than one matching value.
The query below gives you the expected result
CREATE TABLE CaseTab
(Case_Number VARCHAR(10),
Code INT,
UniqueID INT);
INSERT INTO CaseTab VALUES ('a', 1, 1372);
INSERT INTO CaseTab VALUES ('a', 2, 1352);
INSERT INTO CaseTab VALUES ('a', 3, 1325);
INSERT INTO CaseTab VALUES ('b', 1, 1642);
INSERT INTO CaseTab VALUES ('b', 2, 1651);
INSERT INTO CaseTab VALUES ('b', 3, 1623);
INSERT INTO CaseTab VALUES ('c', 1, 1743);
INSERT INTO CaseTab VALUES ('c', 2, 1739);
INSERT INTO CaseTab VALUES ('c', 3, 1720);
WITH v_code_gt_1 AS
(SELECT Case_Number, MAX(UniqueID) AS UniqueID
FROM CaseTab
WHERE Code > 1
GROUP BY Case_Number)
SELECT c1.Case_Number, c1.UniqueID
FROM CaseTab c1 JOIN
v_code_gt_1 c2
ON (c1.Case_Number = c2.Case_Number)
WHERE c1.UniqueID > c2.UniqueID
AND c1.Code = 1;
Basically the query gets the max UniqueID for all cases where code is greater than 1 and compares against the Unique ID for Code 1.
You haven't stated whether there can be cases with code = 1, but no other codes. If so, use LEFT JOIN as below.
WITH v_code_gt_1 AS
(SELECT Case_Number, MAX(UniqueID) AS UniqueID
FROM CaseTab
WHERE Code > 1
GROUP BY Case_Number)
SELECT c1.Case_Number, c1.UniqueID
FROM CaseTab c1 LEFT JOIN
v_code_gt_1 c2
ON (c1.Case_Number = c2.Case_Number)
WHERE c1.UniqueID > ISNULL(c2.UniqueID, 0)
AND c1.Code = 1;

SQL - Combination of Distinct & Count in a table

Need a simple query to summarize result from a table where 3 columns are present:
Order ID, Category & Brand.
The summary should contain order ID, distinct count of category and distinct count of brand belonging to the order ID.
Sample Data:
orderno product brand
1 A Z
1 A X
1 B Y
2 C X
2 B X
3 C X
3 B Y
Expected Result:
orderno product brand
1 2 3
2 2 1
3 2 2
Sample Data & Summary
Use DISTINCT of COUNT to get your expected result:
select orderno, count(distinct product) as product, count(distinct brand) as brand
from testtable
group by orderno
Sample execution with the given data
declare #test1 table (orderno int, product varchar(2), brand varchar(2))
insert into #test1 (orderno, product, brand) values
(1, 'A', 'Z'),
(1, 'A', 'X'),
(1, 'B', 'Y'),
(2, 'C', 'X'),
(2, 'B', 'X'),
(3, 'C', 'X'),
(3, 'B', 'Y');
select orderno, count(distinct product) as product, count(distinct brand) as brand
from #test1
group by orderno
Result:
orderno product brand
1 2 3
2 2 1
3 2 2
Try this,.
select orderno,count(DISTINCT product) product,COUNT(DISTINCT brand) brand
from data
GROUP by orderno
The output.,
orderno product brand
----------- ----------- -----------
1 2 3
2 2 1
3 2 2

double sorted selection from a single table

I have a table with an id as the primary key, and a description as another field.
I want to first select the records that have the id<=4, sorted by description, then I want all the other records (id>4), sorted by description. Can't get there!
select id, descr
from t
order by
case when id <= 4 then 0 else 1 end,
descr
select *, id<=4 as low from table order by low, description
You may want to use an id <= 4 expression in your ORDER BY clause:
SELECT * FROM your_table ORDER BY id <= 4 DESC, description;
Test case (using MySQL):
CREATE TABLE your_table (id int, description varchar(50));
INSERT INTO your_table VALUES (1, 'c');
INSERT INTO your_table VALUES (2, 'a');
INSERT INTO your_table VALUES (3, 'z');
INSERT INTO your_table VALUES (4, 'b');
INSERT INTO your_table VALUES (5, 'g');
INSERT INTO your_table VALUES (6, 'o');
INSERT INTO your_table VALUES (7, 'c');
INSERT INTO your_table VALUES (8, 'p');
Result:
+------+-------------+
| id | description |
+------+-------------+
| 2 | a |
| 4 | b |
| 1 | c |
| 3 | z |
| 7 | c |
| 5 | g |
| 6 | o |
| 8 | p |
+------+-------------+
8 rows in set (0.00 sec)
Related post:
Using MySql, can I sort a column but have 0 come last?
select id, description
from MyTable
order by case when id <= 4 then 0 else 1 end, description
You can use UNION
SELECT * FROM (SELECT * FROM table1 WHERE id <=4 ORDER by description)aaa
UNION
SELECT * FROM (SELECT * FROM table1 WHERE id >4 ORDER by description)bbb
OR
SELECT * FROM table1
ORDER BY
CASE WHEN id <=4 THEN 0
ELSE 1
END, description

Multiple Ranks in one table

I need the following, Can anyone please help me do it.
Rank Cust_Type Cust_Name Revenue
1 Top A 10000
2 Top B 9000
3 Top C 8000
1 Bottom X 5000
2 Bottom Y 6000
3 Bottom Z 7000
I need separate ranks for Top and Bottom Cust_Type and all this is in MySQL.
This is a bit tricky. You may want to use variables, such as in the following example:
SELECT (
CASE cust_type
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := cust_type END
) + 1 AS rank,
cust_type,
cust_name,
revenue
FROM sales,
(SELECT #curRow := 0, #curType := '') r
ORDER BY cust_type DESC, revenue DESC;
The (SELECT #curRow := 0, #curType := '') r part allows the variable initialization without requiring a separate SET command.
Test case:
CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
INSERT INTO sales VALUES ('Top', 'A', 10000);
INSERT INTO sales VALUES ('Top', 'B', 9000);
INSERT INTO sales VALUES ('Top', 'C', 8000);
INSERT INTO sales VALUES ('Bottom', 'X', 5000);
INSERT INTO sales VALUES ('Bottom', 'Y', 6000);
INSERT INTO sales VALUES ('Bottom', 'Z', 7000);
Result:
+------+-----------+-----------+---------+
| rank | cust_type | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Top | A | 10000 |
| 2 | Top | B | 9000 |
| 3 | Top | C | 8000 |
| 1 | Bottom | Z | 7000 |
| 2 | Bottom | Y | 6000 |
| 3 | Bottom | X | 5000 |
+------+-----------+-----------+---------+
6 rows in set (0.00 sec)
Another test case:
CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
INSERT INTO sales VALUES ('Type X', 'A', 7000);
INSERT INTO sales VALUES ('Type X', 'B', 8000);
INSERT INTO sales VALUES ('Type Y', 'C', 5000);
INSERT INTO sales VALUES ('Type Y', 'D', 6000);
INSERT INTO sales VALUES ('Type Y', 'E', 4000);
INSERT INTO sales VALUES ('Type Z', 'F', 4000);
INSERT INTO sales VALUES ('Type Z', 'G', 3000);
Result:
+------+-----------+-----------+---------+
| rank | cust_type | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Type Z | F | 4000 |
| 2 | Type Z | G | 3000 |
| 1 | Type Y | D | 6000 |
| 2 | Type Y | C | 5000 |
| 3 | Type Y | E | 4000 |
| 1 | Type X | B | 8000 |
| 2 | Type X | A | 7000 |
+------+-----------+-----------+---------+
7 rows in set (0.00 sec)
You can obviously order the cust_type in ascending order instead of descending. I used descending just to have Top before Bottom in the original test case.
I found a problem with the solution using CASE, #curRow, and #curType. It depends on the execution plan MySQL uses to process the query. For example, it shows up if you add a join to the query. Then there is no guarantee that the rank is going to be computed correctly.
Making a slight change to the answer:
CREATE TABLE sales (cust_type_id int, cust_name varchar(10), revenue int);
CREATE TABLE cust_type (cust_type_id int, type_name varchar(10));
INSERT INTO cust_type VALUES (1, 'Bottom');
INSERT INTO cust_type VALUES (2, 'Top');
INSERT INTO sales VALUES (2, 'A', 10000);
INSERT INTO sales VALUES (2, 'B', 9000);
INSERT INTO sales VALUES (2, 'C', 8000);
INSERT INTO sales VALUES (1, 'X', 5000);
INSERT INTO sales VALUES (1, 'Y', 6000);
INSERT INTO sales VALUES (1, 'Z', 7000);
If I query only the sales table I get the rank in the correct order, but if I join to the cust_type table the rank values are no longer correct
SELECT (
CASE s.cust_type_id
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := s.cust_type_id END
) AS rank,
t.type_name,
s.cust_name,
s.revenue
FROM sales s,
cust_type t,
(SELECT #curRow := 0, #curType := 0) r
WHERE s.cust_type_id = t.cust_type_id
ORDER BY t.type_name DESC, s.revenue DESC;
Result:
+------+-----------+-----------+---------+
| rank | type_name | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Top | A | 10000 |
| 2 | Top | B | 9000 |
| 3 | Top | C | 8000 |
| 3 | Bottom | Z | 7000 |
| 2 | Bottom | Y | 6000 |
| 1 | Bottom | X | 5000 |
+------+-----------+-----------+---------+
MySQL is running the initial query into a temp table and then the ORDER BY is executing against the temp table after a rank was already computed.
This is similar to Thomas's answer, but a little simpler:
SELECT (SELECT COUNT(Cust_Type) FROM sales
WHERE Cust_Type = S.Cust_Type AND Revenue >= S.Revenue) AS Rank,
Cust_Type,
Cust_Name,
Revenue
FROM sales AS S
ORDER BY Cust_Type DESC, Rank;
I'm not sure how the performance compares to the Daniel's solution, particularly on very large data sets, or if you have to use complex joins.
What is not exactly clear is how the items should be ranked (I assumed by Revenue) or whether you are only pulling a certain number of values (e.g. the top 3 and the bottom 3) so I assumed you wanted all values. Given those assumptions,
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue > T.Revenue ) + 1 As Rank
From Table As T
Where Cust_Type = 'Top'
Union All
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue < T.Revenue ) + 1 As Rank
From Table As T
Where Cust_Type = 'Bottom'
If you were trying to do this in a single non-union query you could do:
Select Cust_Name, Cust_Type
, Case Z.Cust_Type
When 'Top' Then Z.TopRank
Else Z.BottomRank
End As Rank
From (
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue > T.Revenue ) + 1 As TopRank
, (Select Count(*)
From Table As T1
Where T1.Revenue < T.Revenue ) + 1 As BottomRank
From Table As T
) As Z
This works for me by keeping ranking of sales revenue and ordering separate .
SELECT
(Select count(s1.revenue)+1 from sales s1 where s.cust_type_id = s1.cust_type_id and s1.revenue > s.revenue)
As rank,
t.type_name,
s.cust_name,
s.revenue
FROM sales s LEFT JOIN
cust_type t USING(cust_type_id)
Group by t.type_name,s.cust_name,s.revenue DESC order by s.revenue DESC;
For JOIN tables problem, I found a solution.
I create a temporary table, this way MySQL maintain the order of value that I want to rank.
DROP TEMPORARY TABLE IF EXISTS tmp_mytable;
CREATE TEMPORARY TABLE tmp_mytable ENGINE = MEMORY
SELECT mytable.id AS id,
mytable.login AS Login,
cliente.myrank_id AS id_myrank,
mytable.rankvalue AS rankvalue
FROM mytable
INNER JOIN myjoin ON (mytable.id_myjoin = myjoin.id)
ORDER BY 3, 4 DESC;
SELECT id, login, IFNULL(id_myrank, 0) AS id_myrank, rankvalue,
#rank := IF(#prev_born = IFNULL(id_myrank, 0), #rank + 1, 1) AS ranking,
#prev_Born := IFNULL(id_myrank, 0) AS fake_field
FROM tmp_mytable, (select #prev_born := 0, #rank := 0) r
-- HAVING ranking < 20;
*PS: I tried with View creation, but insn't work too.
Here are solutions for MySQL 5.7 and MySQL 8.0
MySQL 5.7 solution
SELECT cust_type,
cust_name,
revenue,
#new_rank := IF(#current_type = cust_type, #new_rank + 1, 1),
#current_type := cust_type
FROM rank_test,
(SELECT #new_rank := null, #current_type := null) r
ORDER BY cust_type DESC, revenue DESC;
MySQL 8.0 solution
SELECT cust_type,
cust_name,
revenue,
RANK() OVER (
PARTITION BY
cust_type
ORDER BY
revenue DESC
) new_rank
FROM rank_test;
Here is schema to try on dbfiddle
CREATE TABLE rank_test (
cust_type varchar(20),
cust_name varchar(20),
revenue numeric(19, 6)
);
INSERT INTO rank_test VALUES ('TOP', 'A', 10000);
INSERT INTO rank_test VALUES ('TOP', 'B', 9000);
INSERT INTO rank_test VALUES ('TOP', 'C', 8000);
INSERT INTO rank_test VALUES ('BOTTOM', 'A', 10000);
INSERT INTO rank_test VALUES ('BOTTOM', 'B', 9000);
INSERT INTO rank_test VALUES ('BOTTOM', 'C', 8000);