Left join same table with count specific value - php

I have one table where I would like to count the amount of names grouped by school, count whether school has one or two genders as well as count the specific occurence of a particular value in 3 different columns. I am able to do the counts in two different tables but I want to join them to make 1 table
FName
School
Gender
Events
Events2
Events3
Ann
Marymount
F
HJ
LJ
TJ
Peter
Marymount
M
100
200
400
Drew
St Hughs
M
100
200
Davis
St Hughs
M
200
Kat
Campion
F
400
Molly
Campion
F
400
Mike
Marymount
M
800
Fran
Campion
M
100
200
These are the 2 separate queries I use successfully and create two different result sets
$sql = "SELECT COUNT(FName) as cnt, COUNT(DISTINCT(Gender)) as gnd, school, COUNT(*) FROM entries WHERE school <> 'Unattached' GROUP BY school";
$sql = "SELECT COUNT(Events + Events2 + Events3) as events, school, COUNT(*) FROM entries WHERE (school <> 'Unattached') AND (Events = '100') OR (Events2 = '100') OR (Events3 = '100') GROUP BY school";
$result = $conn->query($sql);
I have tried using a union but it only gives me results for second query and the count of names is off.
$sql = "
SELECT COUNT(FName) as cnt, COUNT(Events + Events2 + Events3) as events, COUNT(DISTINCT(Gender)) as gnd, school
FROM entries WHERE (school <> 'Unattached')
UNION
SELECT COUNT(FName) as cnt, COUNT(Events + Events2 + Events3) as events, COUNT(DISTINCT(Gender)) as gnd, school
FROM entries WHERE (school <> 'Unattached') AND (Events = '100') OR (Events2 = '100') OR (Events3 = '100')
GROUP BY school";
$result = $conn->query($sql);
The left join gave me no results because I didn't know how to fit the 2 WHERE clauses.
How can I join the two queries to get one table with the following result
School
# of Participants
# Genders
# 100
Marymount
3
2
1
St Hughs
2
1
1
Campion
3
2
1

Use SUM() to add up the number of rows where a condition is true. This works because a boolean value is 1 when it's true, 0 when it's false, so SUM() gets this count.
SELECT school,
COUNT(*) AS num_participants,
COUNT(DISTINCT gender) AS num_genders,
SUM(events = '100' OR events2 = '100' OR events3 = '100') AS num_100
FROM entries
WHERE school != 'Unattached'
GROUP BY school

Related

Find and remove MySQL row duplicates that are right after each other

