SQL Inner Join and Grouping - sql

It's been a while since I've done SQL, but I have a rather pressing issue.
My db-layout is as following:
Now, starting from a Users.ID, I want to get all the Rounds the user has played. The user could be Hosts.HostID, Host.GuestID, or even both. Where he is both, it should not show op in the results.
The results I need from the Query are the Hosts.Name and all the fields of Rounds. In general what I want to do is display a list of all the Hosts (actually these are the Games) in which the user has participated, as a Host or as a Guest, along with perhaps a total score. When clicking on this, some dropdown will appear showing the individual round scores, words, ...
Now I was wondering whether this was possible in a single query. Of course I could do a query getting all the Hosts and then per Host a query for each Round, but that doesn't seem that performant. This is what I've come up with so far:
SELECT Rounds.ID, Rounds.GameID, Rounds.Round, Rounds.Score, Rounds.Word
, Hosts.ID, Hosts.HostID, Hosts.GuestID
FROM Rounds INNER JOIN Hosts
ON Rounds.GameID = Hosts.ID
INNER JOIN Users
ON Hosts.hostID = Users.ID
WHERE Users.ID = 5
The issue is however that it doesn't filter out where the user is both host AND guest, and I can't seem to Group it by Hosts.ID either.

Add Hosts.hostID <> Hosts.guestID to the where clause.

If you are using SQL Server 2005 or later version, you could modify your present query like this:
SELECT Rounds.ID, Rounds.GameID, Rounds.Round, Rounds.Score, Rounds.Word
, Hosts.ID, Hosts.HostID, Hosts.GuestID
FROM Rounds INNER JOIN Hosts
ON Rounds.GameID = Hosts.ID
CROSS APPLY
(
SELECT Hosts.HostID
UNION
SELECT Hosts.GuestID
) AS u (UserID)
WHERE u.UserID = 5
;
The CROSS APPLY clause would produce either one or two rows, depending on whether HostID and GuestID are equal. In either event, the WHERE condition would ultimately leave at most one. Thus, the above query would give you only the games (with all their rounds) where the specified user participated.
And from that query you could easily get to something like this:
SELECT Hosts.ID,
TotalScore = SUM(Rounds.Score)
FROM
...
GROUP BY
Hosts.ID
;

Related

How to join two queries in access

I am incapable of make a query that return me a results as follows:
TABLES: series, usuarios, siguiendo, valoraciones_personales
Each table has got this records:
example: field1(value), field2(value),...
series (I refer a tv show, I am spanish and here we say "serie=tv_show")
1. id_serie(1),id_titulo('Sons of anarchy')
2. id_serie(2),id_titulo('Lost')
usuarios (user)
1. id_usuario(1), nick('david')
siguiendo (a usser follow a series)
1. id_serie(1),id_usuario(1)
2. id_serie(2),id_usuario(1)
valoraciones_personales (personal assessments)
1. id_serie(1),id_usuario(1),nota(8)
Ok, what I want is a result with all records of the table siguiendo, and if that user valued one of that series, it must shows the score (nota in spanish), and if that user didnĀ“t scored that series, I want to show "without score".
The view I want:
*titulo, nota*
- Sons of anarchy, 8
- Lost, without score
Can anyone help me?
Specifically in MSACCESS
Create a query called something like AllUserSeries
SELECT
U.UserID
,U.FullName
,S.SeriesID
,S.SeriesName
FROM
usuarios as U
,series as S
This is the equivalent of a cross join
Then another:
SELECT
A.FullName
,A.SeriesName
,Nz(Cstr(R.Score),"Not Rated") as Rating
FROM
AllUserSeries AS A
LEFT OUTER JOIN valoraciones_personales AS R
ON A.UserID = R.UserID
AND A.SeriesID = R.SeriesID
WHERE
A.UserID = #UserID
The tricky bit is getting a list of all the series a user may have liked. to do this normally i would do a cross join to get all permutations that could exist, then left join from there to the ratings table using Nz to handle null values as you see fit.
*sorry for kinda making up the other column names it was easier for me to use English hope that okay :D

SQL - SELECT with JOINED Table

I'm trying to do a selection from the user tabel. For each user i would like to sum the Timediff for all posts in tblregtime for current user for given date parameters.
Problem is that i need to get information even if there is no registration done at the given date for current user. If no registration i need output that current user has TotalDiff=0. My current SQL doesn't work in this way. It will just give the fname,lname and TotalDiff if there is a post in tblregtime
sql:
select u.fname,u.lname, sum(cast(TIME_TO_SEC(TIMEDIFF(r.edate,r.sdate)) AS UNSIGNED)-r.break_time) as TotalDiff
from tbluser u
RIGHT OUTER JOIN tblregtime r on r.userid=u.id where r.projectid=21
and year(r.sdate)=2013 and month(r.sdate)=10 and day(r.sdate)=7
If you are trying to keep everything in tbluser, then you want a left join instead of a right join. However, you also need to move the where conditions into the on clause. Otherwise, when there is no match, the comparisons will fail (because the r. values will be NULL):
select u.fname,u.lname,
sum(cast(TIME_TO_SEC(TIMEDIFF(r.edate,r.sdate)) AS UNSIGNED)-r.break_time) as TotalDiff
from tbluser u LEFT JOIN
tblregtime r
on r.userid = u.id and
r.projectid = 21 and
year(r.sdate) = 2013 and month(r.sdate) = 10 and day(r.sdate) = 7;
I would also recommend that you change the final date comparison to something like:
r.sdate = '2013-10-07'
This form would allow the use of an index on r.sdate. As you have written it, the SQL engine (at least the SQL engines I am familiar with) would not be smart enough to use the index.

