I want to get the row with maximum Transaction number Grouped on the basis of Code.
CREATE TABLE SaleOrder
(
TransactionNo Int,
SaleOrderDate DATE,
Code VARCHAR(25),
Quantity INT,
TotalAmount Numeric(18,2),
Remarks VARCHAR(25)
)
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-001-OCT-18', 6, 2500, 'Hello');
INSERT INTO SaleOrder VALUES (1, '2018-10-01', 'SO-001-OCT-18', 8, 2600, 'Hello');
INSERT INTO SaleOrder VALUES (2, '2018-10-01', 'SO-001-OCT-18', 12, 3400, 'Hello');
INSERT INTO SaleOrder VALUES (3, '2018-10-01', 'SO-001-OCT-18', 9, 2900, 'Hello');
INSERT INTO SaleOrder VALUES (4, '2018-10-01', 'SO-001-OCT-18', 2, 900, 'Hello');
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-002-OCT-18', 6, 2500, 'Hello');
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-003-OCT-18', 6, 2500, 'Hello');
INSERT INTO SaleOrder VALUES (0, '2018-10-01', 'SO-004-OCT-18', 6, 2500, 'Hello');
SELECT * FROM SaleOrder O
WHERE TransactionNo = (SELECT MAX(ISNULL(TransactionNo, 1)) FROM SaleOrder GROUP BY Code)
Here when TransactionNo is NULL it's not returning any record against it while it should return that too.
There is absolutely no reason to treat NULL as largest possible value. You can always use the ROW_NUMBER trick:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Code ORDER BY TransactionNo DESC) AS RN
FROM SaleOrder
)
SELECT * FROM cte
WHERE RN = 1
Result:
| TransactionNo | SaleOrderDate | Code | Quantity | TotalAmount | Remarks | RN |
|---------------|---------------|---------------|----------|-------------|---------|----|
| 4 | 2018-10-01 | SO-001-OCT-18 | 2 | 900.00 | Hello | 1 |
| NULL | 2018-10-01 | SO-002-OCT-18 | 6 | 2500.00 | Hello | 1 |
| NULL | 2018-10-01 | SO-003-OCT-18 | 6 | 2500.00 | Hello | 1 |
| 0 | 2018-10-01 | SO-004-OCT-18 | 6 | 2500.00 | Hello | 1 |
When TransactionNo is NULL and query returns more than one row that could not assigned to the filter
The below may help
SELECT * FROM SaleOrder O
WHERE TransactionNo = (SELECT TOP 1 MAX(ISNULL(NULL, 1)) FROM SaleOrder GROUP BY Code)
Note that this could take any record with TransactionNo having a NULL value.
Segregating the logic for TransactionNo filter would be easier to extend and maintain. Example below:
DECLARE #TransactionNo int
SELECT TOP 1 #TransactionNo = MAX(ISNULL(TransactionNo, 1)) FROM SaleOrder GROUP BY Code -- (OR) Logic here
SELECT * FROM SaleOrder O
WHERE TransactionNo = #TransactionNo
when you use = select in your where clause its totally wrong because this is possible you have multiple records,so you have to change your code like this:
SELECT MAX(ISNULL(TransactionNo, 1)),code FROM SaleOrder O
GROUP BY Code
but if you want to return only one record you can use it like this:
SELECT * FROM SaleOrder O
WHERE TransactionNo = (SELECT TOP 1 MAX(ISNULL(NULL, 1)) FROM SaleOrder GROUP BY Code)
I think this ISNULL check should solve your problem and replace = with IN subquery can return multiple records
WHERE ISNULL(TransactionNo, 1) IN
Try this:
select TransactionNo,SaleOrderDate,Code,Quantity,TotalAmount,Remarks from (
select TransactionNo,SaleOrderDate,Code,Quantity,TotalAmount,Remarks,
row_number() over (partition by code order by transactionno desc) rn
from (
select TransactionNo,SaleOrderDate,Code,Quantity,TotalAmount,Remarks,
coalesce(transactionno, count(*) over (partition by code) + 1) transactionno2
from SaleOrder
) a
) a where rn = 1
Explanation:
With this line coalesce(transactionno, count(*) over (partition by code) + 1) transactionno2 I assign greatest value per group (partitioned by code) where it's null. But be aware when you have two NULLs, rows will be tied in this case and it would be non-deterministic.
Below code will give you much more info than you requested, you can play with it, add some comment if have any questions.
CREATE TABLE #SaleOrder
(
TransactionNo Int,
#SaleOrderDate DATE,
Code VARCHAR(25),
Quantity INT,
TotalAmount Numeric(18,2),
Remarks VARCHAR(25)
)
INSERT INTO #SaleOrder VALUES (NULL, '2018-10-01', 'SO-001-OCT-18', 6, '2500', 'Hello');
INSERT INTO #SaleOrder VALUES (1, '2018-10-01', 'SO-001-OCT-18', 8, '2600', 'Hello');
INSERT INTO #SaleOrder VALUES (2, '2018-10-01', 'SO-001-OCT-18', 12, '3400', 'Hello');
INSERT INTO #SaleOrder VALUES (3, '2018-10-01', 'SO-001-OCT-18', 9, '2900', 'Hello');
INSERT INTO #SaleOrder VALUES (4, '2018-10-01', 'SO-001-OCT-18', 2, '900', 'Hello');
INSERT INTO #SaleOrder VALUES (NULL, '2018-10-01', 'SO-002-OCT-18', 6, '2500', 'Hello');
INSERT INTO #SaleOrder VALUES (NULL, '2018-10-01', 'SO-003-OCT-18', 6, '2500', 'Hello');
INSERT INTO #SaleOrder VALUES (0, '2018-10-01', 'SO-004-OCT-18', 6, '2500', 'Hello');
-- final select
SELECT top 1 -- optional, if you want to return 1 record
Code,
sum(Quantity) as totalQuantity,
sum(TotalAmount) as totallAmount,
count(1) as totalOrdersPerCode
FROM #SaleOrder O
group by Code
order by count(1) desc
-- drop temp table
drop table #SaleOrder
Related
I am using SQLite 3. I have a table MyTable, as follows:
Create table mytable (ID as INTEGER, OrderID as INTEGER, a as INTGER, b as INTEGER);
Insert into mytable (ID, OrderID,a,b) values (1, 1,1,1);
Insert into mytable (ID, OrderID,a,b) values (1, 2,1,2);
Insert into mytable (ID, OrderID,a,b) values (2, 1,1,3);
Insert into mytable (ID, OrderID,a,b) values (2, 3,2,1);
Insert into mytable (ID, OrderID,a,b) values (3, 1,2,3);
Now if I using the following statement:
Select * from mytable ORDER BY a desc, b desc;
I will get all rows in a different order, as follows:
(3, 1, 2, 3);
(2, 3, 2, 1);
(2, 1, 1, 3);
(1, 2, 1, 2);
(1, 1, 1, 1);
Now I want to update the order ID to the sequence number of the rows appear in the above results, as follows:
(3, 1, 2, 3);
(2, 2, 2, 1);
(2, 3, 1, 3);
(1, 4, 1, 2);
(1, 5, 1, 1);
How to do so?
Try This :-
Select ROW_NUMBER() OVER (ORDER BY (SELECT 1 ) ) AS ordNo, * INTO #TempTable from
mytable ORDER BY a desc, b desc;
UPDATE mytable SET OrderID = ordNo FROM #TempTable WHERE mytable.ID
=#TempTable.ID AND mytable.a=#TempTable.a AND mytable.b=#TempTable.b
Assuming that the a and b values are unique:
update mytable t
set orderid = (select count(*)
from mytable t2
where t2.a > t.a or
(t2.a = t.a and t2.b >= t.b)
);
There is a column [rowid][1] responsible for every rowid
Most tables in a typical SQLite database schema are rowid tables.
Rowid tables are distinguished by the fact that they all have a unique, non-NULL, signed 64-bit integer rowid that is used as the access key for the data in the underlying B-tree storage engine.
Due to old SQLite version didn't support ROW_NUMBER window function, you can use a subquery in select to make it.
You can try to use correlated subquery and UPDATE by rowid.
Schema (SQLite v3.18)
Create table mytable (ID INT, OrderID INT, a INT, b INT);
Insert into mytable (ID, OrderID,a,b) values (1, 1,1,1);
Insert into mytable (ID, OrderID,a,b) values (1, 2,1,2);
Insert into mytable (ID, OrderID,a,b) values (2, 1,1,3);
Insert into mytable (ID, OrderID,a,b) values (2, 3,2,1);
Insert into mytable (ID, OrderID,a,b) values (3, 1,2,3);
update mytable
set orderid=
(
SELECT (select count(*)
from mytable tt
where tt.a > t1.a or
(tt.a = t1.a and tt.b >= t1.b)) rn
FROM mytable t1
where mytable.rowid=t1.rowid
);
Query #1
SELECT * FROM mytable order by OrderID;
| ID | OrderID | a | b |
| --- | ------- | --- | --- |
| 3 | 1 | 2 | 3 |
| 2 | 2 | 2 | 1 |
| 2 | 3 | 1 | 3 |
| 1 | 4 | 1 | 2 |
| 1 | 5 | 1 | 1 |
View on DB Fiddle
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!!!
CREATE TABLE entries (
id serial NOT NULL,
title character varying,
load_sequence integer
);
and data
INSERT INTO entries(title, load_sequence) VALUES ('A', 1);
INSERT INTO entries(title, load_sequence) VALUES ('A', 2);
INSERT INTO entries(title, load_sequence) VALUES ('A', 3);
INSERT INTO entries(title, load_sequence) VALUES ('A', 6);
INSERT INTO entries(title, load_sequence) VALUES ('B', 4);
INSERT INTO entries(title, load_sequence) VALUES ('B', 5);
INSERT INTO entries(title, load_sequence) VALUES ('B', 7);
INSERT INTO entries(title, load_sequence) VALUES ('B', 8);
Is there a way in PostgreSQL to write SQL that groups data by same title segments after ordering them by load_sequence.
I mean:
=# SELECT id, title, load_sequence FROM entries ORDER BY load_sequence;
id | title | load_sequence
----+-------+---------------
9 | A | 1
10 | A | 2
11 | A | 3
13 | B | 4
14 | B | 5
12 | A | 6
15 | B | 7
16 | B | 8
AND I want groups:
=# SELECT title, string_agg(id::text, ',' ORDER BY id) FROM entries ???????????;
so result would be:
title | string_agg
-------+-------------
A | 9,10,11
B | 13,14
A | 12
B | 15,16
You can use the following query:
SELECT title, string_agg(id::text, ',' ORDER BY id)
FROM (
SELECT id, title,
ROW_NUMBER() OVER (ORDER BY load_sequence) -
ROW_NUMBER() OVER (PARTITION BY title
ORDER BY load_sequence) AS grp
FROM entries ) AS t
GROUP BY title, grp
Calculated grp field serves to identify slices of title records having consecutive load_sequence values. Using this field in the GROUP BY clause we can achieve the required aggregation over id values.
Demo here
There's a trick you can use with sum as a window function running over a lagged window for this.
The idea is that when you hit an edge/discontinuity you return 1, otherwise you return 0. You detect the discontinuities using the lag window function.
SELECT title, string_agg(id::text, ', ') FROM (
SELECT
id, title, load_sequence,
sum(title_changed) OVER (ORDER BY load_sequence) AS partition_no
FROM (
SELECT
id, title, load_sequence,
CASE WHEN title = lag(title, 1) OVER (ORDER BY load_sequence) THEN 0 ELSE 1 END AS title_changed FROM entries
) x
) y
GROUP BY partition_no, title;
I have a table with 5 columns ReportId, Date, Area, BuildingName, Amount.
Sample data looks like this :
-------------------------------------------------------
ReportId | Date | Area | BuildingName | Amount
-------------------------------------------------------
1 | 01/01/2013 | S1 | A1-01 | 5
2 | 01/01/2013 | S1 | A1-03 | 5
3 | 01/01/2013 | S2 | A1-05 | 4
4 | 02/01/2013 | S2 | A1-05 | 7
5 | 02/01/2013 | S2 | A1-03 | 9
6 | 03/01/2013 | S1 | A1-03 | 2
7 | 04/01/2013 | S2 | A1-02 | 6
8 | 05/01/2013 | S1 | A1-01 | 7
9 | 06/01/2013 | S1 | A1-02 | 5
10 | 06/01/2013 | S1 | A1-05 | 8
11 | 06/01/2013 | S1 | A1-07 | 5
I need to write a query to get the result like this :
-----------------------------------------------------
Date | Area | BuildingName | Amount | Sum
-----------------------------------------------------
01/01/2013 | S1 | A1-01 | 5 | 12
01/01/2013 | S1 | A1-03 | 5 | 7
01/01/2013 | S2 | A1-05 | 4 | 11
Date value passed as a parameter to the query.
"Area", "BuildingName", "Amount" are records which have the same "Date".
"Sum", is Sum of All "Amount" in the table where has the same "Area" And "BuildingName" in the result of query.
I searched much, but I can't get anything about this ...
Try this query
SELECT #Date AS 'Date'
,t.Area
,t.BuildingName
,t.Amount
,temp.SumAmount
FROM TABLE t INNER JOIN (SELECT Area,
BuildingName,
SUM(Amount) 'SumAmount'
FROM TABLE t
GROUP BY Area, BuildingName) temp
ON temp.Area=t.Area AND
temp.BuildingName=t.BuildingName
Where t.Date= #Date
This should work:
;with filter as (
select Date, Area, BuildingName, Amount
from data
where data.Date = #date
)
select
filter.Date
,filter.Area
,filter.BuildingName
,filter.Amoount
,sum(data.Amount) as [Sum]
from data
join filter
on filter.Area = data.Area
and filter.BuildingName = date.BuildingName
group by
filter.Date
,filter.Area
,filter.BuildingName
;
You can use window version of SUM to get the sum of every Area, BuildingName partition. Then, in an outer query just filter by date, to get the required result set:
SELECT [Date], Area, BuildingName, Amount, [Sum]
FROM (
SELECT [Date], Area, BuildingName, Amount,
SUM(Amount) OVER (PARTITION BY Area, BuildingName) AS [Sum]
FROM mytable ) t
WHERE [Date] = '01/01/2013'
SQL Fiddle Demo
Give this a shot.
--Create table to test with and add in values
CREATE TABLE #tbl1 (ReportID int, [date] date, [area] varchar(2), BuildingName varchar(5), [Amount] int)
INSERT INTO #tbl1 VALUES(1, '20130101', 'S1', 'A1-01', 5)
INSERT INTO #tbl1 VALUES(2, '20130101', 'S1', 'A1-03', 5)
INSERT INTO #tbl1 VALUES(3, '20130101', 'S2', 'A1-05', 4)
INSERT INTO #tbl1 VALUES(4, '20130201', 'S2', 'A1-05', 7)
INSERT INTO #tbl1 VALUES(5, '20130201', 'S2', 'A1-03', 9)
INSERT INTO #tbl1 VALUES(6, '20130301', 'S1', 'A1-03', 2)
INSERT INTO #tbl1 VALUES(7, '20130401', 'S2', 'A1-02', 6)
INSERT INTO #tbl1 VALUES(8, '20130501', 'S1', 'A1-01', 7)
INSERT INTO #tbl1 VALUES(9, '20130601', 'S1', 'A1-02', 5)
INSERT INTO #tbl1 VALUES(10, '20130601', 'S1', 'A1-05', 8)
INSERT INTO #tbl1 VALUES(11, '20130601', 'S1', 'A1-01', 5)
--Declare the date variable you mentioned you wanted to "Pass in"
DECLARE #passedInDate date
set #passedInDate = '20130101'
--Here's the code that matters
select [Date], area, BuildingName, Amount
, (SELECT SUM(Amount) from #tbl1 [sq] where sq.area = tbl1.area and sq.BuildingName = tbl1.BuildingName) as [Sum]
from #tbl1 tbl1
where [DATE] = #passedInDate
--Drop testing table
Drop Table #tbl1
we can achieve the same result by using Row_number and Not in condition In CTE and while doing sum of amount 'A1-01' getting 17 but in your output you have mentioned 12...if you execute all the answers with same data you are going to get sum = 17 only for 'AI-01' .SO PLEASE CHECK IN YOUR DATA
DECLARE #tbl1 TABLE (ReportID int, [date] date, [area] varchar(2), BuildingName varchar(5), [Amount] int)
INSERT INTO #tbl1 VALUES(1, '20130101', 'S1', 'A1-01', 5)
INSERT INTO #tbl1 VALUES(2, '20130101', 'S1', 'A1-03', 5)
INSERT INTO #tbl1 VALUES(3, '20130101', 'S2', 'A1-05', 4)
INSERT INTO #tbl1 VALUES(4, '20130201', 'S2', 'A1-05', 7)
INSERT INTO #tbl1 VALUES(5, '20130201', 'S2', 'A1-03', 9)
INSERT INTO #tbl1 VALUES(6, '20130301', 'S1', 'A1-03', 2)
INSERT INTO #tbl1 VALUES(7, '20130401', 'S2', 'A1-02', 6)
INSERT INTO #tbl1 VALUES(8, '20130501', 'S1', 'A1-01', 7)
INSERT INTO #tbl1 VALUES(9, '20130601', 'S1', 'A1-02', 5)
INSERT INTO #tbl1 VALUES(10, '20130601', 'S1', 'A1-05', 8)
INSERT INTO #tbl1 VALUES(11, '20130601', 'S1', 'A1-01', 0)
;WITH CTE AS (
select
DATE,
area,
buildingname,
amount,RN,R
from (
select
DATE,
area,
buildingname,
amount,
ROW_NUMBER()OVER(PARTITION BY Area ORDER BY date,buildingname)RN,
SUM(AMOUNT)over(PARTITION by buildingname,area )R from #tbl1)A
WHERE RN in( 1,2) )
select
DATE,
Area,
Buildingname,
Amount,R
FROM CTE WHERE DATE NOT IN (SELECT MAX(DATE)D FROM CTE C)
I have a table like this:
Products
(
ID int not null primary key,
Type int not null,
Route varchar(20) null
)
I have a list on the client in this format:
Type=1, Percent=0.4, Route=A
Type=1, Percent=0.4, Route=B
Type=1, Percent=0.2, Route=C
Type=2, Percent=0.5, Route=A
Type=2, Percent=0.5, Route=B
Type=3, Percent=1.0, Route=C
...etc
When done, I'd like to assign 40% of type 1 products to Route A, 40% to Route B and 20% to Route C. Then 50% of type 2 products to Route A and 50% of type 2 products to Route B, etc.
Is there some way to do this in a single update statement?
If not in one giant statement, can it be done in one statement per type or one statement per route? As currently we're doing one per type+route any of the above would be an improvement.
Here's an Oracle statement that I prepared before you posted that you were using SQL-Server, but it might give you some ideas, though you will have to roll your own ratio_to_report analytic function using CTE and self-joins. We calculate the cumulative proportion of each type in the products and client route tables and do a non equi-join on the matching proportion bands. The sample data I have used has some round-offs but these will reduce for larger data sets.
Here's the setup:
create table products (id int not null primary key, "type" int not null, route varchar (20) null);
create table clienttable ( "type" int not null, percent number (10, 2) not null, route varchar (20) not null);
insert into clienttable ("type", percent, route) values (1, 0.4, 'A');
insert into clienttable ("type", percent, route) values (1, 0.4, 'B');
insert into clienttable ("type", percent, route) values (1, 0.2, 'C');
insert into clienttable ("type", percent, route) values (2, 0.5, 'A');
insert into clienttable ("type", percent, route) values (2, 0.5, 'B');
insert into clienttable ("type", percent, route) values (3, 1.0, 'C');
insert into products (id, "type", route) values (1, 1, null);
insert into products (id, "type", route) values (2, 1, null);
insert into products (id, "type", route) values (3, 1, null);
insert into products (id, "type", route) values (4, 1, null);
insert into products (id, "type", route) values (5, 1, null);
insert into products (id, "type", route) values (6, 1, null);
insert into products (id, "type", route) values (7, 1, null);
-- 7 rows for product type 1 so we will expect 3 of route A, 3 of route B, 1 of route C (rounded)
insert into products (id, "type", route) values (8, 2, null);
insert into products (id, "type", route) values (9, 2, null);
insert into products (id, "type", route) values (10, 2, null);
insert into products (id, "type", route) values (11, 2, null);
insert into products (id, "type", route) values (12, 2, null);
-- 5 rows for product type 2 so we will expect 3 of route A and 2 of route B (rounded)
insert into products (id, "type", route) values (13, 3, null);
insert into products (id, "type", route) values (14, 3, null);
-- 2 rows for product type 3 so we will expect 2 of route C
and here's the statement
select prods.id, prods."type", client.route cr from
(
select
p.id,
p."type",
row_number () over (partition by p."type" order by p.id) / count (*) over (partition by p."type") cum_ratio
from
products p
) prods
inner join
(
select "type", route, nvl (lag (cum_ratio, 1) over (partition by "type" order by route), 0) ratio_start, cum_ratio ratio_end from
(select "type", route, sum (rr) over (partition by "type" order by route) cum_ratio
from (select c."type", c.route, ratio_to_report (c.percent) over (partition by "type") rr from clienttable c))) client
on prods."type" = client."type"
and prods.cum_ratio >= client.ratio_start and prods.cum_ratio < client.ratio_end
This gives the following result:-
+----+------+----+
| ID | type | CR |
+----+------+----+
| 1 | 1 | A |
| 2 | 1 | A |
| 3 | 1 | B |
| 4 | 1 | B |
| 5 | 1 | B |
| 6 | 1 | C |
| 8 | 2 | A |
| 9 | 2 | A |
| 10 | 2 | B |
| 11 | 2 | B |
| 13 | 3 | C |
+----+------+----+
How about something like
--For updating type 1, set every route for type 1 as null.
UPDATE MyTable
SET [Route] = null
WHERE [Type] = '1'
--Update Route A(40%)
DECLARE #myVal int;
SET #myVal =CAST(0.4*(SELECT COUNT(*) FROM myTable WHERE [Type]='1') AS INT);
WITH tab AS
(
SELECT TOP (#myVal) *
FROM myTable
)
UPDATE tab
SET [Route] = 'A'
WHERE [Route] is null
--Update Route B (40%)
DECLARE #myVal int;
SET #myVal =CAST(0.4*(SELECT COUNT(*) FROM myTable WHERE [Type]='1') AS INT);
WITH tab AS
(
SELECT TOP (#myVal) *
FROM myTable
)
UPDATE tab
SET [Route] = 'B'
WHERE [Route] is null
--Update Route C (20%)
DECLARE #myVal int;
SET #myVal =CAST(0.2*(SELECT COUNT(*) FROM myTable WHERE [Type]='1') AS INT);
WITH tab AS
(
SELECT TOP (#myVal) *
FROM myTable
)
UPDATE tab
SET [Route] = 'C'
WHERE [Route] is null
I do not know if similar functionality exist in SQL Server. In Oracle there is SAMPLE clause.
Below query selects 10% of rows from a table:
SELECT empno
FROM scott.emp
SAMPLE (10)
/
Then your update would be easy... Maybe smth similar exists in SQL Server. You can also count rows or data then calc percent then update...
WITH po AS
( SELECT
ID,
Type,
ROW_NUMBER() OVER ( PARTITION BY Type
ORDER BY ID
) AS Rn,
COUNT(*) OVER (PARTITION BY Type) AS CntType
FROM
Products
)
, ro AS
( SELECT
Type,
Route,
( SELECT SUM(rr.Percent)
FROM Route AS rr
WHERE rr.Type = r.Type
AND rr.Route <= r.Route
) AS SumPercent
FROM
Routes AS r
)
UPDATE p
SET p.Route =
( SELECT MIN(ro.Route)
FROM ro
WHERE ro.Type = po.Type
AND ro.SumPercent >= po.Rn / po.CntType
)
FROM Products AS p
JOIN
po ON po.ID = p.ID ;