Does setting a virtual column as an index in MySQL improve query speed? - query-optimization

I am searching for a long time on net. But no use. Please help or try to give some ideas how to achieve this.
database version MySQL 5.7.37
server version windows server 2016 datacenter
I have two tables: rep_order, tb_user.
create table rep_order
(
id bigint auto_increment
primary key,
code varchar(32) null,
equ_id bigint(32) not null ,
mileage varchar(10) null,
order_type varchar(5) null ,
report_name varchar(10) null ,
report_phone varchar(15) null ,
contact_name varchar(10) null ,
contact_phone varchar(15) null ,
address varchar(100) null ,
lng varchar(20) null ,
lat varchar(20) null ,
order_level int(1) null ,
des varchar(50) null ,
user_ids json null ,
oper_date timestamp default CURRENT_TIMESTAMP not null,
v_user_ids varchar(500) as (json_unquote(json_extract(`user_ids`, '$[*]')))
);
create index rep_order_equ_id_index
on rep_order (equ_id);
create index v_user_ids
on rep_order (v_user_ids);
create table tb_user
(
user_id bigint not null
primary key,
openid varchar(50) null ,
name varchar(32) null ,
type varchar(10) null ,
mobile varchar(20) null ,
username varchar(50) null ,
wage_id bigint null ,
resign_date date null ,
entry_date date null ,
identity varchar(25) null ,
create_time datetime null ,
password varchar(64) null ,
dept_id bigint null ,
sex enum ('男', '女') null,
client_leader int(1) default 0 not null,
constraint tb_user_identity_uindex
unique (identity),
constraint tb_user_mobile_uindex
unique (mobile),
constraint tb_user_username_uindex
unique (username)
)
comment '用户' charset = utf8;
create index tb_user_name_mobile_index
on tb_user (name, mobile);
create index tb_user_rep_wage_config_id_fk
on tb_user (wage_id);
create index tb_user_sys_dept_dept_id_fk
on tb_user (dept_id);
create index tb_user_type_index
on tb_user (type);
A user_ids field is defined in rep_order, which is a json array type.
Then I created a virtual column v_user_ids for user_ids in rep_order, and set v_user_ids as an index column, the execution of the following statement is also very fast
select o.id, u.user_id
from rep_order o, tb_user u
where locate(concat(u.user_id), o.v_user_ids) > 0
Screenshot of explain execution result
But the execution of the following statement is very slow
select o.id, u.user_id
from rep_order o, tb_user u
where locate(concat(u.user_id), o.v_user_ids) > 0
group by o.id, u.user_id;
Screenshot of explain execution result
It seems that setting the virtual column as an index does not improve the execution speed of the query. Why is this? What can I do to improve the query speed?
Thanks in advance.

Both queries contain this
where locate(concat(u.user_id), o.v_user_ids) > 0
It isn't sargable. That means it can't use random access on the index you created on your virtual column. MySQL must scan the index: examine every entry in it to see if it contains the substring for the user_id you want.
RDBMS indexes work best when there's one value in each column. Your virtual column definition puts multiple values in that column.

Related

MS SQL Server : primary keys, auto increment, and insert into failing

I have this code I am converting from MySQL:
CREATE TABLE "odds_soccer"
(
"id" INT NOT NULL PRIMARY KEY ,
"checked" DATETIME NOT NULL ,
"bookmaker" VARCHAR(255) NOT NULL ,
"gametime" DATETIME NOT NULL ,
"team1" VARCHAR(255) NOT NULL ,
"team2" VARCHAR(255) NOT NULL ,
"odds_1" FLOAT NOT NULL ,
"odds_x" FLOAT NOT NULL ,
"odds_2" FLOAT NOT NULL
);
INSERT INTO "odds_soccer" ("id", "checked", "bookmaker", "gametime", "team1", "team2", "odds_1", "odds_x", "odds_2")
VALUES (null, '2017-03-02 16:00:00', 'example.com', '2017-03-02 04:30:00', 'MYTEAMA', 'MYTEAMB', '3.15', '3.15', '2.70');
Only problem is... It barfs that I can not use null for inserting which is fair enough...
But how do I insert a record where I simply want the ID to auto increment.
I have tried various variations - all failing - I must be missing something obvious...
The correct approach is the following:
CREATE TABLE "odds_soccer" (
"id" INT IDENTITY(1,1) PRIMARY KEY ,
"checked" DATETIME NOT NULL ,
"bookmaker" VARCHAR(255) NOT NULL ,
"gametime" DATETIME NOT NULL ,
"team1" VARCHAR(255) NOT NULL ,
"team2" VARCHAR(255) NOT NULL ,
"odds_1" FLOAT NOT NULL ,
"odds_x" FLOAT NOT NULL ,
"odds_2" FLOAT NOT NULL
);
And this way your id field will be auto incremented every time you insert a new record in the table.
Note that if you have id field auto incremented you don't have to specify it in your insert statement.
INSERT INTO "odds_soccer"
(
"checked",
"bookmaker",
"gametime",
"team1",
"team2",
"odds_1",
"odds_x",
"odds_2"
)
VALUES (
'2017-03-02 16:00:00',
'example.com',
'2017-03-02 04:30:00',
'MYTEAMA',
'MYTEAMB',
'3.15',
'3.15',
'2.70'
);
Link to documentation(IDENTITY)

