MySql AUTO_INCREMENT in INNODB Engine as second column - php

i have a very annoying problem at this moment. i want a table with the following specification:
CREATE TABLE `test` (
`client` INT NOT NULL ,
`id` INT NOT NULL AUTO_INCREMENT ,
`test` INT NOT NULL ,
PRIMARY KEY ( `client` , `id` ) ,
INDEX ( `test` )
) ENGINE = INNODB;
you can see, that i have a primary key with 2 columns. now i want that the id auto_increment column only increments in order to the first column. for example:
## client/id ##
1/1
1/2
1/3
2/1
2/2
2/3
etc.
is this really impossible with innodb? i need innodb because of the transactional features.

You can derive it easily in SELECT statement
set #sno:=0;
set #client:='';
select #sno:=case when #client=client then #sno+1 else 1 end as sno,
#client:=client as client_id from table order by client;

Related

Retrieving last inserted ID for InnoDB composite key table

I have the following table:
CREATE TABLE `my_table` (
composite_pk1 INT NOT NULL ,
composite_pk2 INT NOT NULL ,
data VARCHAR(255) NOT NULL ,
primary key (composite_pk1, composite_pk2)
) ENGINE=InnoDB;
For a given composite_pk1, I wish composite_pk2 to act as an autoincrement primary key. I don't wish to lock the table, and as such plan on using a trigger such as the following:
DELIMITER $$
CREATE TRIGGER my_trigger BEFORE INSERT ON my_table
FOR EACH ROW BEGIN
SET NEW.composite_pk2 = (
SELECT IFNULL(MAX(composite_pk2), 0) + 1
FROM issue_log
WHERE composite_pk1 = NEW.composite_pk1
);
END $$
I can now insert a record:
$stmt=$myDB->prepare('INSERT INTO my_table(composite_pk1, data) VALUES (?,?)');
$stmt->execute([123,'hello']);
How do I get the last inserted composite_pk2? PDO::lastInsertId only works with native autoincrement tables (i.e. not the trigger approach). I "could" later do a SELECT query to get the max value, however, there is no guarantee that another record has snuck in.
You can make composite_pk2 an unique key with auto_increment:
CREATE TABLE `my_table` (
composite_pk1 INT NOT NULL ,
composite_pk2 INT NOT NULL unique auto_increment,
data VARCHAR(255) NOT NULL ,
primary key (composite_pk1, composite_pk2)
) ENGINE=InnoDB;
Now last_insert_id() will return the recently created id for composite_pk2.

Insert values only if an id exists in another table

