I'm currently working on a project that involves using a user-provided charge table to calculate fees.
The table looks like:
MaxAmount Fee
10.00 1.95
20.00 2.95
30.00 3.95
50.00 4.95
As seen in the table above, any MaxAmount up to 10.00 is charged a 1.95 fee. Any MaxAmount between 10.01 and 20.00 is charge a 2.95 fee, etc. Finally, any MaxAmount above 50.00 is charged 4.95.
I'm trying to come up with a sql query that will return the correct fee for a given MaxAmount. However, I'm having trouble doing so. I've tried something similar to the following (assuming a provided MaxAmt of 23.00):
SELECT Fee FROM ChargeTable WHERE 23.00 BETWEEN MaxAmt AND MaxAmt
Of course, this doesn't give me the desired result of 3.95.
I'm having trouble adapting SQL's set-based logic to this type of problem.
Any and all help is greatly appreciated!
If the MaxAmount behaves as the table suggests, then you can use:
select top 1 fee
from ChargeTable ct
where #Price <= MaxAount
order by MaxAmount desc
As you describe it, you really want another row:
MaxAmount Fee
0.00 1.95
10.00 1.95
20.00 2.95
30.00 3.95
50.00 4.95
Your original table does not have enough values. When you have 4 break points, you actually need 5 values -- to handle the two extremes.
With this structure, then you can do:
select top 1 fee
from ChargeTable ct
where #Price >= MaxAount
order by MaxAmount desc
You could try something like:
SELECT min(Fee) FROM Fees WHERE 23<=MaxAmount
Have a look here for an example:
http://sqlfiddle.com/#!2/43f2a/5
Related
I have a Point of Sale system where all checks and tender details exist in a single table. I'm trying to write a SQL query in a way that I can see check totals by tenders to reconcile with cash flow and bank statements. Unfortunately the schema is not mine and can't change it.
One problem I ran into is that there are cases where one check has multiple transactions involving various tenders, therefore I need to do implement (business set) rules to allocate taxes evenly. Those rules might change in the future to say, allocate taxes to CC first if any, so I need to built in some flexibility.
The SQL table looks like this:
CheckID
LineType
TenderName
LineTotal
Tax
1
ItemSold
5.00
0.25
1
TenderTotal
Cash
5.25
2
ItemSold
10.00
0.50
2
TenderTotal
Cash
5.00
2
TenderTotal
VISA
5.50
3
ItemSold
10.00
0.25
3
ItemSold
10.00
0.25
3
TenderTotal
AMEX
10.25
3
TenderTotal
VISA
10.25
4
ItemSold
10.00
0.50
4
TenderTotal
Cash
20.00
4
TenderTotal
Cash
-9.50
The resulting report needs to have one row per tender, with tax equally distributed among check tenders, and net revenue being the difference between total sale and tax.
TenderName
TotalSale
NetRevenue
TaxCollected
Cash
20.75
19.75
1.00
VISA
15.75
15.25
0.50
AMEX
10.25
10.00
0.25
I tried using Select with Exists, also CTE and recursive CTEs, but can't quite figure it out how to do the tax part cleanly. Any other SQL tricks I could try?
We are using SQL Server 2012 at the moment, but have plans in plan to upgrade to 2016 in the near future.
I don't know if the logic is right, but it gets you the results you are after:
WITH Tenders AS(
SELECT V.CheckID,
V.LineType,
V.TenderName,
V.LineTotal,
SUM(CASE WHEN V.TenderName IS NULL THEN V.Tax END) OVER (PARTITION BY V.CheckID) AS Tax
FROM (VALUES(1,'ItemSold',NULL,5.00,0.25),
(1,'TenderTotal','Cash',5.25,NULL),
(2,'ItemSold',NULL,10.00,0.50),
(2,'TenderTotal','Cash',5.00,NULL),
(2,'TenderTotal','VISA',5.50,NULL),
(3,'ItemSold',NULL,10.00,0.25),
(3,'ItemSold',NULL,10.00,0.25),
(3,'TenderTotal','AMEX',10.25,NULL),
(3,'TenderTotal','VISA',10.25,NULL),
(4,'ItemSold',NULL,10.00,0.50),
(4,'TenderTotal','Cash',20.00,NULL),
(4,'TenderTotal','Cash',-9.50,NULL))V(CheckID,LineType,TenderName,LineTotal,Tax))
SELECT T.TenderName,
SUM(T.LineTotal) AS TotalSale,
SUM(T.LineTotal - T.Tax) AS NetRevenue,
SUM(T.Tax) AS TaxCollected
FROM Tenders T
WHERE T.TenderName IS NOT NULL
GROUP BY T.TenderName;
Introduction
In this Q&A we will explore the use of window functions and grouping sets in the same query using PostgreSQL. The reader should be familiar with both of those concepts to get the most out of this post. This fiddle can be used to follow along.
Data
The following table contains a few transactions. For each transaction we have the customer, the city where it took place and the number of units delivered to the customer.
CREATE TABLE transactions (customer TEXT, city TEXT, units INT);
INSERT INTO transactions VALUES ('Bob', 'Manchester', 10),
('Chuck', 'Manchester', 20),
('Bob', 'Concord', 10),
('Tim', 'Manchester', 15),
('Jane', 'Derry', 10),
('Tim', 'Derry', 15),
('Tim', 'Concord', 20),
('Bob', 'Manchester', 20),
('Chuck', 'Concord', 10);
Desired results
I want to be able to produce a report which contains the answers to questions like "what proportion of all transactions is represented by this row" or "what is the ratio of this to all the transactions by this customer". Also I want to be able to answer all such possible questions. With that kind of a report we could simply look at each row and extract information about its relation to the whole report or to specific slices of the data.
First attempt
We could attempt to create such a report by using multiple queries with different GROUP BY clauses and then uniting them with UNION ALL or we can try something like the following:
-- Incorrect results
SELECT customer
, city
, SUM(units) AS "units"
, ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER () , 2) AS "to report"
, ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY customer) , 2) AS "to customer"
, ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY city) , 2) AS "to city"
FROM transactions
GROUP BY CUBE(customer, city)
Here we use CUBE in the GROUP BY clause which will produce groupings corresponding to all possible combinations of the customer and city columns. The numerator of the ratios is an aggregate that corresponds to the units total for that row. Notice that it is the same for all ratios and that it contains the expression used as the "units" column, i.e. SUM(units). Calculating the denominator is more complicated because we need a window function to calculate the total number of units for the wanted slice, i.e. the total for a particular customer or city or the total for the whole report.
Incorrect Results
Unfortunately the ratios produced by the query above are not correct. For example the first row has 10 units which is 7.7% of the total (130), 25% of the total for Bob (40), and 25% of the total for Concord (40) yet the results show less than the correct ratio in all cases. As another example take the row where both "customer" and "city" are NULL, here the "unit" column is 130 and yet the calculated ratio "to report" is 25%. Clearly the denominator in the ratio columns is wrong. How can we get the desired results?
customer
city
units
to report
to customer
to city
Bob
Concord
10
1.92
12.50
12.50
Bob
Manchester
30
5.77
37.50
23.08
Bob
null
40
7.69
50.00
15.38
Chuck
Concord
10
1.92
16.67
12.50
Chuck
Manchester
20
3.85
33.33
15.38
Chuck
null
30
5.77
50.00
11.54
Jane
Derry
10
1.92
50.00
20.00
Jane
null
10
1.92
50.00
3.85
Tim
Concord
20
3.85
20.00
25.00
Tim
Derry
15
2.88
15.00
30.00
Tim
null
50
9.62
50.00
19.23
Tim
Manchester
15
2.88
15.00
11.54
null
null
130
25.00
50.00
50.00
null
Manchester
65
12.50
25.00
50.00
null
Derry
25
4.81
9.62
50.00
null
Concord
40
7.69
15.38
50.00
Why are the results wrong?
Notice that although the results in the question are wrong they are not totally nonsensical. Take, for example, the row in which both "customer" and "city" are NULL. This row has 130 units which is the total number of units in the data, so we should expect the ratio "to report" to be 100% but the result shows 25%, which means that the denominator of the ratio in that case was four times 130, or 520. Take the first row as another example, here we have 10 units and a ratio "to report" of 1.92%, again the denominator is wrong by a factor of four, i.e. the actual ratio should be 7.69%. Clearly the total of the report is taken to be four times what it actually is.
The results for the "to customer" and "to city" columns are wrong as well but by a different factor. Take for example the rows where "customer" is NULL and "city" is not NULL. The ratio "to city" for the three cities is 50% but should be 100%. This is because the denominator of the ratio is twice what it should be. The same thing happens for the "to customer" ratio. The problem lies in the partitioning of the rows. For instance, there is no PARTITION BY clause in the "to report" column, so we are taking into consideration all of the rows of the report, which add up to 520.
Consider that the GROUP BY clause produces four groupings:
(customer, city) - equivalent to GROUP BY customer, city
(customer) - equivalent to GROUP BY customer
(city) - equivalent to GROUP BY city
() - equivalent to using an aggregate without GROUP BY clause
Each of those groupings is a different way to slice the data and for each the units total is 130. In the case of the "to customer" and "to city" columns we have a denominator that is twice as large as it should be. Take for example the cities and notice that the rows where "city" is not NULL contain the units for each city twice: once where "customer" is not NULL and another time where "customer" is NULL. These two categories correspond to the first and third groupings above, respectively. The same can be said for the customers but the rows would correspond to the first and second groupings. Clearly when calculating the denominator of each ratio we need to take into consideration that different rows belong to different slices of the data.
The Key
The key to getting the desired results is to use the GROUPING aggregate function. This function "returns a bit mask indicating which GROUP BY expressions are not included in the current grouping set". In other words it can give a different result for each grouping produced by the GROUP BY clause. We can use this function to help calculate the denominator for each of the ratio columns. To get the desired effect we use the GROUPING function inside the PARTITION BY clause of our window functions like so:
SELECT customer
, city
, SUM(units) AS "units"
, ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY GROUPING(customer, city)) , 2) AS "to report"
, ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY GROUPING(customer, city), customer) , 2) AS "to customer"
, ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY GROUPING(customer, city), city) , 2) AS "to city"
FROM transactions
GROUP BY CUBE(customer, city)
Having the GROUPING function inside the PARTITION BY clause ensures that the denominator for each ratio corresponds only to the rows of a particular grouping. Fortunately we do not have to give much thought to the arguments that we will pass to the GROUPING function. You can simply include all the columns that appear in the GROUP BY, although only the ones that do not appear in all of the grouping sets are necessary.
Desired Result
Now that we are calculating the denominator taking the groupings into consideration we get the correct results.
customer
city
units
to report
to customer
to city
Bob
Manchester
30
23.08
75.00
46.15
Bob
Concord
10
7.69
25.00
25.00
Chuck
Concord
10
7.69
33.33
25.00
Chuck
Manchester
20
15.38
66.67
30.77
Jane
Derry
10
7.69
100.00
40.00
Tim
Concord
20
15.38
40.00
50.00
Tim
Derry
15
11.54
30.00
60.00
Tim
Manchester
15
11.54
30.00
23.08
Bob
null
40
30.77
100.00
30.77
Chuck
null
30
23.08
100.00
23.08
Jane
null
10
7.69
100.00
7.69
Tim
null
50
38.46
100.00
38.46
null
Concord
40
30.77
30.77
100.00
null
Derry
25
19.23
19.23
100.00
null
Manchester
65
50.00
50.00
100.00
null
null
130
100.00
100.00
100.00
I show my table structure below. I want the record like the second structure. Please help me to get this using oracle or SQL.
FLd_Date Fld_Via Fld_Amt
11/1/2013 cash 100.26
11/2/2013 online 123.00
11/3/2013 cash 32.00
11/4/2013 cash 234.00
11/5/2013 cash 125.00
11/6/2013 online 125.00
11/7/2013 cash 200.00
11/8/2013 online 111.00
11/9/2013 online 143.00
11/10/2013 cash 155.00
11/11/2013 online 12.00
11/12/2013 online 142.00
I want the output like this
mode count total
cash 6 846.26
online 6 656
select fld_via as mode,
count(*) as count,
sum(fld_amt) as total
from your_table
group by fld_via
Please try:
select
Fld_Via as "mode",
count(*) as "count",
sum(Fld_Amt) as "total"
from
YourTable
Group by Fld_Via
I am trying to create a query that joins 3 tables with multiple prices and locations. Ex of the data I'm pulling.
Table A - product, product name
Table B - transactionid, date, product, returnreason, quantity, costprice, costamount,
location, journalid
Table C - price1, price2, price3, price4, price5, price6, product, activedate
Table C is updating prices for our products and can be the same from one date to another, or completely change depending on the market. Location in Table B can be up to four different locations.
Ex. Table C (each product is setup basically the same in the tables.)
product price1 price2 price3 price4 price 5 price6 activedate
A 0.00 0.00 0.00 0.00 0.00 0.00 1/1/11
A 0.00 0.00 0.00 0.00 0.00 0.00 2/1/11
A 1.00 0.00 0.63 0.00 1.20 0.20 1/1/12
A 1.20 0.53 0.01 1.00 0.42 0.00 4/1/13
Table B
product transactionid tdate returnreason quantity costprice costamount location journalid
A 00001 5/1/11 def 100 2.00 2.50 100 000010
B 00205 6/13/11 col 250 10.00 15.00 300 000320
A 00207 4/11/13 col 50 5.00 1.50 100 000720
I need to get the information out by the most current price for the item by date. So if I need a yearly report, I can see that for product A - price1 for 1/1/13-4/30/13 was 1.00 and for 5/1/13-present the price is now 1.20.
I have tried many things including sub queries and the latest (which is closest) to limit it by creating a where statement to be activedate<=tdate which will bring me every active date below and equal to the tdate. For this please assume product A is at location 100. How can I limit to the product active during the time frame it would be active?
Most recent...
I added line number in to make sure I got each line of the journal in...just visual.
select distinct(b.transactionid), b.tdate, b.linenum b.product, b.location,
b.journalid, c.price1, c.price2, c.price3, c.price4, c.price5, c.activedate
from Table b inner join
Table c on c.product=b.product
where c.activedate <= b.tdate
When I run this I get all dates that were less than the transaction date, but I need specific. For example the first transaction in my example, it happened on 5/1/11, when I run the query it will give me the results from table c for 1/1/11 and 2/1/11. I just need the date of the price for when the transaction took place.
I am using SUM() function. But SUM() sums the negative value in the column. In a column if the value is positive then it should be added and for negative values should be substracted and not added as the SUM()
20.00
20.00
20.00
20.00
-20.00
20.00
20.00
40.00
20.00
20.00
20.00
20.00
20.00
-20.00
-20.00
20.00
sum() should return 220 and not 440.
Is returning 440.
To subtract negative numbers rather than add them you would use SUM(ABS(col)) but just to check this is what you actually need example results below.
WITH YourTable(col) AS
(
SELECT 2 UNION ALL
SELECT -5
)
SELECT
SUM(ABS(col)) AS [SUM(ABS(col))],
SUM(col) AS [SUM(col)]
FROM YourTable
Returns
SUM(ABS(col)) SUM(col)
------------- -----------
7 -3
SELECT SUM(ABS(Column_Name)) FROM Table_Name;