Is there a way that I can identify rows in SQL Server where a column contains just one word?

I have this schema:
CREATE TABLE [dbo].[Phrase]
(
[PhraseId] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
[English] NVARCHAR(MAX) NOT NULL,
[FormId] INT NULL
PRIMARY KEY CLUSTERED ([PhraseId] ASC)
)
Is there a way I can change the [FormId] to be 1 if there is a single word in the [English] column and a 2 if not ?
The idea would be:
UPDATE Phrase
SET FormId = (CASE WHEN CHARINDEX(TRIM(English),' ',1) > 0 THEN 2
ELSE 1
END
) ;
Note: I didn't verify this so make sure you test it before running the actual update.

Using a table field as part of an identifier

I have coded the following table and I would like to use the the text that will be entered into the YearID to add to a prefixed 'YEAR' in the CAST().
For example if someone named the year 2 then the YearID field would be populated as 'YEAR2'. Currently it will just apply the next number in the sequence after YEAR e.g. YEAR55 etc.
CREATE TABLE Year(
GroupID INT IDENTITY (10000, 1) NOT NULL,
YearID VARCHAR (10) NOT NULL DEFAULT 'YEAR' +
CAST(NEXT VALUE FOR non_identity_incrementer AS VARCHAR(10)),
Year NVARCHAR (50) NOT NULL,
DateTimeModified DATETIME NOT NULL DEFAULT SYSDATETIME(),
Status NVARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED (GroupID)
);
A computed column might be the solution:
CREATE TABLE [Year] (
GroupID INT IDENTITY (10000, 1) NOT NULL,
YearID AS 'YEAR' + [Year],
[Year] NVARCHAR (50) NOT NULL,
DateTimeModified DATETIME NOT NULL DEFAULT SYSDATETIME(),
Status NVARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED (GroupID)
);
When you INSERT a row, the YearID would be equal to 'Year' + [Year]:
INSERT INTO [Year]([Year], Status) VALUES ('2', 'Complete')
SELECT * FROM [Year]
Result:
GroupID YearID Year DateTimeModified Status
----------- ---------- --------- ----------------------- ------------
10000 YEAR2 2 2015-08-17 07:02:49.837 Complete
Note that the computed column is recalculated every time it is used in a query, unless you make it PERSISTED. Making the computed column PERSISTED means that it is physically stored in the table. Read here for more information.
You can do this with a trigger, like this:
DELIMITER //
CREATE TRIGGER year_after_insert
AFTER INSERT
ON Year FOR EACH ROW
BEGIN
set NEW.YearID = concat('YEAR', NEW.Year);
END; //
DELIMITER ;
It will run after insertion on Year, setting YearID based on Year.

creating before INSERT TRIGGER comparing values between two tables

I need help creating a before insert trigger, as i am new to TSQL. below are the two tables.
SALARY table:
CREATE TABLE SALARY
(
StarName varchar(30) NOT NULL,
MovieTitle varchar(30)NOT NULL,
MovieYearMade numeric(4, 0) NOT NULL,
Amount numeric(8, 0) NULL,
PRIMARY KEY (MovieTitle,StarName,MovieYearMade),
)
MOVIESTAR table
CREATE TABLE MOVIESTAR
(
Name varchar(30) NOT NULL,
Address varchar(20),
City varchar(15) DEFAULT ('Palm Springs'),
Gender char(1) NULL CHECK (Gender ='M' OR GENDER ='F'),
BirthYear Numeric(4),
PRIMARY KEY CLUSTERED (Name)
)
I want to create a trigger so when a new movie is added. It prevents adding SALARY.Amount if SALARY.MovieYearMade is before MOVIESTAR.BirthYear.
I am confused as how to define trigger, when I am comparing values in two tables i.e. SALARY and MOVIESTAR.
thanks,
Are you looking for something like this?
CREATE TRIGGER tg_salary ON salary
INSTEAD OF INSERT AS
BEGIN
INSERT INTO salary (StarName, MovieTitle, MovieYearMade, Amount)
SELECT i.StarName, i.MovieTitle, i.MovieYearMade,
CASE WHEN i.MovieYearMade < s.BirthYear THEN NULL ELSE i.Amount END
FROM INSERTED i JOIN moviestar s
ON i.StarName = s.Name
END
Here is SQLFiddle demo

SQL Server 2008 Row Insert and Update timestamps

