Need assistance with this SQL query - php

This is an example of my table. I have multiple entries on multiple rows for a given post_id (this is metadata for posts).
post_id | meta_key | meta_value
________ __________ ___________
| |
1 | _theDate | 2016-03-31
1 | _email | the#email.com
2 | _theDate | 2016-01-06
2 | _email | the#email.com
3 | _theDate | 2017-02-14
3 | _email | other#user.net
4 | _theDate | 2016-10-01
4 | _email | the#email.com
5 | _theDate | 2016-09-25
5 | _email | other#user.net
6 | _theDate | 2015-11-19
6 | _email | other#user.net
What I am trying to accomplish:
I would like to find all instances of a post with the email address the#email.com and the year "2016" in the metadata, and then count those individual posts to find out how many posts were written by the user the#email.com during the year "2016".
For the moment I have managed to find only the instances of the email address using
SELECT DISTINCT post_id
FROM metatable
WHERE meta_value LIKE '%the#email.com%'
This counts the total posts for that user but not only the ones written in 2016.

Here is one method that uses two levels of aggregation :
SELECT COUNT(*)
FROM (SELECT post_id
FROM metatable
WHERE (meta_key = '_email' AND meta_value = 'the#email.com') OR
(meta_key = '_theDate' AND LEFT(meta_value, 4) = '2016')
GROUP BY post_id
HAVING COUNT(DISTINCT meta_key) = 2
) p;
Edit : missing a quote

Here you go:
SELECT t.post_id -- Replace with `SELECT count(*)` to just have the total
FROM table t
WHERE t.meta_key = '_email' AND t.meta_value = 'the#email.com' AND
EXISTS (
SELECT 1
FROM table
WHERE post_id = t.post_id AND YEAR(meta_value) = 2016
meta_key = '_theDate' AND meta_value = xxx
);

SELECT
COUNT(*)
FROM
metatable m1
INNER JOIN metatable m2 ON m2.post_id = m1.post_id
WHERE
AND m1.meta_key = _theDate
AND m1.meta_value LIKE '2016%'
AND m2.meta_key = _email
AND m2.meta_value = 'the#email.com'

Start by joining the table with itself, in order to get a 'id-user-date' structured table:
SELECT email.post_id, email.meta_value as mail, date.meta_value as date from metatable as email
inner join metatable as dateTable
on email.post_id = dateTable.post_id
and email.meta_key = '_email'
and dateTable.meta_key = '_theDate'
And on that you can do what you want:
SELECT count(*) from metatable as email
inner join metatable as dateTable
on email.post_id = dateTable.post_id
and email.meta_key = '_email'
and dateTable.meta_key = '_theDate'
where
email.meta_value = 'the#email.com'
and date.meta_value like '2016%';

Considering that you would like count all where the meta_key is "_theDate" the Select will:
SELECT COUNT(1) FROM table WHERE YEAR(aux_meta_value) = "2016" AND meta_key = '_theDate'

Related

MYSQL Select from tables based on multiple rows

I have a table called user_meta. In that table I have the following columns: ID, userID, meta_key, meta_value
I have another table called users, the only important column there is ID, which I want to compare to the user_meta table rows.
The users table looks like:
ID | email | etc...
1 | email#test.com |
5 | testa#a.com |
6 | .... |
7 | .... |
So say I have a table (user_meta) that looks like:
ID | userID | meta_key | meta_value
2 | 1 | companyID | 2
3 | 1 | user_type | staff
4 | 5 | companyID | 2
5 | 5 | user_type | staff
6 | 6 | companyID | 4
7 | 6 | user_type | customer
I want to retrieve a single row for each userID, but only if the company ID and user_type are correct.
I want to retrieve all users that have the same companyID that I would send in the query, so let's say $companyID=2, and then all users that have the user_type='staff'.
So user_meta.userID must equal users.ID, and user_meta.companyID must equal 2, and user_meta.user_type must equal 'staff'.
I want a list of all users that match these criteria.
A result would be userID 1 & 5 are returned. They both have companyID = 2, and both have user_type = staff
You need to join with user_meta once for each attribute you want to match.
SELECT u.*
FROM users AS u
JOIN user_meta AS m1 ON u.id = m1.userID
JOIN user_meta AS m2 ON u.id = m2.userID
WHERE m1.meta_key = 'companyID' AND m1.meta_value = :companyID
AND m2.meta_key = 'user_type' AND m2.meta_value = 'staff'
Not very sure about your question. I'm assuming this is what you may want:
select * from Users where ID in (
select userID from user_meta where (meta_key = 'companyID' and meta_value = 2) or (meta_key = 'user_type' and meta_value = 'staff')
);
SELECT `users`.`id`,
`Company`.`meta_value`,
`UserType`.`meta_value`
FROM `users`
JOIN `user_meta` `Company`
ON `Company`.`userid` = `users`.`id`
JOIN `user_meta` `UserType`
ON `UserType`.`userid` = `users`.`id`
WHERE `UserType`.`meta_value` = 'staff'
AND `Company`.`meta_value` = 2
https://gyazo.com/de8d9124418f65b993d708c80c309325

