Using Outer Apply To return null values - sql

I have the following tables:
User which has a primary key and Username e.g.
ID Username
1 Fred
2 John
3 Jack
Event which has primary key and event name
ID Eventname
1 Ferrari Road Show
2 Flower Show
UserStatusUpdates table which has primary key, a status update and a userid (foreign key)
ID UserID UserStatus
1 1 Really Good
2 1 Leaving Now
3 2 I concur
And An Event Attendee table which has a primary key and two foreign keys (Event primary key and User primary key)
ID UserID EventID
1 1 1
2 2 1
3 3 1
The problem I am encountering is that I need to return all the event attendees plus their latest status update, however there are cases where the user has not actually made a status update.
This is what my query looks like:
SELECT EventAttendee.*, Users.UserName,Users.USERS_ID,
Users.ThumbnailPic,
Users.CountryName,
ISNULL(UserStatusUpdates.UserStatus,'No Updates')AS LastUpdate,
UserStatusUpdates.MediaTypeID,UserStatusUpdates.USERSTATUS_ID,(UserStatusUpdates.AddDate)
FROM EventAttendee
JOIN Users ON Events.UserID = Users.USERS_ID
OUTER APPLY (SELECT TOP 1 UserStatusUpdates.UserStatus,UserStatusUpdates.MediaTypeID,
UserStatusUpdates.USERSTATUS_ID,UserStatusUpdates.AddDate, UserStatusUpdates.UserID
FROM UserStatusUpdates where UserStatusUpdates.UserID = Users.USERS_ID
ORDER BY AddDate DESC) AS UserStatusUpdates WHERE UserStatusUpdates.UserID = EventAttendee.UserID
WHERE EventAttendee.EventID = #EventID
AND Users.bDeleted = 'False'
AND Users.bSuspended = 'False'
END
How can I get back users who may not have made an update?

Your query has multiple where clauses, and you're already specifying the UserStatusUpdates.UserID = Users.USERS_ID join in the subquery. Try this:
SELECT
EventAttendee.*,
Users.UserName,Users.USERS_ID,
Users.ThumbnailPic,
Users.CountryName,
ISNULL(UserStatusUpdates.UserStatus,'No Updates') AS LastUpdate,
UserStatusUpdates.MediaTypeID,
UserStatusUpdates.USERSTATUS_ID,
UserStatusUpdates.AddDate
FROM
EventAttendee
JOIN Users ON
Events.UserID = Users.USERS_ID
OUTER APPLY (
SELECT TOP 1
UserStatusUpdates.UserStatus,
UserStatusUpdates.MediaTypeID,
UserStatusUpdates.USERSTATUS_ID,
UserStatusUpdates.AddDate,
UserStatusUpdates.UserID
FROM
UserStatusUpdates
WHERE
UserStatusUpdates.UserID = Users.USERS_ID
ORDER BY
AddDate DESC
) AS UserStatusUpdates
WHERE
EventAttendee.EventID = #EventID
AND Users.bDeleted = 'False'
AND Users.bSuspended = 'False'
END

Related

What's the best way to compare the results of 2 Subselects on the same table?

Is there a better way to compare the result of 2 Sub-Selects on the same Table, than using 2 separate Queries?
In the following Query, I'd like to select all incidents where the "client" of the "creator" is not equal to the "client" of another user which I pass later on.
SELECT
*
FROM
incident i
WHERE
i.client_id = 150
AND ( --can this AND be shortend?
SELECT
ur1.CLIENT_ID
FROM
USER ur1
WHERE
ur1.USER = upper(i.CREATOR)
) != (
SELECT
ur2.CLIENT_ID
FROM
USER ur2
WHERE
ur2.USER = upper('other')
)
Minimal reproducable example
Users inside the USER-Table are always Uppercase
Every User is unique
1 User can only have 1 Client_Id
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=99ac066a9abd339cd9a80a5b78716138
If you have the constraints:
ALTER TABLE "USER" ADD CONSTRAINT
user__id_user__pk PRIMARY KEY ("USER");
ALTER TABLE "USER" ADD CONSTRAINT
user__id_user__u UNIQUE (client_id, "USER");
ALTER TABLE incident ADD CONSTRAINT
incident__id_creator__fk FOREIGN KEY (client_id, creator)
REFERENCES "USER" (client_id, "USER");
Then you can use:
SELECT *
FROM incident i
WHERE i.client_id = 150
AND NOT EXISTS (
SELECT u.client_id
FROM "USER" u
WHERE u."USER" = upper('joe')
AND u.client_id = i.client_id
)
If you do not have the foreign key constraint (and just have the unique/PK constraints on the USER table) then:
SELECT *
FROM incident i
WHERE i.client_id = 150
AND EXISTS (
SELECT 1
FROM "USER" u
WHERE u."USER" IN (i.creator, upper('joe'))
HAVING COUNT(DISTINCT client_id) > 1
)
db<>fiddle here
Maybe self join? Something like this:
SELECT i.*
FROM incident i
JOIN USER u1 ON u1.USER = i.creator
JOIN USER u2 ON u2.client_id <> u1.client_id
WHERE i.client_id = 150
AND u2.USER = 'OTHER'
I think you can do it this way,
SELECT i.*
FROM incident i, user ur1, user ur2
WHERE i.client_id = 150
AND ur1.user = UPPER(i.creator)
AND ur2.user = UPPER(‘other’)
AND ur1.client_id != ur2.client_id

