MySQL multiple columns as unordered sets - mysql

I'm somewhat new to MySQL and SQL in general, so hopefully this isn't a simple question.
I have a table that represents items in a customer's basket at checkout. This table represents a situation in which a customer is limited to 3 items, so I currently have a column for each item in the basket. It looks like this:
+------------------------------------------------------+
+ id | item1 | item2 | item3 | val |
+------------------------------------------------------+
where val is just some value associated with the basket. The ordering of the items means nothing in terms of my processing, so in theory I would like to have them represented as an unordered set. This means that a row of (i1,i2,i3,val) is functionally equivalent to (i2,i1,i3,val).
My question is, how do I implement this in my table and/or in SQL such that selecting (i3,i2,i1,val) will return the row for (i1,i2,i3,val)?
I also need to have something that catches uniqueness when I'm inserting. For example, if I insert (i2,i3,i1,newval), I would want the table to update (i1,i2,i3,val) to be (i1,i2,i3,newval).

You could standardise your model by using a 0 to many relation between customer and item:
-- assuming the existing table to be named `yourtable`
-- assuming your customer's table to be named `customer`
-- assuming your customer's id in the customer's table to be named `id`
-- assuming innodb (remove fk constraint if not)
CREATE TABLE `customer_item` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`id_customer` INT(10) NOT NULL,
`item` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE=innodb
SELECT NULL AS `id`, t.id AS `id_customer`, t.`item`
FROM (
SELECT id, item1 AS `item`
FROM
yourtable
UNION
SELECT id, item2 AS `item`
FROM
yourtable
UNION
SELECT id, item3 AS `item`
FROM
yourtable
) t
ORDER BY t.id ASC
;
CREATE INDEX UNIQUE `idx_customer_item_cust` ON `customer_item` (`id_customer`, `item`);
ALTER TABLE `customer_item` ADD CONSTRAINT `fk_customer_item_cust` FOREIGN KEY (`id_customer`) REFERENCES `customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- once you check the data is consistent:
DROP TABLE `yourtable`;
Once that done, no item could be possibly inserted twice for the same customer.
Please note:
the union select skips duplicates already at table creation, in case some items were repeated for some customers
your data is normalised, from the customer to item point of view
your data is still not normalised, from the item point of view. You should have an item table, and the customer_item table should reference the id of items in the item table instead of using item names or description for varchars.

Related

USING STORED PROCEDURE to SUM, GROUP BY and insert to another table with

I have created 3 tables: item, shop and stock. Plus a stored procedure called inserting
which inserts to the shop table with a given item from the item table
CREATE TABLE item(
i_id int(11) auto_increment,
i_name varchar(255) not null,
primary key(i_id));
CREATE TABLE shop(
s_id int(11) auto_increment,
s_name varchar(255) not null,
s_item int(11) not null,
s_qty int(11) not null,
primary key(s_id),
foreign key(s_item) references item(i_id)
);
CREATE TABLE stock(
item int(11) not null,
total int(11) not null
);
CREATE PROCEDURE inserting (
IN shop_name varchar(225),
IN shop_item int(11),
IN shop_qty int(11)
)
BEGIN
INSERT INTO shop(s_name, s_item, s_qty)
VALUES
(shop_name, shop_item, shop_qty);
INSERT INTO STOCK(item, total)
SELECT s_item, SUM(s_qty) FROM shop GROUP BY s_item
ON DUPLICATE KEY UPDATE
item = VALUES(item),
total = VALUES(total);
The first insert works, but on the second insert when it populates the stock table it gives me extra columns, which i'm not expecting.
I have tried using REPLACE INTO and ON DUPLICATE KEY UPDATE to get single results, still the results comes as the following:
SELECT * FROM `stock`;
+------+-------+
| ITEM | TOTAL |
+------+-------+
| 1 | 5 |
| 1 | 9 |
+------+-------+
what I am trying to achieve is, group the ITEM column, and sum up the TOTAL to a single row.
what am I doing wrong here, or missing from the query?
thanks.
For the on duplicate key syntax to work as expected, you need a unique or primary key constraint on the target table, so the database can identify the "duplicate" rows. Same goes for the REPLACE syntax.
But your stock table does not have a primary key. Consider the following DDL instead:
CREATE TABLE stock(
item int(11) primary key,
total int(11) not null
);
Side note: there is no need to reassign column item in the on duplicate key clause, since it's what is used to identify the conflict in the first place. This is good enough:
INSERT INTO STOCK(item, total)
SELECT s_item, SUM(s_qty) FROM shop GROUP BY s_item
ON DUPLICATE KEY UPDATE total = VALUES(total);
If you run this one time, it should work as you expected. But subsequent runs may bring duplicate ITEM because of what #gmb said. The table must have a UNIQUE index or PRIMARY KEY. See more details here
https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

How to add columnName and its value dynamically into MYSQL Table

I have a master table, For e.g Fruits_Details(Master Table) which contains column name as:
f_name | f_price | location
I want to present a form to the user which consist of the above fields but also contains an additional "+" sign from which he can add new details as key, value pair.
For e.g :
fruit_color - red
fruit_season - spring
And many more details like this. I want that these two details should be stored in a different table(Child Table-I will implement foreign key concept).
But I am confused that how will design a query which will dynamically add column name and its related value in my child table.
The child table should have a attibute name and value there has to be nothing dynamic.
child table
-----------
fruit_id
attribute_name
attribute_value
insert into child (fruit_id, attribute_name, attribute_value)
values (1, 'color', 'red')
Since the potential is endless, don't do it by columns, but by rows...
You'll be able to extract the info one way, and display it another.
A table called fruits_attributes, that will contain the columns:
attribute_id, f_id (foreign key), attribute_name, attribute_value
CREATE TABLE `fruits_attributes` (
`attribure_id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`f_id` mediumint(8) unsigned NOT NULL,
`attribure_name` varchar(50) NOT NULL,
`attribure_value` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
KEY `f_id` (`offer_id`),
KEY `attribure_name` (`attribure_name`),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
;
Then a SELECT query should be something like
SELECT f.*, fa.* FROM
Fruits_Details f
LEFT JOIN fruits_attributes fa ON fa.id = f_id

