I have a pre-populated database with two columns. Column 'A' is a unique id which is just an integer, and column 'B' is just some string.
I want to create a new column that is simply the string concatenation of the two columns.
How would I do this using server_default?
Thanks!
You could use a Computed column to hold the value:
import sqlalchmy as sa
tbl = sa.Table(
't73280376',
sa.MetaData(),
sa.Column('a', sa.Integer, primary_key=True),
sa.Column('b', sa.String),
sa.Column('c', sa.String, sa.Computed('a::text || b')),
)
Note that the column cannot be updated independently of a and b.
Passing the same expression to server_default
sa.Column('c', sa.String, server_default=sa.text('a::text || b'))
does not generate valid DDL:
test# CREATE TABLE t73280376 (
test(# a SERIAL NOT NULL,
test(# b VARCHAR,
test(# c VARCHAR DEFAULT a::text || b,
test(# PRIMARY KEY (a)
test(# )
test-# ;
ERROR: cannot use column reference in DEFAULT expression
LINE 4: c VARCHAR DEFAULT a::text || b,
Related
I created a table with the following types:
CREATE OR REPLACE TABLE original_table (
id INT NOT NULL PRIMARY KEY,
f FLOAT,
d DOUBLE
);
And I inserted:
INSERT INTO original_table VALUES(1, 2.2, 2.2);
If I want to make a simple copy of this table, I can use the CREATE TABLE ... SELECT statement, but in addition, I would like to add each field at the time of making the "copy", that is:
CREATE TABLE backup_table SELECT f + 1 as "f", d + 1 as "d" FROM original_table;
Both fields are assigned as DOUBLE:
SELECT COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = 'test_db' AND
TABLE_NAME = 'backup_table' AND
(COLUMN_NAME = 'd' or COLUMN_NAME = 'f');
-> +-------------+-----------+
| COLUMN_NAME | DATA_TYPE |
+-------------+-----------+
| f | double |
+-------------+-----------+
| d | double |
+-------------+-----------+
But the field "f" was calculated with a single precision (FLOAT), while the field "d" with a double precision (DOUBLE), but both were saved as DOUBLE, is there any rule that occurs here , Should field "f" have been assigned as FLOAT, not as a DOUBLE data type?
The only thing I could find in the documentation was:
In this case, the table in the column resulting from the expression
has type INT or BIGINT depending on the length of the integer
expression. If the maximum length of the expression does not fit in an
INT, BIGINT is used instead. The length is taken from the max_length
value of the SELECT result set metadata (see C API Basic Data
Structures)...
this too:
Some conversion of data types might occur. For example, the
AUTO_INCREMENT attribute is not preserved, and VARCHAR columns can
become CHAR columns. Retrained attributes are NULL (or NOT NULL) and,
for those columns that have them, CHARACTER SET, COLLATION, COMMENT,
and the DEFAULT clause.
But I didn't find anything related to floating point expressions.
Note: I know that I can specify the data type explicitly as the documentation says, but what I mainly want to know is what the default behavior is, as it happens with expressions that have integer types.
Thanks.
I have the following table and I need to create new column "index tot new" from other available columns in order to have only unique values:
The way I understand your question is that you want to add a generated ID column to an arbitrary table.
Let's as an example take your data structure:
CREATE TABLE tab
(
date varchar(6),
contract varchar(5),
nation varchar(2),
supplier varchar(20),
type1 varchar(4),
type2 varchar(1),
category varchar(10),
cat_weight integer,
index_tot integer
);
INSERT INTO tab VALUES ('202101', '8400A', 'IT', 'john', 'idro', 'B', 'saf', 1, 23);
Now you add a new column for the self-generated key:
ALTER TABLE tab ADD (index_tot_new varchar(255));
One way to generate an artificial key, that I think you've kind of proposed as part of your question, is to use the existing column values and concatenate them:
UPDATE tab
SET index_tot_new = date || ';' || contract || ';' || nation || ';' || supplier ||';' || type1 ||';' || type2 ||';' || category || ';' || cat_weight|| ';' || index_tot
Thus, your new unique identifier (index_tot_new) for the first record will be 202101;8400A;IT;john;idro;B;saf;1;23.
However, this would not be my preferred option of creating a unique identifier. Imagine, you change the table structure and add another field. This would essentially corrupt you whole index structure.
To me it feels like the easiest and least error-prone approach is to generate an UID for each record by using HANA function NEWUID:
UPDATE tab SET index_tot_new = NEWUID();
You could also think of using a series to generate a numeric ID, but it probably wouldn't add much benefit.
Consider the following schema:
create schema testSchema;
use testSchema;
create table A (
id int not null auto_increment,
name varchar (50),
primary key(id));
create table B (
id int not null auto_increment,
name varchar (50),
primary key(id));
create table C (
id int not null auto_increment,
name varchar (50),
primary key(id));
create table main (
id int not null auto_increment,
typeId int,
type varchar(50),
tableMappingsId int,
primary key (id)
);
create table tableMappings (
id int not null auto_increment,
tableName varchar(50),
primary key (id)
);
insert into A (name) values
('usa'),
('uk'),
('uno');
insert into B (name) values
('earth'),
('mars'),
('jupiter');
insert into C (name) values
('1211'),
('12543'),
('345');
insert into main (typeId, type, tableMappingsId) values
(1,'tableA',1),
(2,'tableB',2),
(3,'tableC',3);
insert into tableMappings (tableName) values ('A'),('B'),('C');
Description:-
I have three tables A, B and C which have id and name.
In main table, type tells from which table (A or B or C) we have to read the name property. typeId tells the id within that table(A or B or C). To do this I have created a tableMappings table which has tableNames. In main table I have created a column tableMappingsId which points to tableMappings.
Is this correct approach? and how can I write a query like following in MySQL:-
select (name property) from the table which is pointed to by this row and mapped by tableMappings?
About your design
If we think in an object-oriented manner, your have a base class constituted of attributes recorded in main table, and derivated by A, B and C with additionnal attributes.
You want to avoid having many attributes in a single table with NULLs depending on records types. This is a good approach. But your method to implement this can be improved.
Answer to your question
You want to select from a table (A, B or C) depending on the value of a field. As far as I know this cant be done without "preparing" the query.
"preparing" the query can be done in multiple manners :
using prepared statements ("pure-SQL" method) : https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html
in a stored procedure or function for example : selecting the type, then testing the type and selecting from the right table
or constituting the query in two times via a script language
Example with prepared statement :
SET #idToSelect = 2;
SELECT
CONCAT('SELECT name FROM ', tableMappings.tableName, ' WHERE Id = ', main.tableMappingsId)
INTO #statement
FROM main
INNER JOIN tableMappings ON tableMappings.tableName = REPLACE(main.type, 'table', '')
WHERE main.id = #idToSelect;
PREPARE stmt FROM #statement;
EXECUTE stmt;
Note : we have to translate 'tableA', 'tableB'... in main.type to match to 'A', 'B'... in tableMappings.tableName, which is not ideal.
But this is not very convenient and efficient.
Other approaches and comments
Selecting from multiple tables : not necessarily a big deal
Basically, you want to avoid SELECT'ing from tables you dont need to read from. But keep in mind that if your schema is correctly indexed, this is not necessarily a big deal. MySQL runs a query optimizer. You could simply LEFT JOIN all of the tables and select from the right table depending on 'type' value :
SET #idToSelect = 2;
SELECT
IFNULL(A.name, IFNULL(B.name, C.name)) AS name
FROM main
LEFT JOIN A ON main.type = 'tableA' AND A.id = main.tableMappingsId
LEFT JOIN B ON main.type = 'tableB' AND B.id = main.tableMappingsId
LEFT JOIN C ON main.type = 'tableC' AND C.id = main.tableMappingsId
WHERE main.id = #idToSelect;
Note that I didn't use tableMappings table
Useless tableMappings trick
You can avoid using this kind of mapping by using the same id in the "children" table as in the main table. This is how many ORM's implement inheritance. I will give an example later in my answer.
A bit irrelevant example
In your question, you want to select the "name" property regardless of the type of the record. But I bet if you have really different types of records, each type holds a different set of properties. If the "name" is a common property between all the types, it should be in the main table. I assume you provided "name" as a simplified example.
But I think in a real case, you'll rarely have to select a field regardless of the type of the object.
Other thing : In data example, you provide records for A, B and C tables which does not match to main records
Final proposition
drop schema testSchema;
create schema testSchema;
use testSchema;
create table main (
id int not null auto_increment,
typeId int,
common_data VARCHAR(50),
primary key (id)
);
create table A (
id int not null,
specific_dataA varchar (50),
primary key(id),
foreign key FK_A (id) references main (id)
);
create table B (
id int not null,
specific_dataB varchar (50),
primary key(id),
foreign key FK_B (id) references main (id)
);
create table C (
id int not null,
specific_dataC varchar (50),
primary key(id),
foreign key FK_C (id) references main (id)
);
insert into main (typeId, common_data) values
(1, 'ABC'),
(2, 'DEF'),
(3, 'GHI');
insert into A (id, specific_dataA) values
(1, 'usa');
insert into B (id, specific_dataB) values
(2, 'mars');
insert into C (id, specific_dataC) values
(3, '345');
Some comments :
typeId in main table is optionnal, but depending on queries you have to do it could be useful to retrieve the type of an object. One field is enough, dont need typeId integer and type varchar.
id's in A, B and C tables are not auto_increment because they have to match to main id's
this design is irrelevant if there is no common attributes, so I put a common data field in main table
I materialized relations by defining foreign keys
Queries examples :
I want the common data field for id 1 :
SELECT common_data FROM main WHERE id = 1;
I know that id 2 is from type B and I want the specific data B :
SELECT specific_dataB FROM B WHERE id = 2;
I know that id 3 is from type C and I want the common data and the specific data C :
SELECT common_data, specific_dataB FROM main INNER JOIN B ON B.id = main.id WHERE main.id = 2;
(best match to your case) I dont know the type of object 3 but I want a specific data depending on its type :
SELECT IFNULL(
A.specific_dataA,
IFNULL(
B.specific_dataB,
C.specific_dataC
)
)
FROM main
LEFT JOIN A on A.id = main.id
LEFT JOIN B on B.id = main.id
LEFT JOIN C on C.id = main.id
WHERE main.id = 3
When working with JSON datatype, is there a way to ensure the input JSON must have elements. I don't mean primary, I want the JSON that gets inserted to at least have the id and name element, it can have more but at the minimum the id and name must be there.
thanks
The function checks what you want:
create or replace function json_has_id_and_name(val json)
returns boolean language sql as $$
select coalesce(
(
select array['id', 'name'] <# array_agg(key)
from json_object_keys(val) key
),
false)
$$;
select json_has_id_and_name('{"id":1, "name":"abc"}'), json_has_id_and_name('{"id":1}');
json_has_id_and_name | json_has_id_and_name
----------------------+----------------------
t | f
(1 row)
You can use it in a check constraint, e.g.:
create table my_table (
id int primary key,
jdata json check (json_has_id_and_name(jdata))
);
insert into my_table values (1, '{"id":1}');
ERROR: new row for relation "my_table" violates check constraint "my_table_jdata_check"
DETAIL: Failing row contains (1, {"id":1}).
Say I have a table with three columns primaryNum, secondaryNum, chosenNum. primarynum and secondaryNum are both number values but chosenNum's value can either be "primaryNum", "secondaryNum", or "both".
The chosenNum field is a column that gives me the option to search for a number in a particular field.
For example: I might want to try to find all rows with the value 10 in the column that is stored in chosenNum. If the value of chosenNum is "both" then the row would be returned if either column (primaryNum, secondaryNum) had a value of 10.
What might my select statement look like?
It might be a better scenario if I say I would like to do a select statement like:
SELECT * FROM aTable WHERE (SELECT bVal FROM bTable WHERE aVal = #varField ) = 0;
Where #varField is the value of the value in the field with the label stored in chosenNum or either field if chosenNum = "both"
This would result in me getting back rows with id 1,2,3,4,6,7,14,15,16,19,20,21,23,24,27
Table A: Create
CREATE TABLE `test`.`aTable` (
`id` INT NOT NULL AUTO_INCREMENT ,
`primaryNum` INT NULL ,
`secondaryNum` INT NULL ,
`chosenNum` CHAR(12) NULL ,
PRIMARY KEY (`id`) );
Table B: Create
CREATE TABLE `test`.`bTable` (
`aVal` INT NULL ,
`bVal` INT NULL );
Table A: Data
INSERT INTO test.aTable VALUES (1,8,7,'secondaryNum'),(2,2,9,'secondaryNum'),(3,7,9,'both'),(4,5,1,'both'),(5,10,3,'secondaryNum'),(6,10,6,'both'),(7,7,8,'both'),(8,10,2,'primaryNum'),(9,2,1,'secondaryNum'),(10,7,2,'secondaryNum'),(11,2,2,'secondaryNum'),(12,5,1,'secondaryNum'),(13,1,6,'primaryNum'),(14,6,6,'both'),(15,4,9,'both'),(16,9,7,'primaryNum'),(17,8,3,'secondaryNum'),(18,10,7,'primaryNum'),(19,8,5,'secondaryNum'),(20,1,7,'both'),(21,7,9,'both'),(22,8,3,'primaryNum'),(23,6,2,'primaryNum'),(24,5,7,'both'),(25,2,1,'both'),(26,5,2,'secondaryNum'),(27,7,8,'primaryNum');
Table B: Data
INSERT INTO test.bTable VALUES (1,1),(2,1),(3,1),(4,1),(5,0),(6,0),(7,0),(8,1),(9,0),(10,1);
You can do something like this:
select *
from MyTable
where (chosenNum in ('both', 'primaryNum') and primaryNum = 10)
or (chosenNum in ('both', 'secondaryNum') and secondaryNum = 10)