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
Related
I have following table 'persons' with same persons in different rows
id | firstname | surname | date_created
------------------------------------------------------
3 | Nelli | Schaller | 2017-08-22 20:57:19
------------------------------------------------------
4 | Carl | Schaller | 2019-06-21 08:29:45
------------------------------------------------------
48 | Nelli | Schaller | 2020-06-25 13:06:09
------------------------------------------------------
49 | Carl | Schaller | 2020-06-25 13:06:09
What I want to get are all unique Schallers with the biggest id / newest date_created value.
I tried this
SELECT id, CONCAT(surname, ", ", firstname) AS person, date_created
FROM persons
WHERE
surname LIKE "schall%"
GROUP by firstname, surname
ORDER BY date_createdDESC, surname ASC LIMIT 0, 10
but get only as expected the first two entries (id 3 and 4) but I need 48 and 49.
As mentioned in some comment in this case the LIKE statement isn't necessary but in real live it will be the source for an autocomplete field so I need the LIKE
Any idea how to manage that?
Use NOT EXISTS:
SELECT p.id, CONCAT(p.surname, ', ', p.firstname) AS person, p.date_created
FROM persons p
WHERE p.surname LIKE '%schall%'
AND NOT EXISTS (SELECT 1 FROM persons WHERE firstname = p.firstname AND surname = p.surname AND id > p.id)
ORDER BY p.date_created DESC, person
If the condition to pick the latest of each group is the column date_created then change:
...AND id > p.id
with
...AND date_created > p.date_created
You could use subquery with group for max id
select t.max_id, t.person, m.date_created
from (
SELECT max(id) max_id, CONCAT(surname, ", ", firstname) AS person
FROM persons
WHERE surname LIKE "schall%"
ORDER BY date_createdDESC, surname ASC
GROUP BY CONCAT(surname, ", ", firstname)
) t
inner join persons m ON CONCAT(m.surname, ", ", m.firstname) = t.person
and m-id = t.max_id
SELECT p.*
FROM persons p
LEFT JOIN persons p2 ON p2.firstname = p.firstname
AND p2.lastname = p.lastname
AND p2.date_created > p.date_created
WHERE p2.id IS NULL
This is SQL Server syntax but MySQL is probably similar.
I'm assuming your id field doesn't need to be checked as well as the date_created since it's an identity column and would be larger anyway for the latter created records, but obviously adjust to your actual data.
I've the following MySQL Table called store
id ref item_no supplier
1 10 x1 usa
2 10 x1 usa
3 11 x1 china
4 12 x2 uk
5 12 x3 uk
6 13 x3 uk
7 13 x3 uk
Now What i'm excepting the output to be is as follows :
id ref item_no supplier
1 10 x1 usa
3 11 x1 china
4 12 x2 uk
5 12 x3 uk
6 13 x3 uk
As you can see item_no x1 and x3 have same ref and supplier source, so what I want is to delete the duplicate record in-order to keep one item_no only !
I've create this PHP code to SELECT results only :
$query1 = "SELECT
DISTINCT(item_no) AS field,
COUNT(item_no) AS fieldCount,
COUNT(ref) AS refcount
FROM
store
GROUP BY item_no HAVING fieldCount > 1";
$result1 = mysql_query($query1);
if(mysql_num_rows($result1)>0){
while ($row1=mysql_fetch_assoc($result1)) {
echo $row1['field']."<br /><br />";
}
} else {
//IGNORE
}
How to tell the query to SELECT Duplicate records properly according to my needs before creating the DELETE query.
Thanks Guys
You can use the following query to produce the required result set:
SELECT t1.*
FROM store AS t1
JOIN (
SELECT MIN(id) AS id, ref, item_no
FROM store
GROUP BY ref, item_no
) AS t2 ON t1.id > t2.id AND t1.ref = t2.ref AND t1.item_no = t2.item_no
Demo here
To DELETE you can use:
DELETE t1
FROM store AS t1
JOIN (
SELECT MIN(id) AS id, ref, item_no
FROM store
GROUP BY ref, item_no
) AS t2 ON t1.id > t2.id AND t1.ref = t2.ref AND t1.item_no = t2.item_no
To find only duplicate records you can use
SELECT * FROM store WHERE id NOT IN
(SELECT id FROM store AS outerStore WHERE id =
(SELECT MAX(id) FROM store AS innerStore
WHERE outerStore.ref = innerStore.ref AND
outerStore.supplier = innerStore.supplier AND outerStore.item_no = innerStore.item_no))
Maybe long, but it should work.
If you want the select of the row to delete use
select * from store
where id not in (
select max(id) from store
group by distinct ref, item_no, supplier);
Or you can directly use a command for direct delete using
delete from store
where id not in (
select max(id) from store
group by distinct ref, item_no, supplier);
I have this query:
SELECT *
FROM `classes`
JOIN `classes_students`
ON `classes`.`id` = `classes_students`.`class`
And I need to add condition for selecting just classes, in which are not currently logged student (user ID is not in classes_students connected with class id) and also count how many students are in that class.
Table structure:
classes: id, name, etc
classes_students: class_id, user_id, etc
Table data:
classes:
1 | test
2 | test2
3 | test3
classes_students:
1 | 1
1 | 2
2 | 3
3 | 4
3 | 5
Expected output if im user with id 1:
classes names (with number of students in):
2 (1 student)
3 (2 students)
All this in one query. It is possible? If yes, how?
Select classid, count(*)
from class
left join student on student.classid = class.classid
group by classid
Glad help for you
Try this query:
$user_id = 1; // current user_id
$query = "SELECT `classes`.`id`, `classes`.`name`, COUNT(*) as students FROM `classes`
JOIN `classes_students`
ON `classes`.`id` = `classes_students`.`class_id`
AND `classes_students`.`user_id` != $user_id
GROUP BY `classes_students`.`class_id`
";
I figured it out! :)
Thank you guys for ur help.
$user_id = 1; // current user_id
$query = "SELECT `classes`.`id`, `classes`.`name`, COUNT(*) as students FROM `classes`
LEFT JOIN `classes_students`
ON `classes`.`id` = `classes_students`.`class_id`
WHERE `classes`.`id` NOT IN (SELECT `class_id` FROM `classes_students` WHERE `user_id`='.$user_id.')
GROUP BY `classes_students`.`class_id`
";
Hi here is my tables..
Table Sites
sid sname uid
---- ---------- ----
1 aaa.com 1
5 bbb.com 1
Table keywords_s
kid skeywoird
---- ----------
1 word1
2 word2
Table matchon
mid uid sid kid
---- ------ ----- -----
1 1 1 1
2 1 1 2
Table rank
mid rank dateon url
---- ------ ------- -----
2 7 08-May-2014 bbb.com/a
2 6 09-May-2014 bbb.com/2
And my query
"SELECT
keywords_s.skeyword,
keywords_s.kid,
sites.sname,
rank.rank,
rank.url,
rank.dateon
FROM matchon
Inner JOIN sites ON sites.sid = matchon.sid
Inner JOIN keywords_s ON keywords_s.kid = matchon.kid
Inner JOIN rank ON rank.mid = matchon.mid
where matchon.uid = :uid and sites.sname = :sname and sites.deactive != '1'
group by keywords_s.skeyword order by rank.rank
"
I am getting output
rank keyword dateon url
---- --------- ------- -----
7 word2 08-May-2014 bbb.com/a
Output needed is
rank keyword dateon url
---- --------- ------- -----
6 word2 09-May-2014 bbb.com/2
Here what i want to get ...
1st Group by keywords_s.skeyword order by rank.rank (this is coming But)
2nd Order by rank.slno desc (not working)
(I need 2nd order to work so i can get latest rank and date with, group by skeyword and order by rank)
SELECT keywords_s.skeywor
, keywords_s.kid
, sites.sname
, rank.rank
, rank.url
, rank.dateon
FROM matchon JOIN sites ON sites.sid = matchon.sid
JOIN keywords_s ON keywords_s.kid = matchon.kid
JOIN rank ON rank.mid = matchon.mid AND
rank.dateon = (SELECT MAX(dateon) FROM rank WHERE mid = matchon.mid)
WHERE matchon.uid = :uid and sites.sname = :sname and sites.deactive != '1'
GROUP BY keywords_s.skeyword order by rank.rank
This should work as per your requirement
"Select * from(SELECT
keywords_s.skeyword,
keywords_s.kid,
sites.sname,
rank.rank,
rank.url,
rank.dateon
FROM matchon
Inner JOIN sites ON sites.sid = matchon.sid
Inner JOIN keywords_s ON keywords_s.kid = matchon.kid
Inner JOIN rank ON rank.mid = matchon.mid
where matchon.uid = :uid and sites.sname = :sname and sites.deactive != '1'
order by rank.rank desc)xyz
group by xyz.skeyword
"
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.