简体   繁体   English

如何根据MySQL中的当前日期和生成的序列号插入一个值?

[英]How to INSERT a value based on the current date and a generated sequence number in MySQL?

I have this MySQL table: 我有这个MySQL表:

CREATE TABLE bills
(
    id_interess     INT UNSIGNED    NOT NULL,
    id_bill         VARCHAR(30)     NULL,
    PRIMARY KEY (id_interess)
) ENGINE=InnoDB;

And now I want to be able to manually insert unique integer for id_interess and automatically generate id_bill so that it consists of a current date and an integer (integer resets on a new year using trigger) like this: 现在我希望能够为id_interess手动插入唯一的整数并自动生成id_bill ,使其包含当前日期和整数(使用触发器在新年重置整数),如下所示:

id_interess |id_bill    |
------------+-----------+
1           |20170912-1 |
2           |20171030-2 |
6           |20171125-3 |
10          |20171231-4 |
200         |20180101-1 |
3           |20180101-2 |
8           |20180102-3 | 

If anyone has direct solution to this using only one query , I would be very glad! 如果有人只使用一个查询直接解决这个问题 ,我会很高兴! I only came up with a solution that uses three queries , but I still get some errors... 我只提出了一个使用三个查询的解决方案,但我仍然遇到一些错误......

My newbie attempt: I created an additional column id_bill_tmp which holds integer part of id_bill like this: 我的新手尝试:我创建了一个附加列id_bill_tmp持有的整数部分id_bill是这样的:

CREATE TABLE bill
(
    id_interess     INT UNSIGNED    NOT NULL,   
    id_bill_tmp     INT UNSIGNED    NULL,
    id_bill         VARCHAR(30)     NULL,
    PRIMARY KEY (id_interess)
) ENGINE=InnoDB;

