SQL Query - Duplicate data more than once based on values assigned - mysql

I am trying to duplicate/copy data from "product" table and add new values in "carrier" column.
Example:
In "product" table, I have a list of products which I will need to assign with another table, "carrier". There are currently 5 carriers in "carrier" table.
In "product_carrier" table, I would like to create new entries here. Product "sample-1" is assigned to carrier 1, 2, 3 and 4. Product "sample-2" is assigned to carrier 5.
Thus, it becomes as such:
sample-1 | 1
sample-1 | 2
sample-1 | 3
sample-1 | 4
sample-2 | 5
This is the database structure of the e-commerce system that I am currently using to assign carriers.
My tables are -
ps_product: id_product
ps_carrier: id_carrier
ps_product_carrier: id_product, id_carrier_reference
My hunch is that, I will need to update 2 set of data groups. Firstly, products set to carrier (1, 2, 3, 4) and another set of products to assign to carrier (5). I will run 2 set of queries to achieve this.
I have no clue as to execute a duplicate of a product and create insert multiple values for different carriers. Currently, I have thousands of products that need to be assigned accordingly.
I have no idea if this is possible and that if you have any advice, that will be truly appreciated.
Thank you.

Assuming you have a table named table_with_product_list (or a list) when you assign all the product you want assign to a carrier
you can use a insert select
eg for multiple carrier (1,2,3,4)
insert into ps_product_reference (id_product, id_carrier_reference)
select table_with_product_list.product_id, t.id_carrier
from table_with_product_list
cross join (
select id_carrier from ps_carrier
where id_carrier in ( 1,2,3,4)
) t
for single carrier (5)
insert into ps_product_reference (id_product, id_carrier_reference)
select table_with_product_list.product_id , 5
from table_with_product_list

As there is nothing in the database to indicate whether a product shall be combined with carriers 1 to 4 or with carrier 5, you'll have to state them yourself. In my opinion there is nothing that speaks against two separate INSERT statements. You can use a text editor or Excel maybe or a programm you quickly write yourself to build the VALUES clause:
insert into ps_product_carrier (id_product, id_carrier_reference)
values
(1111, 1),
(1111, 2),
(1111, 3),
(1111, 4),
(3333, 1),
(3333, 2),
(3333, 3),
(2222, 4),
... ;
commit;
insert into ps_product_carrier (id_product, id_carrier_reference)
values
(2222, 5),
(4444, 5),
... ;
commit;
Or start with the second statement above and then instead of the first query use:
insert into ps_product_carrier (id_product, id_carrier_reference)
select p.id_product, c.id_carrier
from ps_product p
cross join (select id_carrier from ps_carrier where id_carrier in (1,2,3,4)) c
where p.id_product not in
(select id_product from ps_product_carrier where id_carrier_reference = 5);

Related

Many to Many single query optimization

