SQL statement I can't wrap my head around (brain too small) - php

I'm writing sort of a travel-'dating' app.
Users register themselves
Users tell the app if they are male or female
Users tell the app which countries they would like to visit
Users tell the app if they want to travel with males (pref_m=1) or females (pref_f=1)
My tables
table 1: users
id (key) | gender | pref_m | pref_f
------------------------------------
1 male 1 0
2 male 1 1
table 2: countryselection
id (key) | userid | countryid
------------------------------------
1 1 123
2 1 111
3 1 100
4 1 110
5 2 123
6 2 111
7 2 202
8 2 210
So what the select statement has to do
Input: the userid of the current user
Output (in logic): SELECT the userids AND matching countries OF ALL people that want to travel to the same countries as I do, and want to travel with someone that has my gender
(join) Of that selection I obviously only need the people that are of the gender that I am looking for.
ORDERED by people that have the most matching countries with me DESC.
What I have so far (warning: not much)
$sql = "SELECT userid,count(*) AS matches from countryselection";
$sql .= " WHERE countryid IN (SELECT countryid FROM countryselection WHERE userid = :userid) GROUP BY userid ORDER BY matches DESC;";
This gives me a list of all people that want to travel to the same countries as me (and how many countries we have in common)
final note
I'm obviously struggling with the gender-selection part.
Not sure if I have done the right thing to store the user selections in the way that I have.
I might need some guidance there too.
Obviously - thanks all.

SELECT
us2.id, -- etc.
COUNT(cs2.countryid) as countries_in_common
FROM
countryselection cs1 -- let's gather user countries he want to visit
LEFT JOIN -- now let's find other users!
countryselection cs2 ON
(
cs2.userid <> :userid AND -- which are not him
cs2.countryid = cs1.countryid -- and want to visit same countries
)
INNER JOIN -- let's grab our user_data
users us1 ON
(
us1.id = cs1.userid
)
INNER JOIN -- and let's grab other user data!
users us2 ON
(
us2.id = cs2.userid
)
WHERE
cs1.userid = :userid AND -- finding our user countries he want to visit
-- final checks
(
(us1.pref_m = 1 AND us2.gender = 'male')
-- he is looking for male and second user is male
OR
(us1.pref_f = 1 AND us2.gender = 'female')
-- he is looking for female and second user is female
) AND
(
(us2.pref_m = 1 AND us1.gender = 'male')
OR
(us2.pref_f = 1 AND us1.gender = 'female')
)
GROUP BY
cs2.userid -- finally group by user_id
Best thing is there are no sub-queries, and you can easily use this query in many ways. (changing order, group by, and using aggregate functions)

