Updating all columns of a table from another table's data? - mysql

What I want to do is simple but I lack some sql knowledge, so I would truly appreciate any kind of help.
I have table 'dev'.
product1 | product2 | count
---------------------
I also have a table likes.
wishlist_id | user_id | product_id
-------------------------------
1 2 54
2 2 60
3 3 54
7 3 60
.. .. ..
99 99 99
I want to find how many times 2 products where liked together, but instead of creating a view I want to save the data in the dev table.
How can I update the rows so dev table becomes:
dev
-----------------------
product1 product2 count
54 60 2
..
EDIT
I don't know how to actually use the update statement here.
my query is :
SELECT i1.product_id as product1, i2.product_id as product2,
COUNT(*) as num
FROM likes i1
INNER JOIN likes i2 ON i1.wishlist_id = i2.wishlist_id
AND i1.product_id < i2.product_id
GROUP BY product1, product2;

Suppose your dev table has a unique key constraint on the pair of product1, product2:
CREATE TABLE dev (
product1 INT NOT NULL,
product2 INT NOT NULL,
count INT NOT NULL DEFAULT 0,
PRIMARY KEY (product1, product2)
);
Now you can use INSERT...ON DUPLICATE KEY UPDATE to add or replace rows corresponding to pairs of your products. You already had a SELECT statement that generated the rows, now you need to use it in an IODKU.
INSERT INTO dev (product1, product2, count)
SELECT i1.product_id as product1, i2.product_id as product2, COUNT(*) as num
FROM likes i1 INNER JOIN likes i2
ON i1.wishlist_id = i2.wishlist_id AND i1.product_id < i2.product_id
GROUP BY product1, product2
ON DUPLICATE KEY UPDATE count = VALUES(count);
Read more about IODKU here: https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html

Related

How to transform this SQL query to query with GROUP BY and HAVING?

There're products, products_filters, filters_values tables in DB.
products_filters references to products table via product_id column which is a foreign key.
Also products_filters references to filters_values table via filter_value_id column which is also a foreign key
When user selects filters, SQL query which extracts all ids of suitable products is formed.
For example, chosen filters are:
Sex: Male, Female
Brand: Brand1, Brand2, Brand3
How it should work:
It needs to select all products which have filter Sex set to Male OR Female AND filter Brand set to Brand1 OR Brand2 OR Brand3. But products having matching only in one of the chosen filter category either Sex or Brand, must not be selected. It necessiraly to have matching in all selected categories.
I think SQL should look like this:
SELECT product_id FROM products_filters WHERE
(filter_value_id = 1 OR filter_value_id = 2)
AND
(filter_value_id = 3 OR filter_value_id = 4 OR filter_value_id = 5)
Where 1 is Male, 2 is Female, 3 is Brand1, 4 is Brand2, 5 is Brand3.
But this query doesn't work.
In my previous question I was answered that GROUP BY and HAVING may help.
Q: How can I transform SQL above with GROUP BY and HAVING?
Given
drop table if exists t;
create table t(id int ,gender varchar(1), brand varchar(1));
insert into t values
(1,'m',1),(1,'f',2),(1,'m',3),(2,'f',1),(2,'f',2),(2,'f',3),
(3,'m',1),(4,'f',2);
Correlated sub queries with distinct
select distinct id
from t
where
(select count(distinct gender) from t t1 where gender in('m','f') and t1.id = t.id) = 2 and
(select count(distinct brand) from t t1 where brand in(1,2,3) and t1.id = t.id) = 3
;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.002 sec)
SELECT product_id
FROM products_filters AS f1
JOIN products_filters AS f2 USING(product_id)
WHERE f1.filter_value_id IN (1,2)
AND f2.filter_value_id IN (3,4,5)
(I don't think GROUP BY...HAVING COUNT(*) = 2 is reasonable for this case.)
(The EAV schema design is a pain to deal with.)

Mysql update row value in table based on different row

I have the following table structure
TABLE A
Productid price groupId
1 100 A
2 99 A
3 0 A
4 50 B
5 49 B
6 0 B
I populate table A with prices from table B joining on Id. Sometimes table B doesn't have prices.
In cases were b doesn't have price I want to update the price to be another price from that same group, as I can't have a price of zero.
Is there an way to update table a price column using itself based on group? e.g. update productId 3 price to be the price of another product in it's group (1 or 2)
TABLE A after update
Productid price groupId
1 100 A
2 99 A
3 100 A
4 50 B
5 49 B
6 49 B
It seems silly but these are the business rules (it makes sense irl I simplified the problem for the example)
When I tried the following I got Error:
update 'Table A' t1
join (select price ,groupId from 'table A' where Price > 0 group by
groupId) as t2
on t1.groupId = t2.GroupId
SET t1.Price = t2.Price
(conn=58292) Can't reopen table: 'Table A'
I've thought of creating a third temporary table but that seems.... wrong? I am sure there must be a way to do this using update statement and joins
I would phrase the query as:
update tablea a
inner join (select groupId, max(price) price from tablea group by groupId) a1
on a1.groupId = a.groupId
set a.price = a1.price
where a.price = 0 and a1.price > 0
Notes:
the table name should be surrounded with single quotes (those stand for literal strings) - if your table name really contains spaces, then use backticks for quoting (or better, yet, fix the table name!)
I changed the subquery to make it a valid aggregation query - yours has non-aggregated columns that do not belong to the group by clause, which is not a good practice, and might generate errors, depending on the SQL mode of your database
In this demo on DB Fiddlde with your sample data, the content of the table after update is:
Productid | price | groupId
--------: | ----: | :------
1 | 100 | A
2 | 99 | A
3 | 100 | A
4 | 50 | B
5 | 49 | B
6 | 50 | B

Additional rows needed in SQL view

I have the following SQL tables, and require a solution compatible with both MySQL and Postgresql
create table price_level (
id serial primary key,
name varchar(200)
);
create table product (
id serial primary key,
name varchar(200),
base numeric not null,
vat int not null
);
create table product_price (
id serial primary key,
base numeric,
vat numeric,
product_id int not null references product(id) on update cascade on delete cascade,
price_level_id int not null references price_level(id) on update cascade on delete cascade,
unique(product_id,price_level_id)
);
For the SQL structure above I've created a view:
create view view_product as
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
pp.price_level_id
from
product as p
left join
product_price as pp on pp.product_id=p.id
;
These are sample data:
Table price_level
id name
1 A
2 B
3 C
4 D
5 E
Table product
id name base vat
1 Test 100 20
Table product_price
id base vat product_id price_level_id
1 NULL NULL 1 1
2 200 NULL 1 2
3 NULL 10 1 3
Output of the view view_product is:
product_id base vat price_level_id
1 100 20 1
1 200 20 2
1 100 10 3
... and the question is: How do I get output like this?:
product_id base vat price_level_id
1 100 20 1
1 200 20 2
1 100 10 3
1 100 20 4
1 100 20 5
As you see in the example above I need to get D and E price_level as additional rows. How do I create such view/join? It should have good performance also because tables can get big with additional price levels.
Thanks for help.
I would use union to add those records from price_level table that do not have corresponding record in product_price table for a certain product:
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
pp.price_level_id
from
product as p
left join
product_price as pp on pp.product_id=p.id
union distinct
select
p.id as product_id,
p.base,
p.vat,
pl.price_level_id
from
price_level pl
join
product as p
where (p.id, pl.id) not in (select product_id, price_level_id from product_price)
I would use following approach, cross join tables price_level and product. Then just lookup if override exists in product_price table.
SELECT
product.id as product_id,
IFNULL(product_price.base, product.base) as `base`,
IFNULL(product_price.vat, product.vat) as `vat`,
price_level.id as price_level_id
FROM price_level
CROSS JOIN product
LEFT JOIN product_price ON
product_price.price_level_id = price_level.id AND
product_price.product_id = product.id
WHERE product.id = 1
ORDER BY product.id, price_level.id
just remember to use product.id and not product_id in WHERE conditions
Try with:
create view view_product as
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
coalesce(pp.price_level_id,pl.id) --modified row
from
product as p
left join
product_price as pp on pp.product_id=p.id
LEFT JOIN price_level pl on pp.price_level_id=pl.id -- modified row
;
(not tested, but for sure you have to catch the price levels from the properly table)

show all records which contain the latest edited version

I have a table with students names, auto increased NR record, but on each edit of the record, a new one is created copying the NR to the ID field.
But when I try to group the ID records when MAX(NR) it shows me the max number of that ID but when I ask for the remaining of the rocord, it doesn't show me that last record of that group of ID's in
SELECT MAX(`NR`) AS 'mNr',`NR`,`ID`,`Name1`,`Name3`,`Gender`
FROM `Kids` GROUP BY `ID`
This produces results like:
mNr NR ID Name1 Name3 Gender
252 1 1 Alice Carper f
179 2 2 Dorah Fisher f
189 3 3 Racheal King f
173 4 4 Frank Smith m
192 5 5 Patrick Fay m
305 6 6 Gloria Sing f
299 7 7 Bridget Young f
But as you can see, the query shows the highest edit NR, but then continues to give the lowest of the rest of the record, not the record details belonging to that lastest NR...
What am I doing wrong?
This is the sample data:
NR ID Name1 Name3 Gender
1 1 Alice Achand f
2 2 Dorah Achieng f
3 3 Racheal Achieng f
4 4 Francisca Adikin f
5 5 Patrick Adilu m
6 6 Gloria Ajwang f
7 7 Bridget Aketch f
130 5 Patrick Adilu m
129 4 Francisca Adikin f
128 2 Dorah Achieng f
153 4 Francisca Adikin f
173 4 Francisca Adikin f
179 2 Dorah Achieng f
189 3 Racheal Achieng f
192 5 Patrick Adilu m
252 1 Alice Wor f
299 7 Bridget Aketch f
305 6 Gloria Ajwang f
Perhaps without knowing it, you are using an arcane feature of MySQL. MySQL allows you to include columns in the select statement of an aggregation query that are not in aggregation functions or in the group by clause. The engine puts in arbitrary values for these columns.
The correct way to do what you want is with a join:
SELECT k.*
FROM `Kids` k join
(select id, max(nr) as maxnr
from kids
group by id
) m
on k.id = m.id and nr = maxnr;
Here is the explicit explanation in the documentation:
MySQL extends the use of GROUP BY so that the select list can refer to
nonaggregated columns not named in the GROUP BY clause. This means
that the preceding query is legal in MySQL. You can use this feature
to get better performance by avoiding unnecessary column sorting and
grouping. However, this is useful primarily when all values in each
nonaggregated column not named in the GROUP BY are the same for each
group. The server is free to choose any value from each group, so
unless they are the same, the values chosen are indeterminate.
Furthermore, the selection of values from each group cannot be
influenced by adding an ORDER BY clause. Sorting of the result set
occurs after values have been chosen, and ORDER BY does not affect
which values within each group the server chooses.
which you can read in more detail here.
Although #Gordon Linoff's answer is technically correct, using sub-queries may be more resource and time consuming on large data sets.
Depending on the case, I would usually separate the data into two tables students and student_details where
students's table structure is
CREATE TABLE students (
student_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT
);
The idea of this table is to create a unique number for the student and store any other student data that you may not want to save in the revisions.
student_details's table structure is:
CREATE TABLE student_details (
revision_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
student_id INTEGER NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
gender VARCHAR(1),
is_history BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT FOREIGN KEY(student_id) REFERENCES students(student_id) ON DELETE RESTRICT
);
And this table stores the actual data of the student. When the data for a certain student is updated, you just need to update the is_history column to true for the records of that student. Then, when selecting your student data, you'd simply use SELECT student_details.* FROM students LEFT JOIN student_details ON (student_details.student_id = students.student_id AND student_details.is_history = false). This will always return the latest revision of a student's detail.
Inserting a new student
Insert a student INSERT INTO students(student_id) VALUES('');
Get the lasted insert id SELECT LAST_INSERT_ID(); (let's say it returns 1 for the sake of this example)
Insert the student details INSERT INTO student_details(student_id, first_name, last_name, gender) VALUES('1', 'Alice', 'Carper', 'f')
Updating an existing student (let's say student_id = 1)
Set all previous student_detail "revisions" as "history": UPDATE student_details SET is_history = true WHERE student_details.student_id = 1 AND is_history = false
Add the new revision: INSERT INTO student_details(student_id, first_name, last_name, gender) VALUES('1', 'Alice', 'Achand', 'f')
Getting a student and his latest student details
SELECT student_details.* FROM students LEFT JOIN student_details ON (student_details.student_id = students.student_id AND student_details.is_history = false)

Select row belonging to multiple categories

I've got 2 tables. The first table is full of entries. The second table defines what categories that entry belongs to:
Table 1:
entry_id | title
1 | Entry1
2 | Entry2
3 | Entry3
Table 2
entry_id | cat_id
1 | 233
1 | 234
1 | 678
2 | 235
2 | 453
2 | 21
3 | 234
3 | 233
I'm trying to select an entry with a single query of all posts belonging to multiple categories. For example, I want to return the entries belonging to category ids, 233 and 234. I believe this needs a subquery although I'm not quite sure. Any help anybody? :)
Learn about SQL joins.
SELECT * FROM tbl1 JOIN tbl2 USING (entry_id) WHERE cat_id IN (233,234);
See it on sqlfiddle.
UPDATE
To select all entries in both categories, you could group the results of the join and only select those groups that have contain both categories:
SELECT tbl1.*
FROM tbl1 JOIN tbl2 USING (entry_id)
WHERE cat_id IN (233,234)
GROUP BY entry_id
HAVING COUNT(DISTINCT cat_id) = 2
See it on sqlfiddle.
The COUNT(DISTINCT cat_id) can obviously be replaced with the (much less expensive) COUNT(*) if (entry_id, cat_id) is known to be unique in tbl2.
Try this:
select * from entity e
where exists (select * from category c where c.entry_id=e.entry_id AND c.cat_id=233)
and exists (select * from category c where c.entry_id=e.entry_id AND c.cat_id=234)
This returns rows that belong to both 233 and 234 (this is how I read your question, anyway; I may have misunderstood the "belonging to multiple categories" part).