SQL SELECT composite primary key

I have 2 tables User1 and Relationship and it's some kind of user-friends relationship. I need to find all the friends of a user.
For example: I have a user with ID=3 and I need to find all the users who have Relationship.STATUS = 1 with this user.
FYI, I'm a beginner in SQL, so I know it's quite simple task but I can't handle it.
I’ve tried to use JOIN but it wasn't successfully.
SELECT *
FROM USER1
RIGHT JOIN RELATIONSHIP R on USER1.USER1_ID = R.USER_ID_FROM OR USER1.USER1_ID = R.USER_ID_TO
WHERE R.USER_ID_FROM = :id
OR R.USER_ID_TO = :id AND R.STATUS = :status AND USER1_ID != :id;
My tables:
TABLE USER1
(
USER1_ID NUMBER PRIMARY KEY,
USER_NAME NVARCHAR2(64),
REAL_NAME NVARCHAR2(64),
EMAIL NVARCHAR2(64),
PHONE_NUMBER NVARCHAR2(64),
BIRTH_DATE TIMESTAMP,
POST_ID NUMBER,
PASSWORD NVARCHAR2(16)
);
TABLE RELATIONSHIP
(
USER_ID_FROM NUMBER NOT NULL,
USER_ID_TO NUMBER NOT NULL,
STATUS SMALLINT DEFAULT 0,
CONSTRAINT FK_USER_ONE FOREIGN KEY (USER_ID_FROM) REFERENCES USER1 (USER1_ID),
CONSTRAINT FK_USER_TWO FOREIGN KEY (USER_ID_TO) REFERENCES USER1 (USER1_ID),
CONSTRAINT PK_RELATIONSHIP PRIMARY KEY (USER_ID_FROM, USER_ID_TO)
);
If you just want the ids then you can use conditional logic to which which of the user ids you want:
select (case when user_id_from = 1 then user_id_to else user_id_from
end) as other_user_id
from relationship r
where 1 in (user_id_from, user_id_to) and
status = 3;
If you actually want all the user information, I would suggests exists:
select u.*
from user1 u
where exists (select 1
from relationship r
where r.status = 3 and
( (r.user_id_from = 1 and r.user_id_to = u.id) or
(r.user_id_to = 1 and r.user_id_from = u.id)
)
);
Hum not sure but I would try something like this:
SELECT u.* FROM USER1 u LEFT JOIN RELATIONSHIP r ON r.USER_ID_FROM = :id AND r.STATUS = 1 AND u.USER1_ID = r.USER_ID_TO;
Where the id is the ID of the user you want to find the friends.
SELECT *
FROM USER1 U
LEFT JOIN RELATIONSHIP R ON R.USER_ID_FROM = U.ID AND R.[STATUS] = 1
LEFT JOIN USER1 F ON R.[USER_ID_TO] = F.[ID]
WHERE U.[Id] = your_id
Then simply filter the users. Left join is used because a user may not have any friends. And I understood that you want to find the user and any friends for that users.
Try this:
(SELECT *
FROM USER1 u
LEFT JOIN RELATIONSHIP f ON u.USER1_ID = f.USER_ID_FROM
WHERE f.USER_ID_TO= 3 AND f.STATUS = 1)
union
(SELECT *
FROM USER1 o
LEFT JOIN RELATIONSHIP t ON o.USER1_ID = t.USER_ID_TO
WHERE t.USER_ID_FROM= 3 AND t.STATUS = 1)