Table from above would in this case look like this (note that on new year id_bill_tmp is reset to 1 and therefore I can't use AUTO_INCREMENT which can only be used on keys and keys need unique values in a column): 在这种情况下,上面的表格看起来像这样(请注意,在新的一年id_bill_tmp重置为1 ,因此我不能使用只能在键和键上使用的AUTO_INCREMENT需要列中的唯一值):

id_interess |id_bill_tmp   |id_bill    |
------------+--------------+-----------+
1           |1             |20170912-1 |
2           |2             |20171030-2 |
6           |3             |20171125-3 |
10          |4             |20171231-4 |
200         |1             |20180101-1 |
3           |2             |20180101-2 |
6           |3             |20180102-3 | 

So for example to insert 1st row from the above table, table would have to be empty, and I would insert a value in three queries like this: 因此,例如,要从上表中插入第一行,表必须为空,我将在三个查询中插入一个值,如下所示:

1st query: 第一查询:

INSERT INTO racuni (id_interess) VALUES (1);

I do this first because I don't know how to increment a nonexistent value for id_bill_tmp and this helped me to first get id_bill_tmp = NULL : 我这样做是因为我不知道如何为id_bill_tmp增加一个不存在的值,这有助于我首先得到id_bill_tmp = NULL

id_interess |id_bill_tmp   |id_bill    |
------------+--------------+-----------+
1           |[NULL]        |[NULL]     |

2nd query 第二个查询

Now I try to increment id_bill_tmp to become 1 - I tried two queries both fail saying: 现在我尝试将id_bill_tmp增加为1 - 我尝试了两个查询都失败说:

table is specified twice both as a target for 'update' and as a separate source for data table被指定两次作为'update'的目标和作为数据的单独源

This are the queries I tried: 这是我试过的查询:

UPDATE bills
SET id_bill_tmp = (SELECT IFNULL(id_bill_tmp, 0)+1 AS id_bill_tmp FROM bills)
WHERE id_interess = 1;

UPDATE bills
SET id_bill_tmp = (SELECT max(id_bill_tmp)+1 FROM bills)
WHERE id_interess = 1;

3rd query: 第三个查询:

The final step would be to reuse id_bill_tmp as integer part of id_bill like this: 最后一步将重用id_bill_tmp作为整数部分id_bill这样的:

UPDATE bills
SET id_bill = concat(curdate()+0,'-',id_bill_tmp)
WHERE id_interess = 1;

so that I finally get 所以我终于明白了

id_interess |id_bill_tmp   |id_bill    |
------------+--------------+-----------+
1           |1             |20170912-1 |

So if anyone can help me with the 2nd query or even present a solution with a single query or even without using column id_bill_tmp it would be wonderful. 因此,如果任何人都可以帮助我进行第二次查询,甚至可以使用单个查询提供解决方案,甚至不使用列id_bill_tmp ,那就太棒了。

If you are certain to be inserting in chronological order, then this will both bump the number and eliminate the need for the annual trigger: 如果您确定按时间顺序插入,那么这将增加数字并消除年度触发器的需要:

DROP FUNCTION fcn46309431;
DELIMITER //
CREATE FUNCTION fcn46309431 (_max VARCHAR(22))
    RETURNS VARCHAR(22)
    DETERMINISTIC
    SQL SECURITY INVOKER
BEGIN
    RETURN
        CONCAT(DATE_FORMAT(CURDATE(), "%Y%m%d"), '-',
            IF( LEFT(_max, 4) = YEAR(CURDATE()),
                  SUBSTRING_INDEX(_max, '-', -1) + 1,
                  1 ) );
END                  
//
DELIMITER ;

INSERT INTO se46309431 (id_interess, id_bill)
    SELECT 149, fcn46309431(MAX(id_bill)) FROM se46309431;

SELECT * FROM se46309431;

(If you might insert out of date order, then the MAX(..) can mess up.) (如果你可能插入过时的顺序,那么MAX(..)会搞砸。)

Solution #1 - with the extra column 解决方案#1 - 使用额外的列

Demo 演示

http://rextester.com/GOTPA70741 http://rextester.com/GOTPA70741

SQL SQL

INSERT INTO bills (id_interess, id_bill_tmp, id_bill) VALUES (
    1, -- (Change this value appropriately for each insert)
    IF(LEFT((SELECT id_bill FROM 
             (SELECT MAX(CONCAT(LEFT(id_bill, 8),
                                LPAD(SUBSTR(id_bill, 10), 10, 0))) AS id_bill
              FROM bills) b1), 4) = DATE_FORMAT(CURDATE(),'%Y'), 
       IFNULL(
           (SELECT id_bill_tmp
            FROM (SELECT id_bill_tmp
                  FROM bills
                  WHERE CONCAT(LEFT(id_bill, 8),
                               LPAD(SUBSTR(id_bill, 10), 10, 0)) =
                        (SELECT MAX(CONCAT(LEFT(id_bill, 8),
                                           LPAD(SUBSTR(id_bill, 10), 10, 0)))
                         FROM bills)) b2),
           0),
       0)
       + 1,
    CONCAT(DATE_FORMAT(CURDATE(),'%Y%m%d'), '-' , id_bill_tmp));

Notes 笔记

The query looks slightly more complicated that it actually is because of the issue that MySQL won't let you directly use a subselect from the same table that's being inserted into. 实际上,查询看起来稍微复杂一点,因为MySQL不允许您直接使用插入的同一个表中的子选择。 This is circumvented using the method of wrapping another subselect around it as described here . 这是使用所描述的缠绕它的另一个子选择的方法规避了这里

Solution #2 - without the extra column 解决方案#2 - 没有额外的列

Demo 演示

http://rextester.com/IYES40010 http://rextester.com/IYES40010

SQL SQL

INSERT INTO bills (id_interess, id_bill) VALUES (
    1, -- (Change this value appropriately for each insert)
    CONCAT(DATE_FORMAT(CURDATE(),'%Y%m%d'),
           '-' ,
           IF(LEFT((SELECT id_bill
                    FROM (SELECT MAX(CONCAT(LEFT(id_bill, 8),
                                            LPAD(SUBSTR(id_bill, 10), 10, 0))) AS id_bill
                          FROM bills) b1), 4) = DATE_FORMAT(CURDATE(),'%Y'), 
              IFNULL(
                  (SELECT id_bill_tmp
                   FROM (SELECT SUBSTR(MAX(CONCAT(LEFT(id_bill, 8),
                                                  LPAD(SUBSTR(id_bill, 10), 10, 0))), 9)
                                AS id_bill_tmp
                         FROM bills) b2),
                  0),
              0)
              + 1));

Notes 笔记

This is along the same lines as above but gets the numeric value that would have been in id_bill_tmp by extracting from the right part of id_bill from the 10th character position onwards via SUBSTR(id_bill, 10) . 这与上面的行相同,但是通过SUBSTR(id_bill, 10)从第10个字符位置开始从id_bill的右边部分提取,得到id_bill_tmp的数值。

Step by step breakdown 一步一步细分

  1. CONCAT(...) assembles the string by concatenating its parts together. CONCAT(...)通过将其各部分连接在一起来组装字符串。
  2. DATE_FORMAT(CURDATE(),'%Y%m%d') formats the current date as yyyymmdd (eg 20170923 ). DATE_FORMAT(CURDATE(),'%Y%m%d')将当前日期格式化为yyyymmdd (例如20170923 )。
  3. The IF(..., <x>, <y>) is used to check whether the most recent date that is already present is for the current year: If it is then the numeric part should continue by incrementing the sequence, otherwise it is reset to 1. IF(..., <x>, <y>)用于检查当前年份的最新日期是否为当前年份:如果是,那么数字部分应该通过递增序列继续,否则它重置为1。
  4. LEFT(<date>, 4) gets the year from the most recent date - by extracting from the first 4 characters of id_bill . LEFT(<date>, 4)从最近的日期获得年份 - 从id_bill的前4个字符中id_bill
  5. SELECT MAX(...) AS id_bill FROM bills gets the most recent date + sequence number from id_bill and gives this an alias of id_bill . SELECT MAX(...) AS id_bill FROM billsid_bill获取最新的日期+序列号,并给它一个id_bill的别名。 (See the notes above about why the subquery also needs to be given an alias ( b1 ) and then wrapped in another SELECT ). (请参阅上面的注释,了解为什么子查询也需要赋予别名( b1 ),然后包装在另一个SELECT )。 See the two steps below for how a string is constructed such that MAX can be used for the ordering. 请参阅以下两个步骤,了解如何构造字符串,以便MAX可用于排序。
  6. CONCAT(LEFT(id_bill, 8), ...) is constructing a string that can be used for the above ordering by combining the date part with the sequence number padded with zeros. CONCAT(LEFT(id_bill, 8), ...)正在构造一个字符串,通过将日期部分与用零填充的序列号组合,可以用于上述排序。 Eg 201709230000000001 . 例如201709230000000001
  7. LPAD(SUBSTR(id_bill, 10), 10, 0) pads the sequence number with zeros (eg 0000000001 so that MAX can be used for the ordering. (See the comment by Paul Spiegel to understand why this needs to be done - eg so that sequence number 10 is ordered just after 9 rather than just after 1 ). LPAD(SUBSTR(id_bill, 10), 10, 0)用零LPAD(SUBSTR(id_bill, 10), 10, 0)序列号(例如0000000001以便MAX可以用于排序。) (参见Paul Spiegel的评论,了解为什么需要这样做 - 例如序列号109之后而不是在1之后排序。
  8. DATE_FORMAT(CURDATE(),'%Y') formats the current date as a year (eg 2017 ) for the IF comparison mentioned in (3) above. DATE_FORMAT(CURDATE(),'%Y')将当前日期格式化为上述(3)中提到的IF比较的年份(例如2017 )。
  9. IFNULL(<x>, <y>) is used for the very first row since no existing row will be found so the result will be NULL. IFNULL(<x>, <y>)用于第一行,因为不会找到现有的行,因此结果将为NULL。 In this case the numeric part should begin at 1. 在这种情况下,数字部分应从1开始。
  10. SELECT SUBSTR(MAX(...), 9) AS id_bill_tmp FROM bills selects the most recent date + sequence number from id_bill (as described above) and then extracts its sequence number, which is always from character position 9 onwards. SELECT SUBSTR(MAX(...), 9) AS id_bill_tmp FROM billsid_bill选择最近的日期+序列号(如上所述),然后提取其序列号,该序列号始终从字符位置9开始。 Again, this subquery needs to be aliased ( b2 ) and wrapped in another SELECT . 同样,这个子查询需要别名( b2 )并包装在另一个SELECT
  11. + 1 increments the sequence number. + 1递增序列号。 (Note that this is always done since 0 is used in the cases described above where the sequence number should be set to 1). (注意,总是这样做,因为在上述情况下使用0,其中序列号应设置为1)。

A similar solution is shown here: https://www.percona.com/blog/2008/04/02/stored-function-to-generate-sequences/ 此处显示了类似的解决方案: https//www.percona.com/blog/2008/04/02/stored-function-to-generate-sequences/

What you could do is to create a sequence with table, as shown there: 你可以做的是用表创建一个序列,如下所示:

delimiter // create function seq(seq_name char (20)) returns int begin update seq set val=last_insert_id(val+1) where name=seq_name; return last_insert_id(); end // delimiter ; CREATE TABLE `seq` ( `name` varchar(20) NOT NULL, `val` int(10) unsigned NOT NULL, PRIMARY KEY (`name`) )

Then you need to populate the sequence values for each year, like so: insert into seq values('2017',1); insert into seq values('2018',1); insert into seq values('2019',1); ... 然后你需要填充每年的序列值,如下所示: insert into seq values('2017',1); insert into seq values('2018',1); insert into seq values('2019',1); ... insert into seq values('2017',1); insert into seq values('2018',1); insert into seq values('2019',1); ... insert into seq values('2017',1); insert into seq values('2018',1); insert into seq values('2019',1); ... (only need to do this once) insert into seq values('2017',1); insert into seq values('2018',1); insert into seq values('2019',1); ... (只需要这样做一次)

Finally, this should work: insert into bills (id_interess, id_bill) select 123, concat(date_format(now(), '%Y%m%d-'), seq(date_format(now(), '%Y'))); 最后,这应该工作: insert into bills (id_interess, id_bill) select 123, concat(date_format(now(), '%Y%m%d-'), seq(date_format(now(), '%Y')));

Just replace 123 with some real/unique/dynamic id and you should be good to go. 只需用一些真实/唯一/动态ID替换123 ,你就应该好好去。

I think you should redesign your approach to make life easier. 我认为你应该重新设计你的方法,让生活更轻松。 I would design your table as follows: 我会按如下方式设计你的表格:

id_interess |id_counter    |id_bill    |  
------------+--------------+-----------+  
1           |1             |20170912   |
2           |2             |20171231   |
3           |1             |20180101   |

Your desired output for the first row would be "20170912-1", but you would merge id_counter and id_bill in your SQL-Query or in your application logic, not directly in a table ( here is why). 您对第一行的所需输出将是“20170912-1”,但您可以在SQL-Query或应用程序逻辑中合并id_counter和id_bill,而不是直接在表中合并( 就是原因)。

Now you can write your SQL-Statements for that table. 现在,您可以为该表编写SQL语句。

Furthermore, I would advise not to store the counter in the table. 此外,我建议不要将计数器存放在表格中。 You should only read the records' id and date from your database and calculate the id_counter in your application (or even in your SQL-Query). 您应该只从数据库中读取记录的id和日期,并计算应用程序中的id_counter(甚至在SQL-Query中)。

You could also declare your column id_counter as auto_increment and reset it each time, see here . 您也可以将列id_counter声明为auto_increment并每次重置它,请参见此处

One approach to do in single query would be just save the date in your table when ever you update any record. 在单个查询中执行的一种方法是在更新任何记录时保存表中的日期。 For id_bill no., generate a sequence when you want to display the records. 对于id_bill no。,当您想要显示记录时生成序列。

Schema 架构

CREATE TABLE IF NOT EXISTS `bill` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `bill_date` date NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Query 询问

select a.id,concat(DATE_FORMAT(a.bill_date,"%Y%m%d"),'-',a.no) id_bill
from(    
    select b.*,count(b2.bill_date) no
    from bill b
    join bill b2 ON (EXTRACT(YEAR FROM b.bill_date) = EXTRACT(YEAR FROM b2.bill_date) 
      and b.bill_date >= b2.bill_date)
    group by b.id
    order by b.bill_date,no    
) a

Inner query will return you the rank of each record per year by joining the same table outer query just format the data as per your desired view 内部查询将通过加入相同的表外部查询返回每年每条记录的等级,只需根据您所需的视图格式化数据

DEMO DEMO

If for same date there can be more than 1 entries then in inner query the id column which is set to auto_increment can be used to handle this case 如果同一日期可以有多个条目,那么在内部查询中,设置为auto_increment的id列可用于处理此案例

Updated Query 更新的查询

select a.id,concat(DATE_FORMAT(a.bill_date,"%Y%m%d"),'-',a.no) id_bill
from(
    select b.*,count(b2.bill_date) no
    from bill b
    join bill b2 ON (EXTRACT(YEAR FROM b.bill_date) = EXTRACT(YEAR FROM b2.bill_date) 
      and b.id >= b2.id)
    group by b.id
    order by b.bill_date,no
) a

Updated Demo 更新了演示

The following solution requires generated (virtual) columns (available in MySQL 5.7 and MariaDB). 以下解决方案需要生成(虚拟)列(在MySQL 5.7和MariaDB中可用)。

CREATE TABLE bills (
    id_interess   INT UNSIGNED    NOT NULL,
    bill_dt       DATETIME DEFAULT CURRENT_TIMESTAMP,
    bill_year     YEAR AS (year(bill_dt)),
    year_position INT UNSIGNED    NULL,
    id_bill       VARCHAR(30) AS (concat(date_format(bill_dt, '%Y%m%d-'), year_position)),
    PRIMARY KEY (id_interess),
    INDEX (bill_year, year_position)
) ENGINE=InnoDB;

bill_year and id_bill are not stored in the table. bill_yearid_bill不存储在表中。 They are derived from other columns. 它们来自其他列。 However - bill_year is stored in the index, which we need to get the last position for a specific year efficiently (it would also work without the index). 但是 - bill_year存储在索引中,我们需要有效地获取特定年份的最后位置(它也可以在没有索引的情况下工作)。

To insert a new row with the current timestamp: 要插入包含当前时间戳的新行:

insert into bills(id_interess, year_position) 
    select 1, coalesce(max(year_position), 0) + 1
    from bills
    where bill_year = year(now());

You can also use a custom timestamp or date: 您还可以使用自定义时间戳或日期:

insert into bills(id_interess, bill_dt, year_position) 
    select 10, '2016-01-01', coalesce(max(year_position), 0) + 1
    from bills
    where bill_year = year('2016-01-01')

Demo: https://www.db-fiddle.com/f/8pFKQb93LqNPNaD5UhzVwu/0 演示: https//www.db-fiddle.com/f/8pFKQb93LqNPNaD5UhzVwu/0

To get even simpler inserts, you can create a trigger which will calculate year_postion : 为了获得更简单的插入,您可以创建一个计算year_postion的触发器:

CREATE TRIGGER bills_after_insert BEFORE INSERT ON bills FOR EACH ROW
    SET new.year_position = (
        SELECT coalesce(max(year_position), 0) + 1
        FROM bills
        WHERE bill_year = year(coalesce(new.bill_dt, now()))
    );

Now your insert statement would look like: 现在你的insert语句看起来像:

insert into bills(id_interess) values (1);

or 要么

insert into bills(id_interess, bill_dt) values (11, '2016-02-02');

And the select statements: 和选择陈述:

select id_interess, id_bill
from bills
order by id_bill;

Demo: https://www.db-fiddle.com/f/55yqMh4E1tVxbpt9HXnBaS/0 演示: https//www.db-fiddle.com/f/55yqMh4E1tVxbpt9HXnBaS/0

Update 更新

If you really, really need to keep your schema, you can try the following insert statement: 如果您确实需要保留模式,可以尝试以下insert语句:

insert into bills(id_interess, id_bill)
    select
        @id_interess, 
        concat(
            date_format(@date, '%Y%m%d-'),
            coalesce(max(substr(id_bill, 10) + 1), 1)
        )
    from bills 
    where id_bill like concat(year(@date), '%');

Replace @id_interess and @date accordingly. 相应地替换@id_interess@date For @date you can use CURDATE() but also any other date you want. 对于@date您可以使用CURDATE()以及您想要的任何其他日期。 There is no issue inserting dates out of order. 无序插入日期没有问题。 You can even insert dates from 2016 when entries for 2017 already exist. 您可以在2017年的条目已经存在时插入2016年的日期。

Demo: http://rextester.com/BXK47791 演示: http//rextester.com/BXK47791

The LIKE condition in the WHERE clause can use an index on id_bill (if you define it), so the query only need to read the entries from the same year. WHERE子句中的LIKE条件可以使用id_bill上的索引(如果您定义它)​​,因此查询只需要读取同一年的条目。 But there is no way to determine the last counter value efficiently with this schema. 但是没有办法用这个模式有效地确定最后一个计数器值。 The engine will need to read all rows for the cpecified year, extract the counter and search for the MAX value. 引擎需要读取指定年份的所有行,提取计数器并搜索MAX值。 Beside the complexity of the insert statement, this is one more reason to change the schema. 除了insert语句的复杂性之外,这是更改模式的另一个原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM