I have a table storing hourly pay rates and a start and end value associated to each. The theory being that your hourly pay is dependent on your takings sitting between the start and end values.
Table Example - dbo.PayScales
PayScaleId Starting Ending HourlyRate
1 0.00 32.88 12.00
2 32.89 34.20 12.50
3 34.21 35.52 13.00
I have the takings stored in a separate table along with a person id, and I need to lookup the hourlyrate based on the takings (which I am having a complete mind block about)
Table Example - dbo.Employees
EmpId Takings HourlyRate
1 33.50
2 31.19
3 37.00
So my exepected results would be:
EmpId 1 Hourly rate = 12.50
EmpId 2 Hourly rate = 12.00
EmpId 3 Hourly rate = 13.00 as the value is greater than the ending value.
You can use CROSS APPLY together with TOP:
SELECT *
FROM dbo.Employees e
CROSS APPLY(
SELECT TOP 1 p.HourlyRate
FROM dbo.PayScales p
WHERE
e.Takings BETWEEN p.Starting AND p.Ending
OR e.Takings > p.Ending
ORDER BY p.Ending DESC
) t
ONLINE DEMO
Of course #FelixPamittan's answer solves the problem, but with a small change in your data it's down to a simple join.
Change the highest Ending to a really high value (999999999), greater than any Takings, or NULL:
FROM #Employees AS e
JOIN #PayScales AS p
ON e.Takings BETWEEN p.Starting AND p.Ending
-- or
ON e.Takings BETWEEN p.Starting AND COALESCE(p.Ending, 999999999)
Related
I am calculating a field called "Degree Level" in a view. This is a field in the table "Degrees", and the table shows degrees for each faculty member. A faculty member can have more than one degree.
The field "degree level" is also in the table "Crosswalk_Table". I want to choose Degree level for a faculty member based on the max value in the column "Degree_Hierarchy" in the Crosswalk_table.
The code below displays the "Master" instead of the "Doctor" for degree level (which has the higher hierarchy value). ANY help is much appreciated thank you.
CAST (
(SELECT DEGREE_LEVEL
FROM Degrees D, Crosswalk_Table E
WHERE
E.DEGREE_HIERARCHY =
(SELECT MAX (DEGREE_HIERARCHY)
FROM Crosswalk_Table
WHERE DEGREE_CODE = D.FACULTY_DEGREE_CODE)
AND D.FACULTY_DEGREE_CODE = E.DEGREE_CODE
AND D.PERSON_SKEY = SRC.PERSON_SKEY
AND ROWNUM <=1
ORDER BY DEGREE_HIERARCHY DESC)
AS VARCHAR2 (50))
Sample Data:
Degree table:
Person_skey Degree_Code
-------------------------
123456 MA
123456 JD
Crosswalk_Table:
degree_level degree_code degree_hierarchy
---------------------------------------------
master MA 30
doctor JD 40
If you are using Oracle 12 or higher, then you may use a subquery like that (with ORDER BY and LIMIT to 1 row):
SELECT c.DEGREE_LEVEL
FROM Degrees d
JOIN CROSSWALK_TABLE c
ON c.Degree_Code = d.Degree_Code
WHERE d.Person_skey = 123456
ORDER BY c.DEGREE_HIERARCHY DESC
FETCH FIRST ROW ONLY
Please take a look at this simple demo:
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c8d41924c593f4f361de59a611a363cc
I have a car trip data with different car trips. The relevant columns are
id - varchar(32)
sequence - integer - resets to 1 for a new car trip
timestamp - time at which device recorded gps data. It is shown as date in the below pic but assume it as timestamp
Latitude - numeric
Longitude
I am trying to find out car trips that are between a particular origin and destination point. If I enter origin as 40.34, 23.5 and destination as 40.75, 23.9 then the output would be as shown in second picture.
The first picture contains 2 car trips namely abc & def. 'abc' took place on December 18th while 'def' took on December 15th so def appears first in the output. The output table is ordered by timestamp column and sequence column and grouped by id. The output should also contain intermediate points between origin and destination.
I am unable to figure out how to find first trips that pass through particular points.
Input:
Output:
Assuming your car trip data table is named trips:
WITH starts_and_ends AS (
SELECT
starts.id,
starts.sequence AS start_sequence,
ends.sequence AS end_sequence
FROM
trips AS starts
JOIN trips AS ends
ON (starts.id = ends.id AND starts.sequence < ends.sequence)
WHERE
starts.latitude = 40.34 AND
starts.longitude = 23.50 AND
ends.latitude = 40.75 AND
ends.longitude = 23.90
)
SELECT
trips.*
FROM
starts_and_ends,
trips
WHERE
trips.id = starts_and_ends.id AND
sequence BETWEEN starts_and_ends.start_sequence AND starts_and_ends.end_sequence
ORDER BY
trips.id,
trips.sequence,
trips.timestamp;
In WITH query I select start and end point ids and sequence number. Then join it with original table to show the trip.
Output:
abc 2 2017-12-18 40.34 23.50
abc 3 2017-12-18 40.56 23.80
abc 4 2017-12-18 40.75 23.90
def 2 2017-12-15 40.34 23.50
def 3 2017-12-15 40.55 23.59
def 4 2017-12-15 40.80 23.99
def 5 2017-12-15 40.75 23.90
Try row_number() over()
SELECT
*
FROM (
SELECT
t.*
, ROW_NUMBER() OVER (PARTITION BY id ORDER BY sequence, timestamp) AS rn
FROM yourtable t
WHERE Latitude = 40.34
AND Longitude = 23.5
) d
WHERE rn = 1
nb: not sure is timestamp is needed in the ordering, but could be used as tie-beaker perhaps.
I have a query that collects many different columns, and I want to include a column that sums the price of every component in an order. Right now, I already have a column that simply shows the price of every component of an order, but I am not sure how to create this new column.
I would think that the code would go something like this, but I am not really clear on what an aggregate function is or why I get an error regarding the aggregate function when I try to run this code.
SELECT ID, Location, Price, (SUM(PriceDescription) FROM table GROUP BY ID WHERE PriceDescription LIKE 'Cost.%' AS Summary)
FROM table
When I say each component, I mean that every ID I have has many different items that make up the general price. I only want to find out how much money I spend on my supplies that I need for my pressure washers which is why I said `Where PriceDescription LIKE 'Cost.%'
To further explain, I have receipts of every customer I've worked with and in these receipts I write down my cost for the soap that I use and the tools for the pressure washer that I rent. I label all of these with 'Cost.' so it looks like (Cost.Water), (Cost.Soap), (Cost.Gas), (Cost.Tools) and I would like it so for Order 1 it there's a column that sums all the Cost._ prices for the order and for Order 2 it sums all the Cost._ prices for that order. I should also mention that each Order does not have the same number of Costs (sometimes when I use my power washer I might not have to buy gas and occasionally soap).
I hope this makes sense, if not please let me know how I can explain further.
`ID Location Price PriceDescription
1 Park 10 Cost.Water
1 Park 8 Cost.Gas
1 Park 11 Cost.Soap
2 Tom 20 Cost.Water
2 Tom 6 Cost.Soap
3 Matt 15 Cost.Tools
3 Matt 15 Cost.Gas
3 Matt 21 Cost.Tools
4 College 32 Cost.Gas
4 College 22 Cost.Water
4 College 11 Cost.Tools`
I would like for my query to create a column like such
`ID Location Price Summary
1 Park 10 29
1 Park 8
1 Park 11
2 Tom 20 26
2 Tom 6
3 Matt 15 51
3 Matt 15
3 Matt 21
4 College 32 65
4 College 22
4 College 11 `
But if the 'Summary' was printed on every line instead of just at the top one, that would be okay too.
You just require sum(Price) over(Partition by Location) will give total sum as below:
SELECT ID, Location, Price, SUM(Price) over(Partition by Location) AS Summed_Price
FROM yourtable
WHERE PriceDescription LIKE 'Cost.%'
First, if your Price column really contains values that match 'Cost.%', then you can not apply SUM() over it. SUM() expects a number (e.g. INT, FLOAT, REAL or DECIMAL). If it is text then you need to explicitly convert it to a number by adding a CAST or CONVERT clause inside the SUM() call.
Second, your query syntax is wrong: you need GROUP BY, and the SELECT fields are not specified correctly. And you want to SUM() the Price field, not the PriceDescription field (which you can't even sum as I explained)
Assuming that Price is numeric (see my first remark), then this is how it can be done:
SELECT ID
, Location
, Price
, (SELECT SUM(Price)
FROM table
WHERE ID = T1.ID AND Location = T1.Location
) AS Summed_Price
FROM table AS T1
to get exact result like posted in question
Select
T.ID,
T.Location,
T.Price,
CASE WHEN (R) = 1 then RN ELSE NULL END Summary
from (
select
ID,
Location,
Price ,
SUM(Price)OVER(PARTITION BY Location)RN,
ROW_number()OVER(PARTITION BY Location ORDER BY ID )R
from Table
)T
order by T.ID
I wrote an application where the usage of a number of vehicles is recorded as seen in the screenshot.
I'd like to generate a report for gaps (in miles) between individual usages because the vehicles should not be used other than for travel that is recorded in the application.
A gap would occur when the current record beg odometer - previous record end odometer yields a number greater than 0 -- for that specific car. See the different colored circles. How can I achieve this with sql? I'm using oracle (11g) but I imagine the sql will be similar. Thank you.
Sample output:
Vehicle V06
Invoice Date Dest Gap
123 1/2/14 York 14.0
122 1/1/14 Pburg 0.0
Vehicle V05
Invoice Date Dest Gap
121 1/3/14 Mill 0.0
* I realize I should have used test data that includes a gap, though these should be rare in practice. In such a case,
Invoice 67189 would have End Od of 92590 resulting in a GAP of 3.0 miles for 67190
Just use lag():
select vu.*
from (select vu.*,
lag(endod) over (partition by vehicle order by date) as prev_endod
from vehicleusage vu
) vu
where begod <> prev_endod;
Note that the comparison will fail for NULL values, so there is no problem with the first recording for a vehicle.
SELECT
D1.invoiceNo,
D1.vehicalId,
CASE WHEN D1.odBeg > D0.odEnd THEN 'MISSING MILES' ELSE 'EXTRA MILES' END AS Notes
FROM
(SELECT vehicalId,invoiceNo, RANK() OVER(PARTITION BY vehicalId ORDER BY dateUsed) AS KEY_CUR, odBeg,odEnd FROM #DATA) AS D0
INNER JOIN
(SELECT vehicalId,invoiceNo, RANK() OVER(PARTITION BY vehicalId ORDER BY dateUsed) - 1 AS KEY_LAST, odBeg,odEnd FROM #DATA) AS D1
ON D0.vehicalId = D1.vehicalId AND D0.KEY_CUR = D1.KEY_LAST
WHERE
D1.odBeg <> D0.odEnd
I am currently trying to split one value with percentage column. But as most of percentages values are 1/3, I am not able to get aboslute 100% with two decimal points in the value. For example:
Product Supplier percentage totalvalue customer_split
decimal(15,14) (decimal(18,2) decimal(18,2)
-------- -------- ------------ --------------- ---------------
Product1 Supplier1 0.33 10.00 3.33
Product1 Supplier2 0.33 10.00 3.33
Product1 Supplier3 0.33 10.00 3.33
So, here we are missing 0.01 in the value column and suppliers would like to put this missing 0.01 value against any one of the supplier randomly. I have been trying to get this done in a two sets of SQLs with temporary tables, but is there any simple way of doing this. If possible how can I get 0.34 in the percentage column itself for one of the above rows? 0.01 is negligible value, but when the value column is 1000000000 it is significant.
It sounds like you're doing some type of "allocation" here. This is a common problem any time you are trying to allocate something from a higher granulartiy to a lower granularity, and you need to be able to re-aggregate to the total value correctly.
This becomes a much bigger problem when dealing with larger fractions.
For example, if I try to divide a total value of, say $55.30 by eight, I get a decimal value of $6.9125 for each of the eight buckets. Should I round one to $6.92 and the rest to $6.91? If I do, I will lose a cent. I would have to round one to $6.93 and the others to $6.91. This gets worse as you add more buckets to divide by.
In addition, when you start to round, you introduce problems like "Should 33.339 be rounded to 33.34 or 33.33?"
If your business logic is such that you just want to take whatever remainder beyond 2 significant digits may exist and add it to one of the dollar values "randomly" so you don't lose any cents, #Diego is on the right track with this.
Doing it in pure SQL is a bit more difficult. For starters, your percentage isn't 1/3, it's .33, which will yield a total value of 9.9, not 10. I would either store this as a ratio or as a high-precision decimal field (.33333333333333).
P S PCT Total
-- -- ------------ ------
P1 S1 .33333333333 10.00
P2 S2 .33333333333 10.00
P3 S3 .33333333333 10.00
SELECT
BaseTable.P, BaseTable.S,
CASE WHEN BaseTable.S = TotalTable.MinS
THEN BaseTable.BaseAllocatedValue + TotalTable.Remainder
ELSE BaseTable.BaseAllocatedValue
END As AllocatedValue
FROM
(SELECT
P, S, FLOOR((PCT * Total * 100)) / 100 as BaseAllocatedValue,
FROM dataTable) BaseTable
INNER JOIN
(SELECT
P, MIN(S) AS MinS,
SUM((PCT * Total) - FLOOR((PCT * Total * 100)) / 100) as Remainder,
FROM dataTable
GROUP BY P) as TotalTable
ON (BaseTable.P = TotalTable.P)
It appears your calculation is an equal distribution based on the total number of products per supplier. If it is, it may be advantageous to remove the percentage and instead just store the count of items per supplier in the table.
If it is also possible to store a flag indicating the row that should get the remainder value applied to it, you could assign based on that flag instead of randomly.
run this, it will give an idea on how you can solve your problem.
I created a table called orders just with an ID to be easy to understand:
create table orders(
customerID int)
insert into orders values(1)
go 3
insert into orders values(2)
go 3
insert into orders values(3)
go 3
these values represent the 33% you have
1 33.33
2 33.33
3 33.33
now:
create table #tempOrders(
customerID int,
percentage numeric(10,2))
declare #maxOrder int
declare #maxOrderID int
select #maxOrderID = max(customerID) from orders
declare #total numeric(10,2)
select #total =count(*) from orders
insert into #tempOrders
select customerID, cast(100*count(*)/#total as numeric(10,2)) as Percentage
from orders
group by customerID
update #tempOrders set percentage = percentage + (select 100-sum(Percentage) from #tempOrders)
where customerID =#maxOrderID
this code will basically calculate the percentage and the order with the max ID, then it gets the diference from 100 to the percentage sum and add it to the order with the maxID (your random order)
select * from #tempOrders
1 33.33
2 33.33
3 33.34
This should be an easy task using Windowed Aggregate Functions. You probably use them already for the calculation of customer_split:
totalvalue / COUNT(*) OVER (PARTITION BY Product) as customer_split
Now sum up the customer_splits and if there's a difference to total value add (or substract) it to one random row.
SELECT
Product
,Supplier
,totalvalue
,customer_split
+ CASE
WHEN COUNT(*)
OVER (PARTITION BY Product
ROWS UNBOUNDED PRECEDING) = 1 -- get a random row, using row_number/order you might define a specific row
THEN totalvalue - SUM(customer_split)
OVER (PARTITION BY Product)
ELSE 0
END
FROM
(
SELECT
Product
,Supplier
,totalvalue
,totalvalue / COUNT(*) OVER (PARTITION BY Product) AS customer_split
FROM dropme
) AS dt
After more than one trial and test i think i found better solution
Idea
Get Count of all(Count(*)) based on your conditions
Get Row_Number()
Check if (Row_Number() value < Count(*))
Then select round(curr_percentage,2)
Else
Get sum of all other percentage(with round) and subtract it from 100
This steps will select current percentage every time EXCEPT Last one will be
100 - the sum of all other percentages
this is part of my code
Select your_cols
,(Select count(*) from [tbl_Partner_Entity] pa_et where [E_ID] =#E_ID)
AS cnt_all
,(ROW_NUMBER() over ( order by pe.p_id)) as row_num
,Case when (
(ROW_NUMBER() over ( order by pe.p_id)) <
(Select count(*) from [tbl_Partner_Entity] pa_et where [E_ID] =#E_ID))
then round(([partnership_partners_perc]*100),2)
else
100-
((select sum(round(([partnership_partners_perc]*100),2)) FROM [dbo].
[tbl_Partner_Entity] PEE where [E_ID] =#E_ID and pee.P_ID != pe.P_ID))
end AS [partnership_partners_perc_Last]
FROM [dbo].[tbl_Partner_Entity] PE
where [E_ID] =#E_ID