I'm trying to find and remove MySQL row duplicates that are right after each other, instead of finding all, even if they're not straight after each other.
SELECT DISTINCT(content) AS contentMsg, COUNT(*) AS cnt, `ticketId`,`date`
FROM ticketsReplies
WHERE username = 'X'
GROUP BY contentMsg, ticketId
HAVING cnt > 1
ORDER BY cnt DESC
This is my current code. However, this finds duplicates if there's just two of the same answers in one ticket instead of them having to be IDs right after each other (which can happen if you send a POST request, and it fails, and you refresh etc).
How would I go about finding ones that are only 1 ID from each other.
So finding e.g. 1,2,3,4,5,6,7 instead of 1,3,9,11
E.g. if you have
ID EMAIL
---------------------- --------------------
1 aaa
2 bbb
3 bbb
4 bbb
5 ddd
6 eee
7 aaa
8 aaa
9 bbb
If you have this, it should find the following IDs:
2,3,4 but not 9 as it's not directly after 4 even though its a duplicate.
It should also find 7,8 but not 1 as they are not right after each other.
E.g.:
SELECT id
FROM
( SELECT x.id FROM my_table x JOIN my_table y ON y.email = x.email AND y.id = x.id + 1 ) a
UNION
( SELECT y.id FROM my_table x JOIN my_table y ON y.email = x.email AND y.id = x.id + 1 );
If there are gaps in your id list (eg 5, 6, 9, 11), simply comparing id = id+1 wouldn't work. The solution I came up with is to create two identical temporary tables with sequential row-numbers. In that case you can safely compare the rows based on their number, even if the id's have gaps.
DELETE FROM tab WHERE id IN (
SELECT A.id
FROM
(
SELECT row_nr, id, email FROM (
SELECT
(#cnt1 := #cnt1 + 1) AS row_nr,
t.id,t.email
FROM tab AS t
CROSS JOIN (SELECT #cnt1 := 0) AS d
ORDER BY t.id
) x
) A
INNER JOIN
(
SELECT row_nr, id, email FROM (
SELECT
(#cnt2 := #cnt2 + 1) AS row_nr,
t.id,t.email
FROM tab AS t
CROSS JOIN (SELECT #cnt2 := 0) AS d
ORDER BY t.id
) x
) B
ON A.row_nr-1 = B.row_nr AND A.email=B.email
)
The two (SELECT row_nr, id, email FROM ... ) x parts create two identical tables A and B like
row_nr id email
1 1 aaa
2 4 aaa
3 5 bbb
4 9 aaa
5 11 aaa
Then you can compare the sequential row-nr's and email:
ON A.row_nr-1 = B.row_nr AND A.email=B.email
Selecting the result-id's gives you the id's 4, 11 which are the duplicates. Then you can delete those id's:
DELETE FROM tab WHERE id IN ( ... )
Here is a Fiddle to test the SELECT part.
NOTE: Before you try this at home, please backup your table!

Calculate net change in ranking after "update", MYSQL

I have a two MYSQL tables:
Table-1
id catid title user_rating
123 8 title-one 3
321 8 title-two 5
and
Table-2
listing_id title user_rating
123 title-one 3
321 title-two 5
Plus, I have this query that calculates the current rank of each "title" based on "user_rating".
SELECT
MAX(x.rank) AS rank
FROM
(SELECT
a.id,
a.catid,
a.title,
b.listing_id,
#rank:=#rank + 1 AS rank
FROM
`table-1` a
INNER JOIN `table-2` b ON a.id = b.listing_id
CROSS JOIN (SELECT #rank:=0) r
WHERE
catid = '8'
ORDER BY user_rating DESC) x
WHERE
id = 123
Now, my issue: I want to calculate the difference in "ranking" (rank) when I update the "user_rating" value.
Please, note: the "user_rating" value is updated by a php script that allow users to vote for a specific content (range 1 to 5, step 0.5).
What's the best way to get the difference between the "previous rank" and "current rank" after the update?
Thanks in advance to all.

PDO select and count IN HAVING DISTINCT to appear in dropdown

I have this mysql select working great. It returns the proper data. I can't seem to get the context correct to place the actual count of the custnum so it will appear on the end of the dropdown option select.
This statement returns the proper location names
$statement = $pdo->prepare("SELECT locationname FROM location WHERE locationname IN (SELECT locationname FROM location_user WHERE custnum= :custnum GROUP BY locationname HAVING COUNT( DISTINCT email) < 6 )");
$statement->execute(array(':custnum' => $session->custnum));
while($row = $statement->fetch(PDO::FETCH_ASSOC)){
echo'<option value="'.$row['locationname'].'">'.$row['locationname'].'('. $row['COUNT(total)'] .')</option>';
}
Here's one of my attempts to grab the total for each custnum
$statement = $pdo->prepare("SELECT locationname, COUNT(custnum) AS total FROM location WHERE locationname IN (SELECT locationname FROM location_user WHERE custnum= :custnum GROUP BY locationname HAVING COUNT( DISTINCT email) < 6 )");
$statement->execute(array(':custnum' => $session->custnum));
while($row = $statement->fetch(PDO::FETCH_ASSOC)){
echo'<option value="'.$row['locationname'].'">'.$row['locationname'].'('. $row['total'] .')</option>';
}
Here's my tables
table location table location_user
custnum | locationname custnum | locationname | email | userlevel
1 location1 1 location1 1me#you.com 3
1 location2 1 location1 1me#you.com 1
1 location1 2me#you.com 2
1 location1 3me#you.com 2
1 location1 4me#you.com 2
1 location1 5me#you.com 2
1 location2 1me#you.com 2
1 location2 1me#you.com 3
The first select returns
location1()
location2()
The second select returns
location1(2)
I actually need the count of the distinct email which the query is doing and returning only the locationnames of the distinct email in the table less then 6 but how do I get the actual number of distinct emails for each locationname.
This select will retrieve the total for DISTINCT email, but how do I combine the two into one for my while loop?
$statement2 = $pdo->prepare("SELECT COUNT(email) AS total FROM location_user WHERE custnum= :custnum GROUP BY locationname HAVING COUNT( DISTINCT email) < 6");
$statement2->execute(array(':custnum' => $session->custnum));
Here's the working version from the help of Peter and a little prodding from Tin.
$statement = $pdo->prepare("SELECT l.locationname, COUNT(DISTINCT lu.email) AS total
FROM location l LEFT JOIN location_user lu ON l.locationname = lu.locationname AND l.custnum = lu.custnum WHERE l.custnum = :custnum GROUP BY l.locationname HAVING COUNT(DISTINCT lu.email) < 5 ");
$statement->execute(array(':custnum' => $session->custnum));
while($row = $statement->fetch(PDO::FETCH_ASSOC)){
echo'<option value="'.$row['locationname'].'">'.$row['locationname'].'('. $row['total'] .')</option>';
}
Here's another version that I'm working on to skip the user that adds the locations to the table. This user will always have a userlevel > 2. The uselevel is placed in the location_user table only as a value between 1-9. So I still need the location name but I don't want their location included in the count. I just realized that I could actually go a better route because the only email that I want to count will have a userlevel of 2. I was using the distinct email to filter out the userlevel of 1. I'll give it a go. The below version drops my locations that arn't in the location_user table but it's returning the proper count.
SELECT l.locationname, COUNT(lu.userlevel) AS total
FROM location l LEFT JOIN location_user lu
ON l.locationname = lu.locationname
AND l.custnum = lu.custnum
WHERE l.custnum = :custnum
AND lu.userlevel = 2
GROUP BY l.locationname
HAVING COUNT(lu.userlevel) < 6
UPDATE2: based on your comments. Try it this way
SELECT l.locationname, COUNT(DISTINCT lu.email) AS total
FROM location l LEFT JOIN location_user lu
ON l.locationname = lu.locationname
AND l.custnum = lu.custnum
AND lu.userlevel < 3 -- consider only users with user level < 3
WHERE l.custnum = ?
GROUP BY l.locationname
HAVING COUNT(DISTINCT lu.email) < 6
Sample output:
| LOCATIONNAME | TOTAL |
|--------------|-------|
| location1 | 5 |
| location2 | 1 |
| location3 | 0 |
Here is SQLFiddle demo
you don't actually need to query from table location since you already have locationname field from table location_user
SELECT locationname, count(DISTINCT email) as total FROM location_user WHERE custnum = :custnum GROUP BY locationname HAVING count(DISTINCT email) < 6

get SUM of another row of GROUP BY'd rows

I have this table:
This selection is is duplicated many times for different var_lines (which pretty much work as one row of data, or respondent for a survey) and set_codes (different survey codes).
With this query:
SELECT
*, COUNT(*) AS total
FROM
`data`
WHERE
`var_name` = 'GND.NEWS.INT'
AND(
`set_code` = 'BAN11A-GND'
OR `set_code` = 'BAN09A-GND'
OR `set_code` = 'ALG11A-GND'
)
AND `country_id` = '5'
GROUP BY
`data_content`,
`set_code`
ORDER BY
`set_code`,
`data_content`
The query basically counts the number of answers for a specific question. Then groups them survey (set_code).
What I need is for each of the grouped data_content answers for GND.NEWS.INT to also show the SUM of all the corresponding GND_WT with the same var_line.
For example if I had this:
data_id data_content var_name var_line
1 2 GND.NEW.INT 1
2 1.4 GND_WT 1
3 2 GND.NEW.INT 2
4 1.6 GND_WT 2
5 3 GND.NEW.INT 3
6 0.6 GND_WT 3
I would get something like this:
data_id data_content var_name var_line total weight
1 2 GND.NEW.INT 1 2 3
5 3 GND.NEW.INT 3 1 0.6
Thanks for any help.
Your requirements are not exactly clear, but I think the following gives you what you want:
select d1.data_id,
d1.data_content,
d1.var_name,
d1.var_line,
t.total,
w.weight
from data d1
inner join
(
select data_content,
count(data_content) Total
from data
group by data_content
) t
on d1.data_content = t.data_content
inner join
(
select var_line,
sum(case when var_name = 'GND_WT' then data_content end) weight
from data
group by var_line
) w
on d1.var_line = w.var_line
where d1.var_name = 'GND.NEW.INT'
See SQL Fiddle with Demo
This Query can be suitable for your specific example:
select st.data_id,
st.data_content,
st.var_name,
st.var_line,
count(st.data_id) as total,
sum(st1.data_content) as weight
from data st
left join data st1 on st1.var_name = 'GND_WT' AND st1.var_line=st.var_line
where st.var_name='GND.NEW.INT'
group by st.data_content
Regards,
Luis.

How to limit / offset query that returns several rows per unique ID?

My issue is that I need to paginate data from this query:
function search($search_term, $limit, $offset)
{
$id = $this->auth->get_user_id();
$query = $this->db->query("
SELECT user_id,
first_name,
cars_name,
cars_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY first_name ASC
");
$search_data = array();
foreach ($query->result() as $row) {
$search_data[$row->user_id]['name'] = $row->first_name;
$search_data[$row->user_id]['cars'][$row->cars_id] = array(
'cars_name' => $row->cars_name);
}
return $search_data;
}
A sample data table / query response would be:
1 JOE HONDA 123
1 JOE TOYOTA 124
2 MAC VW 125
2 MAC HONDA 126
2 MAC TESLA 127
3 STU SUBARU 128
3 STU KIA 129
-----------
Page 1
-----------
1 JOE HONDA 123
TOYOTA 124
2 MAC VW 125
HONDA 126
------------
Page 2
------------
3 STU SUBARU 128
KIA 129
If I enter a limit and offset at the end of MySQL query
...
LIMIT $limit
OFFSET $offset;
");
the limit and offset are applied to the total number of rows, not the the number of rows grouped by user.
I've tried using GROUP BY but was unable to make it work.
My goal is to make the query as above but LIMIT and OFFSET the query by a number of rows that counts users, not all rows.
Any ideas?
I don't see a way to do this in one query. My solution would be to get the count of unique ID's using a group by query with the same parameters:
SELECT COUNT(1) AS uid_count
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
GROUP BY user_profiles.user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
Then fetch the uid_countmysql_num_rows and use that to calculate pagination variables for the above query.
The solution really is to use a GROUP BY clause:
SELECT SQL_CALC_FOUND_ROWS
user_id,
first_name,
cars_name,
cars_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
GROUP BY user_id
ORDER BY first_name ASC
LIMIT 100
The order is important. GROUP BY first, then ORDER BY, and then OFFSET/LIMIT.
Notice the SQL_CALC_FOUND_ROWS up there? After the query has executed, if you want to get the total row count (including those who aren't returned because of the LIMIT clause), just use:
SELECT FOUND_ROWS() AS `count`
And fetch the count column.
However, like you said, the rows will collapse and you will lose some cars_name and cars_id values.
Another solution is to use GROUP_CONCAT, then split it in PHP:
SELECT
user_id,
first_name,
GROUP_CONCAT(cars_name SEPARATOR ','),
GROUP_CONCAT(cars_id SEPARATOR ','),
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY first_name ASC
LIMIT 100
This would give you something like:
1 JOE HONDA,TOYOTA 123,124
2 MAC VW,HONDA,TESLA 125,126,127
3 STU SUBARU,KIA 128,129
If you want to get a list like this
Page 1
----------------------
1 JOE HONDA 123
1 JOE TOYOTA 124
Page 2
----------------------
2 MAC VW 125
2 MAC HONDA 126
2 MAC TESLA 127
Page 3
----------------------
3 STU SUBARU 128
3 STU KIA 129
Forget about limit, do this instead:
A - First retrieve a list of user id's and insert that into a temp table
CREATE TEMPORARY TABLE `test`.`temp_user_ids` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE = MEMORY
B - Next insert the relavant user_id's into the table.
INSERT INTO temp_user_ids
SELECT null, user_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY user_id DESC /*insert in reverse order !*/
The lowest user_id is the last_insert_id in the temptable, and the temp_table
items are in sequential order.
C - Set the SQL #var #current_id to the last_insert_id in the temp_table.
SELECT #current_id:= LAST_INSERT_ID()
D - Next select relevant rows from the table, using only the user_id you want.
SELECT count(*) as row_count,
up.user_id,
first_name,
group_concat(cars_name) as car_names,
group_concat(cars_id) as car_ids,
FROM user_profiles up
LEFT JOIN cars
ON cars.id_fk = up.user_id
INNER JOIN temp_user_ids t
ON (t.user_id = up.user_id)
WHERE t.id = #current_id
GROUP BY up.user_id
ORDER BY cars.id
E - Now lower the #current_id
SELECT #current_id:= #current_id - 1;
F - And repeat step D and E until there's no more rows to be had.
The first field row_count tells you the number of rows aggregated in the fields
car_names and car_ids. You can separate these fields by using php's explode.

Categories