SQL SUM() by value of another column

I am creating a generic key/value data store in MS SQL Server. I have a UserDecimalData table defined as follows
create table [UserDecimalData] (
[UserID] nvarchar(64) not null,
[ValueKey] uniqueidentifier not null,
[Value] decimal null
);
alter table [UserDecimalData]
add constraint PK_UserDecimalData primary key clustered ([UserID], [Date], [ValueKey]);
and as an example, here is some data that uses a GUID ending with 0001 to indicate an individual's score in a game, and a GUID ending with 0002 to indicate which team a user is on.
UserID ValueKey Value
Dave 00000000-0000-0000-0000-000000000001 35
Dave 00000000-0000-0000-0000-000000000002 1
Phil 00000000-0000-0000-0000-000000000001 35
Phil 00000000-0000-0000-0000-000000000002 1
Pete 00000000-0000-0000-0000-000000000001 35
Pete 00000000-0000-0000-0000-000000000002 2
I can easily find the score for an individual by searching for their UserID + ValueKey ending with 0001. I can also find out which team an individual is in by searching for their UserID + the value of the row where the ValueKey ends with 0002.
My question is, how can I get a list of team scores? In this case team 1 would have 70 and team 2 would have 35.
Here is one method:
select sum(udd.value)
from UserDecimalData udd
where udd.ValueKey like '%1' and
exists (select 1
from UserDecimalData udd2
where udd2.ValueKey like '%2' and
udd2.UserId = udd.UserId
);
select v1.Value as Team, sum(v2.value) as TeamScore
from userdecimaldata v1
join userdecimaldata v2 on v2.UserId = v1.UserId and v2.[Date] = v1.[Date]
where v1.ValueKey = #clankey and v2.ValueKey = #scoreKey
group by v1.Value
OUTPUT
Team TeamSccore
1 70
2 35
--first way
with commands as (
select UserID as UserID, Value as command
from UserDecimalData
where ValueKey like '%2'
)
select c.command, sum(Value)
from UserDecimalData u
left join commands c on u.UserID = c.UserID
where ValueKey like '%1'
group by command
GO
--second way
select c.command, sum(Value)
from UserDecimalData u
left join (
select UserID as UserID, Value as command
from UserDecimalData
where ValueKey like '%2'
) c on u.UserID = c.UserID
where ValueKey like '%1'
group by command
GO
--results
--1 70
--2 35
select u1.Value, sum(u0.Value)
from UserDecimalData u1
left join UserDecimalData u0 on u0.UserID = u1.UserID and u0.ValueKey like '%1'
where u1.ValueKey like '%2'
group by u1.Value
GO

group by not grouping aggregate?

