I am currently working on a site utilizing the WP-Property plugin for Wordpress.
Basically I am attempting to display a counter next to a text search link for a particular price range.
Below is the query I am running which does the price range count perfectly fine.
$apartprice1 = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->postmeta WHERE meta_value BETWEEN 50000 AND 74999");
if (0 < $apartprice1) $apartprice1 = number_format($apartprice1);
I would however like to extend on this to display only "Apartments" which are within this particular price range.
Now bear in mind that within the meta_value column the property type values (apartments, houses, freeholds) are also stored.
Any boffins about whom can demystify this for me please?
edit: Some further elaboration below:
Table name is postmeta
meta_id post_id meta_key meta_value
348 41 property_type apartment
358 41 price 698000
So I would like to be able to do a count of a particular price range which is also based on a specific property type
This table structure makes it somewhat difficult/inefficient to do the type of multifacet searching you're looking for. To ensure that all WHERE clauses are met, you'll need to INNER JOIN the meta table to itself. This gives you a temporary data format that's bit more suitable for your query.
SELECT m1.post_id
FROM meta m1
INNER JOIN meta m2 ON m1.post_id = m2.post_id
WHERE m1.meta_key = 'price'
AND m1.meta_value
BETWEEN 50000 AND 7000000 AND m2.meta_key = 'property_type'
AND m2.meta_value = 'apartment'
Keep in mind, for every additional facet you add, you'll need to do an additional INNER JOIN. Also, if you don't have one already, I'd recommend an index on the post_id column.
To get the count, just change what's in the SELECT clause.
SELECT COUNT(*)
FROM meta m1
INNER JOIN meta m2 ON m1.post_id = m2.post_id
WHERE m1.meta_key = 'price'
AND m1.meta_value
BETWEEN 50000 AND 7000000 AND m2.meta_key = 'property_type'
AND m2.meta_value = 'apartment'
If you are looking for displaying the count for each of the property values the partments, houses, freeholds, you should add a GROUP BY property_type
SELECT property_type, COUNT(*)
FROM $wpdb->postmeta
WHERE meta_value BETWEEN 50000 AND 74999
GROUP BY property_type
Update: You have to pivot your rows into columns in order to get the data the way you described like so:
SELECT t.property_type, COUNT(t.post_id) AS 'type count'
FROM
(
SELECT t.post_id,
MAX(CASE WHEN t.meta_key = 'property_type' THEN t.meta_value END) AS property_type,
MAX(CASE WHEN t.meta_key = 'price' THEN CAST(t.meta_value AS DECIMAL) END) AS Price
FROM postmeta t
GROUP BY t.post_id
) t
WHERE t.property_type = 'apartment'
AND t.Price BETWEEN 600000 AND 700000
GROUP BY t.property_type
Note that the meta_value field is supposed to store both a string values for property types as well as numeric values for prices so I CAST it to DECIMAL in order to select it as numeric data type so that you can perform calculation on it later on. I also used MAX as an aggregate function as a work around to pivot the rows.
Here is a demo in SQL fiddle.
This what you usually encounter in such a design model which is what they called Entity–attribute–value model. You can find a lot of useful thread about this data model under the tag eav. You can also find more useful pages about this design model in the following answer
Related
For those familiar with Magento, I'm attempting to write a query to create new upsell entries in the catalog_product_link table, but with some unique criteria. My knowledge of MySQL (or any SQL) is basic at best.
I have two types of products in a table, which I can uniquely identify by joining a few other tables. I can get a unique list of the two types of product results like so:
SELECT cpev.entity_id AS product_id, cpev.value AS name
FROM catalog_product_entity_varchar AS cpev
INNER JOIN catalog_product_entity AS cpe
ON cpe.entity_id = cpev.entity_id
INNER JOIN eav_attribute AS ea
ON ea.attribute_id = cpev.attribute_id
INNER JOIN catalog_category_product AS cat
ON cat.product_id = cpev.entity_id
WHERE cat.category_id IN ( 41 )
AND ea.attribute_code = 'name'
GROUP BY product_id
ORDER BY product_id;
...where the two lists of results are based on changing WHERE cat.category_id IN ( 41 ).
What I'm attempting to do is match a single row from the first list of results to one or more rows from the second list based on the name. The full name from the first list will match the first portion of the name from the second list plus some text; for example:
list1, name: Afinia L801
list2, name: Afinia L801 On-site Maintenance
Contract
For each match, I need to insert a new row in a different table, like so:
INSERT INTO catalog_product_link (product_id, linked_product_id, link_type_id)
SELECT #idFromList1 AS product_id, #idFromList2 as linked_product_id, 4 as link_type_id
...
I'm not sure if this is something that can be accomplished with only SQL, or if this is a case where I need to write (for example) a PHP script to pull the two lists from the db, match them up, and then insert the results. But I'm still learning SQL so I'm trying to get a better grasp of what I can do with just a MySQL query.
You can select from the same table twice.
SELECT cpev.entity_id AS product_id, cpev.value AS name
FROM catalog_product_entity_varchar AS cpev
INNER JOIN catalog_product_entity AS cpe
ON cpe.entity_id = cpev.entity_id
INNER JOIN eav_attribute AS ea
ON ea.attribute_id = cpev.attribute_id
INNER JOIN catalog_category_product AS cat
ON cat.product_id = cpev.entity_id
INNER JOIN catalog_category_product AS cat2
ON cat2.product_id = cpev.entity_id
WHERE cat.category_id IN ( 41 )
AND cat2.category_id IN ( 42 )
AND ea.attribute_code = 'name'
GROUP BY product_id
ORDER BY product_id;
I have a mysql table called custom_values where in the table exists two columns, One called "custome_field_id" and one called "value".
The "value" column holds two different values that I need to pull and display on my page. One value is an "order number" value and the other is a "date" value. The "custom_field_id" column shows a custom_field_id of 33 for the "Date" value and 237 for the "order" value
What I would like to do is structure my php query so that in one select statement, I can display both of these values.
for example end up with an $actual_date and $Order variable that I can use.
Here is my current select statement that at this moment retrieves the "order" value. But I need to show both... I tried using Alias in in my select statement but it didnt work for me...
$stmt = $con->prepare("SELECT custom_values.value, issues.subject, issue_statuses.name,
issues.due_date, issues.id as issuesid,
users.firstname, users.lastname
FROM redmine.issues join redmine.projects on issues.project_id=projects.id
left join redmine.users on issues.assigned_to_id=users.id
left join redmine.custom_values ON issues.id=custom_values.customized_id
left join issue_statuses on issues.status_id=issue_statuses.id
where projects.id='".$_SESSION['id']."'
and custom_values.custom_field_id=237 ORDER BY $order ASC");
$stmt->execute();
Any help is appreciated!
SELECT v_order.value my_order
, v_date.value date
, i.subject
, s.name
, i.due_date
, i.id issuesid
, u.firstname
, u.lastname
FROM issues i
JOIN projects p
ON p.id = i.project_id
LEFT
JOIN users u
ON u.id = i.assigned_to_id
LEFT
JOIN custom_values v_order
ON v_order.customized_id = i.id
AND v_order.custom_field_id = 237
LEFT
JOIN custom_values v_date
ON v_date.customized_id = i.id
AND v_date.custom_field_id = 33
LEFT
JOIN issue_statuses s
ON s.id = i.status_id
WHERE p.id = "_SESSION['id']"
ORDER
BY $order ASC
I'd strongly advise to build this in Ruby as a Redmine plugin then. Dealing with the database at this level is error-prone; working with the CustomField and CustomValue classes in Redmine however, will do all the abstraction for you.
You could even use the Redmine API [1] to access the data using HTTP.
[1] http://www.redmine.org/projects/redmine/wiki/Rest_api
If the following is right:
In the same database, you have 2 columns first contains 2 different numbers in case of two different values in the second column; therefore you have 2 rows per item to pull out
Please correct me if I missunderstood.
Try the following:
... AND ( custom_values.custom_field_id=value_1 OR custom_values.custom_field_id=value_2 ) ORDER ...
I currently have the following EAV table:
id
field_name
field_value
And a standard table:
id
first_name
last_name
I am joining the standard table onto the EAV table for each value that matches the ID, so my query looks something like this:
SELECT id, first_name, last_name, fieldname1, fieldname2
FROM standard_table
LEFT JOIN EAV AS fieldname1 ON
(fieldname1.id = standard_table.id AND fieldname1.field_name = 'fieldname1')
LEFT JOIN EAV AS fieldname2 ON
(fieldname2.id = standard_table.id AND fieldname2.field_name = 'fieldname2');
This has been working fine, up until today where I now have 62 custom fields in my EAV table, this means that my query is joining onto 62 tables and so hitting the MySQL table join limit and failing.
The whole query seems like a bad way of doing it, how can I rewrite this so it is quicker and doesn't require 62 table joins.
You can also use aggregation for EAV. The query looks like:
SELECT st.id, st.first_name, st.last_name,
MAX(CASE WHEN EAV.field_name = 'fieldname1' THEN fieldname1 END),
MAX(CASE WHEN EAV.field_name = 'fieldname2' THEN fieldname2 END)
FROM standard_table st JOIN
EAV
ON EAV.id = st.id
GROUP BY st.id, st.first_name, st.last_name;
As you get more and more columns, this can perform better than dozens of joins.
The other provided answer was the inspiration for this however I below is the query I actually used:
SELECT st.id, st.first_name, st.last_name,
(CASE WHEN `EAV`.`field_name` = 'fieldname1' THEN `EAV`.`field_value` END) AS 'fieldname1',
(CASE WHEN `EAV`.`field_name` = 'fieldname2' THEN `EAV`.`field_value` END) AS 'fieldname2',
FROM standard_table st JOIN
EAV
ON EAV.id = st.id
GROUP BY st.id;
Consider this SQL query:
SELECT * FROM
(
SELECT * FROM wp_postmeta
WHERE
(
meta_key = 'latitude' AND meta_value *1 BETWEEN 47.3641471102 AND 47.3731524898)
OR (meta_key = 'longitude' AND meta_value *1 BETWEEN 8.53253429117 AND 8.54583070883)
)
AS PostMeta, wp_posts
WHERE wp_posts.id = PostMeta.post_id order by post_id asc
It gives me all records that match either latitude between certain values OR the longitude between certain values. What I want to get is the records that match both values.
But changing the 'OR' in the query to 'AND' will give me zero results.
I think I need to do a subquery of somekind but dont know how to do it tho.
Anyone?
You really just need to split your latitude sub query up from your longitude sub query. Now that I have more time, I'll rework the query more explicitly:
SELECT
/* You're only interested in the wp_posts data, right? */
/* You don't care about the data from wp_postmeta. */
/* Only select the data you're actually going to use. */
/* Ideally you'd specify each column in wp_posts that you're */
/* going to use and ignore the rest */
p.*
FROM
wp_posts p
JOIN (SELECT post_id FROM wp_postmeta WHERE meta_key = 'latitude' AND CAST(meta_value AS DECIMAL) BETWEEN 47.3641471102 AND 47.3731524898) lat ON p.id = lat.post_id
JOIN (SELECT post_id FROM wp_postmeta WHERE meta_key = 'longitude' AND CAST(meta_value AS DECIMAL) BETWEEN 8.53253429117 AND 8.54583070883) long ON p.id = long.post_id
ORDER BY
post_id ASC
... as no single wp_postmeta record will be both a latitude and a longitude, you can't (at least, not nearly as simply as this) match them both in the same WHERE clause. So instead, create two separate calls to the wp_postmeta table, one for latitude and one for longitude, and just force them both to match (hence the INNER JOIN, rather than a LEFT JOIN)
This is a bit beyond my skills and I had a lot of help from the good people at SO to get this far. What I need now is to put in a MATCH() ... AGAINST() but I don't know where to insert it?
The query I have is (and this is the short version):
SELECT
SQL_CALC_FOUND_ROWS i.idItems RowCount,
i.* Items,
# Create a JSON formatted field
CONCAT('{',GROUP_CONCAT('"',Attributes.key, '":"', CONVERT(Attributes.value,CHAR),'"'),'}') as Attributes,
IF (te.Key IS NULL,tp.Key,te.Key) as Type,
tc.Value Color,
l.* Location,
c.Name,
c.Mobile,
c.Mail
FROM
(SELECT ItemID, ats.Key, ats.Value FROM attributeStrings as ats
UNION ALL
SELECT ItemID, ati.Key, ati.Value FROM attributeIntegers as ati
) Attributes
JOIN Items i ON
i.idItems = Attributes.ItemID
AND CheckIn >= DATE_SUB('2011-02-16 00:00:00',INTERVAL 90 DAY)
AND CheckIn <= DATE_ADD('2011-02-16 23:59:59',INTERVAL 90 DAY)
AND Checkout IS NULL
LEFT JOIN Customers c ON c.idCustomers = i.CustomerID
LEFT JOIN attributeintegers atli ON atli.itemid = i.idItems AND atli.key = 'Location'
LEFT JOIN locations l ON l.id = atli.value
LEFT JOIN attributestrings atts ON atts.itemid = i.idItems AND atts.key = 'Type' LEFT
JOIN Lists tp ON tp.value = atts.value
LEFT JOIN attributestrings attes ON attes.itemid = i.idItems AND attes.key = 'Tech' LEFT
JOIN Lists te ON te.value = attes.value
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT
JOIN Strings tc ON tc.StringID = atci.value
GROUP BY Attributes.ItemID
ORDER BY CheckIn DESC
Now I need to get this statement in here somewhere
MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
As you can see there is a table called attributestrings and it has 3 columns: ItemID,*Key* and Value. I need to search the column Value for the words in the AGAINST() and only show results matching this and the other criterias such as the Date and Checkout above.
I tried to add the statement after the AND Checkout IS NULL like this:
AND Checkout IS NULL
AND MATCH(Attributes.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I had to use the Attributes.Value instead of attributestrings because it didn't found the table. This only resulted in the CONCATENATED column Attributes only contained the value "Nokia", even if there where more to CONCATENATE.
I hope someone are willing to take on this challenge...
// Tank you.
[EDIT]
I tried to put in the WHERE before the GROUP as Tim Fultz sugested, but I get the error
Unknown column 'attributestrings.Value' in 'Where clause'
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT JOIN Strings tc ON tc.StringID = atci.value
WHERE MATCH(attributestrings.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
GROUP BY Attributes.ItemID
Typically this is put in the Where clause:
WHERE MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I think I came up with a solution...
I added another:
LEFT JOIN attributestrings fts ON fts.itemid = i.idItems AND MATCH(fts.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
Just before the GROUP BY... and then included fts.Value ftsv in the main select statement. Now I could insert a HAVING ftsv IS NOT NULL between GROUP BY... and ORDER BY...
This gave me the result I wanted but the query starts to get a bit slow now...
There is a problem in your query when you are assigning table names with as use every where in you query the name you assigned. Like you gave attributestrings as ats now use ats everywhere and this will work.