VLOOKUP-style range lookup in T-SQL - sql-server

Here's a tricky problem I haven't quite been able to get my head around. I'm using SQL Server 2008, and I have a sparse range table that looks like this:
Range Profession
----- ----------
0 Office Worker
23 Construction
54 Medical
Then I have another table with values that are within these ranges. I'd like to construct a query which joins these two tables and gives me the Profession value that is less than or equal to the given value. So let's say my other table looks like this:
Value
29
1
60
Then I'd like my join to return:
Value Profession
----- ----------
29 Construction
1 Office Worker
60 Medical
(because 29>the 23 for Construction but <=the 54 for Medical)
Is there any way I can get SQL to bend to my will in this manner, short of actually blowing out the range table to include every possible value?
Thank you.

Easist Way to do this is to add a another column to you sparse range table.
LowRange HighRange Profession
0 22 Office Worker
23 53 Construction
54 999999 Medical
Then use a query like this to get the range(table 2 is the one with the 29,1,60 values):
SELECT Table_2.JoinKey as Value, Table_1.Description as Profession
FROM Table_1 INNER JOIN Table_2
ON Table_2.JoinKey => Table_1.LowRangeKey
AND Table_2.JoinKey <= Table_1.HighRangeKey;

You could use CROSS APPLY:
select v.Value, p.Profession
from tblValues v
cross apply
(select top(1) pr.Profession
from tblProfessionRanges pr
where pr.Range <= v.Value ORDER BY pr.[Range] DESC) p
It should be faster than using max and doesn't need a max-range do be maintained.

I think I understand your problem. I created a table called professions with your values and a map_vals table with the look up values. Then I came up with this:
select p.range as `range1`, p.profession, v.value from professions p
inner join map_vals v ON v.value >= p.range
where p.range =
(select max(p3.range) from professions p3 where p3.range <= v.value)
order by v.value
which when given these values...
value
29
0
60
1
23
54
returns
range1 profession value
0 Office Worker 0
0 Office Worker 1
23 Construction 23
23 Construction 29
54 Medical 54
54 Medical 60
EDIT:
You could also use CROSS APPLY as shown by manfred-sorg but it requires an ORDER BY DESC or you will get the following:
select v.Value, p.Profession
from tblValues v
cross apply
(select top(1) pr.Profession
from tblProfessionRanges pr
where pr.Range <= v.Value) p
produces
Value Profession
----------- --------------------------------------------------
29 Office Worker
1 Office Worker
60 Office Worker
to get your desired result you need to change it to:
select v.Value, p.Profession
from tblValues v
cross apply
(select top(1) pr.Profession
from tblProfessionRanges pr
where pr.Range <= v.Value ORDER BY pr.[Range] DESC) p
Value Profession
----------- --------------------------------------------------
29 Construction
1 Office Worker
60 Medical
However, the sorting required here makes it less efficient than using MAX.

Related

Get value where ID in one table equals to the ID in another table

I am stuck on this SQL problem which may be easier than I think. So in a nutshell, how do I go about selecting the cost from the appropriate garage when the GarageHistID in the GarageCosts table equals to the ID in the GarageHistory table?
GarageCosts
GarageID Cost Version GarageHistID
950 213 1 455
950 342 3 NULL
GarageHistory
ID VendorID Version GarageID
454 44 1 NULL
455 2 1 950
456 44 2 NULL
Expected Output:
VendorID Cost Version
2 213 1
44 0 1
44 0 2
This is just a left join coalescing a null to zero.
SELECT
gh.VendorID,
ISNULL(gc.Cost,0) AS Cost,
gh.Version
FROM GarageHistory gh
LEFT JOIN GarageCost gc
ON gh.GarageID = gc.GarageID
AND gh.VersionID = gc.VersionID
There is no (specific) need to have bi-directional keys in your 2 tables, but you could use either for the join (along with VersionID).
The following query gives the exact results you mentioned in your question. You can use left join to join the two tables based on GarageHistID field in GarageCosts table and ID field in GarageHistory table
SELECT
gh.VendorID,
ISNULL(gc.Cost,0) AS Cost,
gh.[Version]
FROM GarageHistory gh
left JOIN GarageCosts gc
ON gh.ID = gc.GarageHistID
order by gc.Cost desc

SQL Server query to display all columns but with distinct values in one of the columns (not grouping anything)