Let's say I am trying to build an opinion poll app, such that I can create a template of an opinion poll, give it multiple sections/questions, assign multiple people to different copies of a given question, create varying measures (happyness, succesfulness, greenness) and assign different questions different weights to apply to all of these measures.
Something like so:
CREATE TABLE users (
id SERIAL NOT NULL PRIMARY KEY
);
CREATE TABLE opinion_poll_templates (
id SERIAL NOT NULL PRIMARY KEY
);
CREATE TABLE opinion_poll_instances (
id SERIAL NOT NULL PRIMARY KEY,
template_id INTEGER NOT NULL REFERENCES opinion_poll_templates(id)
);
CREATE TABLE section_templates (
id SERIAL NOT NULL PRIMARY KEY,
opinion_poll_id INTEGER NOT NULL REFERENCES opinion_poll_templates(id)
);
CREATE TABLE section_instances (
id SERIAL NOT NULL PRIMARY KEY,
opinion_poll_id INTEGER NOT NULL REFERENCES opinion_poll_instances(id),
template_id INTEGER NOT NULL REFERENCES section_templates(id)
);
CREATE TABLE question_templates (
id SERIAL NOT NULL PRIMARY KEY,
section_id INTEGER NOT NULL REFERENCES section_templates(id)
);
CREATE TABLE measure_templates (
id SERIAL NOT NULL PRIMARY KEY,
opinion_poll_id INTEGER NOT NULL REFERENCES opinion_poll_templates(id)
);
CREATE TABLE answer_options (
id SERIAL NOT NULL PRIMARY KEY,
question_template_id INTEGER NOT NULL REFERENCES question_templates(id),
weight FLOAT8
);
CREATE TABLE question_instances (
id SERIAL NOT NULL PRIMARY KEY,
template_id INTEGER NOT NULL REFERENCES question_templates(id),
opinion_poll_id INTEGER NOT NULL REFERENCES opinion_poll_instances(id),
section_id INTEGER NOT NULL REFERENCES section_instances(id),
answer_option_id INTEGER NOT NULL REFERENCES answer_options(id),
contributor_id INTEGER
);
CREATE TABLE measure_instances (
id SERIAL NOT NULL PRIMARY KEY,
opinion_poll_id INTEGER NOT NULL REFERENCES opinion_poll_instances(id),
template_id INTEGER NOT NULL REFERENCES measure_templates(id),
total_score INTEGER
);
CREATE TABLE scores (
id SERIAL NOT NULL PRIMARY KEY,
question_template_id INTEGER NOT NULL REFERENCES question_templates(id),
measure_template_id INTEGER NOT NULL REFERENCES measure_templates(id),
score INTEGER NOT NULL
);
Now let's say I am interested in the per measureInstance (per measure assigned to an opinion poll) cross question, cross user average?
WITH weighted_score AS (
SELECT AVG(answer_options.weight), measure_instances.id
FROM question_instances
INNER JOIN answer_options ON question_instances.template_id = answer_options.question_template_id
INNER JOIN scores ON question_instances.template_id = scores.question_template_id
INNER JOIN measure_instances ON measure_instances.template_id=scores.measure_template_id
WHERE measure_instances.opinion_poll_id = question_instances.opinion_poll_id
GROUP BY measure_instances.id
)
UPDATE measure_instances
SET total_score=(SELECT avg FROM weighted_score
WHERE weighted_score.id = measure_instances.id)*100
RETURNING total_score;
This seems to not only not group as expected, but produced incorrect results.
Why is the result an integer rather then a float? Why is the result not being grouped by measure instance instead being identical across all?
And why is the result incorrect for any of them?
A demonstration: http://sqlfiddle.com/#!15/dcce8/1
EDIT: In working through explaining exactly what I wanted, I realized the source of my problem was that I was simply adding percentages, rather then normalizing across questions as a percentage.
My new and improved sql is:
WITH per_question_percentage AS (
SELECT SUM(answer_options.weight)/COUNT(question_instances.id) percentage, question_templates.id qid, opinion_poll_instances.id oid
FROM question_instances
INNER JOIN answer_options ON question_instances.answer_option_id = answer_options.id
INNER JOIN question_templates ON question_templates.id = question_instances.template_id
INNER JOIN opinion_poll_instances ON opinion_poll_instances.id = question_instances.opinion_poll_id
GROUP BY question_templates.id, opinion_poll_instances.id
), max_per_measure AS (
SELECT SUM(scores.score), measure_instances.id mid, measure_instances.opinion_poll_id oid
FROM measure_instances
INNER JOIN scores ON scores.measure_template_id=measure_instances.template_id
GROUP BY measure_instances.id, measure_instances.opinion_poll_id
), per_measure_per_opinion_poll AS (
SELECT per_question_percentage.percentage * scores.score score, measure_instances.id mid, measure_instances.opinion_poll_id oid
FROM question_instances
INNER JOIN scores ON question_instances.template_id = scores.question_template_id
INNER JOIN measure_instances ON measure_instances.template_id = scores.measure_template_id
INNER JOIN max_per_measure ON measure_instances.id = max_per_measure.mid
INNER JOIN per_question_percentage ON per_question_percentage.qid = question_instances.template_id
WHERE measure_instances.opinion_poll_id = question_instances.opinion_poll_id AND question_instances.opinion_poll_id = per_question_percentage.oid
GROUP BY measure_instances.id, measure_instances.opinion_poll_id, per_question_percentage.percentage, scores.score
)
UPDATE measure_instances
SET total_score = subquery.result*100
FROM (SELECT SUM(per_measure_per_opinion_poll.score)/max_per_measure.sum result, per_measure_per_opinion_poll.mid, per_measure_per_opinion_poll.oid
FROM max_per_measure, per_measure_per_opinion_poll
WHERE per_measure_per_opinion_poll.mid = max_per_measure.mid
AND per_measure_per_opinion_poll.oid = max_per_measure.oid
GROUP BY max_per_measure.sum, per_measure_per_opinion_poll.mid, per_measure_per_opinion_poll.oid)
AS subquery(result, mid, oid)
WHERE measure_instances.id = subquery.mid
AND measure_instances.opinion_poll_id = subquery.oid
RETURNING total_score;
Is this canonical sql? Is there anything I should be aware of with this kind of CTE chaining (or otherwise)? Is there a more efficient way to achieve the same thing?
This is a bit long for a comment.
I don't understand the questions.
Why is the result an integer rather then a float?
Because measure_instances.total_score is an integer and that is what the returning clause is returning.
Why is the result not being grouped by measure instance instead being identical across all?
When I run the CTE independently, the values are 0.45. The data and logic dictate the same values.
And why is the result incorrect for any of them?
I think you mean "for all of them". In any case, the results look correct to me.
If you run this query against data in your demo:
SELECT
answer_options.weight, measure_instances.id
FROM
question_instances
INNER JOIN
answer_options ON question_instances.template_id = answer_options.question_template_id
INNER JOIN
scores ON question_instances.template_id = scores.question_template_id
INNER JOIN
measure_instances ON measure_instances.template_id=scores.measure_template_id
WHERE
measure_instances.opinion_poll_id = question_instances.opinion_poll_id
ORDER BY
2;
You will get:
| weight | id |
|--------|----|
| 0.5 | 1 |
| 0.25 | 1 |
| 0.25 | 1 |
| 0.75 | 1 |
| 0.5 | 1 |
| 0.75 | 2 |
| 0.5 | 2 |
| 0.25 | 2 |
| 0.5 | 2 |
| 0.25 | 2 |
If you calculate averages by hand, you will get:
For id=1 ==> 0.5+0.25+0.25+0.75 + 0.5 = 2.25 ==> 2.25 / 5 = 0.45
For id=2 ==> 0.75 + 0.5 + 0.25 + 0.5 + 0.25 = 2.25 ==> 2.25 / 5 = 0.45
It seems to me, that this query is working perfectly.
Please explain why these results are wrong to you, and what do you expect to get from the above data and query?