Basically, the following is what I wish to do:
INSERT INTO table
(
column1, column2
)
VALUES
(
?, ?
)
WHERE EXISTS
(
SELECT 1
FROM table2
WHERE id = ?
)
In other words: provided an id exists in table2, the values should be inserted, otherwise nothing should happen.
What is the correct syntax for this task?
I found that this does what I need.
INSERT INTO table1
(
column1, column2
)
SELECT ?, ?
FROM
table2
WHERE EXISTS
(
SELECT 1
FROM table2
WHERE id = ?
)
LIMIT 1
I have no mysql database available but similar to other databases and after reading the reference it must be something like
INSERT INTO table (column1, column2)
SELECT ?, ? FROM table2 WHERE id=?
I know this is a pretty old question that's already been answered, but a friend of mine helped set up a database with a "constraint" where an insert or update had to have 1 value match one value from a different table in the same database exactly in order to fire off.
I'm not very good with MySQL calls yet so I don't understand exactly what it does... but it may help you cut down on the code necessary to do what you want?
(Note: I had to modify it a bit to remove some sensitive names & such from it, but it should be a good representation.)
-- -----------------------------------------------------
-- Table `db1`.`table1`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `db1`.`table1` (
`id` INT NOT NULL AUTO_INCREMENT ,
`av_key` VARCHAR(40) NOT NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `db1`.`table2`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `db1`.`table2` (
`bkid` INT NOT NULL AUTO_INCREMENT ,
`id` INT NOT NULL ,
`data` VARCHAR(6144) NULL ,
PRIMARY KEY (`bkid`) ,
INDEX `id_idx` (`id` ASC) ,
CONSTRAINT `id`
FOREIGN KEY (`id` )
REFERENCES `db1`.`table1` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
With this, you just need to do the insert/update statement, and it'll just fail if there's no match for "id" in both tables.
Don't know if you already found out or if that's what you needed, but figured it was worth sharing!

mysql 'likes' structure table

I'm working on a web site where users can post articles with this table structure :
CREATE TABLE IF NOT EXISTS `articles` (
`id_articles` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_users` int(10) unsigned NOT NULL,
`articles` text NOT NULL,
PRIMARY KEY (`id_articles`),
UNIQUE KEY `id_articles` (`id_articles`),
KEY `id_users` (`id_users`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Each user can 'like' the articles.
Is that the right way below to create a 'like table' :
CREATE TABLE IF NOT EXISTS `articles_likes` (
`id_articles` int(10) unsigned NOT NULL,
`id_users` int(10) unsigned NOT NULL,
KEY `id_articles` (`id_articles`,`id_users`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
It is correct but you will want to add separte indexes on id_articles and id_users (also you might want to name the columns 'id_article' and 'id_user' for sanity).
CREATE TABLE IF NOT EXISTS `article_likes` (
`id_article` int(10) unsigned NOT NULL,
`id_user` int(10) unsigned NOT NULL,
KEY `id_article` (`id_article`),
KEY `id_user` (`id_user`)
) ENGINE=InnoDB;
The reason you want separate indexes is because in mysql if you create an index on columns (A, B) that index will be used in queries having in the where clause column A, or columns A and B.
In your case for example if you made a query "SELECT * FROM article_likes WHERE id_user=X" this query would not use an index.
An ever better option would be to add a combined index and a separate index on the second column from the combined index. Like this:
CREATE TABLE IF NOT EXISTS `article_likes` (
`id_article` int(10) unsigned NOT NULL,
`id_user` int(10) unsigned NOT NULL,
KEY `id_article_user` (`id_article`, `id_user`),
KEY `id_user` (`id_user`)
) ENGINE=InnoDB;
This way you would have optimal performance on queries like 'WHERE id_user=X', "WHERE id_article=X', "WHERE id_article=X AND id_user=Y"
This is a valid way Chris. You can use COUNT() to match the id_articles in the articles_likes table against the current article you are viewing in articles.
$articles_id = 23;
mysql_query("SELECT COUNT(*) FROM articles_likes
WHERE id_articles = ".$articles_id);
You can also just leave COUNT() (MySQL) out and instantly know which users are the "likers" of the articles and use count() (PHP) on the returned Array to duplicate the effect of COUNT() in MySQL.
i would have a total of 3 tables. an articles table, and the user id could be a column in that for users who submit articles , but you need a separate user table since not all users will submit articles (i am assuming), and then a 3rd table for likes, that takes the primary key from users and the primary key from articles and uses them as foreign keys. so each time an article is liked, an entry is made in the 3rd table

MySQL / PHP - Conversation System

I'm having a really hard time figuring this one out. I am trying to make a conversation system and it would work like this:
A logged in user would have a link to "conversations". On that page a list of all conversation.titles would show up. When a conversation-name is clicked the conversation messages (mostly between 2 users, but could be more) will show up like this:
Conversation tiltle...
|------------------------------------------------------------|
|Send-button| Textfield for new message here |
|----------------|-------------------------------------------|
Sender: Hi....... (08/24/11 - 12PM)
receiver: Hi!..... (08/24/11 - 11PM
Sender: bla blah... (08/24/11 - 10PM)
etc..
I have no problem listing messages in the conversation itself, but rather getting the list of conversations a user participates in.
I get multiple results of the same instance.
I would like a query that produces a list of all conversations a logged in user has participated in either by sending a message, recieving a message or both.
I guess i would need a nested query, but i dont know how to do that. So i'm a bit stuck here, trying to inner-join all tables doesnt cut the cheese.
This is the database mysql design / ER-modell:
-- -----------------------------------------------------
-- Table `firm`.`conversation`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`conversation` (
`conversation_id` INT NOT NULL AUTO_INCREMENT ,
`title` VARCHAR(45) NULL ,
`date` TIMESTAMP NULL ,
PRIMARY KEY (`subject_id`) )
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `firm`.`message`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`message` (
`message_id` INT NOT NULL AUTO_INCREMENT ,
`message` LONGTEXT NULL ,
`date` TIMESTAMP NOT NULL ,
`read_sender` BINARY NOT NULL ,
`read_receiver` BINARY NOT NULL ,
`deleted_receiver` BINARY NOT NULL ,
`deleted_sender` BINARY NOT NULL ,
`conversation_id` INT NOT NULL ,
PRIMARY KEY (`message_id`) ,
INDEX `fk_message_subject1` (`subject_id` ASC) ,
CONSTRAINT `fk_message_con1`
FOREIGN KEY (`conversation_id` )
REFERENCES `firm`.`conversation` (`conversation_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1
COLLATE = latin1_swedish_ci;
-- -----------------------------------------------------
-- Table `firm`.`outbox`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`outbox` (
`outbox_id` INT NOT NULL AUTO_INCREMENT ,
`user_id` INT(10) NOT NULL ,
`conversation_id` INT(10) NOT NULL ,
`date` TIMESTAMP NOT NULL ,
PRIMARY KEY (`outbox_id`) ,
INDEX `fk_outbox_users1` (`user_id` ASC) ,
INDEX `fk_utbox_con1` (`conversation_id` ASC) ,
CONSTRAINT `fk_outbox_users1`
FOREIGN KEY (`user_id` )
REFERENCES `firm`.`users` (`user_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_outbox_con1`
FOREIGN KEY (`conversation_id` )
REFERENCES `firm`.`conversation` (`conversation_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
-- -----------------------------------------------------
-- Table `firm`.`inbox`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`inbox` (
`inbox_id` INT NOT NULL ,
`user_id` INT(10) NOT NULL ,
`message_id` INT NOT NULL ,
`read` BINARY NOT NULL ,
`date` TIMESTAMP NOT NULL ,
PRIMARY KEY (`inbox_id`) ,
INDEX `fk_inbox_users1` (`user_id` ASC) ,
INDEX `fk_inbox_message` (`message_id` ASC) ,
CONSTRAINT `fk_inbox_users1`
FOREIGN KEY (`user_id` )
REFERENCES `firm`.`users` (`user_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_inbox_message1`
FOREIGN KEY (`message_id` )
REFERENCES `frim`.`message` (`message_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
SELECT DISTINCT conversation.conversation_id
FROM conversation
INNER JOIN message ON message.conversation_id = conversation.conversation_id
INNER JOIN inbox ON inbox.message_id = message.message_id
INNER JOIN outbox ON inbox.message_id = message.message_id
WHERE outbox.user_id = #USER#
OR inbox.user_id = #USER#
This join should work. DISTINCT because a user is likely to have participated as a sender and a recipient.

How to turn rows into columns?

I have a database where I store keywords grouped into projects and data related to each keyword, then I display datagrids foreach project with one row per keyword and several columns all retrieved from the same table "data". I have 4 tables, keywords, projects, group_keywords and data. "keywords" only stores the keyword, "projects" the project name, "group_keywords" the keywords ids for the keywords assigned to that project, and "data" is where all the data foreach keyword goes, identified by a foreign key for the keywords.id, and a name column to identify the data name.
Now to retrieve the keywords + all the data for a project I use this query:
SELECT * FROM `group_keywords`
INNER JOIN keywords on keywords.id = keyword_id
INNER JOIN data ON data.id = keywords.id
WHERE `group_id` = (SELECT `id` FROM `projects` WHERE `name` = 'ProjectName'
This gives me something like
id group_id keyword_id id keyword id name value
12 5 52 52 absorption food 52 data_name_x1 6
12 5 52 52 absorption food 52 data_name_x2 8
12 5 52 52 absorption food 52 data_name_x3 26
12 5 52 52 absorption food 52 data_name_x4 2
...
But what I want is to get:
id group_id keyword_id id keyword id data_name_x1 data_name_x2 data_name_x3 data_name_x4
12 5 52 52 absorption food 52 6 8 26 2
...
So I can sort and use pagination for the datagrids easly, otherwise I have no idea how to do it, because when using big data sets I can't just dump everything into an array, too much data.
This is the schema:
-- --------------------------------------------------------
-- Table structure for table `keywords`
CREATE TABLE IF NOT EXISTS `keywords` (
`id` int(10) unsigned NOT NULL auto_increment,
`keyword` varchar(255) NOT NULL,
UNIQUE KEY `id` (`id`),
UNIQUE KEY `keyword` (`keyword`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=115386 ;
-- --------------------------------------------------------
-- Table structure for table `data`
CREATE TABLE IF NOT EXISTS `data` (
`id` int(10) unsigned NOT NULL,
`name` varchar(100) NOT NULL,
`value` varchar(15) NOT NULL,
UNIQUE KEY `id` (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
-- Table structure for table `projects`
--
CREATE TABLE IF NOT EXISTS `projects` (
`id` int(10) NOT NULL auto_increment,
`name` varchar(100) NOT NULL,
`parent` varchar(100) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=21 ;
-- --------------------------------------------------------
-- Table structure for table `group_keywords`
CREATE TABLE IF NOT EXISTS `group_keywords` (
`id` int(10) NOT NULL auto_increment,
`group_id` int(10) NOT NULL,
`keyword_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `group_id` (`group_id`,`keyword_id`),
KEY `keyword_id` (`keyword_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=119503 ;
-- --------------------------------------------------------
-- Constraints for table `data`
--
ALTER TABLE `data`
ADD CONSTRAINT `data_ibfk_1` FOREIGN KEY (`id`) REFERENCES `keywords` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- --------------------------------------------------------
-- Constraints for table `group_keywords`
--
ALTER TABLE `group_keywords`
ADD CONSTRAINT `group_keywords_ibfk_1` FOREIGN KEY (`keyword_id`) REFERENCES `keywords` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
This operation is traditionally called "unpivot" and a few RDBMSs support it, but MySQL doesn't appear to be one of them. You have two options, do it in SQL or do it in PHP. In MySQL it looks something like this with self-joins (I don't know which field qualifies as an ID field for you, so forgive me creating my own example). From a performance perspective, make sure you index both the ID and Column Name, otherwise these joins will crawl.
shapes
ID Name Value
1 Color Red
1 Shape Circle
... for more "columns"
2 Color Green
2 Shape Square
... for more "columns"
SELECT
A.ID,
B.Value as Color,
C.Value as Shape
... for more "columns"
FROM shapes A
LEFT JOIN shapes B ON B.ID = A.ID AND B.Name = 'Color'
LEFT JOIN shapes C ON C.ID = A.ID AND C.Name = 'Shape'
... for more "columns"
Which should net us (unless my head-SQL-parser is misrunning tonight):
ID Color Shape
1 Red Circle
2 Green Square
For the PHP version, you don't necessarily have to load up an array, you can stream it. Sort by the PK and walk down it setting the properties. In pseudocode:
Set X to undefined
Get a Record
Check the ID property, if it's different than X, create a new object, set X to the new ID, and yield the previous object
Set the property of the object based on the "Name" column of our result
Hope this helps!

Categories