Use left join but the value no showing?

I have 2 table, game_jnship_equip_list is store equipment basic info, game_jnship_equip is store for what does player have.
Here is game_jnship_equip_list data:
+--------+----------------+----------+----------+
| ID | desc | name | type |
+--------+----------------+----------+----------+
| 1 | hello_weapon | weapon | 1 |
| 2 | hello_shirt | shirt | 2 |
| 3 | hell_weapon | Hweapon | 1 |
+-----------------------------------------------+
Here is game_jnship_equip data:
+------+----------+--------------+------------+------------+
| ID | userid | itemcode | atfigure | eposition |
+------+----------+--------------+------------+------------+
| 1 | 1 | 1 | 100 | 1 |
| 2 | 1 | 2 | 500 | 2 |
+----------------------------------------------------------+
And I query as below:
$quequip = DB::query("SELECT t1.*,t2.name AS weaponame, t2.edesc AS weapondesc, t3.atfigure AS atkbonus, t4.name AS shirtname, t4.edesc AS shirtdesc FROM ".DB::table('game_jnship_equip')." t1 LEFT JOIN ".DB::table('game_jnship_equip_list')." t2 ON (t1.itemid = t2.id AND t1.eposition = '1') LEFT JOIN ".DB::table('game_jnship_equip')." t3 ON (t2.type = t3.eposition) LEFT JOIN ".DB::table('game_jnship_equip_list')." t4 ON (t1.itemid = t4.id AND t1.eposition = '2') WHERE t1.uid = 'userid' AND t1.status = '1'");
$ruequip = DB::fetch($quequip);
But, I only can get value as below:
$ruequip['weaponame'] = weapon;
$ruequip['weapodesc'] = hello_weapon;
$ruequip['atkbonus'] = 100;
Then about the t4 all blank.
$ruequip['shirtname'] = ;
$ruequip['shirtdesc'] = ;
I want it to show value as below:
$ruequip['shirtname'] = shirt;
$ruequip['shirtdesc'] = hello_shirt;
So how to fix this? and my DB::query function, it cannot allow DB::query(SELECT * FROM xxxx (SELECT * FROM)), means 2 select inside 1 DB::query, system will reject by safety issue.
Thank you.
You can do this with conditional aggregation using CASE EXPRESSION instead of 3 left joins :
SELECT s.id,s.userid,s.itemcode,s.atfigure,s.eposition,
MAX(CASE WHEN s.eposition = 1 THEN s.desc END) as weap_desc,
MAX(CASE WHEN s.eposition = 1 THEN s.name END) as weap_name,
MAX(CASE WHEN s.eposition = 2 THEN s.desc END) as shirt_desc,
MAX(CASE WHEN s.eposition = 2 THEN s.name END) as shirt_name
FROM (SELECT t.id,t.userid,t.itemcode,t.atfigure,t.eposition,t2.desc,t2.name
FROM ".DB::table('game_jnship_equip')." t
LEFT OUTER JOIN ".DB::table('game_jnship_equip_list')." t2
ON(t1.itemid = t2.id AND t.eposition = t2.type) )s
GROUP BY s.user_id
Why not a union ?
SELECT
list.id as weaponid,
list.desc as weapondesc,
list.name as weaponame,
quip.atfigure as atkbonus
from ".DB::table('game_jnship_equip_list')." as list
left join ".DB::table('game_jnship_equip')." as quip
on (quip.itemcode = list.id)
Where list.type = 1
UNION
SELECT
list.id as shirtid,
list.desc as shirtdesc,
list.name as shirtname,
quip.atfigure as atkbonus
from elist as list
left join equip as quip
on (quip.itemcode = list.id)
Where list.type = 2
This will get you a specific type 2 and a specific type 1. I didn't added the other user and position conditions but you can do that yourself.