Using (IN operator) OR condition in Where clause as AND condition

Please look at following image, I have explained my requirements in the image.
alt text http://img30.imageshack.us/img30/5668/shippment.png
I can't use here WHERE UsageTypeid IN(1,2,3,4) because this will behave as an OR condition and fetch all records.
I just want those records, of first table, which are attached with all 4 ShipmentToID .
All others which are attached with 3 or less ShipmentToIDs are not needed in result set.
Thanks.
if (EntityId, UsageTypeId) is unique:
select s.PrimaryKeyField, s.ShipmentId from shipment s, item a
where s.PrimaryKeyField = a.EntityId and a.UsageTypeId in (1,2,3,4)
group by s.PrimaryKeyField, s.ShipmentId having count(*) = 4
otherwise, 4-way join for the 4 fields,
select distinct s.* from shipment s, item a, item b, item c, item d where
s.PrimaryKeyField = a.EntityId = b.EntityId = c.EntityId = d.EntityId and
a.UsageTypeId = 1 and b.UsageTypeId = 2 and c.UsageTypeId = 3 and
d.UsageTypeId = 4
you'll want appropriate index on (EntityId, UsageTypeId) so it doesn't hang...
If there will never be duplicates of the UsageTypeId-EntityId combo in the 2nd table, so you'll never see:
EntityUsageTypeId | EntityId | UsageTypeId
22685 | 4477 | 1
22687 | 4477 | 1
You can count matching EntityIds in that table.
WHERE (count(*) in <tablename> WHERE EntityId = 4477) = 4
DECLARE #numShippingMethods int;
SELECT #numShippingMethods = COUNT(*)
FROM shippedToTable;
SELECT tbl1.shipmentID, COUNT(UsageTypeId) as Usages
FROM tbl2 JOIN tbl1 ON tbl2.EntityId = tbl1.EntityId
GROUP BY tbl1.EntityID
HAVING COUNT(UsageTypeId) = #numShippingMethods
This way is preferred to the multiple join against same table method, as you can simply modify the IN clause and the COUNT without needing to add or subtract more tables to the query when your list of IDs changes:
select EntityId, ShipmentId
from (
select EntityId
from (
select EntityId
from EntityUsage eu
where UsageTypeId in (1,2,3,4)
group by EntityId, UsageTypeId
) b
group by EntityId
having count(*) = 4
) a
inner join Shipment s on a.EntityId = s.EntityId