I have a table with 106 columns. One of those columns is a "Type" column with 16 types.
I want 16 rows, where the Type is distinct. So, row 1 has a type of "Construction", row 2 has a type of "Elevator PVT", etc.
Using Navicat.
From what I've found (and understood) so far, I can't use Distinct (because that looks across all rows), I can't use Group By (because that's for aggregating data, which I'm not looking to do), so I'm stuck.
Please be gentle- I'm really really new at this.
Below is a part of the table (how can I share this normally?)- it's really big so I didn't share the whole thing. Below is a partial result I'm looking for, where the Violation_Type is unique and the rest of the columns display.
Got it.. Sheesh... (took me forever, but got it...)
D_ID B_ID V_ID V_Type S_ID c_f d_y l_u p_s du_p
------ ------ ------- -------------- ------ ----- ------ ------ ----- ------
184 117 V 032 Elevator PVT 2 8 0 0
4 140 V 100 Construction 1 8 0 0
10 116 V 122 Electric 1 8 2005 0 0
11 117 V 033 Boiler Local 1 0 2005 0 0
You can use ROW_NUMBER for this:
SELECT *
FROM(
SELECT *,
rn = ROW_NUMBER() OVER(PARTITION BY V_Type ORDER BY (SELECT NULL))
FROM tbl
)t
WHERE rn = 1
Modify the ORDER BY depending on what row you want to prioritize.
From the documentation:
Returns the sequential number of a row within a partition of a result
set, starting at 1 for the first row in each partition.
This means that for every row within a partition (specified by the PARTITION BY clause), sql-server assigns a number from 1 depending on the order specified in the ORDER BY clause.
ROW_NUMBER requires an ORDER BY clause. SELECT NULL tells the sql-server that we do not want to enforce a particular order. We just want the rows numbered by partition.
The WHERE rn = 1 obviously filters only rows that has a ROW_NUMBER of 1. This gives you one row for every V_TYPE available.

Joining two SQL tables based on the equality of few columns

I am trying to Create a SQL View by joining two SQL tables and return only the lowest value from second table and all the rows from first table similar to left join.
My problem can be clearly explained with the below example.
Table1
Id Product Grade Term Bid Offer
100 ABC A Q1 10 20
101 ABC A Q1 5 25
102 XYZ A Q2 25 30
103 XYZ B Q2 20 30
Table2
Id Product Grade Term TradeValue
1 ABC A Q1 100
2 ABC A Q1 95
3 XYZ B Q2 100
In the above data I want to join Table1 and Table2 when ever the columns Product,Grade and Term from both the tables are equal and return all the rows from Table1 while joining the lowest Value of the column TradeValue from Table2 to the first record of the match and making TradeValue as NULL for other rows of the resultant View and the resultant View should have the Id of Table2 as LTID
So the resultant SQL View should be
RESULT
Id Product Grade Term Bid Offer TradeValue LTID
100 ABC A Q1 10 20 95 2
101 ABC A Q1 5 25 NULL 2
102 XYZ A Q2 25 30 NULL NULL
103 XYZ B Q2 20 30 100 3
I tried using the following query
CREATE VIEW [dbo].[ViewCC]
AS
SELECT
a.Id,a.Product,a.Grade,a.Term,a.Bid,a.Offer,
b.TradeValue
FROM Table1 AS a
left JOIN (SELECT Product,Grade,Term,MIN(TradeValue) TradeValue from Table2 Group by Product,Grade,Term,) AS b
ON b.Product=a.Product
and b.Grade=a.Grade
and b.Term=a.Term
GO
The above Query returned the following data which is apt to the query I wrote but that is not what I was trying to get
Id Product Grade Term Bid Offer TradeValue
100 ABC A Q1 10 20 95
101 ABC A Q1 5 25 95 --This should be null
102 XYZ A Q2 25 30 NULL
103 XYZ B Q2 20 30 100
As we can see minimum value of TradeValue being assigned to all matching rows in Table1 and also I was not able to return Id As LTID from Table2 as I have issues with group by clause as I cannot group it by b.Id as it returns too many rows.
May I know a better way to deal with this?
You need a row number attached to each record from Table1, so that the requirement of only joining the first record from each group of Table1 can be fulfilled:
CREATE VIEW [dbo].[ViewCC]
AS
SELECT a.Id, a.Product, a.Grade, a.Term, a.Bid, a.Offer,
b.TradeValue, b.Id AS LTID
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY Product, Grade, Term ORDER BY Id) AS rn
FROM Table1
) a
OUTER APPLY (
SELECT TOP 1 CASE WHEN rn = 1 THEN TradeValue
ELSE NULL
END AS TradeValue, Id
FROM Table2
WHERE Product=a.Product AND Grade=a.Grade AND Term=a.Term
ORDER BY TradeValue) b
GO
OUTER APPLY returns a table expression containing either the matching record from Table2 with the lowest TradeValue, or NULL if no matching record exists.