How can I put a condition on the way of join?

I have this table structure:
// QandA
+----+---------------------+----------------------------------------+------+---------+
| Id | title | content | type | related |
+----+---------------------+----------------------------------------+------+---------+
| 1 | title of question 1 | content of question1 | 0 | 1 |
| 2 | | content of first answer for question1 | 1 | 1 |
| 3 | title of question 2 | content of question2 | 0 | 3 |
| 4 | | content of second answer for question1 | 1 | 1 |
| 5 | | content of first answer for question2 | 1 | 3 |
+----+---------------------+----------------------------------------+------+---------+
type column: 0 means it is a question and 1 means it is a answer.
related column: for question this column is containing the id of itself and for answer this column is containing the id of its question.
Also there is other dependent tables:
// Votes
+----+---------+---------+-------+
| id | post_id | user_id | value |
+----+---------+---------+-------+
| 1 | 1 | 1234 | 1 |
| 2 | 2 | 1234 | -1 |
| 3 | 1 | 4321 | 1 |
+----+---------+---------+-------+
// Favorites
+----+---------+---------+
| id | post_id | user_id |
+----+---------+---------+
| 1 | 1 | 1234 |
| 2 | 1 | 4321 |
+----+---------+---------+
Ok well, This is the main note in my question: Favorites table is only belong to the questions (not answers). Answers can never be favorite (just questions can be)
Also here is my query:
SELECT
p.title, p.content,
vv.value AS cuvv -- cuvv is stand for current_user_vote_value,
CASE WHEN ff.id IS NOT NULL THEN '2' ELSE '3' END AS cuf -- current_user_favorite
(SELECT SUM(v.value) FROM Votes v WHERE p.id = v.post_id) AS total_votes,
(SELECT COUNT(1) FROM Favorites f WHERE p.id = f.post_id) AS total_favorites,
FROM QandA p
LEFT JOIN Votes vv ON p.id = vv.post_id AND vv.user_id = :user_id_1
LEFT JOIN favorites ff ON p.id = ff.post_id AND f.user_id = :user_id_2
WHERE p.related = :id
Note: For cuf, 2 means current user has marked this question as favorite and 3 means he didn't have (in other word, 3 means this question isn't favorite for current user).
Ok, let me pass some parameters to query and execute it: (as an example)
$user_id = 1234;
$id = 1;
$sth->bindValue(":user_id_1", $user_id, PDO::PARAM_INT);
$sth->bindValue(":user_id_2", $user_id, PDO::PARAM_INT);
$sth->bindValue(":id", $id, PDO::PARAM_INT);
$sth->execute();
And here is the output:
-- cuvv is stand for current_user_vote_value
-- cuf is stand for current_user_favorite
+--------------+----------------------+------+-----+-------------+-----------------+
| title | content | cuvv | cuf | total_votes | total_favorites |
+--------------+----------------------+------+-----+-------------+-----------------+
| title of ... | content of que ... | 1 | 2 | 2 | 2 |
| | content of fir ... | -1 | 3 | -1 | 0 |
| | content of sec ... | NULL | 3 | 0 | 0 |
+--------------+----------------------+------+-----+-------------+-----------------+
Ok So, What's my question?
These two columns cuf and total_favorites are just belong to questions (type = 0). But my query doesn't know it. I mean my query calculates the number of total favorites for all rows, and I want to know, how can tell it: calculate cuf and total_favorites only for questions, not both questions and answers?
In other word, I need to put a IF condition to check if p.type = 0 then execute these two lines:
(SELECT COUNT(1) FROM Favorites f WHERE p.id = f.post_id) AS total_favorites,
and
LEFT JOIN favorites ff ON p.id = ff.post_id AND f.user_id = :user_id_2
Otherwise doesn't execute those two lines, because if p.type = 1, then those two lines are waste and useless.
How can I implement that condition and improve that query?
One way you may want to try is to query the favorite and votes table only once in subqueries, and calculate both the user and all values at once.
SELECT
q.title, q.content,
IFNULL(vv.user_val, 0) cuvv, IFNULL(vv.all_val, 0) total_votes,
IFNULL(ff.user_fav, 0) cuf, IFNULL(ff.all_fav, 0) total_favorites
FROM QandA q
LEFT JOIN (
SELECT post_id,
SUM(value) all_val, SUM(CASE WHEN user_id=1234 THEN value END) user_val
FROM votes GROUP BY post_id
) vv
ON vv.post_id = q.id
LEFT JOIN (
SELECT post_id,
COUNT(1) all_fav, COUNT(CASE WHEN user_id=1234 THEN 1 END) user_fav
FROM favorites GROUP BY post_id
) ff
ON q.type=0 AND ff.post_id = q.id
WHERE q.related = 1;
An SQLfiddle to test with.
Try this:
SELECT
p.id, p.type,p.title, p.content,
vv.value AS cuvv,
CASE WHEN ff.id IS NOT NULL THEN '2' ELSE '3' END AS cuf,
(SELECT SUM(v.value) FROM Votes v WHERE p.id = v.post_id) AS total_votes,
(SELECT COUNT(1) FROM Favorites f WHERE p.id = f.post_id) AS total_favorites
FROM QandA p
LEFT JOIN Votes vv ON p.id = vv.post_id AND vv.user_id = '1234'
LEFT JOIN Favorites ff ON p.id = ff.post_id AND ff.user_id = '1234'
WHERE p.related = 1 and p.type=0
union all
SELECT
p.id, p.type,p.title, p.content,
vv.value AS cuvv,
'3' AS cuf,
(SELECT SUM(v.value) FROM Votes v WHERE p.id = v.post_id) AS total_votes,
NULL AS total_favorites
FROM QandA p
LEFT JOIN Votes vv ON p.id = vv.post_id AND vv.user_id = '1234'
WHERE p.related = 1 and p.type=1;

