How to duplicate data from application level? Not database level - mysql

I am stuck for duplicate data from my application.
Let's say I have:
PetBooks Table:
id
book_name
Pet:
id
book_id
code
name
type
Petbooks
1 MyPet
Pet:
1 1 CAT1 Josh Cat
2 1 CAT2 Ron Cat
3 1 DOG1 Max Dog
Question 1:
How can I duplicate Petbook and all its pets?
So After I duplicate the petbook, it should look like
Petbooks
1 MyPet
2 MyPet(Copy)
Pet:
1 1 CAT1 Josh Cat
2 1 CAT2 Ron Cat
3 1 DOG1 Max Dog
4 2 CAT1 Josh Cat
5 2 CAT2 Ron Cat
6 2 DOG1 Max Dog
The solution I think is, to get all of the pet where book_id = 1, then looping and insert one by one, but it's so slow especially when there are a lot of data.

Well, you can do this somewhat painfully -- assuming that ids are assigned automatically and some column other than id is unique in the original data. Let me call that column name.
Then:
insert into petbooks (book_name)
select concat('Copy of ', book_name)
from petbooks;
Then for pets, we have to look up the new id. We can create a look-up table on the fly using aggregation:
insert into pets (book_id, code, name, type)
select pb.max_id, code, name, type)
from pets p join
(select name, min(id) as min_id, max(id) as max_id
from petbooks
group by name
) pb
on pb.min_id = p.book_id

You may handle the relationship manually one by one, and combine them to a procedure. You may use the LAST_INSERT_ID() function to get the latest id. For example:
CREATE PROCEDURE copy_petbook(petbook_id INT)
BEGIN
INSERT INTO PetBooks (book_name)
SELECT CONCAT(book_name, '(Copy)')
FROM Petbooks
WHERE id = petbook_id;
DECLARE last_insert_id BIGINT;
SET last_insert_id = LAST_INSERT_ID();
INSERT INTO Pet (book_id, code, name, type)
SELECT lid.last_insert_id , code, name, type
FROM Pet JOIN (SELECT last_insert_id) as lid
WHERE book_id = petbook_id;
END
If you want to use UUIDs for the id column, you may change the signature to CHAR(36) and then use the UUID() function:
CREATE PROCEDURE copy_petbook(petbook_id CHAR(36))
BEGIN
DECLARE uuid CHAR(36);
-- Generate an UUID which has not been used.
REPEAT
SET uuid = UUID();
UNTIL (SELECT COUNT(*) FROM PetBooks WHERE id = uuid) = 0 END REPEAT;
-- Insert rows with the UUID
INSERT INTO PetBooks (id, book_name)
SELECT u.uuid, CONCAT(book_name, '(Copy)')
FROM Petbooks JOIN (SELECT uuid) as u
WHERE id = petbook_id;
INSERT INTO Pet (book_id, code, name, type)
SELECT u.uuid, code, name, type
FROM Pet JOIN (SELECT uuid) as u
WHERE book_id = petbook_id;
END
You may generate more UUIDs in the same way if you are also using an UUID for the id column in other tables.
Finally, just call the procedure:
CALL copy_petbook(/* Put the id here. */);

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.)

Inner joining on to temp table and insert to table in MySql