MySQL cleanup table from duplicated entries AND relink FK in depending table

Here is my situation: I have 2 tables, patient and study.
Each table has its own PK using autoincrement.
In my case, the pat_id should be unique. It's not declared as unique at database level since it could be non unique is some uses (it's not a home made system). I found out how to configure the system to consider the pat_id as unique, but I need now to cleanup the database for duplicated patients AND relink duplicated patients in study table to remaining unique patient, before deleting the duplicated patients.
Patient table:
CREATE TABLE `patient` (
`pk` BIGINT(20) NOT NULL AUTO_INCREMENT,
`pat_id` VARCHAR(250) COLLATE latin1_bin DEFAULT NULL,
...
`pat_name` VARCHAR(250) COLLATE latin1_bin DEFAULT NULL,
...
`pat_custom1` VARCHAR(250) COLLATE latin1_bin DEFAULT NULL
....
PRIMARY KEY (`pk`)
)ENGINE=InnoDB;
Study table:
CREATE TABLE `study` (
`pk` BIGINT(20) NOT NULL AUTO_INCREMENT,
`patient_fk` BIGINT(20) DEFAULT NULL,
...
PRIMARY KEY (`pk`),
...
CONSTRAINT `patient_fk` FOREIGN KEY (`patient_fk`) REFERENCES `patient` (`pk`)
)ENGINE=InnoDB;
I found some similar questions, but not exactly the same issue, especially it was missing the link of the foreign keys to the remaining unique patient.
Cleanup Update for Duplicate Entries
Update only first record from duplicate entries in MySQL
This is how I did.
I reused an unused field in patient table to mark non duplicated (N), 1st of duplicated (X), and other duplicated patients (Y). You could also add a column for this (and drop it after use).
Here are the steps I followed to cleanup my database:
/*1: List duplicated */
select pk,pat_id, t.`pat_id_issuer`, t.`pat_name`, t.pat_custom1
from patient t
where pat_id in (
select pat_id from (
select pat_id, count(*)
from patient
group by 1
having count(*)>1
) xxx);
/*2: Delete orphan patients */
delete from patient where pk not in (select patient_fk from study);
/*3: Reset flag for duplicated (or not) patients*/
update patient t set t.`pat_custom1`='N';
/*4: Mark all duplicated */
update patient t set t.`pat_custom1`='Y'
where pat_id in (
select pat_id from (
select pat_id, count(*)
from patient
group by 1
having count(*)>1
) xxx) ;
/*5: Unmark the 1st of the duplicated*/
update patient t
join (select pk from (
select min(pk) as pk, pat_id from patient
where pat_custom1='Y'
group by pat_id
) xxx ) x
on (x.pk=t.pk)
set t.`pat_custom1`='X'
where pat_custom1='Y'
;
/*6: Verify update is correct*/
select pk, pat_id,pat_custom1
from `patient`
where pat_custom1!='N'
order by pat_id, pat_custom1;
/*7: Verify studies linked to duplicated patient */
select p.* from study s
join patient p on (p.pk=s.patient_fk)
where p.pat_custom1='Y';
/*8: Relink duplicated patients */
update study s
join patient p on (p.pk=s.patient_fk)
set patient_fk = (select pk from patient pp
where pp.pat_id=p.pat_id and pp.pat_custom1='X')
where p.pat_custom1='Y';
/*9: Delete newly orphan patients */
delete from patient where pk not in (select patient_fk from study);
/* 10: reset flag */
update patient t set t.`pat_custom1`=null;
/* 11: Commit changes */
commit;
There is certainly a shorter way, with a some smarter (complicated?) SQL, but I personally prefer the simple way. This also allows me to check each step is doing what I expect.