How to call columns without using Group By clause

I am new to sql, i had one doubt
select t1.PayeeCode,t1.PayeeName,t1.PayeeIFSCCode,t1.grossAmount as ga,t2.Deduction
as da,(t1.grossAmount-t2.Deduction) as SubAmount from (
(select PayeeCode,PayeeName,PayeeIFSCCode,PayeeBankAcNo,sum(PayeeAmount) as
grossAmount from tblPayees where AccountType='g' group by payeeCode, PayeeName,PayeeIFSCCode,PayeeBankAcNo) t1
inner join
(select PayeeCode,sum(PayeeAmount) as deduction from tblPayees where
AccountType='d' group by payeeCode) t2
on t1.PayeeCode=t2.PayeeCode
)
curent result
PayeeCode payeename payeeifsccode ga da subamount type
--------------------------------------------------------------
p1 x 123 1300 1400 100 g
p1 z 34 450 550 100 g
p1 y 35 150 150 0 d
p2 z 45 150 100 50 d
expected result:
PayeeCode payeename payeeifsccode ga da subamount type
--------------------------------------------------------------
p1 x 123 1750 1950 200 g
p1 y 35 300 250 50 d
Here this is the Column 'tblPayees.PayeeName' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I know here why eeror is occured but , i dont want to group by payeeName, i wnat only payeecode.How to do that?
help me please
The problem is that you either need to include the column in group by clause or use an aggregate in select list like MIN, MAX, etc. otherwise the database engine doesn't know what to do with it i.e. what value from the group to select.
Now are you sure you cannot group on both columns? Can you have different PayeeName for the same PayeeCode? If not, you can include it in group by:
select PayeeCode, PayeeName from tblPayees group by PayeeCode, PayeeName
If PayeeName can vary for the same PayeeCode then you need to tell the database engine which value you want to select by means of aggregate function:
select PayeeCode, Max(PayeeName) as PayeeName from tblPayees group by PayeeCode
It is even possible to concatenate the PayeeName values in a comma-separated list, but this is out of scope of this question I guess.

T-SQL getting all unique groups with their usage count

How do I find the unique groups that are present in my table, and display how often that type of group is used?
For example (SQL Server 2008R2)
So, I would like to find out how many times the combination of
PMI 100
RT 100
VT 100
is present in my table and for how many itemid's it is used;
These three form a group because together they are assigned to a single itemid. The same combination is assigned to id 2527 and 2529, so therefore this group is used at least twice. (usagecount = 2)
(and I want to know that for all types of groups that are appearing)
The entire dataset is quite large, about 5.000.000 records, so I'd like to avoid using a cursor.
The number of code/pct combinations per itemid varies between 1 and 6.
The values in the "code" field are not known up front, there are more than a dozen values on average
I tried using pivot, but I got stuck eventually and I also tried various combinations of GROUP-BY and counts.
Any bright ideas?
Example output:
code pct groupid usagecount
PMI 100 1 234
RT 100 1 234
VT 100 1 234
CD 5 2 567
PMI 100 2 567
VT 100 2 567
PMI 100 3 123
PT 100 3 123
VT 100 3 123
RT 100 4 39
VT 100 4 39
etc
Just using a simple group:
SELECT
code
, pct
, COUNT(*)
FROM myTable
GROUP BY
code
, pct
Not too sure if that's more like what you're looking for:
select
uniqueGrp
, count(*)
from (
select distinct
itemid
from myTable
) as I
cross apply (
select
cast(code as varchar(max)) + cast(pct as varchar(max)) + '_'
from myTable
where myTable.itemid = I.itemid
order by code, pct
for xml path('')
) as x(uniqueGrp)
group by uniqueGrp
Either of these should return each combination of code and percentage with a group id for the code and the total number of instances of the code against it. You can use them for also adding the number of instances of the specific code/pct combo too for determining % contribution etc
select
distinct
t.code, t.pct, v.groupcol, v.vol
from
[tablename] t
inner join (select code, rank() over(order by count(*)) as groupcol,
count(*) as vol from [tablename] s
group by code) v on v.code=t.code
or
select
t.code, t.pct, v.groupcol, v.vol
from
(select code, pct from [tablename] group by code, pct) t
inner join (select code, rank() over(order by count(*)) as groupcol,
count(*) as vol from [tablename] s
group by code) v on v.code=t.code
Grouping by Code, and Pct should be enough I think. See the following :
select code,pct,count(p.*)
from [table] as p
group by code,pct

Resources