I have a list of strings. Each of them have categories which are separated out by a '/'.
For example:
animals/domestic/dog
animals/domestic/cat
What I want to do with these categories is to insert into a MySql categories table.
The table has 4 columns:
id (int auto increment), category_name (nvarchar), parent_id (int), is_active (bit)
The logic around inserting these should be as follows:
The main categories (animals) should have a parent_id of 0.
The child categories will have the id of their parent as parent_id.
There cannot be two active categories with the same category name.
I have tried to implement the following logic:
Get a distinct list of strings.
From these, get a distinct list of main categories.
Insert the distinct main categories to the categories table with a parent ID of 0.
Organise each of the categories in pairs and get distinct pairs:
(animals, domestic)
(domestic, dog)
(domestic, cat)
Get the matching id for each of the parent categories and insert in to the child's parent_id
SQL:
/*INSERT ALL THE FIRST PARENT CATEGORIES WITH A PARENT ID OF 0*/
INSERT INTO categories (category_name, parent_id, is_active)
VALUES ('animals', 0, 1);
/*INSERT ALL THE CATEGORIES IN PAIRS TO TEMP TABLE*/
CREATE TEMPORARY TABLE tempcat(parent nvarchar(256), child nvarchar(256));
INSERT INTO tempcat
VALUES ('animals', 'domestic'),('domestic', 'dog'),('domestic','cat');
/*INSERT INTO THE CATEGORIES TABLE*/
INSERT INTO categories(category_name, parent_id, is_active)
SELECT tempcat.child, categories.id, 1
FROM categories
INNER JOIN tempcat
ON categories.category_name = tempcat.parent;
WHERE categories.is_active = 1;
/*DISPOSE THE TEMPORARY TABLE*/
DROP TEMPORARY TABLE tempcat;
Issue:
After the query is run I expect 4 entries in the categories table.
But I only get 2.
I can see that the temp table has correct entries before doing the last inner join.
I can't seem to figure out why the categories table wouldn't have the other two rows.
Any guidance in the right direction is highly appreciated.
Update #1
Suppose the specifications said 'There cannot be two active categories with the same category name that had the same parent IDs'.
For example, if there were two strings as (animals/domestic/cat), (animals/outdoor/cat) there should be two entries for cat with IDs of domestic and outdoor as parent_id's.
CREATE TABLE categories (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
category_name VARCHAR(64),
parent_id INT UNSIGNED NOT NULL DEFAULT 0,
is_active CHAR(1) NULL,
UNIQUE INDEX idx_name_active (category_name));
CREATE TABLE source_data (path TEXT);
INSERT INTO source_data VALUES ('animals/domestic/dog'), ('animals/domestic/cat');
CREATE PROCEDURE update_categories_table()
BEGIN
DECLARE cnt INT DEFAULT 0;
INSERT IGNORE INTO categories (category_name, parent_id, is_active)
SELECT SUBSTRING_INDEX(path, '/', 1), 0, '1'
FROM source_data;
iteration: LOOP
SELECT COUNT(*) INTO cnt
FROM source_data
WHERE LOCATE('/', path);
IF NOT cnt THEN
LEAVE iteration;
END IF;
INSERT IGNORE INTO categories (category_name, parent_id, is_active)
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(source_data.path, '/', 2), '/', -1),
categories.id,
'1'
FROM source_data, categories
WHERE SUBSTRING_INDEX(source_data.path, '/', 1) = categories.category_name;
UPDATE source_data
SET path = SUBSTRING(path FROM 1 + LOCATE('/', path));
END LOOP iteration;
TRUNCATE source_data;
END
call update_categories_table;
SELECT * FROM categories;
id | category_name | parent_id | is_active
-: | :------------ | --------: | :--------
1 | animals | 0 | 1
4 | domestic | 1 | 1
7 | dog | 4 | 1
8 | cat | 4 | 1
db<>fiddle here
In MySQL 8, you can do this with a single query:
with splits as (
select 1 as n, substring_index(cats, '/', 1) as cat, cats
from strings union all
select 2 as n, substring_index(substring_index(cats, '/', 2), '/', -1) as cat, cats
from strings
where cats like '%/%' union all
select 3 as n, substring_index(substring_index(cats, '/', 3), '/', -1) as cat, cats
from strings
where cats like '%/%/%'
),
splits_n as (
select s.*, dense_rank() over (order by n, cat) as new_id
from splits s
),
splits_np as (
select s.*, sp.new_id as parent_id
from splits_n s left join
splits_n sp
on sp.cats = s.cats and sp.n = s.n - 1
)
select distinct new_id as id, cat, parent_id, 1 as is_active
from splits_np s;
Here is a db<>fiddle.
Unfortunately, this is much more painful in earlier versions.

Stored procedure counting trouble

I have a table [users] that I wish to count the number of each occurrence of Movie_ID and update the record in a different table called [total]. So for Movie_ID=81212 it would send the value 2 to my [total] table.
like below:
------------------------------------
| [users] | [total]
+---------+---------+ +---------+-------------+
|Movie_ID |Player_ID| |Movie_ID | Player_Count|
+---------+---------+ +---------+-------------+
|81212 |P3912 | | 81212 | 2 |
+---------+---------+ +---------+-------------+
|12821 |P4851 | | 12821 | 1 |
+---------+---------+ +---------+-------------+
|81212 |P5121 |
+---------+---------+
(movie_ID + player_ID form composite key
so Movie_ID does not need to be unique)
So i'm trying to accomplish this with a stored procedure, this is what I have so far: I'm not sure how to code the part where it loops through every entry in the [users] table in order to find each occurrence of movie_id and sums it up.
DELIMITER //
CREATE PROCEDURE `movie_total` (OUT movie_count int(5))
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE movie_count int(5);
SELECT count(movie_id) AS movie_count FROM users
foreach unique row in Users ;
IF (SELECT COUNT(*) FROM users WHERE movie_id) > 0
THEN
INSERT INTO total (:movie_id, :Player_Count) VALUES (movie_id, movie_count);
END //
To update this field you can use a query like this -
UPDATE
total t
JOIN (SELECT Movie_ID, COUNT(*) cnt FROM users GROUP BY Movie_ID) m
ON t.Movie_ID = m.Movie_ID
SET
t.Player_Count = cnt
BUT: Do you really need a total table? You always can get this information using SELECT query; and the information in the total table may be out of date.
I think you can do this without a loop:
update total set total.Player_Count = (select COUNT(Movie_ID) from users where total.Movie_ID=users.Movie_ID group by (Movie_ID));