Group by 2 columns regardless of order

The following query
select a.message, a.sender_id, a.rec_id, a.id, a.is_seen, b.total_msg, b.last_id, users.name
from tbl_message a left join users on (users.id=a.sender_id)
inner join
(select sender_id, rec_id, max(id) last_id, count(*) total_msg
from tbl_message group by sender_id,rec_id
)b on a.id=b.last_id
order by a.id desc
gives the result as below:
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
| message | sender_id | rec_id | id | is_seen | total_msq | last_id | name |
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
| latest testing l5 aug | 2 | 1 | 12 | 0 | 5 | 12 | B |
| testing | 1 | 2 | 11 | 1 | 3 | 11 | A |
| this msg of A | 1 | 3 | 9 | 0 | 1 | 9 | A |
| this is again 3rd msg of C | 3 | 1 | 6 | 1 | 3 | 6 | C |
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
I want the result as:
For sender_id/rec_id = 1 or 2 id = 12 and for sender_id/rec_id = 1 or 3 id = 9
It sounds like you want to group rows by the sender_id,rec_id participants pair regardless of which order they appear in (i.e. sender_id,rec_id or rec_id,sender_id should be part of the same group).
If so, change your group by from
group by sender_id, rec_id
to
group by least(sender_id,rec_id), greatest(sender_id,rec_id)
Using greatest and least will ensure that each conversation will be grouped by the participants regardless of which order they appear in.
Looks like you need to join with all the grouped columns
Try this
SELECT a.message
,a.sender_id
,a.rec_id
,a.id
,a.is_seen
,b.total_msg
,b.last_id
,users.NAME
FROM tbl_message a
LEFT JOIN users ON (users.id = a.sender_id)
INNER JOIN (
SELECT sender_id
,rec_id
,max(id) last_id
,count(*) total_msg
FROM tbl_message
GROUP BY sender_id
,rec_id
) b ON a.sender_id=b.sender_id and a.rec_id=b.rec_id and a.id = b.last_id
ORDER BY a.id DESC
I think you need to remove one column from the GROUP BY clause of the derived table (subquery)
select a.message, a.sender_id, a.rec_id, a.id, a.is_seen, b.total_msg, b.last_id, users.name
from tbl_message a left join users on (users.id=a.sender_id)
inner join
(select sender_id, rec_id, max(id) last_id, count(*) total_msg
from tbl_message group by sender_id
)b on a.id=b.last_id
order by a.id desc
I would expect this to result in rows only for the following
SenderID ID
2 12
1 9
3 6

