I have 2 tables named player and team, which are tied together in a many-to-many relationship via a third table named player_team.
The table structure & current query can be found: http://sqlfiddle.com/#!2/e6db4/18
I need a query, that returns the player's data, along with team.id, which includes the player's maximum rating. Also, if the rating is <= X, then that player should be excluded.
The example query returns the correct results, but is quite inefficient.
Obviously, the same results can be achieved by accessing each table's rows only once, but the question is how to achieve this? (I'd prefer replies that are in PostgreSQL dialect)
Here is a query that you could try:
with all_comb as (
select p.id as PLID,
t.id as TID,
t.rating as RATING
from player p,
team t,
player_team pt
where p.id = pt.pid
and t.id = pt.tid
and t.rating >0)
select distinct
a.PLID,
a.TID,
a.RATING
from all_comb a
where a.RATING = (
select max(b.RATING)
from all_comb b
where a.PLID = b.PLID)
order by PLID;
The sqlfiddle is here.
Related
I would like to take values from one table column and subtract those values from another column from another table.
I was able to achieve this by joining those tables and then subtracting both columns from each other.
Data from first table:
SELECT max_participants FROM courses ORDER BY id;
Data from second table:
SELECT COUNT(id) FROM participations GROUP BY course_id ORDER BY course_id;
Here is some code:
SELECT max_participants - participations AS free_places FROM
(
SELECT max_participants, COUNT(participations.id) AS participations
FROM courses
INNER JOIN participations ON participations.course_id = courses.id
GROUP BY courses.max_participants, participations.course_id
ORDER BY participations.course_id
) AS course_places;
In general, it works, but I was wondering, if there is some way to make it simplier or maybe my approach isn't correct and this code will not work in some conditions? Maybe it needs to be optimized.
I've read some information about not to rely on natural order of result set in databases and that information made my doubts to appear.
If you want the values per course, I would recommend:
SELECT c.id, (c.max_participants - COUNT(p.id)) AS free_places
FROM courses c LEFT JOIN
participations p
ON p.course_id = c.id
GROUP BY c.id, c.max_participants
ORDER BY 1;
Note the LEFT JOIN to be sure all courses are included, even those with no participants.
The overall number is a little tricker. One method is to use the above as a subquery. Alternatively, you can pre-aggregate each table:
select c.max_participants - p.num_participants
from (select sum(max_participants) as max_participants from courses) c cross join
(select count(*) as num_participants from participants from participations) p;
Here's what I have:
Person
name = varchar
Helmet
person = foreignkey -> Person
is_safe = boolean
Now, for a batch job, I need to query (no ORM, just raw SQL) for all Person that have 0 Helmet that are safe. I could obviously just loop through each Person in the database, etc., but I need to do this in a single query and limit it to 100 at a time (there are novemdecillions of these suckers in the database), and remove each Person. I don't need the Helmet records for each to be attached in the result. I only need the Person records (naturally deleting will cascade), but I can't simply issue a DELETE in place of my SELECT because there are things I need to do elsewhere before deleting them.
I'm using Postgres, but I'd prefer to use a query that's more or less DB agnostic, if possible.
Here's what I've abstractly come up with:
SELECT * FROM person
WHERE (SELECT COUNT(*) FROM helmet
WHERE person_id = person.id AND is_safe = false) = 0
LIMIT 100
This is clearly not valid SQL, but I'm hoping there is a functionally equivalent, but valid version.
select *
from person
where person_id not in
(
select person_id
from helmet
where is_safe = false
)
SELECT *
FROM (
SELECT p.*
FROM Person p
INNER JOIN Helmet h ON p.id = h.person
GROUP BY p.id
HAVING SUM(h.is_safe) = 0
) inner_select
LIMIT 100
So, this query consists of two parts:
The workhorse of the query is the inner query. This query joins together each person with all his helmets. It then uses GROUP BY two relate all rows for a specific person together. Once we have a group, we can use aggregate functions on each group, and in this case we use SUM to count the number of helmets that are safe. The SUM is used by the HAVING-clause to only select groups that have the SUM of safe helmets (i.e the number of safe helmets) equal to zero.
The outer query ensures that the LIMIT is applied to the result of the inner query, and not the rows of the tables needed to calculate an accurate result.
SELECT person.*
FROM person
LEFT JOIN (SELECT DISTINCT person_id FROM helmet) AS T2 ON person.id = T2.person_id
WHERE T2.person_id IS NULL
LIMIT 100
I ended up using:
SELECT *
FROM person p
WHERE NOT EXISTS
(
SELECT h.person_id
FROM helmet h
WHERE h.person_id = p.id
AND is_safe = true
)
LIMIT 100
which turns out to stop scanning the table once it finds 100 results that match.
I have two tables with a many to one relationship which represent lots and bids within an auction system. Each lot can have zero or more bids associated with it. Each bid is associated with exactly one lot.
My table structure (with irrelevant fields removed) looks something like this:
For one type of auction the winning bid is the lowest unique bid for a given lot.
E.g. if there are four bids for a given lot: [1, 1, 2, 4] the lowest unique bid is 2 (not 1).
So far I have been able to construct a query which will find the lowest unique bid for a single specific lot (assuming the lot ID is 123):
SELECT id, value FROM bid
WHERE lot = 123
AND amount = (
SELECT value FROM bid
WHERE lot = 123
GROUP BY value HAVING COUNT(*) = 1
ORDER BY value
)
This works as I would expect (although I'm not sure it's the most graceful approach).
I would now like to construct a query which will get the lowest unique bids for all lots at once. Essentially I want to perform a JOIN on the two tables where one column is the result of something similar to the above query. I'm at a loss as to how to use the same approach for finding the lowest unique bid in a JOIN though.
Am I on the wrong track with this approach to finding the lowest unique bid? Is there another way I can achieve the same result?
Can anyone help me expand this query into a JOIN?
Is this even possible in SQL or will I have to do it in my application proper?
Thanks in advance.
(I am using SQLite 3.5.9 as found in Android 2.1)
You can use group by with a "having" condition to find the set of bids without duplicate amounts for each lot.
select lotname, amt
from lot inner join bid on lot.id = bid.lotid
group by lotname, amt having count(*) = 1
You can in turn make that query an inline view and select the lowest bid from it for each lot.
select lotname, min(amt)
from
(
select lotname, amt
from lot inner join bid on lot.id = bid.lotid
group by lotname, amt having count(*) = 1
) as X
group by X.lotname
EDIT: Here's how to get the bid id using this approach, using nested inline views:
select bid.id as WinningBidId, Y.lotname, bid.amt
from
bid
join
(
select x.lotid, lotname, min(amt) as TheMinAmt
from
(
select lot.id as lotid, lotname, amt
from lot inner join bid on lot.id = bid.lotid
group by lot.id, lotname, amt
having count(*)=1
) as X
group by x.lotid, x.lotname
) as Y
on Y.lotid = bid.lotid and Y.TheMinAmt = Bid.amt
I think you need some subqueries to get to your desired data:
SELECT [b].[id] AS [BidID], [l].[id] AS [LotID],
[l].[Name] AS [Lot], [b].[value] AS [BidValue]
FROM [bid] [b]
INNER JOIN [lot] [l] ON [b].[lot] = [l].[id]
WHERE [b].[id] =
(SELECT TOP 1 [min].[id]
FROM [bid] [min]
WHERE [min].[lot] = [b].[lot]
AND NOT EXISTS(SELECT *
FROM [bid] [check]
WHERE [check].[lot] = [min].[lot]
AND [check].[value] = [min].[value]
AND [check].[id] <> [min].[id])
ORDER BY [min].[value] ASC)
The most inner query (within the exists) checks if there are no other bids on that lot, having the same value.
The query in the middle (top 1) determines the minimum bid of all unique bids on that lot.
The outer query makes this happen for all lots, that have bids.
SELECT lot.name, ( SELECT MIN(bid.value) FROM bid Where bid.lot = lot.ID) AS MinBid
FROM Lot INNER JOIN
bid on lot.ID = bid.ID
If I understand you correctly this will give you everylot and their smallest bid
I have a table for players, a table for teams, and a table for team_players (using SQLServer 2005). Not all players are on a team. I would like to craft a query that will return one row for each non-team player, and one row for each team (i.e. there is one result row representing all 20 players on the Knicks, and that result row contains just the Knicks' team_id, but all non-team players get their own rows with unique player_id in the results set).
I am currently trying to have my result set consist of just one column, and am doing it like so:
SELECT DISTINCT ISNULL(tp.team_id, p.player_id) FROM players p
LEFT JOIN team_players tp ON tp.player_id = p.id
My question is: how can I allow this query to be ordered by teams with the most players DESC, and then by player name alphabetical for the non-team players? Is this possible with my current query base? Should I use a different method, like perhaps a UNION, to make this possible?
As in Martin's case, this is untested:
;with cteCombineData as (
select t.id, null as player_name, count(*) as player_count
from team t
inner join team_players tp
on t.id = tp.team_id
group by t.id
union all
select p.id, p.player_name, 0 as player_count
from players p
left join team_players tp
on p.id = tp.player_id
where tp.player_id is null
)
select id
from cteCombineData
order by player_count desc, player_name
I have 2 tables in SQL Server 2005 db with structures represented as such:
CAR:
CarID bigint,
CarField bigint,
CarFieldValue varchar(50);
TEMP: CarField bigint, CarFieldValue varchar(50);
Now the TEMP table is actually a table variable containing data collected through a search facility. Based on the data contained in TEMP, I wish to filter out and get all DISTINCT CarID's from the CAR table exactly matching those rows in the TEMP table. A simple Inner Join works well, but I want to only get back the CarID's that match ALL the rows in TEMP exactly. Basically, each row in TEMP is supposed to be denote an AND filter, whereas, with the current inner join query, they are acting more like OR filters. The more rows in TEMP, the less rows I expect showing in my result-set for CAR. I hope Im making sense with this...if not please let me know and I'll try to clarify.
Any ideas on how I can make this work?
Thank u!
You use COUNT, GROUP BY and HAVING to find the cars that have exactly that many mathicng rows as you expect:
select CarID
from CAR c
join TEMP t on c.CarField = t.CarField and c.CarFieldValue = t.CarFieldValue
group by CarID
having COUNT(*) = <the number you expect>;
You can even make <the number you expect> be a scalar subquery like select COUNT(*) from TEMP.
SELECT *
FROM (
SELECT CarID,
COUNT(CarID) NumberMatches
FROM CAR c INNER JOIN
TEMP t ON c.CarField = t.CarField
AND c.CarFieldValue = t.CarFieldValue
GROUP BY CarID
) CarNums
WHERE NumberMatches = (SELECT COUNT(1) FROM TEMP)
Haven't tested this, but I don't think you need a count to do what you want. This query ought to be substantially faster because it avoids a potentially huge number of counts. This query finds all the cars which are missing a value and then filters them out.
select distinct carid from car where carid not in
(
select
carid
from
car c
left outer join temp t on
c.carfield = t.carfield
and c.carfieldvalue = t.carfieldvalue
where
t.carfield is null
)
Hrm...
;WITH FilteredCars
AS
(
SELECT C.CarId
FROM Car C
INNER JOIN Temp Criteria
ON C.CarField = Criteria.CarField
AND C.CarFieldValue = Critera.CarFieldValue
GROUP BY C.CarId
HAVING COUNT(*) = (SELECT COUNT(*) FROM Temp)
)
SELECT *
FROM FilteredCars F
INNER JOIN Car C ON F.CarId = C.CarId
The basic premise is that for ALL criteria to match an INNER JOIN against your temp table must produce as many records as there are within that table. The HAVING clause at the end of the FilteredCars query should widdle the results down to those that match all criteria.