Stored procedure to return rows with unique columns

I have a SQL Server 2008 table with the following columns:
ID = uniqueidentifier
EntryID = uniqueidentifier
EntryType = nvarchar(128)
In this table some of the rows may have the same EntryType value. What I want to do is run a query that will return me the rows where the EntryType field is unique. The only way I thought of doing this is to group by this field then check for groups with just a single entry. Here is some example data:
11C5AEEB-6435-489D-B353-6E8D63FCD1AD, 46F95579-0AB6-4EAC-927C-7259C2F1E046, Ford
01DBC8EE-78E4-4544-A816-87086BD45DDE, EBB689E3-1379-4E22-98B2-C6BD8EBB0F9D, VW
E948C6D2-0E6E-4AC7-9799-83C5EB180219, 46F95579-0AB6-4EAC-927C-7259C2F1E046, Ford
E70806DC-9D43-4341-AEF8-4252612AF00B, 3A3D2602-DB92-412B-AA4E-8FA70438A00A, Ford
D4460A15-2C4B-475E-B5D9-82C625C10DF7, 3EA31E10-4941-46D3-B241-B091259A2AF4, Lexus
I want to run a stored procedure that when applied to the above data will just return the VW entry as the EntryType column is unique.
;WITH x AS
(
-- first, identify rows where only one EntryType exists:
SELECT EntryType FROM dbo.table_name
GROUP BY EntryType HAVING COUNT(*) = 1
)
-- now join to that from the main table:
SELECT t.ID, t.EntryID, t.EntryType
FROM dbo.table_name AS t
INNER JOIN x
ON x.EntryType = t.EntryType;
In SQL Server 2008 you can use count() with over().
select T.ID, T.EntryID, T.EntryType
from
(
select ID, EntryID, EntryType,
count(*) over(partition by EntryType) as C
from YourTable
) as T
where T.C = 1
SQL Fiddle
The derived table will give you
ID EntryID EntryType C
-------- -------- --------- --
11C5AEEB.. 46F95579.. Ford 3
E948C6D2.. 46F95579.. Ford 3
E70806DC.. 3A3D2602.. Ford 3
D4460A15.. 3EA31E10.. Lexus 1
01DBC8EE.. EBB689E3.. VW 1
And the main query picks the rows where the count C is 1.

How to get ID for INSERT if name already used, else generate new one

I'm having a bit of trouble with an INSERT query.
I have a table I'm inserting a value into that's like this:
TABLE cars
ID Brand Model B_ID
---------------------------
1 Ford Escort 1
2 Ford Focus 1
3 Nissan Micra 2
4 Renault Megane 3
5 Ford Mustang 1
ID is unique and B_ID is the same ID for every same brand.
When inserting a new entry I want to be able to check if a brand is already in there and use that same B_ID otherwise I want to increment the highest B_ID and insert that.
I've got this far:
INSERT INTO 'cars' ('brand', 'model', 'B_ID')
VALUES (
'Nissan'
'Note'
'SELECT B_ID FROM cars WHERE brand = 'Nissan'
)
How can I get the highest B_ID and increment it by one if there is no match with my subquery because it's a new brand?
I'm using MySQL.
INSERT INTO `cars` (`brand`, `model`, `B_ID`)
select 'Nissan', 'Note', coalesce(Max(B_ID),0)+1 FROM cars WHERE brand = 'Nissan'
Until you normalize your tables:
INSERT INTO cars
(brand, model, B_ID)
SELECT 'Nissan'
, 'Note'
, COALESCE( ( SELECT B_ID
FROM cars
WHERE brand = 'Nissan'
LIMIT 1
)
, ( SELECT MAX(B_ID)
FROM cars
) + 1
, 1 --- this is for the case when the table is empty
)
Also notice that if you have multiple concurrent INSERT, you may end with rows that have different brand but same B_ID.