Complicated MySQL Query - php

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

Related

Need assistance with this SQL query

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'

How to subtract values of two different columns from two different tables?

Example Table Structure
Table 1
ID | Name | Price
-----------------------------
1 | Casio | 30
2 | Titan | 40
Table 2
ID | Place | Price
-----------------------------
1 | Cali | 30
2 | Mexi | 10
Operation to perform:
Table1(Price) - Table2(Price) for ID = 1
New Table 1
ID | Name | Price
-----------------------------
1 | Casio | 0
2 | Titan | 40
ID matches in both tables
You should consider another database design to handle this case.
But to answer your question, you can create a view :
create view Differences2 as (
select t1.id, t1.price - t2.price
from t1, t2
where t1.id = t2.id
)
As you told both table will have same ID column you can use following query.
SELECT table1.ID, table1.Name, (table1.Price-table2.Price) AS Price
FROM table1
INNER JOIN table2 ON table1.ID = table2.ID
If you want to update record you can use following:
UPDATE table1
INNER JOIN table2 ON table1.ID = table2.ID
SET table1.Price = (table1.Price-table2.Price)

Connecting 3 MySQL tables, what am I doing wrong

I have a 3 tables with the following data in MySQL
tags table
id | groupId | name |
-----------------------------
1 | 1 | tag1 |
2 | 1 | tag2 |
3 | 1 | tag3 |
4 | 1 | tag4 |
groupId column here is unimportant right now.
practice table
id | name |
----------------------
1 | practice 1 |
2 | practice 2 |
3 | practice 3 |
4 | practice 4 |
and a bridge between the two (practiceTag table)
id | practiceId | tagId |
-----------------------------------
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
4 | 2 | 1 |
But when I try to use the query:
SELECT practice.name
FROM practice
INNER JOIN practiceTag ON practiceTag.practiceId = practice.id
INNER JOIN tags ON practiceTag.tagId = tags.id
WHERE (tags.id = "1"
AND tags.id = "2"
AND tags.id = "3")
it doesn't return anything. And thats ok, since I know my query is kinda messed up. But thats as closest as I can get to show you, what I would like my query to do.
I've searched the forums and find out that I can use a group_concat. But I just can't get it to work.
Any help would be appreciated.
Thanks, Sebastian
I think your tags.id column is integer not string, so your sql query would be like this:
SELECT practice.name
FROM practice
INNER JOIN practiceTag ON practiceTag.practiceId = practice.id
INNER JOIN tags ON practiceTag.tagId = tags.id
WHERE tags.id in (1,2,3);
You in clause not and
SELECT practice.name
FROM practice
INNER JOIN practiceTag ON practiceTag.practiceId = practice.id
INNER JOIN tags ON practiceTag.tagId = tags.id
WHERE tags.id in ( '1', '2', '3') ;
if the id is numeric
SELECT practice.name
FROM practice
INNER JOIN practiceTag ON practiceTag.practiceId = practice.id
INNER JOIN tags ON practiceTag.tagId = tags.id
WHERE tags.id in ( 1, 2, 3) ;
the query is equivalent
SELECT distinct practice.name
FROM practice
INNER JOIN practiceTag ON practiceTag.practiceId = practice.id
INNER JOIN tags ON practiceTag.tagId in (1,2,3)
And if you want only the practice.name when all the 3 tagsId match the you should use temp table for getting the join table
SELECT distinct practice.name
FROM practice
inner join (
select practiceId from
( select distinct practiceTag.practiceId as praticeId, practiceTag.tagId as tagId
from tags ) as t1
where tagId in (1,2,3)
having count(*) > 2
group by praticeId ) as t2;
In your WHERE part you ask for results that have id 1, 2 and 3 at the same time.
Replace the AND in your WHERE with OR

Table inside of another