MySQL: Generate Autokey but use same for multiple rows

I have a mysql table that stores a mapping from an ID to a set of values:
CREATE TABLE `mapping` (
`ID` bigint(20) unsigned NOT NULL,
`Value` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
This table is a list of values and the ID of a row selects the set, this value belongs to.
So the column ID is unique per set, but not unique per row.
I insert data into the table using the following statement:
INSERT INTO `mapping`
SELECT 5, `value` FROM `set1`;
In this example I calculated and set the ID manually to 5.
It would be great if mysql could set this ID automatically. I know the autokey feature, but using it will not work, because all rows inserted with the same insert statement should have the same ID.
So each insert statement should generate a new ID and then use it for all inserted rows.
Is there a way to accomplish this?
I am not convinced to it (I'm not sure whether locking table is good idea, I think it's not), but this might help:
lock tables `mapping` as m write, m as m1 read;
insert into m
select (select max(id) + 1 from m1), `value` from `set1`;
ulock tables;
One option is to have an additional table with an autogenerated key on single rows. Insert (with or without an necessary or appropriate other data) into that table, thus generating the new ID, and then use the generated key to insert into the mapping table.
This moves you to a world where the non-unique id is a foreign key reference to a truly unique key. Much more in keeping with typical relational database thinking.

Can anyone give many to many MySQL step-by-step guide?

after searching up and down, reading all possible articles and tutorials, I grasped the basics of the concept, but still cannot do it, and so as many others as I can see.
Can someone please post 100% practical and dummy proof guide to creation and most basic usage of MySQL many to many relationship, I'm so sure many will benefit from it.
What I have is a table that has a number of items, say I1, I2, I3...
I have another table, with a number of properties, say P1, P2, P3...
For each of the items, each of the properties may hold false or true, for example
I1 has properties P2 and P3
I2 has properties P1, P2 and P3
I3 has properties P1
...
So how to go about creating the relationship? (please give code if possible)
And once created, how to
insert properties for some item I
read which properties apply to some existing item I
Thanks in advance
Step 1 - Setup tables:
You should have a table structure like below (*'s are primary keys):
Item
---------
ItemId*
ItemName
ItemProperties
--------------
ItemId
PropertyId
Properties
----------
PropertyId*
PropertyName
Step 2 - Set foreign key relationships:
Both columns in the ItemProperties table are foreign keys to their corresponding table (ItemId to Item table, PropertyId to Properties table)
Step 3 - Code:
To associate the properties with PropertyIds 35 and 44 to the Item with ID 111 you would run the following:
INSERT INTO ItemProperties (ItemId,PropertyId) VALUES (111,35)
INSERT INTO ItemProperties (ItemId,PropertyId) VALUES (111,44)
To select all properties associated with an item you would run the following:
SELECT ip.PropertyId, ip.PropertyName
FROM Item as i
INNER JOIN ItemProperties as ip ON i.ItemId = ip.ItemId
WHERE i.ItemId = 111
Create an intermediary table between the two, that has ids from both the other tables. That way there can be multiple references (rows) in this table connecting different items to multiple different properties. Does this make sense?
what I would do is create a table called ItemProperties with columns ItemId, PropertyId and PropertyValue.
the key (ItemId, PropertyId) would unique for this table.
This links products to properties, allowing multiple items with the same name.
CREATE TABLE products (
productid int(11) NOT NULL AUTO_INCREMENT,
productname varchar(20) NOT NULL,
description text NOT NULL,
PRIMARY KEY (productid),
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE products_property (
relationid int(11) NOT NULL AUTO_INCREMENT,
productid int(11) NOT NULL,
propertyname int(11) NOT NULL,
PRIMARY KEY (relationid)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE properties (
property varchar(30) NOT NULL,
value enum('true','false') NOT NULL,
PRIMARY KEY (property)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;