I need to add two columns to a database table in SQL Server 2008 R2:
createTS - date and time when row is inserted
updateTS - date and time when row is updated
I have a few questions:
What column data type should I employ for each of these?
createTS needs to be set only once, when the row is inserted. When I tried the datetime type for this column and added a Default Value or Binding of getdate(), the column value was appropriately set. Is this the best way to fulfill the purpose of this column? I considered timestamp data type, but that is, in my opinion, nearly a misnomer!
updateTS needs to be set to the date and time of the moment when the row is updated. In SQL Server, there is no ON UPDATE CURRENT_TIMESTAMP (as in MySQL), so it looks like I have to resort to using a trigger. Is this right and how would I go about doing that?
So there is a starting point for anyone who would like to answer this question, here is the create table script:
CREATE TABLE [dbo].[names]
(
[name] [nvarchar](64) NOT NULL,
[createTS] [datetime] NOT NULL CONSTRAINT [DF_names_createTS] DEFAULT (getdate()),
[updateTS] [datetime] NOT NULL,
CONSTRAINT [PK_names] PRIMARY KEY CLUSTERED
(
[name] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
try
CREATE TABLE [dbo].[Names]
(
[Name] [nvarchar](64) NOT NULL,
[CreateTS] [smalldatetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
[UpdateTS] [smalldatetime] NOT NULL
)
PS
I think a smalldatetime is good enough. You may decide differently.
Can you not do this at the "moment of impact" ?
In Sql Server, this is common:
Update dbo.MyTable
Set
ColA = #SomeValue ,
UpdateDS = CURRENT_TIMESTAMP
Where...........
Sql Server has a "timestamp" datatype.
But it may not be what you think.
Here is a reference:
http://msdn.microsoft.com/en-us/library/ms182776(v=sql.90).aspx
Here is a little RowVersion (synonym for timestamp) example:
CREATE TABLE [dbo].[Names]
(
[Name] [nvarchar](64) NOT NULL,
RowVers rowversion ,
[CreateTS] [datetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
[UpdateTS] [datetime] NOT NULL
)
INSERT INTO dbo.Names (Name,UpdateTS)
select 'John' , CURRENT_TIMESTAMP
UNION ALL select 'Mary' , CURRENT_TIMESTAMP
UNION ALL select 'Paul' , CURRENT_TIMESTAMP
select * , ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]
Update dbo.Names Set Name = Name
select * , ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]
Maybe a complete working example:
DROP TABLE [dbo].[Names]
GO
CREATE TABLE [dbo].[Names]
(
[Name] [nvarchar](64) NOT NULL,
RowVers rowversion ,
[CreateTS] [datetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
[UpdateTS] [datetime] NOT NULL
)
GO
CREATE TRIGGER dbo.trgKeepUpdateDateInSync_ByeByeBye ON dbo.Names
AFTER INSERT, UPDATE
AS
BEGIN
Update dbo.Names Set UpdateTS = CURRENT_TIMESTAMP from dbo.Names myAlias , inserted triggerInsertedTable where
triggerInsertedTable.Name = myAlias.Name
END
GO
INSERT INTO dbo.Names (Name,UpdateTS)
select 'John' , CURRENT_TIMESTAMP
UNION ALL select 'Mary' , CURRENT_TIMESTAMP
UNION ALL select 'Paul' , CURRENT_TIMESTAMP
select * , ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]
Update dbo.Names Set Name = Name , UpdateTS = '03/03/2003' /* notice that even though I set it to 2003, the trigger takes over */
select * , ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]
Matching on the "Name" value is probably not wise.
Try this more mainstream example with a SurrogateKey
DROP TABLE [dbo].[Names]
GO
CREATE TABLE [dbo].[Names]
(
SurrogateKey int not null Primary Key Identity (1001,1),
[Name] [nvarchar](64) NOT NULL,
RowVers rowversion ,
[CreateTS] [datetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
[UpdateTS] [datetime] NOT NULL
)
GO
CREATE TRIGGER dbo.trgKeepUpdateDateInSync_ByeByeBye ON dbo.Names
AFTER UPDATE
AS
BEGIN
UPDATE dbo.Names
SET UpdateTS = CURRENT_TIMESTAMP
From dbo.Names myAlias
WHERE exists ( select null from inserted triggerInsertedTable where myAlias.SurrogateKey = triggerInsertedTable.SurrogateKey)
END
GO
INSERT INTO dbo.Names (Name,UpdateTS)
select 'John' , CURRENT_TIMESTAMP
UNION ALL select 'Mary' , CURRENT_TIMESTAMP
UNION ALL select 'Paul' , CURRENT_TIMESTAMP
select * , ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]
Update dbo.Names Set Name = Name , UpdateTS = '03/03/2003' /* notice that even though I set it to 2003, the trigger takes over */
select * , ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]
As an alternative to using a trigger, you might like to consider creating a stored procedure to handle the INSERTs that takes most of the columns as arguments and gets the CURRENT_TIMESTAMP which it includes in the final INSERT to the database. You could do the same for the CREATE. You may also be able to set things up so that users cannot execute INSERT and CREATE statements other than via the stored procedures.
I have to admit that I haven't actually done this myself so I'm not at all sure of the details.

Resources