I want to plan my trips publicly so other people can join me. So, I have set-up an PHP site.
I have this tables:
trips:
+----+---------+------------+------------+-------------------------+
| id | title | date_start | date_end | marker_adress |
+----+---------+------------+------------+-------------------------+
| 1 | Berlin | 2015-07-10 | 2015-07-11 | Potsdamer Platz, Berlin |
| 2 | Hamburg | 2015-07-16 | 2015-07-18 | Jungfernstieg, Hamburg |
+----+---------+------------+------------+-------------------------+
fellows:
+----+---------+---------------+
| id | trip_id | twittername |
+----+---------+---------------+
| 1 | 1 | prtyengopls |
| 2 | 1 | itobi_yt |
| 3 | 1 | jessisadancer |
| 4 | 2 | jessisadancer |
| 5 | 2 | woelfch3n |
+----+---------+---------------+
For displaying sake, I want to query them in one query. How can I query the database so I have something like this? (I know, it's JSON but it shows the structure very well.)
{
"id": 1,
"date_start": "2015-07-10",
"date_end": "2015-07-11",
"marker_adress": "Potsdamer Platz, Berlin",
"fellows": [
{
"id": 1,
"twittername": "prtyengopls"
},
{
"id": 2,
"twittername": "itobi_yt"
},
{
"id": 3,
"twittername": "jessisadancer"
}
]
}
First you have to use a LEFT JOIN like this:
SELECT
t.id AS tripID,
t.title AS title,
t.date_start AS dateStart,
t.date_end AS dateEnd,
t.marker_address AS markerAddress,
GROUP_CONCAT(DISTINCT CAST(f.id AS CHAR)) AS fellowID,
GROUP_CONCAT(DISTINCT CAST(f.twittername AS CHAR)) AS twitterName
FROM trips t LEFT JOIN fellows f ON t.id = f.trip_id
GROUP BY t.id
By using this you will get a single row for each trip and you can loop over fellowID and twitterName for each row, as it will be comma delimited list like this:
fellowID: 1,2,3
twitterName: prtyengopls,itobi_yt,jessisadancer
Edit 1: I got a new column to trips called checked which is a boolean.
Could you update your query, so only trips that have this boolean
toggled on are displayed?
SELECT
t.id AS tripID,
t.title AS title,
t.date_start AS dateStart,
t.date_end AS dateEnd,
t.marker_address AS markerAddress,
GROUP_CONCAT(DISTINCT CAST(f.id AS CHAR)) AS fellowID,
GROUP_CONCAT(DISTINCT CAST(f.twittername AS CHAR)) AS twitterName
FROM trips t INNER JOIN fellows f
ON t.id = f.trip_id AND t.checked = 1
GROUP BY t.id
I don't think it's possible like you expected in SQL. Just to answer your question and show you the problem: you have to use a join (left, inner or something ... depends of your database structure; I always prefer left joins if possible) to get all information in just one query:
SELECT * FROM trips t LEFT JOIN fellows f on t.id = f.trip_id WHERE t.id = 1;
But you always will get the trip information with every row, and for that you have to process it afterwards. You will never able to select such a nested structure, you will always get a flat one.
So I would recommend to split it into two queries like this:
SELECT * FROM trips WHERE id = 1;
SELECT * FROM fellows WHERE trip_id = 1;
You will have to process the information afterwards too, but you just select the wanted information from your database.
Hope that helps.

Find the max value of a column, then group by another column in same table

I have a similar problem to the one solved here, but when I try the solution I think it fails because I have things set up differently..
I have a doc table with...
(unfortunately table cant be edited due to it being an old system)
+-------+--------+----------+--------+
| Docid | title | revision | linkid |
+-------+--------+----------+--------+
| 1 | docone | 1 | 1 |
| 2 | doctwo | 1 | 2 |
| 3 | docone | 2 | 1 |
|4 | docone | 3 | 1 |
+-------+--------+----------+--------+
On a page that lists all the documents I want to list only the latest revision of each document. Doc1 for example is on revision 3 so I want that one and not the other 2. Doc2 is only on revision 1 so show that one.
Based on the problem in the other post I have writen my query as follows......
$query_docs = "
SELECT `document`.*, doctype.*
FROM `document`
INNER JOIN doctype
ON `document`.iddoctypes = doctype.iddoctypes
WHERE `document`.revision = (
SELECT MAX(`document`.revision) AS revision
FROM `document`
)
GROUP BY `document`.linkid
ORDER BY `document`.doccreation DESC";
I have had to link to another table to get the document type (just to make the query harder).
Try this, I made a couple minor changes
SELECT document.*, doctype.*
FROM document
INNER JOIN doctype
ON document.iddoctypes = doctype.iddoctypes
WHERE document.revision = (
SELECT MAX(d1.revision)
FROM document d1
WHERE document.linkid = d1.linkid
)
ORDER BY document.doccreation DESC
Just a wild guess: I think you have to add group by title in your (select max(...) ...) subquery.
So the complete statement will be this:
$query_docs = "
SELECT `document`.*, doctype.*
FROM `document`
INNER JOIN doctype
ON `document`.iddoctypes = doctype.iddoctypes
WHERE `document`.revision = (
SELECT MAX(`document`.revision) AS revision
FROM `document`
--GROUP BY `document`.title
GROUP BY `document`.linkid
)
GROUP BY `document`.linkid
ORDER BY `document`.doccreation DESC";
Am I missing something? This seems completely straightforward...
SELECT x.*
FROM my_table x
JOIN (SELECT title,MAX(revision) max_revision FROM my_table GROUP BY title) y
ON y.title = x.title
AND y.max_revision = x.revision;

Categories