I have a products table...
alt text http://img357.imageshack.us/img357/6393/productscx5.gif
and a revisions table, which is supposed to track changes to product info
alt text http://img124.imageshack.us/img124/1139/revisionslz5.gif
I try to query the database for all products, with their most recent revision...
select
*
from `products` as `p`
left join `revisions` as `r` on `r`.`product_id` = `p`.`product_id`
group by `p`.`product_id`
order by `r`.`modified` desc
but I always just get the first revision. I need to do this in one select (ie no sub queries). I can manage it in mssql, is this even possible in mysql?
Here's how I'd do it:
SELECT p.*, r.*
FROM products AS p
JOIN revisions AS r USING (product_id)
LEFT OUTER JOIN revisions AS r2
ON (r.product_id = r2.product_id AND r.modified < r2.modified)
WHERE r2.revision_id IS NULL;
In other words: find the revision for which no other revision exists with the same product_id and a greater modified value.
Begin and end dates on your history table would make this possible.(leaving the most recent end date null and stamping end dates on the previous record as you insert a new one)
Otherwise you will have to use a sub-query.
That same query is parsable in MySQL.
Why are you using a Left JOIN instead of an INNER join or a RIGHT join?
Also if you want to go about this in a different way, you have the MAX function at your disposal.
Related
I have a table call orders with the ordernumber and another table call order_detail with ordernumber and product number.
I tried to query:
Select a.* from orders as a
join
order_detail as b on a.ordernum = b.ordernum
where b.prodnum ='Bike28B' or b.prodnum = 'ridinggloves'
group by ordernum;
It will give the the result with orders having Bike28B but not the result with the order have both attributes.
You have unintentionally stumbled upon mysql's quirky grouping implementation, which does non-standard things. Try listing all columns from
select a.*
from orders as a
join order_detail as b
on a.ordernum = b.ordernum
and b.prodnum in ('Bike28B', 'ridinggloves')
group by <list every column in order>
having count(*) = 2
The having clause ensures both child rows were found.
Note also the use of in (...) instead of or, which will have a much better chance if using an index (if one exists) on that column. I also moved that condition into the join clause, because it's a join condition (although it would work in both places).
I've been trying to figure this one out for a long time but am starting to give up.
To simplify the case, let's say I've got 2 tables. Main table is articles and I'm left joining it with contracts. The contracts table has an end date. I only want to pick 1 (one) row from here per article, selecting the latest contract_to date.
I've tried tried something like LEFT JOIN contracts ON (contracts.article = articles.id) ORDER BY contract_to DESC LIMIT 1 but obviously it's not working.
How do I go about doing this?
Please pretend that the date ranges on each row in the contracts table below are different.
Also the latest date is not the same for all article contracts, so I can't just determine what the latest date is and then stick it into a WHERE clause.
To get the latest contract_to value, you'll need a MAX() aggregate. The proper way to do this is to use a subquery join to get only the article and MAX(contract_to) values, then join that with the remaining values of the row. Finally, that whole structure can be joined against the articles table.
SELECT
articles.*,
contracts.*
FROM
articles
/* Join against a subquery which returns only the article and latest contract_to */
LEFT JOIN (
SELECT article, MAX(contract_to) AS contract_to
FROM contracts
GROUP BY article
) maxcontract ON articles.article_id = maxcontract.article
/* and join that against the rest of the contracts table, on those two column values */
JOIN contracts
ON maxcontract.article = contracts.article
AND maxcontract.contract_to = contracts.contract_to
Since MySQL is lenient about the contents of the GROUP BY clause, this method may not actually be necessary, joining separately against the contracts table, and you could probably do it with the subquery join alone, but that won't work in most other RDBMS and this is really the right way to do it without relying on MySQL's weird behavior.
MySQL doesn't have the nice analytic functions that some DBMSes offer for this, but you could write (for example):
SELECT ...
FROM articles
LEFT
OUTER
JOIN ( SELECT article,
MAX(contract_to) AS contract_to
FROM contracts
GROUP
BY article
) articles_to_max_contracts
ON articles_to_max_contracts.article = articles.id
LEFT
OUTER
JOIN contracts
ON contracts.article = articles.id
AND contracts.contract_to = articles_to_max_contracts.contract_to
;
To get just the price, you can also do this with a correlated subquery:
select a.*,
(select price
from contracts c
where a.article = c.article
order by contract_from desc
limit 1
) as lastPrice
from articles a;
With an index on contracts(article, contract_from) this should even be relatively efficient.
This question already has an answer here:
Syntax error due to using a reserved word as a table or column name in MySQL
(1 answer)
Closed 8 years ago.
I have this MySQL query but it seems to be getting an error while I try to run it. Since I'm a newbie I'd like some advice of what I should do to correct it. I just want to show the name, quantity and order date of the orders that has 1 or more pending products. Thanks a lot!
select product.name, order_details.quantity, order.date from product,order_details,order
inner join order on order_details.order_id=order.id
inner join product on order_details.product_id=product.id
inner join customer on order.cust_id=costumer.id WHERE order.pending=>1
You have a table called order. This word has special significance in SQL. Your options are to rename the table, or quote it whenever you want to query from it.
Easiest solution is to change.
inner join order ....
to
inner join `order`
Be sure to use back-quotes around the table name.
You have a table named 'order', which is a reserved word in SQL.
One solution is to prefix the table name with the database name as explained in Craic Computing blog
Another one is to wrap the table name with the ` character as you can read in this StackOverflow question
You can try something like :
SELECT product.name, order_details.quantity, `order`.date
FROM product
INNER JOIN order_details ON product.id = order_detail.product_id
INNER JOIN `order` ON `order`.id = order_detail.order_id
WHERE `order`.pending >= 1
As said in other answers, orderis a reserved keyword in SQL, surround it with backquotes.
Maybe you should store the pending information in the order_detail table (1 if pending, 0 if not), in order to keep track of which product is still pending instead of incrementing/decrementing the order.pending field.
In this case, you could make the following query :
SELECT product.name, order_details.quantity, `order`.date
FROM product
INNER JOIN order_details ON product.id = order_detail.product_id
INNER JOIN `order` ON `order`.id = order_detail.order_id
WHERE `order_detail`.pending = 1
Which would return all the products still pending in your orders instead of every product from orders in which maybe only one is pending.
I have two tables with a one to many relationship, offer and offer_rows
I want to fetch multiple offers with their content rows. That on it's own is not difficult, I just use an
INNER JOIN on offer.offer_id = offer_rows.offer_id
However, the offer_rows table contains a field called revision and the query needs to always fetch all the rows with the highest revision number. Is this possible with a single query?
I realize I could change the database design, by adding a third table called offer_revision, I could join this table with a select condition to fetch the latest revision number and then connect this table to the rows. This however would take considerable refactoring so I only want to do it if I have to.
I also want to do this with a direct query - no stored procedures.
Of course it is possible:
SELECT o.*, r.revision, r.something_else
FROM offer o,
offer_rows r
WHERE o.offer_id = r.offer_id
AND r.revision = (
SELECT max(revision)
FROM offer_rows
WHERE offer_id = o.offer_id
)
You can select all the rows from offer_rows with the MAX(revision) and then JOIN the offer table (no nested query will be required):
SELECT *, MAX(revision) as latest_revision
FROM offer_rows or
INNER JOIN offer o USING( offer_id )
GROUP BY offer_id
Yes this is possible with a single query. You could have a subquery that get's the highest revision in the WHERE clause.
I've used the following comparison to get a latest version entry:
AND `outer`.`version` = (
SELECT MAX( `inner`.`version` )
FROM `content` `inner`
WHERE `inner`.`id` = `outer`.`id`
AND `inner`.`language` = `outer`.`language`
)
I have two tables, one for downloads and one for uploads. They are almost identical but with some other columns that differs them. I want to generate a list of stats for each date for each item in the table.
I use these two queries but have to merge the data in php after running them. I would like to instead run them in a single query, where it would return the columns from both queries in each row grouped by the date. Sometimes there isn't any download data, only upload data, and in all my previous tries it skipped the row if it couldn't find log data from both rows.
How do I merge these two queries into one, where it would display data even if it's just available in one of the tables?
SELECT DATE(upload_date_added) as upload_date, SUM(upload_size) as upload_traffic, SUM(upload_files) as upload_files
FROM packages_uploads
WHERE upload_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY upload_date
ORDER BY upload_date DESC
SELECT DATE(download_date_added) as download_date, SUM(download_size) as download_traffic, SUM(download_files) as download_files
FROM packages_downloads
WHERE download_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY download_date
ORDER BY download_date DESC
I want to get result rows like this:
date, upload_traffic, upload_files, download_traffic, download_files
All help appreciated!
Your two queries can be executed and then combined with the UNION cluase along with an extra field to identify Uploads and Downloads on separate lines:
SELECT
'Uploads' TransmissionType,
DATE(upload_date_added) as TransmissionDate,
SUM(upload_size) as TransmissionTraffic,
SUM(upload_files) as TransmittedFileCount
FROM
packages_uploads
WHERE upload_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY upload_date
ORDER BY upload_date DESC
UNION
SELECT
'Downloads',
DATE(download_date_added),
SUM(download_size),
SUM(download_files)
FROM packages_downloads
WHERE download_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY download_date
ORDER BY download_date DESC;
Give it a Try !!!
What you're asking can only work for rows that have the same add date for upload and download. In this case I think this SQL should work:
SELECT
DATE(u.upload_date_added) as date,
SUM(u.upload_size) as upload_traffic,
SUM(u.upload_files) as upload_files,
SUM(d.download_size) as download_traffic,
SUM(d.download_files) as download_files
FROM
packages_uploads u, packages_downloads d
WHERE u.upload_date_added = d.download_date_added
AND u.upload_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY date
ORDER BY date DESC
Without knowing the schema is hard to give the exact answer so please see the following as a concept not a direct answer.
You could try left join, im not sure if the table package exists but the following may be food for thought
SELECT
p.id,
up.date as upload_date
dwn.date as download_date
FROM
package p
LEFT JOIN package_uploads up ON
( up.package_id = p.id WHERE up.upload_date = 'etc' )
LEFT JOIN package_downloads dwn ON
( dwn.package_id = p.id WHERE up.upload_date = 'etc' )
The above will select all the packages and attempt to join and where the value does not join it will return null.
There is number of ways that you can do this. You can join using primary key and foreign key. In case if you do not have relationship between tables,
You can use,
LEFT JOIN / LEFT OUTER JOIN
Returns all records from the left table and the matched
records from the right table. The result is NULL from the
right side when there is no match.
RIGHT JOIN / RIGHT OUTER JOIN
Returns all records from the right table and the matched
records from the left table. The result is NULL from the left
side when there is no match.
FULL OUTER JOIN
Return all records when there is a match in either left or right table records.
UNION
Is used to combine the result-set of two or more SELECT statements.
Each SELECT statement within UNION must have the same number of,
columns The columns must also have similar data types The columns in,
each SELECT statement must also be in the same order.
INNER JOIN
Select records that have matching values in both tables. -this is good for your situation.
INTERSECT
Does not support MySQL.
NATURAL JOIN
All the column names should be matched.
Since you dont need to update these you can create a view from joining tables then you can use less query in your PHP. But views cannot update. And you did not mentioned about relationship between tables. Because of that I have to go with the UNION.
Like this,
CREATE VIEW checkStatus
AS
SELECT
DATE(upload_date_added) as upload_date,
SUM(upload_size) as upload_traffic,
SUM(upload_files) as upload_files
FROM packages_uploads
WHERE upload_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY upload_date
ORDER BY upload_date DESC
UNION
SELECT
DATE(download_date_added) as download_date,
SUM(download_size) as download_traffic,
SUM(download_files) as download_files
FROM packages_downloads
WHERE download_date_added BETWEEN '2011-10-26' AND '2011-11-16'
GROUP BY download_date
ORDER BY download_date DESC
Then anywhere you want to select you just need one line:
SELECT * FROM checkStatus
learn more.