I want to write an efficient query which returns a list of fruits by type, the lowest price for the type of fruit and the name of the fruit. Right now, I have a query which return me the fruit type and the lowest price for that type (see below). But I am unable to get the name of the cheapest fruit.
Any idea how I can achieve that? Thanks.
CREATE TABLE Fruits (
[type] nvarchar(250),
[variety] nvarchar(250),
[price] money
)
GO
INSERT INTO Fruits VALUES ('Apple', 'Gala', 2.79)
INSERT INTO Fruits VALUES ('Apple', 'Fuji', 0.24)
INSERT INTO Fruits VALUES ('Apple', 'Limbertwig', 2.87)
INSERT INTO Fruits VALUES ('Orange', 'Valencia', 3.59)
INSERT INTO Fruits VALUES ('Pear', 'Bradford', 6.05)
SELECT type, MIN(price)
FROM Fruits
GROUP BY [type]
Use:
SELECT f.*
FROM FRUITS f
JOIN (SELECT t.type,
MIN(t.price) AS min_price
FROM FRUITS t
GROUP BY t.type) x ON x.type = f.type
AND x.min_price = f.price
I gather you're using SQL Server - if v2005 or newer, you could also use analytic/rank/windowing functions instead:
SELECT f.type, f.variety, f.price
FROM (SELECT t.type, t.variety, t.price,
ROW_NUMBER() OVER (PARTITION BY t.type ORDER BY t.price) AS rank
FROM FRUITS t) f
WHERE f.rank = 1
There are a number of ways to do this, one solution is below.
SELECT F2.type, f2.variety, f2.price
FROM
(
SELECT type, min(price) as price
FROM Fruits
GROUP BY [type]
) as MinData
INNER JOIN Fruits F2
ON (MinData.type = Type = F2.Type
AND MinData.price = F2.Price)
Keep in mind that if you have multiple items in a category with the same price at the minimum you will get multiple results.
There's a simple trick you can use for this sort of query if your table has a surrogate primary key. (Actually, you can do it without one, but it's more convoluted.)
The setup:
if object_id('tempdb..#Fruits') is not null drop table #Fruits
create table #Fruits (
[id] int identity(1,1) not null,
[type] nvarchar(250),
[variety] nvarchar(250),
[price] money
)
insert into #Fruits ([type], [variety], [price])
select 'Apple', 'Gala', 2.79 union all
select 'Apple', 'Fuji', 0.24 union all
select 'Apple', 'Limbertwig', 2.87 union all
select 'Orange', 'Valencia', 3.59 union all
select 'Pear', 'Bradford', 6.05
And now the SQL:
select * -- no stars in PROD!
from #Fruits a
where
a.id in (
select top 1 x.id
from #Fruits x
where x.[type] = a.[type]
order by x.price
)
Related
In TSQL, how do we select a value from a table with no relationship to the other.
I'm trying to select what rank an ID has, by looking up their score in another table. I was going to join and then use a 'where Score between From and TO' but, I had no luck joining.
Table_A
ID
Score
A
67
B
569
C
123
Table_B
From
To
Rank
1
99
Top100
100
499
Top500
500
999
Top1000
Expected query result:
ID
Rank
A
Top100
B
Top1000
C
Top500
I started with
Select ID From Table_A
Inner Join
I got lost here because there is no relationship
I could get the result using a scalar function, but in terms of performance, where Table_A has over 500k rows, it seemed a little sluggish because Table_B not only holds rank, but has other columns I need for the query.
For example:
Table_B
From
To
Rank
Level
Color
Category
1
99
Top100
Gold
Green
1
100
499
Top500
Silver
Yellow
5
500
999
Top1000
Bronze
Red
100
Basically, if I can be shown how to query at least the rank, I can get the other columns as well.
You can use BETWEEN as the JOIN condition, like so:
CREATE TABLE #Table_A
(
ID VARCHAR(255),
Score INT
)
;
INSERT #Table_A ([ID], [Score]) VALUES
('A', 67),
('B', 569),
('C', 123);
CREATE TABLE #Table_B
(
[From] INT,
[To] INT,
[Rank] VARCHAR(255)
)
INSERT #Table_B ([From], [To], [Rank]) VALUES
( 1, 99, 'Top100'), (100, 499, 'Top500'), (500, 999, 'Top1000');
-- Query here
SELECT A.[ID], B.[Rank]
FROM #Table_A A
INNER JOIN #Table_B B ON A.Score BETWEEN B.[From] AND B.[To]
You could also use CROSS APPLY to get your favorite result
;WITH Table_A AS
(
SELECT 'A' ID, 67 Score
Union
SELECT 'B' ID, 569 Score
Union
SELECT 'C' ID, 123 Score
),
Table_B AS
(
SELECT 1 [From], 99 [To], 'Top100' [Rank]
Union
SELECT 100 [From], 499 [To], 'Top500' [Rank]
Union
SELECT 500 [From], 999 [To], 'Top1000' [Rank]
)
SELECT Table_A.[ID], Table_B.[Rank]
FROM Table_A
CROSS APPLY Table_B
WHERE Table_A.Score
BETWEEN Table_B.[From] AND Table_B.[To]
GO
I have the following table named FruitCountry and I would like to check number of unique entries per fruit, I think I can achieve that by first grouping by fruit then countryID, then select distinct data and use COUNT() function to find the size, yet is there a less tedious way to achieve that? Thanks!
Fruit CountryID
-----------------
Apple 1
Apple 2
Apple 1
Grapes 3
Grapes 3
Fruit # of unique entries
-----------------
Apple 2
Grapes 1
Just count the unique countryid.
select Fruit, count(distinct CountryId)
from FruitCountry
group by Fruit
Use count(distinct columnname) to achieve distinct count of a column within the group:
select Fruit, count(distinct CountryId) [# of unique entries]
from FruitCountry
group by Fruit
If you have large amount of data it is a good idea to use a temporary table.
IF OBJECT_ID(N'tempdb..#fruits') IS NOT NULL DROP TABLE #fruits
IF OBJECT_ID(N'tempdb..#temp') IS NOT NULL DROP TABLE #temp
CREATE TABLE #fruits(Fruit VARCHAR(10), Id INT )
--INSERT INTO #fruits
INSERT INTO #fruits ( Fruit, Id ) VALUES ('Apple', 1 )
INSERT INTO #fruits ( Fruit, Id ) VALUES ( 'Apple', 2 )
INSERT INTO #fruits ( Fruit, Id ) VALUES ('Apple', 1 )
INSERT INTO #fruits ( Fruit, Id ) VALUES ('Grapes', 3 )
INSERT INTO #fruits ( Fruit, Id ) VALUES ('Grapes', 3 )
--SELECT * FROM #fruits
--Temp Table to hold Distinct Values
CREATE TABLE #temp (Fruit VARCHAR(10) , Id INT )
INSERT INTO #temp (Fruit, id ) SELECT DISTINCT Fruit, id FROM #fruits
--Final Query to get NoOfUniqueEntries
SELECT Fruit, count (*) AS NoOfUniqueEntries FROM #temp
GROUP BY Fruit
DROP TABLE #temp, #fruits
I want to compare values of two rows in one table,
The table is like follows.
I want to find record where -F sku greater than normal one
SKU ASIN Price
CT0144 B013VNZNYU 20.99
CT0144-F B013VNZNYU 17.64
Try like this,
DECLARE #table TABLE (
SKU VARCHAR(50)
,ASIN VARCHAR(50)
,Price FLOAT
)
INSERT INTO #table
VALUES (
'CT0144'
,'B013VNZNYU'
,'20.99'
)
,(
'CT0144-F'
,'B013VNZNYU'
,'17.64'
)
,(
'CT0144'
,'B013VNZNU'
,'10.99'
)
,(
'CT0144-F'
,'B013VNZNU'
,'18.64'
)
SELECT *
FROM #table
SELECT A.ASIN
,A.FPrice
,B.Normal
FROM (
SELECT ASIN
,MAX(PRICE) AS FPrice
FROM #table t1
WHERE SKU LIKE '%F'
GROUP BY ASIN
) A
INNER JOIN (
SELECT ASIN
,MAX(PRICE) AS Normal
FROM #table t1
WHERE SKU NOT LIKE '%F'
GROUP BY ASIN
) B ON A.ASIN = B.ASIN
WHERE A.FPrice > B.Normal
Try this
select t1.SKU,t1.ASIN,t1.Price from table as t1 inner join
(
select ASIN, max(price) as price from table group by ASIN
) as t2
on t1.ASIN=t2.ASIN and t1.price=t2.price
where t1.SKU like '%-F'
I have two types of sub query's in the statement.
First of all some sample data.
Table
CAT ID Weight GROUP
1 1 200 A
1 2 300 B
1 3 250 B
1 1 200 A
1 4 200 A
One sub query is a count of distinct IDs which works as expected.
( SELECT COUNT (distinct t1.ID)
FROM table t1
WHERE t1.group = 'A'
GROUP BY t1.cat)
AS [count],
The other sub query is a sum of the weight
( SELECT SUM(t1.weight)
FROM table t1
WHERE t1.group = 'A'
GROUP BY t1.cat)
AS [weight],
This doesn't give me what i need as it will total 600 when I want it to total 400 as i want only to use unique ID's as the first query does.
However by adding distinct...
( SELECT SUM(DISTINCT t1.weight)
FROM table t1
WHERE t1.group = 'A'
GROUP BY t1.cat)
AS [weight],
This only returns 200 as it is using distinct weight, what i want is it to use distinct ID in this, but how can i do this while still only selecting the weight?
Something like (logically speaking as this doesn't work)
( SELECT SUM(t1.weight)
FROM table t1
WHERE t1.group = 'A'
AND t1.ID IS DISTINCT
GROUP BY t1.cat)
AS [weight],
SELECT cat,SUM(weight) AS [weight] FROM
(SELECT *,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY ID) as rn
FROM table ) as tbl
WHERE [group] = 'A' AND rn=1
GROUP BY cat
I might be missing something, as I've looked at your sample data and what I believe is your desired output, but can you not just do a simple GROUP BY and SUM:
CREATE TABLE SampleData
([CAT] int, [ID] int, [Weight] int, [GROUP] varchar(1))
;
INSERT INTO SampleData
([CAT], [ID], [Weight], [GROUP])
VALUES
(1, 1, 200, 'A'),
(1, 2, 300, 'B'),
(1, 3, 250, 'B'),
(1, 1, 200, 'A'),
(1, 4, 200, 'A')
;
SELECT ID, COUNT(ID) AS [Counter], SUM(Weight) AS SumWeight
FROM SampleData
WHERE [GROUP] = 'A'
GROUP BY ID
To produce:
ID Counter SumWeight
1 2 400
4 1 200
let's say result of my select statements as follows (I have 5 of those):
Id Animal AnimalId
1 Dog Dog1
1 Cat Cat57
Id Transport TransportId
2 Car Car100
2 Plane Plane500
I'd like to get a result as follows:
Id Animal AnimalId Transport TransportId
1 Dog Dog1
1 Cat Cat57
2 Car Car100
2 Plane Plane500
What I can do is I can crate a tablevariable and specify all possible columns and insert records from each select statement into it. But maybe better solution like PIVOT?
Edit
queries: 1st: Select CategoryId as Id, Animal, AnimalId from Animal
2nd: Select CategoryId as Id, Transport, TransportId from Transport
How about this, if you need them in the same rows, this gets the row_number() for each row and joins on those:
select a.id,
a.aname,
a.aid,
t.tname,
t.tid
from
(
select id, aname, aid, row_number() over(order by aid) rn
from animal
) a
left join
(
select id, tname, tid, row_number() over(order by tid) rn
from transport
) t
on a.rn = t.rn
see SQL Fiddle with Demo
If you don't need them in the same row, then use UNION ALL:
select id, aname, aid, 'Animal' tbl
from animal
union all
select id, tname, tid, 'Transport'
from transport
see SQL Fiddle with Demo
Edit #1, here is a version with an UNPIVOT and PIVOT:
select an_id, [aname], [aid], [tname], [tid]
from
(
select *, row_number() over(partition by col order by col) rn
from animal
unpivot
(
value
for col in (aname, aid)
) u
union all
select *, row_number() over(partition by col order by col) rn
from transport
unpivot
(
value
for col in (tname, tid)
) u
) x1
pivot
(
min(value)
for col in([aname], [aid], [tname], [tid])
) p
order by an_id
see SQL Fiddle with Demo
This would do it for you:
SELECT
ID, field1, field2, '' as field3, '' as field4
FROM sometable
UNION ALL
SELECT
ID, '', '', field3, field4
FROM someothertable
create table Animal (
Animal varchar(50)
,AnimalID varchar(50)
)
create table Transport (
Transport varchar(50)
,TransportID varchar(50)
)
insert into Animal values ('Dog', 'Dog1')
insert into Animal values ('Cat', 'Cat57')
insert into Transport values ('Car', 'Car100')
insert into Transport values ('Plane', 'Plane500')
select ID = 1
,A.Animal
,A.AnimalID
,Transport = ''
,TransportID = ''
from Animal A
union
select ID = 2
,Animal = ''
,AnimalID = ''
,T.Transport
,T.TransportID
from Transport T
To get it in the format you want, select the values you want, and then null (or an empty string) for the other columns.
SELECT
CategoryId as Id,
Animal as 'Animal',
AnimalId as 'AnimalId',
null as 'Transport',
null as 'TransportId'
FROM Animal
UNION
SELECT
CategoryId as Id,
null as 'Animal',
null as 'AnimalId',
Transport as 'Transport',
TransportId as 'TransportId'
FROM Transport
I'm still not sure of the purpose of this, but this should give the output you want.
You shouldn't need to pivot, your results are already fine.
If you want, you can just UNION all 5 statements together in the same format as the first select: ID/Category/CategoryID. Then you'll get one long result set with all 5 sets appended 3 columns wide.
Is that what you want? Or do you need to distinguish between 'categories'?
given your example, try:
Select CategoryId as Id, Animal, AnimalId from Animal
union all
Select CategoryId as Id, Transport, TransportId from Transport
if you want, you can alias the columns like:
Select CategoryId as Id, Animal as category, AnimalId as categoryID from Animal
union all
Select CategoryId as Id, Transport, TransportId from Transport
you really don't need to pivot, just space out your columns like you were thinking initially. You don't pivot to move columns, you pivot to perform an aggregate function over grouped data.