Complicated MySQL Query

I'm creating a site in wordpress which holds information on television programs. I'm using custom fields to select each post.
The table looks something like this
+----+---------+----------+------------+
| id | post_id | meta_key | meta_value |
+----+---------+----------+------------+
| 1 | 1 | name | Smallville |
| 2 | 1 | season | 1 |
| 3 | 1 | episode | 1 |
| 4 | 2 | name | Smallville |
| 5 | 2 | season | 1 |
| 6 | 2 | episode | 2 |
+----+---------+----------+------------+
Basically what I need to do is select all of the tv shows with the name "Smallville" and sort them by season then by episodes. I thought it would be fairly simple but everything I have tried returns nothing.
Could you please explain how I can do this?
You can do something like this:
SELECT
t1.post_id,
t1.meta_value AS name,
t2.meta_value AS season,
t3.meta_value AS episode
FROM
(
SELECT *
FROM the_table
WHERE meta_key = 'name'
) t1
INNER JOIN
(
SELECT *
FROM the_table
WHERE meta_key = 'season'
) t2 ON t1.post_id = t2.post_id
INNER JOIN
(
SELECT *
FROM the_table
WHERE meta_key = 'episode'
) t3 ON t1.post_id = t3.post_id
This will give you the result:
| post_id | name | season | episode |
-------------------------------------------
| 1 | Smallville | 1 | 1 |
| 2 | Smallville | 1 | 2 |
In this form it is much easier for any operations.
What you need is to add:
WHERE name = 'Smallville'
ORDER BY season, episode
Combine the rows using a self-join, and you're good to go:
SELECT *
FROM yourtable name
INNER JOIN yourtable season
on season.post_id = name.post_id
and season.meta_key = 'season'
INNER JOIN yourtable episode
on episode.post_id = name.post_id
and episode.meta_key = 'episode'
WHERE name.meta_key = 'name'
and name.meta_value = 'Smallville'
ORDER BY season.meta_value, episode.meta_value
A more general case: sort-of conversion from your format to a more normal relational DB format:
SELECT (SELECT meta_value FROM data t1 WHERE t1.post_id = t0.post_id AND meta_key = "season") AS season,
(SELECT meta_value FROM data t1 WHERE t1.post_id = t0.post_id AND meta_key = "episode") AS episode
FROM data t0 WHERE meta_key = "name" AND meta_value = "Smallville"
For the actual sorting you can't reuse the season / episode values (those aren't assigned yet while sorting), so you have to copy/paste the subquery into the ORDER BY clause:
ORDER BY (SELECT ... "season") ASC, (SELECT ... "episode") ASC,
No need to do direct SQL.
You've got access to the SQL query through the WP_Query object. Check out the filters surrounding the where clause in the WP_Query object (there is more than 1 way to get at it) and simply modify the default WP_Query parts before they're concatenated together.
Start by setting up a WP_Query object that gets all the posts by postmeta key & postmeta value, and then tack on a bit more to the where clause to do some extra conditionals.
There's another filter that allows you to get at the ordering section of the SQL query so you can modify that.
There's no reason to hand write SQL here, just modify what has already been built for you.
the idea is to join the table to itself 3 times where for each of them take rows for a given meta_key:
SELECT t1.meta_value name, t2.meta_value season, t3.meta_value episode
FROM table t1
JOIN table t2 on t1.post_id = t2.post_id and t2.meta_key = 'season'
JOIN table t3 on t1.post_id = t3.post_id and t3.meta_key = 'episode'
WHERE t1.meta_key = 'name'
AND t1.meta_value = 'Smallville'
ORDER BY t2.meta_value, t3.meta_value

Categories