Select first or random row in group by

I have this query using PostgreSQL 9.1 (9.2 as soon as our hosting platform upgrades):
SELECT
media_files.album,
media_files.artist,
ARRAY_AGG (media_files. ID) AS media_file_ids
FROM
media_files
INNER JOIN playlist_media_files ON media_files.id = playlist_media_files.media_file_id
WHERE
playlist_media_files.playlist_id = 1
GROUP BY
media_files.album,
media_files.artist
ORDER BY
media_files.album ASC
and it's working fine, the goal was to extract album/artist combinations and in the result set have an array of media files ids for that particular combo.
The problem is that I have another column in media files, which is artwork.
artwork is unique for each media file (even in the same album) but in the result set I need to return just the first of the set.
So, for an album that has 10 media files, I also have 10 corresponding artworks, but I would like just to return the first (or a random picked one for that collection).
Is that possible to do with only SQL/Window Functions (first_value over..)?
Yes, it's possible. First, let's tweak your query by adding alias and explicit column qualifiers so it's clear what comes from where - assuming I've guessed correctly, since I can't be sure without table definitions:
SELECT
mf.album,
mf.artist,
ARRAY_AGG (mf.id) AS media_file_ids
FROM
"media_files" mf
INNER JOIN "playlist_media_files" pmf ON mf.id = pmf.media_file_id
WHERE
pmf.playlist_id = 1
GROUP BY
mf.album,
mf.artist
ORDER BY
mf.album ASC
Now you can either use a subquery in the SELECT list or maybe use DISTINCT ON, though it looks like any solution based on DISTINCT ON will be so convoluted as not to be worth it.
What you really want is something like an pick_arbitrary_value_agg aggregate that just picks the first value it sees and throws the rest away. There is no such aggregate and it isn't really worth implementing it for the job. You could use min(artwork) or max(artwork) and you may find that this actually performs better than the later solutions.
To use a subquery, leave the ORDER BY as it is and add the following as an extra column in your SELECT list:
(SELECT mf2.artwork
FROM media_files mf2
WHERE mf2.artist = mf.artist
AND mf2.album = mf.album
LIMIT 1) AS picked_artwork
You can at a performance cost randomize the selected artwork by adding ORDER BY random() before the LIMIT 1 above.
Alternately, here's a quick and dirty way to implement selection of a random row in-line:
(array_agg(artwork))[width_bucket(random(),0,1,count(artwork)::integer)]
Since there's no sample data I can't test these modifications. Let me know if there's an issue.
"First" pick
Wouldn't it be simpler / cheaper to just use min():
SELECT m.album
,m.artist
,array_agg(m.id) AS media_file_ids
,min(m.artwork) AS artwork
FROM playlist_media_files p
JOIN media_files m ON m.id = p.media_file_id
WHERE p.playlist_id = 1
GROUP BY m.album, m.artist
ORDER BY m.album, m.artist;
Abitrary / random pick
If you are looking for a random selection, #Craig already provided a solution with truly random picks.
You could also use a CTE to avoid additional scans on the (possibly big) base table and then run two separate (cheap) subqueries on the small result set.
For arbitrary selection - not truly random, the result will depend on the physical order of rows in the table and implementation-specifics:
WITH x AS (
SELECT m.album, m.artist, m.id, m.artwork
FROM playlist_media_files p
JOIN media_files m ON m.id = p.media_file_id
)
SELECT a.album, a.artist, a.media_file_ids, b.artwork
FROM (
SELECT album, artist, array_agg(id) AS media_file_ids
FROM x
) a
JOIN (
SELECT DISTINCT ON (1,2) album, artist, artwork
FROM x
) b USING (album, artist);
For truly random results, you can add an ORDER BY .. random() like this to subquery b:
JOIN (
SELECT DISTINCT ON (1, 2) album, artist, artwork
FROM x
ORDER BY 1, 2, random()
) b USING (album, artist);

SQL: order by how many rows there are (using COUNT?)

For SMF, I'm making a roster for the members of my clan (please don't come with "You should ask SMF", because that is completely irrelevant; this is just contextual information).
I need it to select all members (from smf_members) and order it by how many permissions they have in smf_permissions (so the script can determine who is higher in rank).
You can retrieve how many permissions there are by using: COUNT(permission) FROM smf_permissions.
I am now using this SQL:
SELECT DISTINCT(m.id_member), m.real_name, m.date_registered
FROM smf_members AS m, smf_permissions AS p
WHERE m.id_group=p.id_group
ORDER BY COUNT(p.permission)
However, this only returns one row! How to return several rows?
Cheers,
Aart
You need a GROUP BY. I've also rewritten with explicit JOIN syntax. You might need to change to LEFT JOIN if you want to include members with zero permissions.
SELECT m.id_member,
m.real_name,
m.date_registered,
COUNT(p.permission) AS N
FROM smf_members AS m
JOIN smf_permissions AS p
ON m.id_group = p.id_group
GROUP BY m.id_member,
m.real_name,
m.date_registered
ORDER BY COUNT(p.permission)

