SQL: one to many, select just one - sql

I am writing a small tool for managing the people registered to an association.
I have a table USERS and a table CARDS. Each user can have more than one card (because cards expire).
With one query I would like to extract ALL user information plus ALL card information only for the most recent card (field: ISSUEDATE).
Here is a very basic example:
USERS table
IDUSER NAME SURNAME
------------------------
1 Robert Hill
CARDS table
IDCARD IDOWNER ISSUEDATE DESC
----------------------------------------
1 1 2010-01-01 'CARD ONE'
2 1 2010-08-18 'CARD TWO'
Basically I would like to get back from my query these fields:
IDUSER NAME SURNAME IDCARD IDOWNER ISSUEDATE DESC
---------------------------------------------------------------
1 Robert Hill 2 1 2010-08-18 'CARD TWO'
I tried, but I cannot manage :(
EDIT:
What happens if there is NO CARD linked to the user? (one user is registered but the association did not give him/her any card yet). Is there any way to adapt this query in order to get back user information anyway? The idea is to get all possible information for each user. This information is later used via JSON on ad ExtJS application.
Thanks for your answers.

Option 1 with NOT EXISTS query:
select u.IDUSER, u.NAME, u.SURNAME,
c.IDCARD, c.IDOWNER, c.ISSUEDATE, c.DESC
from USERS u
join CARDS c on u.IDUSER = c.IDOWNER
where not exists (select 1 from CARDS where IDOWDER = u.IDUSER and ISSUEDATE > c.ISSUEDATE)
Option 2 with LIMIT
select u.IDUSER, u.NAME, u.SURNAME,
c.IDCARD, c.IDOWNER, c.ISSUEDATE, c.DESC
from USERS u
join CARDS c on u.IDUSER = c.IDOWNER
order by c.ISSUEDATE desc
LIMIT 1

select u.IDUSER, u.NAME, u.SURNAME,
c.IDCARD, c.IDOWNER, c.ISSUEDATE, c.DESC
from USERS u
join CARDS c on u.IDUSER = c.IDOWNER and c.IDCARD in (
/* select biggest IDCARD numbers that has same IDOWNER thus eliminating multiple rows and remaining only 1 for each IDOWNER */
select MAX(IDCARD) as IDCARD
from USERS join CARDS on USERS.IDUSER = CARDS.IDOWNER
group by CARDS.IDOWNER
)

Altho LIMIT is not generally alowed in mysql subqueries, LIMIT 1 is an exception, so
"Select just one" is a perfect for that case.
LEFT JOIN insures that you will also have users without card data (card data will be NULL, you can use ifnull(desc, 'No Card Issued') to display that at your will)
and subquery insures that only the most recent card data for the user will be selected.
SELECT *
FROM users u
LEFT JOIN cards c
ON c.idcard = (SELECT idcard
FROM cards c1
WHERE c1.idowner = u.iduser
ORDER BY issuedate DESC
LIMIT 1)

Related

How to sum up max values from another table with some filtering

I have 3 tables
User Table
id
Name
1
Mike
2
Sam
Score Table
id
UserId
CourseId
Score
1
1
1
5
2
1
1
10
3
1
2
5
Course Table
id
Name
1
Course 1
2
Course 2
What I'm trying to return is rows for each user to display user id and user name along with the sum of the maximum score per course for that user
In the example tables the output I'd like to see is
Result
User_Id
User_Name
Total_Score
1
Mike
15
2
Sam
0
The SQL I've tried so far is:
select TOP(3) u.Id as User_Id, u.UserName as User_Name, SUM(maxScores) as Total_Score
from Users as u,
(select MAX(s.Score) as maxScores
from Scores as s
inner join Courses as c
on s.CourseId = c.Id
group by s.UserId, c.Id
) x
group by u.Id, u.UserName
I want to use a having clause to link the Users to Scores after the group by in the sub query but I get a exception saying:
The multi-part identifier "u.Id" could not be bound
It works if I hard code a user id in the having clause I want to add but it needs to be dynamic and I'm stuck on how to do this
What would be the correct way to structure the query?
You were close, you just needed to return s.UserId from the sub-query and correctly join the sub-query to your Users table (I've joined in reverse order to you because to me its more logical to start with the base data and then join on more details as required). Taking note of the scope of aliases i.e. aliases inside your sub-query are not available in your outer query.
select u.Id as [User_Id], u.UserName as [User_Name]
, sum(maxScore) as Total_Score
from (
select s.UserId, max(s.Score) as maxScore
from Scores as s
inner join Courses as c on s.CourseId = c.Id
group by s.UserId, c.Id
) as x
inner join Users as u on u.Id = x.UserId
group by u.Id, u.UserName;

INNER JOIN of pagevies, contacts and companies - duplicated entries

In short: 3 table inner join duplicates records
I have data in BigQuery in 3 tables:
Pageviews with columns:
timestamp
user_id
title
path
Contacts with columns:
website_user_id
email
company_id
Companies with columns:
id
name
I want to display all recorded pageviews and, if user and/or company is known, display this data next to pageview.
First, I join contact and pageviews data (SQL is generated by Metabase business intelligence tool):
SELECT
`analytics.pageviews`.`timestamp` AS `timestamp`,
`analytics.pageviews`.`title` AS `title`,
`analytics.pageviews`.`path` AS `path`,
`Contacts`.`email` AS `email`
FROM `analytics.pageviews`
INNER JOIN `analytics.contacts` `Contacts` ON `analytics.pageviews`.`user_id` = `Contacts`.`website_user_id`
ORDER BY `timestamp` DESC
It works as expected and I can see pageviews attributed to known contacts.
Next, I'd like to show pageviews of contacts with known company and which company is this:
SELECT
`analytics.pageviews`.`timestamp` AS `timestamp`,
`analytics.pageviews`.`title` AS `title`,
`analytics.pageviews`.`path` AS `path`,
`Contacts`.`email` AS `email`,
`Companies`.`name` AS `name`
FROM `analytics.pageviews`
INNER JOIN `analytics.contacts` `Contacts` ON `analytics.pageviews`.`user_id` = `Contacts`.`website_user_id`
INNER JOIN `analytics.companies` `Companies` ON `Contacts`.`company_id` = `Companies`.`id`
ORDER BY `timestamp` DESC
With this query I would expect to see only pageviews where associated contact AND company are known (just another column for company name). The problem is, I get duplicate rows for every pageview (sometimes 5, sometimes 20 identical rows).
I want to avoid selecting DISTINCT timestamps because it can lead to excluding valid pageviews from different users but with identical timestamp.
How to approach this?
Your description sounds like you have duplciates in companies. This is easy to test for:
select c.id, count(*)
from `analytics.companies` c
group by c.id
having count(*) >= 2;
You can get the details using window functions:
select c.*
from (select c.*, count(*) over (partition by c.id) as cnt
from `analytics.companies` c
) c
where cnt >= 2
order by cnt desc, id;

To display only the active status for one specific id and for same id all status should be disabled

I want to display person list who are Active for only one application starting 'a' and all other application should be 'DISABLED'.
Person id is in one table and application details will be in another table.
Have to select the Person id who is satisfies the following condition. I have attached the model result in which I have joined the three table . I need to select user as below(Highlighted),
person_id is a primary key.
The query used as follows:
SELECT * FROM (select p.person_id from ur_username u join ur_username_person up on u.username_id=up.username_id
join ur_person p on up.person_id=p.person_id
WHERE ( U.USERNAME LIKE 'A-%' OR U.USERNAME LIKE 'a-%')
AND u.status='ACTIVE' AND U.ROLE_ID =123 ) E
WHERE p.person_id IN ( select p.person_id from ur_username u join ur_username_person up on u.username_id=up.username_id
join ur_person p on up.person_id=p.person_id
WHERE ( U.USERNAME NOT LIKE 'A-%' OR U.USERNAME NOT LIKE 'a-%')
AND u.status='DISABLED' AND U.ROLE_ID =123 )
I am getting the person who is active in any other application also.
In that above table i want the username a-pri (starts with a-) also with status 'ACTIVE' all the other status for application id 123 is disabled . no other username is active for that 123 id.
We can try aggregating on the Application_id and then checking two things. First, we can check that a username a-pri who is active appears once, and only once. Second, we can assert that only one record appears as active. These two conditions would seem to meet your requirements.
SELECT
Application_id
FROM yourTable
GROUP BY
Application_id
HAVING
SUM(CASE WHEN username='a-pri' AND status='ACTIVE' THEN 1 ELSE 0 END) = 1 AND
SUM(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END) = 1;
Follow the link below which uses a larger sample data set to cover more edge cases.
Demo
Probably I do not correctly understand your request, but here is my code:
Select username from your_rable where status = 'ACTIVE';
If this is not what you intend to receive, please provide more dataset, maybe for more Application_id and also the needed result.

SQL query (Join without duplicates)

I have tables users and topics. Every user can have from 0 to several topics (one-to-many relationship).
How I can get only those users which have at least one topic?
I need all columns from users (without columns from topics) and without duplicates in table users. In last column I need number of topics.
UPDATED:
Should be like this:
SELECT user.*, count(topic.id)
FROM ad
LEFT JOIN topic ON user.id = topic.ad
GROUP BY user.id
HAVING count(topic.id) > 0;
but it takes 0 result. But it should not be 0.
Firstly you need to have your two tables, because you have left limited information about your table structure I will use an example to explain how this works, you should then be able to easily apply this to your own tables.
Firstly you need to have two tables (which you do)
Table "user"
id | name
1 | Joe Bloggs
2 | Eddy Ready
Table "topic"
topicid | userid | topic
1 | 1 | Breakfast
2 | 1 | Lunch
3 | 1 | Dinner
Now asking for a count against each user is done using the follwing;
SELECT user.name, count(topic.topicid)
FROM user
INNER JOIN topic ON user.id = topic.userid
GROUP BY user.name
If you use a left join, this will include records from the "user" table which does not have any rows in the "topic" table, however if you use an INNER JOIN this will ONLY include users who have a matching value in both tables.
I.e. because the user id "2" (which we use to join) is not listed in the topic table you will not get any results for this user.
Hope that helps!
use inner join and distinct
select distinct user_table.id
from user_table
inner join topics_table on topic_table.user_id = user_table.id
select u.id
, u.name
, count(b.topicName)
from user u
left join topic t on t.userid = u.id
group by u.id, u.name
You can select topic number per user and then join it with user data. Something like this:
with t as
(
select userid, count(*) as n
from topic
group by userid
)
SELECT user.*, t.n
FROM user
JOIN t ON user.id = t.userid

How to count users owning a certain game using one SQL query?

I have a table of games, like this:
ID | game name
1 legend of zelda
2 metal gear solid
3 resident evil
And another table of users owning those games, like this:
ID | User ID | Game ID
1 510 2
2 879 2
3 213 3
I need to make a list of games with a number of users owning them. From the above, the result would be:
legend of zelda (0 users)
metal gear solid (2 users)
resident evil (1 user)
How do I do the above using only 1 SQL query?
SELECT g."game name" AS name, COUNT(u.ID) AS users
FROM games AS g LEFT JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP BY g."game name"
I've used SQL standard double quotes around the space-containing delimited identifiers since you didn't identify which sub-species of SQL you are using.
This gives you two columns of output - the game name and a simple count. If you want the decorative '(0 users)' and '(1 user)' notations, then you are into some more serious pain unless your DBMS provides a convenient function to handle the correct inflections for different numbers of an object in your language (apparently English - but the rules vary by language).
Doing the simple-minded computerese:
SELECT TRIM(r.name) || ' (' || r.users || ' users)'
FROM (SELECT g."game name" AS name, COUNT(u.ID) AS users
FROM games AS g LEFT JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP BY g."game name") AS r
Or, slightly more sophisticated (but English-only):
SELECT TRIM(r.name) || ' (' || r.users || ' user' ||
CASE r.users WHEN 1 THEN '' ELSE 's' END || ')'
FROM (SELECT g."game name" AS name, COUNT(u.ID) AS users
FROM games AS g LEFT JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP BY g."game name") AS r
However, in my book, SQL is for the data; presentation is best done in the application. Therefore, I'd probably use the first query.
Join them, group on game id, and select the count(*) of rows, or count(distinct users). If one guy owns the game twice, and you want that to count as only one owner, distinct users.
SELECT game.name, count(*)
FROM games
JOIN ownershipInfo
ON games.id = ownershipInfo.id
GROUP BY game.name --missing from original answer, oops
That will give you the number of rows.
Since you want to include where there are zero, do "LEFT JOIN" instead of plain join. If you want the count not of rows but distinct owners:
SELECT game.name, count(distinct ownershipInfo.ownerID)
FROM games
JOIN ownershipInfo
ON games.id = ownershipInfo.id
GROUP BY game.name --missing from original answer, oops
As jonathon points out you need a GROUP BY to get the aggregate information. And here I just took the expedient of grouping by name - usually you'd group by the game's ID, and do something more like:
SELECT max(game.id), count(distinct ownershipInfo.ownerID)
FROM games
LEFT JOIN ownershipInfo
ON games.id = ownershipInfo.id
GROUP BY games.id
That way if two games have the same name, you'd get a row for both (of course it wouldn't be self explanatory but maybe you'd also have MAX(publisher) or something to clarify which one was which)
SELECT g."game name" AS name, COUNT(*) AS users
FROM games AS g
INNER JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP
BY g."game name"
UNION
SELECT g."game name" AS name, 0 AS users
FROM games AS g
WHERE NOT EXISTS (
SELECT *
FROM game_users AS u
WHERE g.ID = u."Game ID"
);