I have two tables: Project and ProjectFieldValue
I am needing to return results from the ProjectFieldValue based on multiple different key/value options in the table. I can get it to work with one key/value pair, but once I add another AND statement to the query it returns nothing.
Here is a sample of my tables followed by my query...
Project Table
----------------------
id | name
----------------------
1 | Project #1
ProjectFieldValue Table
I have millions of records like this and they are all stored in this table and associated to a specific Project.
----------------------------------------------------------------------------------------
id | project_id | text_value | date_value | field_key
----------------------------------------------------------------------------------------
1 | 1 | Active | NULL | contract_status
2 | 1 | NULL | 2020-06-02 00:01:58 | listing_date
3 | 1 | Seller | NULL | contract_client_type
4 | 1 | Active | NULL | contract_option
Here are my queries broken down by what works and doesn't work:
This does work, but, it is searching on 1 key/value pair...
SELECT p.name, p.id
FROM ProjectFieldValue pfv
LEFT JOIN Project p
ON pfv.project_id = p.id
WHERE (pfv.text_value IN ( SELECT text_value FROM ProjectFieldValue WHERE text_value IN ('Active')) AND field_key = 'contract_status')
GROUP BY p.id
This doesn't work because it is searching on 3 key/value pairs...
SELECT p.name, p.id
FROM ProjectFieldValue pfv
LEFT JOIN Project p
ON pfv.project_id = p.id
WHERE (pfv.text_value IN ( SELECT text_value FROM ProjectFieldValue WHERE text_value IN ('Active')) AND field_key = 'contract_status')
AND (pfv.text_value IN ( SELECT text_value FROM ProjectFieldValue WHERE text_value IN ('Seller')) AND field_key = 'contract_client_type')
AND (pfv.date_value between '2020-07-08 00:00:00' AND '2020-07-11 23:59:59' AND pfv.field_key = 'listing_date')
GROUP BY p.id
Goal
Ultimately, what I would need to be able to do is search on unlimited key/value pairs in this table and return all results grouped by the p.id
Thanks for your help!
This should do the thing.
SELECT p.name, p.id
FROM ProjectFieldValue pfv
LEFT JOIN Project p
ON pfv.project_id = p.id
WHERE (field_key = 'contract_status' AND pfv.text_value = 'Active')
OR (field_key = 'contract_client_type' AND pfv.text_value = 'Seller')
OR (pfv.field_key = 'listing_date' AND pfv.date_value between '2020-07-08 00:00:00' AND '2020-07-11 23:59:59')
GROUP BY p.id;
But I have doubt why you are Left joining Project and ProjectFieldValue. A simple Inner Join should solve your purpose. As you are grouping by on p.id. You may encourage a lot of NULLed columns. So I would suggest below.
SELECT p.name, p.id
FROM Project p
JOIN ProjectFieldValue pfv
ON p.id = pfv.project_id
WHERE (field_key = 'contract_status' AND pfv.text_value = 'Active')
OR (field_key = 'contract_client_type' AND pfv.text_value = 'Seller')
OR (pfv.field_key = 'listing_date' AND pfv.date_value between '2020-07-08 00:00:00' AND '2020-07-11 23:59:59')
GROUP BY p.id;
Related
I have the MySql tables for candidates, candidate-skills, and skills.
Which is the best way to select the candidates with all the skills
I tried using the following query. But it is not accurate.
Select `t`.*, GROUP_CONCAT(DISTINCT(s.name)) as skills,
GROUP_CONCAT(DISTINCT(s.id)) as skill_ids
FROM `candidates` `t`
LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
where s.id in ('8','10')
GROUP BY `t`.`id`
ORDER BY `t`.`id` desc
The two points what I want are:
All the skills should be shown (when commenting the where conditions in the )
Records with all skills are to be shown. (The records with one skill is also showing as I am using the where in array)
I am using codeigniter framework.
http://sqlfiddle.com/#!9/b75c3/49
instead of where use having clause.
select `t`.*, GROUP_CONCAT(DISTINCT(s.name)) as skills,
GROUP_CONCAT(DISTINCT(s.id)) as skill_ids
FROM `candidates` `t`
LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
GROUP BY `t`.`id`
having find_in_set ('8', skill_ids) and find_in_set ('10', skill_ids)
ORDER BY `t`.`id` desc
in Codeigniter
//take all skill ids in array
$ids=['8','10'];
$this->db->select("t.*");
$this->db->select("GROUP_CONCAT(DISTINCT(s.name)) as skills");
$this->db->select("GROUP_CONCAT(DISTINCT(s.id)) as skill_ids");
$this->db->from("candidates t");
$this->db->join("candidate-skills cs","t.id = cs.can_id");
$this->db->join("skills s","cs.skill_id = s.id");
$this->db->group_by("t.id");
foreach ($ids as $id) {
$this->db->having("find_in_set ('$id', skill_ids)");
}
$this->db->order_by("t.id","desc");
$query=$this->db->get();
$candidates=$query->result();
The most flexible way is using multiple JOINs; GROUP_CONCAT and comma delimited lists are considered an antipattern and it might not work if the concatenation isn't done exactly in the correct order (skill set 1,2,5 is considered not the same as 1,5,2).
SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
JOIN skills AS sk1 ON (cs.skill_id = sk1.id)
JOIN skills AS sk2 ON (cs.skill_id = sk2.id)
...other sk(N)...
WHERE (sk1.skill = 'waterskiing')
AND (sk2.skill = 'snowboarding')
...
;
This allows easy tailoring of skills if, for example, each skill has a skill level and you need for snowboarding to be skilled at or above level 5. This kind of flexibility is a hell to do with GROUP_CONCAT.
But for simple matching, you can do it faster by selecting the skills you want and just counting them:
SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
WHERE cs.skill_id IN (1, 7, 24, 19, 115)
GROUP BY c.id
HAVING COUNT(1) = 5;
(In more proper SQL you'd need to indicate explicitly all fields of c instead of "c.*", and repeat them in the GROUP BY clause. More clever RDBMS servers will not care as long as you group by c's primary key. MySQL currently does not care anyway, but in strict mode, it would).
For each skill you run a single, fast query on skills to retrieve its ID and assemble the query above.
Or you can do in a single, larger query as long as you have an exact match for the skill:
SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
JOIN skills AS s ON (cs.skill_id = s.id)
WHERE s.skill IN ('javascript', 'html5', 'php')
GROUP BY c.id
HAVING COUNT(1) = 3;
Since you want this in PHP:
$skills = array('javascript', 'html5', 'php');
$skno = count($skills);
$set = implode(',', array_fill('?', $skno));
$params = $skills;
$params[] = $skno;
$query = "SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
JOIN skills AS s ON (cs.skill_id = s.id)
WHERE s.skill IN ({$set})
GROUP BY c.id
HAVING COUNT(1) = ?";
$stmt = $db->prepare($query);
$stmt->execute($params);
while ($candidate = $stmt->fetch(PDO::FETCH_ASSOC)) {
...
}
Maybe this
select t.* ,
s.skills,s.skills_id
FROM `candidates` `t`
join
(
select t.id tid, group_concat(s.name) skills, group_concat(s.id order by s.id) skills_id
FROM `candidates` `t`
LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
group by t.id
) s
on s.tid = t.id
where instr(skills_id,'8,10') > 0
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| id | name | created_on | modified_on | is_deleted | skills | skills_id |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| 1 | Eugine | 2017-05-23 11:44:30 | NULL | N | zend framework 2,bootstrap,wordpress | 8,10,12 |
| 2 | Frinoy Francis | 2017-05-23 16:44:29 | NULL | N | html,html5,zend framework 2,php,bootstrap | 1,4,8,10,11 |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
2 rows in set (0.03 sec)
MariaDB [sandbox]> select t.* ,
-> s.skills,s.skills_id
-> FROM `candidates` `t`
-> join
-> (
-> select t.id tid, group_concat(s.name) skills, group_concat(s.id order by s.id) skills_id
-> FROM `candidates` `t`
-> LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
-> LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
-> group by t.id
-> ) s
-> on s.tid = t.id
-> where instr(skills_id,'') > 0
-> ;
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| id | name | created_on | modified_on | is_deleted | skills | skills_id |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| 1 | Eugine | 2017-05-23 11:44:30 | NULL | N | zend framework 2,bootstrap,wordpress | 8,10,12 |
| 2 | Frinoy Francis | 2017-05-23 16:44:29 | NULL | N | html,html5,zend framework 2,php,bootstrap | 1,4,8,10,11 |
| 3 | Arun | 2017-05-28 12:56:24 | NULL | N | bootstrap | 8 |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
3 rows in set (0.03 sec)
I need some help making the second join with the tables below. I got some help from here previously where it was suggested I need to add a second JOIN, however, this is where I'm stuck and need some assistance.
wp-posts
-----------------
id | Post_Title |
-----------------
01 | Event 1 |
-----------------
02 | Event 2 |
-----------------
wp-postmeta
-------------------------------------------------------
meta_id | post_id | meta_key | meta_value |
-----------------------------------------------------
456 | 01 | _EventStartDate | 01/01/2017 |
-------------------------------------------------- ---
789 | 01 | _EventEndDate | 05/02/2017 |
-----------------------------------------------------
The end result I'm after is something like;
Title - starts on <_EventStartDate > and ends on <_EventEndDate>
and im using the following to get the data:
$result = $wpdb->get_results ( "
SELECT $wpdb->posts.ID, $wpdb->posts.post_content, $wpdb->postmeta.meta_id,
$wpdb->postmeta.post_id, $wpdb->postmeta.meta_key, $wpdb->postmeta.meta_value, $wpdb->posts.post_title
FROM $wpdb->posts INNER JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id
WHERE $wpdb->postmeta.meta_key = '_EventStartDate'
ORDER BY $wpdb->postmeta.meta_value " );
Now I've been told that the WHERE will just return a single row and that I need to make the second JOIN using $wpdb->postmeta.meta_key = '_EventStartDate' but after hours of trying to implement this I'm unable to get and data back at all.
If someone could help solve this it would be immensely helpful as I have a few more queries I would like to write and I'm guessing I will need to use the same principal with them too.
Thanks for reading!!
You can do it with a single inner join, grouping by the post ID and then splitting start date and end date:
select
wpposts.post_content,
substring_index(GROUP_CONCAT(meta_value order by str_to_date(meta_value,'%d/%m/%Y')), ',', 1) as start_date ,
substring_index(GROUP_CONCAT(meta_value order by str_to_date(meta_value,'%d/%m/%Y')), ',', -1) as end_date
from wpposts inner join wppostmeta
on wpposts.id = wppostmeta.post_id
where wppostmeta.meta_key='_EventStartDate' or wppostmeta.meta_key='_EventEndDate'
group by wppostmeta.post_id
You can get the start_date and end_date in one row using group by like this:
select
t1.*,
max(case when meta_key = '_EventStartDate' then meta_value end) start_date,
max(case when meta_key = '_EventEndDate' then meta_value end) end_date
from wp_posts t1
inner join wp_postmeta t2
on t1.id = t2.post_id
group by t1.post_id;
if ONLY_FULL_GROUP_BY is enabled, change the group by clause to:
group by t1.post_id, t1.Post_title;
Assuming there is only one _EventStartDate and one _EventEndDate per post in wp-postmeta, a simplified query would be:
SELECT p.post_title, pm1.meta_value AS start_date, pm2.meta_value AS end_date
FROM wp-posts p
INNER JOIN wp-postmeta pm1
ON p.post_id = pm1.post_id
AND pm1.meta_ekey = '_EventStartDate'
INNER JOIN wp-postmeta pm2
ON p.post_id = pm2.post_id
AND pm2.meta_ekey = '_EventEndDate'
I've two tables, for example:
post:
id | author | content | date
1 | Lucas | Hello! | 2016
2 | Igor | Hi! | 2016
comment:
id | post_id | content | date
1 | 2 | hehehe | 2016
2 | 1 | hahaha | 2016
3 | 2 | huhuhu | 2016
And I to do a SELECT that return all posts and a COUNT of rows of all comments with post.id = comment.id.
So, I tried:
SELECT p.id, p.author, p.content, p.date, COUNT(*) AS numComments FROM post p LEFT JOIN comment ON p.id = post_id WHERE p.author = '$author' GROUP BY p.id DESC LIMIT 12
And I got do it. But, even when no exists comments with p.id = post_id he returns 1.
So, I tried:
SELECT p.id, p.author, p.content, p.date, CASE WHEN COUNT(*) < 1 THEN '0' ELSE COUNT(*) END AS numComments FROM post p LEFT JOIN comment ON p.id = post_id WHERE p.author = '$author' GROUP BY p.id DESC LIMIT 12
But the result is the same. How to do this?
As outer joins return a row even if there's no matching data you need to count a column from the inner table, usually it's the column used in join:
SELECT p.id, p.author, p.content, p.date, COUNT(post_id) AS numComments
FROM post p LEFT JOIN comment ON p.id = post_id
WHERE p.author = '$author'
GROUP BY p.id -- seems to be mysql, otherwise you need to add more columns to the list
If you don't want to show rows with a zero count simply switch to an
INNER JOIN.
you can get count by this way, also last is order by not group by:
SELECT p.id, p.author, p.content, p.date,
(select COUNT(*) from comment where p.id = comment.post_id) AS numComments FROM post p
WHERE p.author = '$author'
ORDER BY p.id DESC LIMIT 12
I'm trying to show post by order them with sum of comment and like.
There are three table using in this query post,comment and like
for table like it has column type that keep value like or unlike.
SQL
SELECT (SELECT COUNT(id) AS count_comment
FROM comment WHERE comment.post_id = post.post_id),
(SELECT COUNT(id) AS count_like
FROM like WHERE like.post_id = post.post_id AND like.type = 'like'),
(SELECT COUNT(id) AS count_unlike
FROM like WHERE like.post_id = post.post_id AND like.type = 'unlike'),
post.* FROM post
ORDER BY (count_comment + count_like - count_unlike) DESC;
So, this is an example when it shows on the page
post_id | comment | like | unlike | (comment+like-unlike)
4 | 5 | 3 | 1 | 7
1 | 2 | 3 | 0 | 5
2 | 1 | 1 | 4 | -2
... | ... | ... | ... | ...
My problem is my SQL is very slow, please suggest another way if it can. I've tried to use JOIN but i can't figured out how its SQL should be, please help thanks.
Using a derived table for each of the counts, the query below counts comments, likes, unlikes for each post and then joins the counts to the post table by post_id.
SELECT
p.post_id,
COALESCE(c.comment_count,0) comment_count,
COALESCE(l.like_count,0) like_count,
COALESCE(ul.unlike_count,0) unlike_count,
(COALESCE(c.comment_count,0)
+ COALESCE(l.like_count,0)
- COALESCE(ul.unlike_count,0)) total
FROM post p
LEFT JOIN (
SELECT c.post_id,
COUNT(*) comment_count
FROM comment c
GROUP BY c.post_id
) c ON c.post_id = p.post_id
LEFT JOIN (
SELECT l.post_id,
COUNT(*) like_count
FROM like l
WHERE l.type = 'like'
GROUP BY l.post_id
) l ON l.post_id = p.post_id
LEFT JOIN (
SELECT ul.post_id,
COUNT(*) unlike_count
FROM like ul
WHERE ul.type = 'unlike'
GROUP BY ul.post_id
) ul ON ul.post_id = p.post_id
ORDER BY total DESC
I have 3 tables.
myMembers
------------------------------------
id | username | privacy
------------------------------------
1 | userA | 0
2 | userB | 1
3 | userC | 0
4 | userD | 1
following
--------------------------------
id | user_id | follower_id
--------------------------------
1 | 2 | 1
posts
-------------------------------------
id | userID | username | statusMsg
--------------------------------------
1 | 4 | userD | Issac Newton is genius
2 | 2 | userB | Newton Saw apple
3 | 3 | userC | Newtonian Physics
4 | 1 | userA | Calculus came from Sir Newton
There is a search field. When a logged in user searches for 'keyword' in table 'posts', I want to omit results from those users who has set his privacy to '1' and WHERE searcher is not following user B.
The query should logically do this.
SELECT * from posts WHERE (match the keyword)
AND (
if (poster's privacy (which is set in myMembers)==1){
if (seacher is following poster){
select this post
}
}
else { select this post
}
)
LIMIT results to 5 rows
So for a keyword "Newton",
if userA is searching, rows 2,3,4 from 'posts' should be returned.
if userD is searching, only rows 1, 3 and 4 from 'posts' should be returned,
based on privacy and following
Edit: Tagging for future searches: IF condition within WHERE Clause in mySql
Please, try this query (also on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
LEFT JOIN following f ON p.user_id = f.user_id
JOIN members searcher ON searcher.username = 'userA'
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.follower_id = searcher.id)
OR m.id = searcher.id)
AND p.status_msg LIKE '%New%'
ORDER BY p.id
LIMIT 5;
I removed username field from posts table, as it is redundant. Also, I named tables and columns slightly different, so query might need cosmetic changes for your schema.
The first line in the WHERE clause is the one that you're looking for, it selects posts in the following order:
First posts from members without privacy;
Then posts from members that are followed by the current searcher;
Finally, posts of the member himself.
EDIT:
This query is using original identifiers:
SELECT p.id, p.`userID`, m.username, m.privacy,
searcher.username "Searcher", p.`statusMsg`
FROM posts p
JOIN `myMembers` m ON m.id = p.`userID`
LEFT JOIN following f ON p.`userID` = f.user_id
JOIN `myMembers` searcher ON searcher.username = 'userD'
WHERE (m.privacy = 0 OR f.follower_id = searcher.id OR m.id = searcher.id)
AND p.`statusMsg` LIKE '%New%'
ORDER BY p.id
LIMIT 5;
EDIT 2:
To avoid duplicates in case there're several followers for the user from the posts table, join and filtering conditions should be changed the following way (on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
JOIN members searcher ON searcher.username = 'userC'
LEFT JOIN following f ON p.user_id = f.user_id
AND follower_id = searcher.id
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.id IS NOT NULL)
OR m.id = searcher.id)
ORDER BY p.id
LIMIT 5;
Try the following:
SET #my_user_id= 1;
SELECT * FROM posts p
INNER JOIN myMembers m ON p.user_id= m.id
WHERE statusMsg LIKE '%'
AND privacy=0
AND user_id IN (SELECT follower_id FROM following f WHERE f.user_id=#my_user_id)
LIMIT 5
try this:
SELECT a.*
FROM posts a
LEFT JOIN (SELECT user_id
FROM following a1
INNER JOIN myMembers b1
ON a1.follower_id = b1.id
WHERE a1.follower_id = 1 AND
b1.privacy = 1
) b
ON a.userID = b.user_id AND
WHERE a.statusMsg LIKE '%search%' AND
b.user_id IS NULL
LIMIT 5;
or better approach without subquery:
SELECT a.*
FROM posts a
LEFT JOIN myMembers b
ON a.userID = b.id AND
b.privacy = 1
LEFT JOIN following c
ON a.userID = c.user_id AND
c.follower_id = 1
WHERE a.statusMsg LIKE '%search%' AND
b.id IS NULL AND
c.user_id IS NULL
LIMIT 5;
See: A Visual Explanation of SQL Joins