I need to write mysql query for table like this:
id | product_id | version | status
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 0
4 | 2 | 9 | 0
5 | 2 | 10 | 0
I need to get rows (unique for product_id - one for each product_id) but:
-if there is row for product_id with status=1 - grab it
-it there is no row as described get row with higher value or version
So for described table result should be
id | product_id | version | status
2 | 1 | 2 | 1
5 | 2 | 10 | 0
My only idea is to get rows with status 1, and then make second query using WHERE product_id NOT IN and then order by version DESC and GROUP BY product_id
Can join back to the table in this case
SELECT p1.id, p1.product_id, p1.version, p1.status FROM products p1
LEFT OUTER JOIN (
SELECT MAX(version) AS version FROM products p2
) p2 ON p1.version = p2.version OR p1.status = 1
GROUP BY p1.product_id
SQL Fiddle
You can achieve it with a UNION
select myTable.id, product_id, version, status from myTable
where id in(select id from myTable where status > 0)
union
select myTable.id, product_id, version, status from myTable
join (select max(id) as id from myTable group by product_id)
as m on m.id = myTable.id
and product_id not in(select product_id from myTable where status > 0 )
I tried to search posts, but I only found solutions for SQL Server/Access. I need a solution in MySQL (5.X).
I have a table (called history) with 3 columns: hostid, itemname, itemvalue.
If I do a select (select * from history), it will return
+--------+----------+-----------+
| hostid | itemname | itemvalue |
+--------+----------+-----------+
| 1 | A | 10 |
+--------+----------+-----------+
| 1 | B | 3 |
+--------+----------+-----------+
| 2 | A | 9 |
+--------+----------+-----------+
| 2 | C | 40 |
+--------+----------+-----------+
How do I query the database to return something like
+--------+------+-----+-----+
| hostid | A | B | C |
+--------+------+-----+-----+
| 1 | 10 | 3 | 0 |
+--------+------+-----+-----+
| 2 | 9 | 0 | 40 |
+--------+------+-----+-----+
I'm going to add a somewhat longer and more detailed explanation of the steps to take to solve this problem. I apologize if it's too long.
I'll start out with the base you've given and use it to define a couple of terms that I'll use for the rest of this post. This will be the base table:
select * from history;
+--------+----------+-----------+
| hostid | itemname | itemvalue |
+--------+----------+-----------+
| 1 | A | 10 |
| 1 | B | 3 |
| 2 | A | 9 |
| 2 | C | 40 |
+--------+----------+-----------+
This will be our goal, the pretty pivot table:
select * from history_itemvalue_pivot;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | 0 |
| 2 | 9 | 0 | 40 |
+--------+------+------+------+
Values in the history.hostid column will become y-values in the pivot table. Values in the history.itemname column will become x-values (for obvious reasons).
When I have to solve the problem of creating a pivot table, I tackle it using a three-step process (with an optional fourth step):
select the columns of interest, i.e. y-values and x-values
extend the base table with extra columns -- one for each x-value
group and aggregate the extended table -- one group for each y-value
(optional) prettify the aggregated table
Let's apply these steps to your problem and see what we get:
Step 1: select columns of interest. In the desired result, hostid provides the y-values and itemname provides the x-values.
Step 2: extend the base table with extra columns. We typically need one column per x-value. Recall that our x-value column is itemname:
create view history_extended as (
select
history.*,
case when itemname = "A" then itemvalue end as A,
case when itemname = "B" then itemvalue end as B,
case when itemname = "C" then itemvalue end as C
from history
);
select * from history_extended;
+--------+----------+-----------+------+------+------+
| hostid | itemname | itemvalue | A | B | C |
+--------+----------+-----------+------+------+------+
| 1 | A | 10 | 10 | NULL | NULL |
| 1 | B | 3 | NULL | 3 | NULL |
| 2 | A | 9 | 9 | NULL | NULL |
| 2 | C | 40 | NULL | NULL | 40 |
+--------+----------+-----------+------+------+------+
Note that we didn't change the number of rows -- we just added extra columns. Also note the pattern of NULLs -- a row with itemname = "A" has a non-null value for new column A, and null values for the other new columns.
Step 3: group and aggregate the extended table. We need to group by hostid, since it provides the y-values:
create view history_itemvalue_pivot as (
select
hostid,
sum(A) as A,
sum(B) as B,
sum(C) as C
from history_extended
group by hostid
);
select * from history_itemvalue_pivot;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | NULL |
| 2 | 9 | NULL | 40 |
+--------+------+------+------+
(Note that we now have one row per y-value.) Okay, we're almost there! We just need to get rid of those ugly NULLs.
Step 4: prettify. We're just going to replace any null values with zeroes so the result set is nicer to look at:
create view history_itemvalue_pivot_pretty as (
select
hostid,
coalesce(A, 0) as A,
coalesce(B, 0) as B,
coalesce(C, 0) as C
from history_itemvalue_pivot
);
select * from history_itemvalue_pivot_pretty;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | 0 |
| 2 | 9 | 0 | 40 |
+--------+------+------+------+
And we're done -- we've built a nice, pretty pivot table using MySQL.
Considerations when applying this procedure:
what value to use in the extra columns. I used itemvalue in this example
what "neutral" value to use in the extra columns. I used NULL, but it could also be 0 or "", depending on your exact situation
what aggregate function to use when grouping. I used sum, but count and max are also often used (max is often used when building one-row "objects" that had been spread across many rows)
using multiple columns for y-values. This solution isn't limited to using a single column for the y-values -- just plug the extra columns into the group by clause (and don't forget to select them)
Known limitations:
this solution doesn't allow n columns in the pivot table -- each pivot column needs to be manually added when extending the base table. So for 5 or 10 x-values, this solution is nice. For 100, not so nice. There are some solutions with stored procedures generating a query, but they're ugly and difficult to get right. I currently don't know of a good way to solve this problem when the pivot table needs to have lots of columns.
SELECT
hostid,
sum( if( itemname = 'A', itemvalue, 0 ) ) AS A,
sum( if( itemname = 'B', itemvalue, 0 ) ) AS B,
sum( if( itemname = 'C', itemvalue, 0 ) ) AS C
FROM
bob
GROUP BY
hostid;
Another option,especially useful if you have many items you need to pivot is to let mysql build the query for you:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'ifnull(SUM(case when itemname = ''',
itemname,
''' then itemvalue end),0) AS `',
itemname, '`'
)
) INTO #sql
FROM
history;
SET #sql = CONCAT('SELECT hostid, ', #sql, '
FROM history
GROUP BY hostid');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
FIDDLE
Added some extra values to see it working
GROUP_CONCAT has a default value of 1000 so if you have a really big query change this parameter before running it
SET SESSION group_concat_max_len = 1000000;
Test:
DROP TABLE IF EXISTS history;
CREATE TABLE history
(hostid INT,
itemname VARCHAR(5),
itemvalue INT);
INSERT INTO history VALUES(1,'A',10),(1,'B',3),(2,'A',9),
(2,'C',40),(2,'D',5),
(3,'A',14),(3,'B',67),(3,'D',8);
hostid A B C D
1 10 3 0 0
2 9 0 40 5
3 14 67 0 8
Taking advantage of Matt Fenwick's idea that helped me to solve the problem (a lot of thanks), let's reduce it to only one query:
select
history.*,
coalesce(sum(case when itemname = "A" then itemvalue end), 0) as A,
coalesce(sum(case when itemname = "B" then itemvalue end), 0) as B,
coalesce(sum(case when itemname = "C" then itemvalue end), 0) as C
from history
group by hostid
I edit Agung Sagita's answer from subquery to join.
I'm not sure about how much difference between this 2 way, but just for another reference.
SELECT hostid, T2.VALUE AS A, T3.VALUE AS B, T4.VALUE AS C
FROM TableTest AS T1
LEFT JOIN TableTest T2 ON T2.hostid=T1.hostid AND T2.ITEMNAME='A'
LEFT JOIN TableTest T3 ON T3.hostid=T1.hostid AND T3.ITEMNAME='B'
LEFT JOIN TableTest T4 ON T4.hostid=T1.hostid AND T4.ITEMNAME='C'
use subquery
SELECT hostid,
(SELECT VALUE FROM TableTest WHERE ITEMNAME='A' AND hostid = t1.hostid) AS A,
(SELECT VALUE FROM TableTest WHERE ITEMNAME='B' AND hostid = t1.hostid) AS B,
(SELECT VALUE FROM TableTest WHERE ITEMNAME='C' AND hostid = t1.hostid) AS C
FROM TableTest AS T1
GROUP BY hostid
but it will be a problem if sub query resulting more than a row, use further aggregate function in the subquery
If you could use MariaDB there is a very very easy solution.
Since MariaDB-10.02 there has been added a new storage engine called CONNECT that can help us to convert the results of another query or table into a pivot table, just like what you want:
You can have a look at the docs.
First of all install the connect storage engine.
Now the pivot column of our table is itemname and the data for each item is located in itemvalue column, so we can have the result pivot table using this query:
create table pivot_table
engine=connect table_type=pivot tabname=history
option_list='PivotCol=itemname,FncCol=itemvalue';
Now we can select what we want from the pivot_table:
select * from pivot_table
More details here
My solution :
select h.hostid, sum(ifnull(h.A,0)) as A, sum(ifnull(h.B,0)) as B, sum(ifnull(h.C,0)) as C from (
select
hostid,
case when itemName = 'A' then itemvalue end as A,
case when itemName = 'B' then itemvalue end as B,
case when itemName = 'C' then itemvalue end as C
from history
) h group by hostid
It produces the expected results in the submitted case.
I make that into Group By hostId then it will show only first row with values,
like:
A B C
1 10
2 3
I figure out one way to make my reports converting rows to columns almost dynamic using simple querys. You can see and test it online here.
The number of columns of query is fixed but the values are dynamic and based on values of rows. You can build it So, I use one query to build the table header and another one to see the values:
SELECT distinct concat('<th>',itemname,'</th>') as column_name_table_header FROM history order by 1;
SELECT
hostid
,(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue else '' end) as col1
,(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue else '' end) as col2
,(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue else '' end) as col3
,(case when itemname = (select distinct itemname from history a order by 1 limit 3,1) then itemvalue else '' end) as col4
FROM history order by 1;
You can summarize it, too:
SELECT
hostid
,sum(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue end) as A
,sum(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue end) as B
,sum(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue end) as C
FROM history group by hostid order by 1;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | NULL |
| 2 | 9 | NULL | 40 |
+--------+------+------+------+
Results of RexTester:
http://rextester.com/ZSWKS28923
For one real example of use, this report bellow show in columns the hours of departures arrivals of boat/bus with a visual schedule. You will see one additional column not used at the last col without confuse the visualization:
** ticketing system to of sell ticket online and presential
This isn't the exact answer you are looking for but it was a solution that i needed on my project and hope this helps someone. This will list 1 to n row items separated by commas. Group_Concat makes this possible in MySQL.
select
cemetery.cemetery_id as "Cemetery_ID",
GROUP_CONCAT(distinct(names.name)) as "Cemetery_Name",
cemetery.latitude as Latitude,
cemetery.longitude as Longitude,
c.Contact_Info,
d.Direction_Type,
d.Directions
from cemetery
left join cemetery_names on cemetery.cemetery_id = cemetery_names.cemetery_id
left join names on cemetery_names.name_id = names.name_id
left join cemetery_contact on cemetery.cemetery_id = cemetery_contact.cemetery_id
left join
(
select
cemetery_contact.cemetery_id as cID,
group_concat(contacts.name, char(32), phone.number) as Contact_Info
from cemetery_contact
left join contacts on cemetery_contact.contact_id = contacts.contact_id
left join phone on cemetery_contact.contact_id = phone.contact_id
group by cID
)
as c on c.cID = cemetery.cemetery_id
left join
(
select
cemetery_id as dID,
group_concat(direction_type.direction_type) as Direction_Type,
group_concat(directions.value , char(13), char(9)) as Directions
from directions
left join direction_type on directions.type = direction_type.direction_type_id
group by dID
)
as d on d.dID = cemetery.cemetery_id
group by Cemetery_ID
This cemetery has two common names so the names are listed in different rows connected by a single id but two name ids and the query produces something like this
CemeteryID Cemetery_Name Latitude
1 Appleton,Sulpher Springs 35.4276242832293
You can use a couple of LEFT JOINs. Kindly use this code
SELECT t.hostid,
COALESCE(t1.itemvalue, 0) A,
COALESCE(t2.itemvalue, 0) B,
COALESCE(t3.itemvalue, 0) C
FROM history t
LEFT JOIN history t1
ON t1.hostid = t.hostid
AND t1.itemname = 'A'
LEFT JOIN history t2
ON t2.hostid = t.hostid
AND t2.itemname = 'B'
LEFT JOIN history t3
ON t3.hostid = t.hostid
AND t3.itemname = 'C'
GROUP BY t.hostid
I'm sorry to say this and maybe I'm not solving your problem exactly but PostgreSQL is 10 years older than MySQL and is extremely advanced compared to MySQL and there's many ways to achieve this easily. Install PostgreSQL and execute this query
CREATE EXTENSION tablefunc;
then voila! And here's extensive documentation: PostgreSQL: Documentation: 9.1: tablefunc or this query
CREATE EXTENSION hstore;
then again voila! PostgreSQL: Documentation: 9.0: hstore
I am trying to add a product filter feature. Each product could have an arbitrary number of filters.
I have set up my table like so:
row_id products_id filters_id
1 1 1
2 2 2
3 3 3
4 4 3
5 1 3
So the table has a list of product_ids and corresponding filter_ids. As this seemed like such a simple table, I assumed that the script would be easy. But I am really struggling to get my head around it.
There query I got to, was:
SELECT p.products_id
, p.products_name
, p.products_short_desc
, p.products_price
, p.products_url
, p.products_image
, p.products_short_desc
, p.contact_pricing
, GROUP_CONCAT(ptc.categories_id) category_id
FROM products p
LEFT
JOIN prod_to_cat ptc
ON ptc.products_id = p.products_id
AND ptc.categories_id IN (3)
WHERE p.products_status = 1
GROUP
BY p.products_id
HAVING COUNT(DISTINCT ptc.categories_id) = 1
ORDER
BY p.products_price
As an example, if just one filter was selected, and it was id 3. This failed to work properly, even with much tweaking. It also seems fair to complex, for something I thought would have been so simple.
So essentially, I am trying to work out how, with this table, can I select all the products, that have all the match filters_id's?
So if if filter 1 and 3 were selected, I would want to yield the results of all products that match, i.e. are in the table with their product id and corresponding filter_ids. In this example, it would just return products_id 1, if just filters id 3 was selected it would return product 3,4 and 1. How is this achieved?
It seems fairly straightforward to me:
DROP TABLE IF EXISTS product_filters;
CREATE TABLE product_filters
(product_id INT NOT NULL
,filter_id INT NOT NULL
,PRIMARY KEY(product_id,filter_id)
);
INSERT INTO product_filters VALUES
(1,1),
(2,2),
(3,3),
(4,3),
(1,3);
SELECT product_id
FROM product_filters
WHERE filter_id IN(1,3)
GROUP
BY product_id
HAVING COUNT(*) = 2;
+------------+
| product_id |
+------------+
| 1 |
+------------+
SELECT product_id
FROM product_filters
WHERE filter_id IN(3)
GROUP
BY product_id
HAVING COUNT(*) = 1;
+------------+
| product_id |
+------------+
| 1 |
| 3 |
| 4 |
+------------+
My site is a Prestashop (1.5.6.2).
Some of my product can have a lower price according to the number of product ordered. And I'd like to mention somewhere the minimum price of the product (so I need the maximum amount of reduction to make this happen).
Table 1 (my price is in this table)
+------------+-------+
| id.product | price |
+------------+-------+
| 1 | 1000 |
+------------+-------+
Table 2 (my reduction is in this table)
+------------+--------+-----------+
| id.product | amount | reduction |
+------------+--------+-----------+
| 1 | 2 | 100 |
| 1 | 3 | 200 |
| 1 | 4 | 300 |
+------------+--------+-----------+
According to this is example, I would like to display:
Product 1 from 700 euros
1000 - 300 (which is the maximum reduction on product.id 1) = 700
(I'd like to UPDATE the existing price because this is a second field which I create actually called price_from but i didn't want to make the example too complicate)
This is my code so far:
UPDATE table1
INNER JOIN table2 ON (table1.id_product = table2.id_product )
SET table1.price = table1.price - (SELECT MAX(table2.reduction) FROM table2 GROUP BY id_product)
Any ideas?
If you only want to display the modified price use this:
select t1.id_product, (price - max_reduction) as new_price
from table1 t1 inner join (
select id_product, max(reduction) max_reduction FROM table2
GROUP BY id_product
) t2 on t1.id_product = t2.id_product
If you want to modify the price try this:
update table1 t1, (
select id_product, MAX(t2.reduction) as max_reduction
FROM table2 t2
GROUP BY id_product) t2
SET t1.price = t1.price - t2.max_reduction
WHERE t1.id_product = t2.id_product;
Try this:
update table1
inner join (SELECT max(`reduction`) as maxprice, id FROM table2 group by id ) t
on
table1.id = t.id
SET
table1.price = table1.price - t.maxprice
I've got two tables where I want to JOIN one with the other. The JOIN works fine, but I get two records returned. This is (looking at my query) correct, but I need to return one of the two records based on a condition. Can this be managed with a single query?
table oc_order_product:
product_id | name | quantity | order_id | store_url
51 | prod1 | 1 | 17 | http://www.opencarttest.com/
48 | prod2 | 3 | 17 | http://www.opencarttest.com/
table product:
oc_product_id | price | store_url
51 | 50.00 | http://www.opencarttest.com/
51 | 89.95 |
48 | 15.00 |
My current query:
SELECT
oop.product_id,
oop.name,
oop.quantity,
p.price,
p.store_url
FROM
oc_order_product AS oop
JOIN
product AS p
ON
p.oc_product_id = oop.product_id
WHERE
oop.order_id='17' AND oop.store_url='http://www.opencarttest.com/'
This returns:
product_id | name | price | store_url
51 | prod1 | 50.00 | http://www.opencarttest.com/
51 | prod1 | 89.95 |
48 | prod2 | 15.00 |
But I need only this:
product_id | name | price | store_url
51 | prod1 | 50.00 | http://www.opencarttest.com/
48 | prod2 | 15.00 |
I only need one result from product that matched the oc_product_id. But it can be one of both products. I need the product with the store_url equals the oc_order_product.store_url, or else the product without the store_url.
EDIT: Added trailing slashes
This will give you what you are looking for, however at the expense of performance:
SELECT
oop.product_id,
oop.name,
oop.quantity,
p.price,
p.store_url
FROM
oc_order_product AS oop
JOIN
product AS p
ON
p.oc_product_id = oop.product_id
and
(p.store_url = oop.store_url
or
not exists (select 1 from product where oc_product_id=p.oc_product_id and store_url is not null)
)
WHERE
oop.order_id='17' AND oop.store_url='http://www.opencarttest.com'
Here is the sqlfiddle.
Note that with your specific data as shown in the question, this won't work - and neither will your own query - as you have URL's with the trailing slash in one table and without it in the other. In my example, I removed trailing slashes in all places.
Updated - I made an assumption that fields were empty strings instead of NULLs
Thanks to #aleks-g since I stole his SQLFiddle for this code test
Here is one way - Using LEFT JOINs and COALESCES
SELECT
oop.product_id,
oop.name,
oop.quantity,
COALESCE(p_filled.price, p.price) AS price,
COALESCE(p_filled.store_url, p.store_url) AS store_url
FROM oc_order_product AS oop
LEFT JOIN product AS p_filled
ON p_filled.oc_product_id = oop.product_id
AND oop.store_url= p_filled.store_url
LEFT JOIN product AS p
ON p.oc_product_id = oop.product_id
AND COALESCE(p_filled.store_url,'') = ''
WHERE
oop.order_id='17' AND oop.store_url='http://www.opencarttest.com';
You can use the mysql variable in your query to record the occurrence of product id and by using CASE you can check either product has url OR product is not repeated then select it,some columns in query are just to elaborate the working
SELECT
oop.product_id,
oop.name,
oop.quantity,
p.price,
p.store_url,
(CASE when #test=oop.product_id then 'repeated'
else 'new' end) occurance,
(CASE when p.store_url =oop.store_url OR #test!=oop.product_id
then 'has'
else 'none' end) url,
#test:=oop.product_id `testvar`
FROM
oc_order_product AS oop
join (SELECT #test:=0) t
JOIN
(SELECT * FROM product ORDER BY store_url DESC) AS p
ON
p.oc_product_id = oop.product_id
WHERE
oop.order_id='17'
HAVING url='has'
Fiddle Demo
$rs = $db->Select('products p, products_images pi','p.*,image_id',"p.product_id=pi.product_id","product_id=$a1");
Here's another way to do it, without requiring a subquery:
SELECT
oop.product_id,
oop.name,
oop.quantity,
IFNULL(p1.price, p2.price),
p1.store_url
FROM
oc_order_product AS oop
LEFT JOIN
product AS p1
ON
p1.oc_product_id = oop.product_id AND p1.store_url = oop.store_url
LEFT JOIN
product AS p2
ON
p2.oc_product_id = oop.product_id
WHERE
oop.order_id='17'
AND oop.store_url='http://www.opencarttest.com/'
AND p1.oc_product_id IS NOT NULL OR p2.oc_product_id IS NOT NULL