I have 3 tables with many to many relation
CREATE TABLE news(id int, content varchar(64));
CREATE TABLE tags(id int, name varchar(64));
CREATE TABLE news_tags(id int, tag_id int, news_id int);
INSERT INTO news VALUES
(1, "Hello, world!"),
(2, "Test news"),
(3, "test news 2"),
(4, "test news 3"),
(5, "test news 4");
INSERT INTO tags VALUES
(1, "general tag"),
(2, "sub tag 1"),
(3, "sub tag 2"),
(4, "normal tag");
INSERT INTO news_tags VALUES
(1, 1, 1),
(2, 2, 1),
(3, 3, 1);
INSERT INTO news_tags VALUES
(4, 1, 2),
(5, 2, 2),
(6, 1, 3),
(7, 4, 3),
(7, 2, 4),
(8, 3, 4),
(9, 1, 5);
I want to select news_id what
On relation have only general (id 1 on example) tag and dont have any other sub tag (on exmpl id 3)
Have pair of tags general + sub tag (id 2)
I create a query
SELECT news_id FROM news_tags WHERE tag_id = 1 OR tag_id = 2
GROUP BY news_id
HAVING COUNT(news_id) = 2
UNION
SELECT news_id FROM news_tags WHERE tag_id = 1 AND news_id not in (SELECT news_id FROM news_tags WHERE tag_id in (2,3));
but have 2 problems
I think its not optimization way (have 2 select with union + sub select query)
if i what search more one pairs sub tags i need add new select with union
How can i optimizate this query ?
live example http://www.sqlfiddle.com/#!9/1067b7/1/0
Your question is unclear because the concepts of "sub" tags and "general" tags are not defined.
But if you want to handle multiple conditions at the same time, you can still use one GROUP BY and HAVING clause.
For instance, if you wanted news_ids that met either of these conditions:
tag_id = 1
Or both tag_id = 2 and tag_id = 3
Then you can use:
SELECT nt.news_id
FROM news_tags nt
GROUP BY nt.news_id
HAVING (COUNT(*) = 1 AND MIN(nt.tag_id) = 1) OR
SUM( nt.tag_id IN (2, 3) ) = 2;
You can easily extend this idea to the descriptions of the tags (but you need to join in the tags table for that.
I would suggest based on the extra comments about blocking tags, to do a redesign.
Assigning tags to news items is good, but your table should look like news_tags(news_id, tag_id), and primary key is over both news_id, and tag_id field.
If you want to make tags blocking, one way is to add another Many-To-Many relation, called news_blocking_tags(news_id, tag_id). Or you can define your news_tags(news_id, tag_id, is_blocking), so you know which tags are blocking, and which are just tags.
Optimising starts with designing the database. We can only give general pointers here. Good that you know what the outcome needs to be, that's already half the design!
Look like i found solution without made change on schema tables
SELECT news_id, tag_id
FROM news_tags
WHERE tag_id in (1,2,3)
GROUP BY news_id
HAVING (COUNT(news_id) = 1 AND tag_id = 1) OR (count(news_id) = 2 and tag_id in (1,2));
first we find all news with blocked tags , than with HAVING filter result
(COUNT(news_id) = 1 AND tag_id = 1)
find all record with only "blocked" tag for all countrys (its part can use for on many to many find all records what use only single tag)
(count(news_id) = 2 and tag_id in (1,2))
find pairs of "blocked" tag and country tag
if we need more country we need add OR and
(count(news_id) = 2 and tag_id in (1,3))
i need do some test but look like its work nice , and better that my first query
I've done tagging many times. I recommend not using a many-to-many mapping table to a tag table. Instead, combine them into
CREATE TABLE tags (
news_id MEDIUMINT UNSIGNED NOT NULL, -- Assuming you don't need more than 16M
tag VARCHAR(...) NOT NULL,
PRIMARY KEY(news_id, tag), -- For going one way
INDEX(tag, news_id) -- For going the other way
) ENGINE=InnoDB; -- Important due to the way indexes are handled
Adding an auto_inc is a waste of space and speed.
Yeah, some queries get a big gnarly. But any technique leads to messy code; I belive that this schema is the best.

Subquery returns different result than when called on its own

Using GROUP BY in a MySQL subquery returns results not appearing when calling the GROUP BY query on its own.
This example is about as stripped down as I can make it.
I feel like I'm missing something elementary about using GROUP BY in a subquery, but haven't found an existing question or tutorial that addresses this particular issue.
First, build the table:
CREATE TABLE Person (
Id INT
Email VARCHAR(20)
);
INSERT INTO Person VALUES (1, "a#b.com");
INSERT INTO Person VALUES (2, "c#d.com");
INSERT INTO Person VALUES (3, "a#b.com");
INSERT INTO Person VALUES (5, "c#d.com");
INSERT INTO Person VALUES (7, "e#f.com");
INSERT INTO Person VALUES (11, "e#f.com");
INSERT INTO Person VALUES (13, "g#h.com");
Now compare these two pairs of queries:
Test 1: subquery using explicit IDs
SELECT Id FROM Person WHERE Id IN (1, 2, 7, 13)
returns Id (1, 2, 7, 13)
SELECT Id FROM Person WHERE Id IN (SELECT Id FROM Person WHERE Id IN (1, 2, 7, 13))
returns Id (1, 2, 7, 13)
Test 2: subquery using GROUP BY for Email uniqueness (gives first in each group)
SELECT Id FROM Person GROUP BY Email
returns Id (1, 2, 7, 13)
SELECT Id FROM Person WHERE Id IN (SELECT Id FROM Person GROUP BY Email)
returns Id (1, 2, 3, 5, 7, 11, 13)... not (1, 2, 7, 13), as expected.
I expected the output of the compound query on both of these tests to be
Id (1, 2, 7, 13)
since the subquery in each outputs
Id (1, 2, 7, 13)
as input to the top-level query.
This leads me to believe that the displayed results are not actually the full results (at least, when it comes to a GROUP BY). Any elucidation on this confusing situation would be greatly appreciated.
SELECT Id FROM Person GROUP BY Email
This query is invalid according to the SQL standard. You group by Email. So you get one result row per Email. Then you want to show the ID for the Email. But there is not the ID per Email, there can be many. For Email = 'a#b.com' for instance there are the IDs 1 and 3. The DBMS should raise an error. But MySQL silently substitutes this with
SELECT ANY_VALUE(Id) FROM Person GROUP BY Email
i.e. returns an arbitrarily chosen value. It's left to chance whether the query returns ID 1 or 3 for Email = 'a#b.com'.
This explains why you get different results. One time the DBMS chooses this value one time the other.
Group BY should be use with aggregation function.
(If you need distinct result use the DISTINCT clause and not use group by improperly)
In mysql versone < 5.7 the The use of group by without aggregation function produce unpredictable result ..
in mysql >= 5.7 is not allowed by default and this kind of use produce error.
If you want control the result based on group by you should use a proper aggregation function eg: min() or max()
SELECT Id
FROM Person
WHERE Id IN ( SELECT min(Id )
FROM Person
GROUP BY Email
)

sql select multiple rows based on conditions of two or more columns

I am trying to select multiple rows based on 3 columns matching particular criteria. For a single search I do the following:
SELECT user_id
FROM users_to_users
WHERE user_id = '1' AND contact_user_id = '9' AND contact_blocked = 1
I would like to submit a set of values to return multiple rows.
so my values would be as such:
('1', '9', 1), ('2, '9', 1),('3', '9', 1) etc...
And return user_id's for the rows which match. In essence I'm trying to see which users have blocked user '9' so that I could then add only the users that are not blocked to the next statement.
Being very unfamiliar with SQL what I thought might work was the following:
SELECT user_id
FROM users_to_users
WHERE (user_id, contact_user_id, contact_blocked) VALUES (...)
But unable to do that. Is there any way to select multiple rows based on matching conditions for multiple columns?
Are you trying to use tuples with in? If so, this works:
SELECT user_id
FROM users_to_users
WHERE (user_id, contact_user_id, contact_blocked) in ( (1, 9, 1), (2, 9, 1), (3, 9, 1) )
There may be other ways, however, to accomplish your ultimate goal.

How to check a column for duplicate values and return rows containing them

I am writing a script operating on SQL. The query is to check picked column (in this case kolumna1, kolumna2 or kolumna3) for duplicate values. Then, after finding these values I want to return every row containing such value. For example, looking at the table I have, if I look through kolumna1 column one of the duplicate values would be in row (id) 2 and 8. So in this case I would want to return whole second and eighth rows.
Of course main goal is to return every row with a duplicate value, this was just a simpler example.
The table:
INSERT INTO `tabela_testowa` (`id`, `kolumna1`, `kolumna2`, `kolumna3`, `kolumna4`) VALUES
(1, 'wartosc1', 'wartosc2', 'wartosc3', 1),
(2, 'warosc21', 'wartosc22', 'wartosc23', 5),
(3, 'wartosc31', 'wartosc22', 'wartosc32', 6),
(4, 'wartosc54', 'wartosc43', 'wartosc45', 4),
(5, 'wartosc43', 'wartosc23', 'wartosc34', 4),
(6, 'wartosc43', 'wartosc54', 'wartosc43', 2),
(7, 'wartosc54', 'wartosc52', 'wartosc53', 8),
(8, 'wartosc21', 'wartosc22', 'wartosc43', 4),
(9, 'wartosc43', 'wartosc33', 'wartosc45', 9),
(10, 'wartosc87', 'wartosc62', 'wartosc11', 3);
so far I've managed to write a query that almost works properly. By almost I mean that it returns the duplicates, but only one time for each one.
Query:
SELECT id, kolumna1
FROM tabela_testowa
GROUP BY kolumna1
HAVING ( COUNT(kolumna1) > 1 );
Edit: I am using mysql. Also, to clarify I want to search through only one column that I choose in search for the duplicate values, then display whole rows containing them (of course I mean both rows, like the 2nd and 8th ones).
If am not wrong you are looking for this
Works in both Mysql And Sql Server
Correlated Sub-Query method
select *
from Yourtable A
Where Exists (select 1 from Yourtable B where a.kolumna1 =b.kolumna1
HAVING COUNT(1) > 1 )
Another approach, filtering kolumna1 which is having more than one record and joining with your table
Select A.* From Yourtable A
(
select kolumna1
From Yourtable
Group by kolumna1
Having Count(1) > 1
) B
ON A.kolumna1= B.kolumna1
Update : Scope of the alias name ends when the select query ends you cannot use it like that. Try this way
SELECT *
FROM tabela_testowa tab1
WHERE EXISTS (SELECT 1
FROM tabela_testowa tab2
WHERE tab1.kolumna1 = tab2.kolumna1
HAVING Count(1) > 1)
If you are using SQL SERVER
Select * From
(
select *,Count(1)Over(Partition by kolumna1) as cnt
from Yourtable
)A
Where cnt > 1

Multiple Join on same table with different columns

I have problems making a SQL request.
Here is my tables:
CREATE TABLE dates(
id INT PRIMARY KEY,
obj_id INT,
dispo_date text
);
CREATE TABLE option(
id INT PRIMARY KEY,
obj_id INT,
random_option INT
);
CREATE TABLE obj(
id INT PRIMARY KEY,
);
and a random date that the user gives me and some options.
I'd like to select everything on both tables which correspond to an obj having his date equal to the user's date.
let's say that DATE = "22/01/2013" and OPTIONS = 3.
SELECT * FROM obj
INNER JOIN dates
ON dates.obj_id=obj.id
INNER JOIN option
ON option.obj_id=obj.id
WHERE dates.dispo_date="22/01/2013"
AND option.random_option=3;
That just gives me everything from my obj table with, for each one, the same dates and options without filtering anything.
Can someone give me some pointers about what I'm doing wrong ?
SOLUTION:
Since everybody seemed to get what I was looking for I restarted my SQL server and since, everything works ...
Thanks for your help and sorry for the time-loss :-(
As far as I can see, there is nothing wrong with the query.
When I try it, it returns only the obj rows where there is a corresponding date and a corresponding option.
insert into dates values
(1, 1, '22/01/2013'),
(2, 1, '23/01/2013'),
(3, 2, '22/01/2013'),
(4, 2, '23/01/2013'),
(5, 3, '23/01/2013'),
(6, 3, '24/01/2013');
insert into `option` values
(1, 1, 4),
(2, 1, 5),
(3, 2, 3),
(4, 2, 4),
(5, 3, 3),
(6, 3, 4);
insert into obj values
(1),
(2),
(3)
With this data it should filter out obj 1 because there is no option 3 for it, and filter out obj 3 because there is no date 22 for it.
Result:
ID OBJ_ID DISPO_DATE RANDOM_OPTION
-------------------------------------
2 2 22/01/2013 3
Demo: http://sqlfiddle.com/#!2/a398f/1
Change your line
WHERE dates.dispo_date="22/01/2013"
for
WHERE DATE(dates.dispo_date)="22/01/2013"
Handling dates in text fields is a little tricky (also bad practice). Make sure both dates are in the same format.
First, I'm a little confused on which ID's map to which tables. I might respectfully suggest that the id field in DATES be renamed to date_id, the id in OPTION be renamed to option_id, and the id in obj to obj_id. Makes those relationships MUCH clearer for folks looking in through the keyhole. I'm going in a bit of a circle making sure I understand your relationships properly. On that basis, I may be understanding your problem incorrectly.
I think you have obj.id->dates.obj_id, and option.obj_id->dates.obj_id, so on that basis, I think your query has to be a bit more complicated:
This gives you object dates:
Select *
from obj obj
join dates d
on obj.id=d.obj_id
This gives you user dates:
select *
from option o
join dates d
on o.obj_id=d.obj_id
To get the result of objects and users having the same dates, you'd need to hook these two together:
select *
from (Select *
from obj obj
join dates d
on obj.id=d.obj_id) a
join (select *
from option o
join dates d
on o.obj_id=d.obj_id) b
on a.dispo_date=b.dispo_date
where b.random=3
I hope this is useful. Good luck.