MySQL to return only last date / time record

We have a database that stores vehicle's gps position, date, time, vehicle identification, lat, long, speed, etc., every minute.
The following select pulls each vehicle position and info, but the problem is that returns the first record, and I need the last record (current position), based on date (datagps.Fecha) and time (datagps.Hora). This is the select:
SELECT configgps.Fichagps,
datacar.Ficha,
groups.Nombre,
datagps.Hora,
datagps.Fecha,
datagps.Velocidad,
datagps.Status,
datagps.Calleune,
datagps.Calletowo,
datagps.Temp,
datagps.Longitud,
datagps.Latitud,
datagps.Evento,
datagps.Direccion,
datagps.Provincia
FROM asigvehiculos
INNER JOIN datacar ON (asigvehiculos.Iddatacar = datacar.Id)
INNER JOIN configgps ON (datacar.Configgps = configgps.Id)
INNER JOIN clientdata ON (asigvehiculos.Idgroup = clientdata.group)
INNER JOIN groups ON (clientdata.group = groups.Id)
INNER JOIN datagps ON (configgps.Fichagps = datagps.Fichagps)
Group by Fichagps;
I need same result I'm getting, but instead of the older record I need the most recent
(LAST datagps.Fecha / datagps.Hora).
How can I accomplish this?
Add ORDER BY datagps.Fecha DESC, datagps.Hora DESC LIMIT 1 to your query.
I'm not sure why you are having any problems with this as Lex's answers seem good.
I would start putting ORDER BY's in your query so it puts them in an order, when it's showing the record you want as the first one in the list, then add the LIMIT.
If you want the most recent, then the following should be good enough:
ORDER BY datagps.Fecha DESC, datagps.Hora DESC
If you simply want the record that was added to the database most recently (irregardless of the date/time fields), then you could (assuming you have an auto-incremental primary key in the datagps table (I assume it's called dataID for this example)):
ORDER BY datagps.dataID DESC
If these aren't showing the data you want - then there is something missing from your example (maybe data-types aren't DATETIME fields? - if not - then maybe a CONVERT to change them from their current type before ORDERing BY would be a good idea)
EDIT:
I've seen the screenshot and I'm confused as to what the issue is still. That appears to be showing everything in order. Are you implying that there are many more than 5 records? How many are you expecting?
Do you mean: for each record returned, you want the one row from the table datagps with the latest date and time attached to the result? If so, how about this:
# To show how the query will be executed
# comment to return actual results
EXPLAIN
SELECT
configgps.Fichagps, datacar.Ficha, groups.Nombre, datagps.Hora, datagps.Fecha,
datagps.Velocidad, datagps.Status, datagps.Calleune, datagps.Calletowo,
datagps.Temp, datagps.Longitud, datagps.Latitud, datagps.Evento,
datagps.Direccion, datagps.Provincia
FROM asigvehiculos
INNER JOIN datacar ON (asigvehiculos.Iddatacar = datacar.Id)
INNER JOIN configgps ON (datacar.Configgps = configgps.Id)
INNER JOIN clientdata ON (asigvehiculos.Idgroup = clientdata.group)
INNER JOIN groups ON (clientdata.group = groups.Id)
INNER JOIN datagps ON (configgps.Fichagps = datagps.Fichagps)
########### Add this section
LEFT JOIN datagps b ON (
configgps.Fichagps = b.Fichagps
# wrong condition
#AND datagps.Hora < b.Hora
#AND datagps.Fecha < b.Fecha)
# might prevent indexes to be used
AND (datagps.Fecha < b.Fecha OR (datagps.Fecha = b.Fecha AND datagps.Hora < b.Hora))
WHERE b.Fichagps IS NULL
###########
Group by configgps.Fichagps;
Similar question here only that that one uses outer joins.
Edit (again):
The conditions are wrong so corrected it. Can you show us the output of the above EXPLAIN query so we can pinpoint where the bottle neck is?
As hurikhan77 said, it will be better if you could convert both of the the columns into a single datetime field - though I'm guessing this would not be possible for your case (since your database is already being used?)
Though if you can convert it, the condition (on the join) would become:
AND datagps.FechaHora < b.FechaHora
After that, add an index for datagps.FechaHora and the query would be fast(er).
What you probably want is getting the maximum of (Fecha,Hora) per grouped dataset? This is a little complicated to accomplish with your column types. You should combine Fecha and Hora into one column of type DATETIME. Then it's easy to just SELECT MAX(FechaHora) ... GROUP BY Fichagps.
It could have helped if you posted your table structure to understand the problem.