It's pretty easy if you don't do the sorting by most countries in common (you could do it in code later if the result sets won't be too large):
SELECT
o.id userid, u_cs.countryid
FROM users u
JOIN countryselection u_cs ON (u.id = u_cs.userid)
JOIN countryselection o_cs ON (u_cs.countryid = o_cs.countryid)
JOIN users o ON (o_cs.userid = o.id)
WHERE
u.id = :userid AND -- The user we want
u.id <> o.id AND -- Exclude ourselves
( -- Check whether the other person is
-- compatible with us
(u.pref_m = 1 AND o.gender = 'male') OR
(u.pref_f = 1 AND o.gender = 'female')
) AND
( -- Check whether we're compatible with the
-- other person
(o.pref_m = 1 AND u.gender = 'male') OR
(o.pref_f = 1 AND u.gender = 'female')
)
SQL Fiddle
If you do want the sorting, I think the best option is to use GROUP_CONCAT (because MySQL sucks and doesn't support windowing/analytic functions).
SELECT
o.id userid, GROUP_CONCAT(u_cs.countryid) countries
FROM users u
JOIN countryselection u_cs ON (u.id = u_cs.userid)
JOIN countryselection o_cs ON (u_cs.countryid = o_cs.countryid)
JOIN users o ON (o_cs.userid = o.id)
WHERE
u.id = :userid AND -- The user we want
u.id <> o.id AND -- Exclude ourselves
( -- Check whether the other person is
-- compatible with us
(u.pref_m = 1 AND o.gender = 'male') OR
(u.pref_f = 1 AND o.gender = 'female')
) AND
( -- Check whether we're compatible with the
-- other person
(o.pref_m = 1 AND u.gender = 'male') OR
(o.pref_f = 1 AND u.gender = 'female')
)
GROUP BY o.id
ORDER BY COUNT(u_cs.countryid) DESC
You could probably pull this off with some nasty subqueries too, but I get the feeling it'll kill performance.
SQL Fiddle

SELECT t4.id, COUNT(t4.id) AS frequency
FROM users t1
LEFT JOIN countryselection t2
ON t1.id = t2.userid
INNER JOIN countryselection t3
ON t2.userid != t3.userid AND t2.countryid = t3.countryid
INNER JOIN users t4
ON t3.userid = t4.id
AND ((t4.pref_m = 1 AND t1.gender = 'male' OR t4.pref_f = 1 AND t1.gender = 'female')
AND (t1.pref_m = 1 AND t4.gender = 'male' OR t1.pref_f = 1 AND t4.gender = 'female'))
WHERE t1.id = ?
GROUP BY t4.id
ORDER BY frequency DESC
Similar to others here but using appropriate join types and join conditions instead of where conditions.
From MySQL docs:
The conditional_expr used with ON is any conditional expression of the
form that can be used in a WHERE clause. Generally, you should use the
ON clause for conditions that specify how to join tables, and the
WHERE clause to restrict which rows you want in the result set.

I think this works
select me.id meid
, them.id themid
, me.gender mygender
, them.gender themgender
, me.pref_m mepref_m
, me.pref_f mepref_f
, them.pref_m thempref_m
, them.pref_f thempref_f
, csme.countryid
from users me
cross
join users them
inner
join countryselection csme
on me.id = csme.userid
inner
join countryselection csthem
on them.id = csthem.userid
where csme.countryid = csthem.countryid
and ((me.gender = 'male' and them.pref_m) or (me.gender = 'female' and them.pref_f))
and ((them.gender = 'male' and me.pref_m) or (them.gender = 'female' and me.pref_f))
and me.id != them.id
and me.id = 2
http://sqlfiddle.com/#!2/06351/25/0
I intentionally left out the group by so that the results are more easily verified.

inferred DDL :
create table users (id int, gender text, pref_m bool, pref_f bool);
create table countryselection (id int, userid int, countryid int);
Here is CSV that you can .import (using sqlite3, after doing .separator ,) into the tables in the question:
users.csv:
1,male,1,0
2,male,1,1
countryselection.csv
1,1,123
2,1,111
3,1,100
4,1,110
5,2,123
6,2,111
7,2,202
8,2,210
peter's sql, edited to use field names from question:
SELECT
us2.id,
COUNT(cs2.*) as countries_in_common
FROM
countryselection cs1
LEFT JOIN
countryselection cs2 ON
(
cs2.userid <> $userid AND
cs2.countryid = cs1.countryid
)
LEFT JOIN
users us1 ON
(
us1.id = cs1.userid
)
LEFT JOIN
users us2 ON
(
us2.id = cs2.userid
)
WHERE
cs1.userid = $userid AND
cs2.userid IS NOT NULL AND
(
(us1.pref_m = 1 AND us2.gender = 'male')
OR
(us1.pref_f = 1 AND us2.gender = 'female')
) AND
(
(us2.pref_m = 1 AND us1.gender = 'male')
OR
(us2.pref_f = 1 AND us1.gender = 'female')
)
GROUP BY
cs2.userid
;
you can execute it like this:
sqlite3 myDBname < peters_sql.sql
setting $userid to 1, i get 2 as output.

UPDATE: (added the countries)
SELECT u1.id AS uid1
, u2.id AS uid2
, cs.countryid
FROM users u1
, users u2
JOIN countryselection cs ON cs.userid = u2.id
-- WHERE u1.id < u2.id -- tiebreaker
WHERE u1.id = 12345
AND EXISTS (
SELECT * FROM countryselection cs1
JOIN countryselection cs2 ON cs1.countryid = cs2.countryid
WHERE cs1.userid = u1.id
AND cs2.userid = u2.id
)
AND ((u1.pref_m = True AND u2.gender = 'male')
OR (u1.pref_f = True AND u2.gender = 'female') )
-- the love must be mutual ...
AND ((u2.pref_m = True AND u1.gender = 'male')
OR (u2.pref_f = True AND u1.gender = 'female') )
;

Related

Multiple LEFT JOINS with Multiple COUNT() count all colum

I have 4 tables.
table groups
| ID | NAME |
1 Premium
2 Silver
table user
| ID | group_id | NAME |
1 1 Serhan
2 2 Farhat
table user_statistics
| ID | user_id | TYPE |
1 1 1
2 2 0
table votes
| ID | user_id | VOTE |
1 1 1
2 2 0
3 1 0
I created an sql query to retrieve user details who is in same group. It's worked! Then I want to retrieve any of vote that have been voted to user in groups. I want to count the vote. So basically I've made this sql query.
global $conn;
$res_groups = array();
$stmt = $conn->prepare("
SELECT * FROM groups
");
$stmt->execute();
$stmt->setFetchMode(PDO::FETCH_ASSOC);
while ($group = $stmt->fetch()){
$groups = array();
$groups['id'] = $group['id'];
$groups['name'] = $group['name'];
$user_arr = array();
$stmts = $conn->prepare('
SELECT l.*,
(SELECT MAX(ls.date) from user_statistics ls WHERE ls.user_id = l.id GROUP BY ls.user_id) as ls_date,
(SELECT SUM(IF(ls.type="0", ls.type, 0)) FROM user_statistics ls WHERE ls.user_id = l.id GROUP BY ls.user_id) as ls_us,
(SELECT SUM(IF(ls.type="1", ls.type, 0)) FROM user_statistics ls WHERE ls.user_id = l.id GROUP BY ls.user_id) as ls_uk,
(SELECT COUNT(*) FROM user_statistics WHERE type="1" AND ls.user_id = l.id GROUP BY ls.user_id) as totals,
(SELECT COUNT(*) FROM votes v WHERE v.vote=0 AND confirm=0 AND v.user_id = l.id) as badvote,
(SELECT COUNT(*) FROM votes v WHERE v.vote=1 AND confirm=0 AND v.user_id = l.id) as goodvote
FROM user l
LEFT JOIN
user_statistics ls on l.id = ls.user_id
LEFT JOIN votes v on v.user_id = l.id
WHERE l.group_id = '.$groups['id'].' AND status = 1
GROUP BY l.id,ls.user_id
');
$stmts->execute();
$stmts->setFetchMode(PDO::FETCH_ASSOC);
while ($usr = $stmts->fetch()){
$totalvote=($usr['badvote']+$usr['goodvote']);
if($totalvote>0){
$badvote=bcdiv($usr['badvote']*100/$totalvote,1,2);
$goodvote=bcdiv($usr['goodvote']*100/$totalvote,1,2);
}else{
$badvote=0;
$goodvote=0;
}
$votes[] = array(
"count" => $usr['badvote'],
"percent" => $badvote
);
$votes[] = array(
"count" => $usr['goodvote'],
"percent" => $goodvote
);
$user_arr[] = array(
"id" => $usr['id'],
"group_id" => $usr['group_id'],
"name" => $usr['name'],
"votes_summary" => $votes
);
}
$groups['list'] = $user_arr;
$res_groups[] = $groups;
}
All code seems work unless one thing. The VOTE is always return to count all column in my database VOTES and apply the data to all of my user. What I want is to get how many vote that each user get based on vote type GOOD or BAD.
Any help will be nice.
Looking to your code you could refactor your query avoiding select subquery one for each row and using two subquery with group by in join.
You have also the same code for bad and good vote could be you need different code for obtain different values
SELECT l.*
, t1.ls_date
, t1.ls_us
, t1.ls_uk
, t2.totals
, t2.badvote
, t2.goodvote
FROM user l
INNER JOIN (
SELECT ls.user_id
, MAX(ls.date) ls_date
, SUM(IF(ls.type="0", ls.type, 0)) ls_us
, SUM(IF(ls.type="1", ls.type, 0)) ls_uk
from user_statistics ls
GROUP BY ls.user_id
) t1 on t1.user_id = l.id
LEFT JOIN (
SELECT v.user_id
, sum( case when type="1" then 1 else 0 end ) totals
/* these are the same */
, sum ( case when v.vote=1 AND confirm=0 then 1 else 0 END ) badvote
/* these are the same */
, sum ( case when v.vote=1 AND confirm=0 then 1 else 0 END ) goodvote
FROM votes v
) t2 ON t2.user_id = l.id
WHERE l.group_id = '.$groups['id'].'
AND status = 1
And you should avoid the use of PHP var in SQL (you are at risk for SQL injection). For this you should take a look at your db driver for prepared statement and binding value, or at least be sure you sanitize properly the php var content

Postgresql select people you may know orderded by number of mutual friends

I'm building a social network, and I want my members to be able to easily find new friends.
Just like in Facebook, I want to suggest them some people they may know by the number of mutual friends they have.
My PostgreSQL database structure for friendships is as following:
> PROFILES_FRIENDSHIPS
> ----------------------
> - fri_profile_from // int, Person who sent the friendship request
> - fri_profile_to // int, Person who received the friendship request
> - fri_accepted // tinyint, has the person accepted the friendship request?
This is the query I figured out in PostgreSQL for finding the number of mutual friends between 2 profiles (profile with ID 24, and profile with ID 26):
SELECT COUNT(*)
FROM profiles AS p
INNER JOIN (
SELECT (
CASE WHEN ( 26 = f.fri_profile_from ) THEN f.fri_profile_to
ELSE f.fri_profile_from END) AS fri_profile_from
FROM profiles_friendships AS f
WHERE 1 = 1
AND (f.fri_profile_to = 26 OR f.fri_profile_from = 26)
AND fri_accepted = 1)
AS f1
ON (f1.fri_profile_from = p.pro_id)
INNER JOIN (
SELECT (
CASE WHEN ( 24 = f.fri_profile_from ) THEN f.fri_profile_to
ELSE f.fri_profile_from END) AS fri_profile_from
FROM profiles_friendships AS f
WHERE 1 = 1
AND (f.fri_profile_to = 24 OR f.fri_profile_from = 24)
AND fri_accepted = 1)
AS f2
ON (f2.fri_profile_from = p.pro_id)
Now I have been trying to convert this query to make it find the profiles with the most mutual friends of me, but who are not friends with me. But without success... I also researched a lot of examples on this website, but most them are working with double records in the friendships table. Like if 24 is friends with 26, there are 2 records: (24, 26) and (26, 24). That makes them easier to join and to find mutual friends, but that's not how I want to build my database.
If someone could help me to get started on this query, I would be very grateful.
WITH friends AS(
SELECT p.pro_id, CASE WHEN f.fri_profile_from = p.pro_id THEN f.fri_profile_to
ELSE f.fri_profile_from END AS friend_id
FROM profiles
)
SELECT f2.pro_id, count(*) as friend_count
FROM friends AS f1
JOIN friends AS f2
ON f1.friend_id=f2.friend_id
AND f1.pro_id != f2.pro_id
AND f1.pro_id != f2.friend_id
WHERE f1.pro_id = :user_id
GROUP BY f2.pro_id
ORDER BY friend_count;
Step 1 Get everyone that isnt a friend
Select *
From profiles
where (from/to_friendship is not myID)
step 2 include a column with # of mutual friends and order by it
select *,
(select count(*) from [mutual friends query]) as NrOfMutualFriends)
From profiles
where (from/to_friendship is not myID)
Order by NrOfMutualFriends
Edit: Mutual Friends query:
Step 1 select all my friends and all his friends
select if(from = myId, to, from) as myfriendids
from PROFILES_FRIENDSHIPS where from = myid or to = myid
select if(from = hisId, to, from) as hisfriendids
from PROFILES_FRIENDSHIPS where from = hisId or to = hisId
Step 2 Combine these queries into 1
select count(*)
from
( select if(from = myId, to, from) as myfriendids
from PROFILES_FRIENDSHIPS where from = myid or to = myid) myfriends
inner join
( select if(from = hisId, to, from) as hisfriendids
from PROFILES_FRIENDSHIPS where from = hisId or to = hisId) hisfriends
on myfriendsids = hisfriendsids
you can easily create inline view in double records format:
with cte_friends(user_id, friend_id) as (
select
fri_profile_from, fri_profile_to
from PROFILES_FRIENDSHIPS
where fri_accepted = 1
union all -- or union if there could be duplicates
select
fri_profile_to, fri_profile_from
from PROFILES_FRIENDSHIPS
where fri_accepted = 1
)
select
f2.friend_id, count(distinct f2.user_id)
from cte_friends as f1
inner join cte_friends as f2 on f2.user_id = f1.friend_id
left outer join cte_friends as f3 on f3.user_id = f2.friend_id and f3.friend_id = f1.user_id
where
f1.user_id = 1 and f3.user_id is null and
f2.friend_id != 1
group by f2.friend_id
order by 2 desc
sql fiddle demo

codeigniter join only most recent row

I am making a dating app similar to tindler, where users can like or dislike each other. If two users both like each other, they should be able to chat with each other. I have come up with the following query to handle pulling a list of users you can chat/have chats with already - the problem i'm having is i only want to pull the most recent chat message, solely to display a little blurb before you click into the chat itself. My query works, but it returns the oldest (lowest ID) chat record, instead of the newest. Order by does not seem to have an impact on returning the correct result.
$data = $this->db->select('users.id,display_name,city,state,gender,users_pictures.picture,users_chats.message')
->join('users_pictures','users_pictures.user_id=users.id')
->join('users_chats','users_chats.user_id=users.id OR users_chats.foreign_user_id=users.id','left outer')
->where('EXISTS (SELECT 1 FROM users_likes_dislikes ld WHERE (ld.foreign_user_id = '.$this->user_id.' AND ld.user_id=users.id AND ld.event_type=1) OR (SELECT 1 FROM users_likes_dislikes ld WHERE ld.foreign_user_id = users.id AND ld.user_id='.$this->user_id.' AND ld.event_type=1))', '', FALSE)
->where('NOT EXISTS (SELECT 1 FROM users_blocks ub WHERE (ub.foreign_user_id = users.id AND ub.user_id='.$this->user_id.') OR (SELECT 1 FROM users_blocks ub WHERE ub.foreign_user_id = '.$this->user_id.' AND ub.user_id=users.id))', '', FALSE)
->where('((users_chats.user_id='.$this->user_id.' OR users_chats.foreign_user_id='.$this->user_id.') OR (users_chats.user_id is NULL AND users_chats.foreign_user_id is NULL))')
->order_by('users_chats.id','DESC')
->group_by('users.id')
->get('users')
->result_array();
Here is the current mysql table for users_chats:
id user_id foreign_user_id message created
1 1 4 test 2013-05-22 15:42:44
2 1 4 test2 2013-05-22 15:44:38
I assumed the order_by would ensure that the test2 message is what displayed.
Here is sample output:
Array ( [0] => Array ( [id] => 4 [display_name] => testinguser [city] => west hills [state] => ca [gender] => 2 [picture] => testasdfasdf.jpg [message] => test ) )
Any help is much appreciated :)
edit - the query itself (without group by, this works but i need it to group to the user.id so that i don't have multiple entries for the same user in the array):
SELECT
`users`.`id`,
`display_name`,
`city`,
`state`,
`gender`,
`users_pictures`.`picture`,
`users_chats`.`message`
FROM (`users`)
JOIN `users_pictures`
ON `users_pictures`.`user_id` = `users`.`id`
JOIN `users_chats`
ON `users_chats`.`user_id` = `users`.`id`
OR users_chats.foreign_user_id = users.id
WHERE EXISTS(SELECT
1
FROM users_likes_dislikes ld
WHERE (ld.foreign_user_id = 1
AND ld.user_id = users.id
AND ld.event_type = 1)
OR (SELECT
1
FROM users_likes_dislikes ld
WHERE ld.foreign_user_id = users.id
AND ld.user_id = 1
AND ld.event_type = 1))
AND NOT EXISTS(SELECT
1
FROM users_blocks ub
WHERE (ub.foreign_user_id = users.id
AND ub.user_id = 1)
OR (SELECT
1
FROM users_blocks ub
WHERE ub.foreign_user_id = 1
AND ub.user_id = users.id))
AND ((users_chats.user_id = 1
OR users_chats.foreign_user_id = 1)
OR (users_chats.user_id is NULL
AND users_chats.foreign_user_id is NULL))
ORDER BY `users_chats`.`created` DESC
Your group by clause could be the culprit here. I believe that the grouping operation happens first, leaving you with your first result.
Rather than selecting all these rows (when there's tons it'll take longer), you should specify how many you want - it looks to me like that isn't too far out of the picture here, anyways. Specify how many you want, get rid of the group by clause, and you should order by date since you HAVE a date column.
Does that help?
I'm not sure how to do it with your db Abstraction, but the query you want is
SELECT
`users`.`id`,
`display_name`,
`city`,
`state`,
`gender`,
`users_pictures`.`picture`,
chats1.`message`
FROM (`users`)
JOIN `users_pictures`
ON `users_pictures`.`user_id` = `users`.`id`
JOIN `users_chats` AS chats1
ON (chats1.`user_id` = `users`.`id`
OR chats1.foreign_user_id = users.id)
Here comes the important part
AND NOT EXISTS(
SELECT *
FROM users_chats AS chats2
WHERE ((chats2.user_id = chats1.user_id AND chats2.foreign_user_id = chats1.foreign_user_id)
OR (chats2.user_id = chats1.foreign_user_id AND chats1.user_id = chats2.foreign_user_id))
AND chats2.created_date > chats1.created_date --which I assume is a time stamp
)
It's not pretty, I know.
WHERE EXISTS(SELECT 1
FROM users_likes_dislikes ld
WHERE (ld.foreign_user_id = 1
AND ld.user_id = users.id
AND ld.event_type = 1)
OR (SELECT 1
FROM users_likes_dislikes ld
WHERE ld.foreign_user_id = users.id
AND ld.user_id = 1
AND ld.event_type = 1)
)
AND NOT EXISTS(SELECT 1
FROM users_blocks ub
WHERE (ub.foreign_user_id = users.id
AND ub.user_id = 1)
OR (SELECT
1
FROM users_blocks ub
WHERE ub.foreign_user_id = 1
AND ub.user_id = users.id)
)
AND ((chats1.user_id = 1
OR chats1.foreign_user_id = 1)
OR (chats1.user_id is NULL
AND chats1.foreign_user_id is NULL))
ORDER BY `users_chats`.`created` DESC
Basically, only successfully join if there's no more recent message. There are some better native solutions - TSQL (Microsoft SQL Server) has CROSS APPLY, which would be great here - but without knowing more about your DB layer, I can't be sure. You may want to considered re-architecting your chat structure:
Users(int id /*also other user info*/)
chats(int id, datetime date_initiated, bool /*or bit, or short int*/ is_active)
chat_users (int chat_id, int user_id)
chat_messages (int chat_id, int user_id /*author*/, datetime date_sent, varchar(n) message)
With a structure like that, you could get all your most recent messages like this:
SELECT *
FROM Users AS u
INNER JOIN chat_users AS cu
ON u.id = cu.user_id
INNER JOIN chats AS c
ON c.id = cu.chat_id
AND c.is_active = 1
INNER JOIN chat_messages AS m
ON m.chat_id = c.id
AND NOT EXISTS (
SELECT 1
FROM chat_messages AS m2
WHERE m2.chat_id = m.chat_id
AND m.date_sent < m2.date_sent
)
INNER JOIN Users as sender
ON m.user_id = sender.id
WHERE u.id = ###
ORDER BY m.date_sent DESC
You could even create a "Chat most recent message" view like:
CREATE VIEW Chat_Recent AS
SELECT * /* WHATEVER YOU LIKE */
FROM chats AS c
INNER JOIN chat_messages AS m
ON m.chat_id = c.id
AND NOT EXISTS (
SELECT 1
FROM chat_messages AS m2
WHERE m2.chat_id = m.chat_id
AND m.date_sent < m2.date_sent
)
INNER JOIN Users as sender
ON m.user_id = sender.id
Try MySQL MAX():
$this->db->join('(SELECT MAX(message) AS lastMsg FROM users_chats WHERE users_chats.user_id=users.id OR users_chats.foreign_user_id=users.id GROUP BY users.id)', 'left outer');
Then add the "lastMsg" on your select.

Muliple Joins sum values based on two different critiea with sort

I have a database that has 4 tables.
Table 1 - "company" table with company_id as the key
Table 2 - "users" table with the user_id as the key
Table 3 - "teams" table that references the company_id and the user_id (So that user can belong to multiple teams.
Table 4 - "points" table that references the company_id, the user_id, points_earned (Numeric value of points given), exchange_dte (0 - if the user has not used the points, otherwise a unixtime value)
Given a known company_id, I am trying to call all users that belong to that "team" and show their total points and their un-exchanged points. The following MySQL will only give the first user on company's #1 team. There are currently 5 users in the database all with a number of points earned, some exchanged, some not.
SELECT
users.user_id AS u_id,
SUM(points.points_earned) AS ttl_points,
SUM(case when exchange_dte = '0' then points.points_earned else 0 end) AS unused_points
FROM users
INNER JOIN teams ON teams.user_id = users.user_id
INNER JOIN points ON points.user_id = users.user_id
WHERE (teams.company_id = '1' AND points.company_id = '1' AND users.user_active = '1');
So then I tried to add the user_id to the Sum calls. And end up with the same thing.
SELECT
users.user_id AS u_id,
SUM(case when points.user_id = users.user_id then points.points_earned else 0 end) AS ttl_points,
SUM(case when points.exchange_dte = '0' AND points.user_id = users.user_id then points.points_earned else 0 end) AS unused_points
FROM users
INNER JOIN teams ON teams.user_id = users.user_id
INNER JOIN points ON points.user_id = users.user_id
WHERE (teams.company_id = '1' AND points.company_id = '1' AND users.user_active = '1')
ORDER BY ttl_points;
The interesting thing is, the point totals for the first user appear to be all the points in the database, even though they have a user_id and company_id associated with them
Thoughts?
You're trying to do a SUM without using GROUP BY: not sure if it will work for you but try this adding a GROUP BY users.user_id after the end of the query and see if that helps you out.
SELECT
users.user_id AS u_id,
SUM(case when points.user_id = users.user_id then points.points_earned else 0 end) AS ttl_points,
SUM(case when points.exchange_dte = '0' AND points.user_id = users.user_id then points.points_earned else 0 end) AS unused_points
FROM users
INNER JOIN teams ON teams.user_id = users.user_id
INNER JOIN points ON points.user_id = users.user_id
WHERE (teams.company_id = '1' AND points.company_id = '1' AND users.user_active = '1') GROUP BY users.user_id
ORDER BY ttl_points;

mysql joining two table

table user:
id_u* f_name l_name
----------------------
1 andi mitchel
2 sarah bench
3 kirsty larx
table voucher:
id_v* id_user id_target
1 1 2
2 2 3
quite confused how to join those table with two foreign keys
$db->query("SELECT * FROM voucher v
LEFT JOIN user u ON u.id_u = v.id_user
LEFT JOIN user u1 ON u1.id_u = v.id_target
WHERE .... ")
echoing while loop... and returns nothing??
while($r = $q->fetch_array(MYSQLI_ASSOC)) :
echo $r['u.f_name'];
echo $r['u1.f_name'];
endwhile;
Your JOIN seems absolutely correct. The only issue is that you have joined table user twice, therefore you have columns with same name (like f_name). The database will assign different (but arbitrary) names to these columns. You can override this behaviour with the AS keyword:
$db->query("SELECT v.*
, u.f_name AS user_f_name
, u.l_name AS user_l_name
, ta.f_name AS target_f_name
, ta.l_name AS target_l_name
FROM voucher v
LEFT JOIN user u ON u.id_u = v.id_user
LEFT JOIN user ta ON ta.id_u = v.id_target
WHERE .... ")
Then:
while($r = $q->fetch_array(MYSQLI_ASSOC)) :
echo $r['user_f_name'];
echo $r['target_f_name'];
endwhile;
And I think you can replace the LEFT JOINs with (inner) JOINs. Unless you have id_user or id_target values referencing non-existing userids (id_u).
It looks like you are asking for all people who are in the voucher table regardless of them being in position 1 (user) or position 2 (target)... Then, showing that person's name.
This query does a pre-query of each possible person and their position basis (via WhichPosition).
SELECT STRAIGHT_JOIN
AllVoucherUsers.WhatPosition,
u.*
FROM
( select distinct
v.id_user,
'1' as WhatPosition
from voucher v
union select distinct
v.id_target as id_user,
'2' as WhatPosition
from voucher v
) AllVoucherUsers
join users u
on AllVoucherUsres.id_user = u.id_u
If you only want ONE instance of a given person -- REGARDLESS of their position, just strip out all instances of the "WhatPosition" reference...
SELECT STRAIGHT_JOIN
u.*
FROM
( select distinct
v.id_user
from voucher v
union select distinct
v.id_target as id_user
from voucher v
) AllVoucherUsers
join users u
on AllVoucherUsres.id_user = u.id_u
SELECT * FROM voucher v
LEFT JOIN user u ON u.id_u = v.id_user OR u.id_u = v.id_target
WHERE ....
how about:
SELECT * FROM voucher JOIN user ON id_user = id_u
Simpler still:
SELECT * FROM voucher, user WHERE id_user = id_u

Categories