Overwrite default values from source table using union - mysql

In my table, I have few specific keys and few generic keys. Specific data can be got by combining specific key column values on top of generic key column values.
Specific key | Generic key | Col1 | Col2 |
Null | generic key 1 | defaultVal1 | default Val2 |
Specific key1 | generic key 1 | Null | Specific val2 |
In this case my specific record should need to look like,
Specific key1 | generic key 1 | defaultVal1 | Specific val2|
I am trying to achieve this using union statement, but, it is overwriting the Null value for Specific key1 on top of generic defaultVal1.
Hence I would like to get columns overwritten on top of generic records when it is not null. If the columns of specific record is null, then I want to retain the default value.
EDIT:
I tried to provide info as simple as possible, looks like it attracts more downvotes. Here I am trying to explain my schema bit further:
I have 2 tables namely ids_link, core_params
ids_link table contents:
Unique key : specificid
| globalid | specificid | type |
| gid1 | sid1 | type1 |
| gid1 | sid2 | type2 |
| gid2 | sid3 | type1 |
| gid2 | sid4 | type2 |
| gid3 | sid5 | type1 |
core_params table :
Uniquekey : id
| id | coreparam1 | coreparam2 |
| gid1 | defaultVal1 | defaultVal2 |
| sid1 | NULL | sid1Val2 |
| sid2 | sid2val1 | NULL |
| sid3 | sid3val1 | NULL |
In short, more than one specific id share a global id. The global ID will have default values for its corresponding specific ids. The specific id will have specific content that needs to be overwritten on top of its corresponding global id values and returned.
For example)
If I want to return all the specific records for type 1, then my output will be,
| specificid | globalid | type | coreparam1 | coreparam2 |
| sid1 | gid1 | type1 | defaultVal1 | sid1val2 |
| sid3 | gid2 | type1 | sid3val1 | NULL |
If you would have noticed here, for sid1, in core_params table, there is no value for coreparam1. But it is backfilled using the default value from the gid1 record.
For sid3, there is no default record to backfill for coreparam2. Hence it coreparams2 field stays null. I am trying to write a sql query to achieve this. The query I tried :
select specificid,globalid,type,coreparam1,coreparam2 from ids_link left join core_params on ids_link.globalid=coreparams.id where type='type1' union all select specificid,globalid,type,coreparam1,coreparam2 from ids_link left join core_params on ids_link.specificid=coreparams.id where type='type1' LIMIT 10;
But in this, if there are NULL values for records in core_params table for specifickeys like for sid1, coreparam1 column is null, I want that to be backfilled with that of its global value (defaultVal1). Kindly let me know if you need more info.

Maybe this will help to re-create your scenario.
-- Create test table
CREATE TABLE [dbo].[Test](
[Col1] [nvarchar](40) NULL,
[Col2] [nvarchar](40) NULL,
[Col3] [nvarchar](40) NULL,
[Col4] [nvarchar](40) NULL,
)
--Insert test rows
insert into Test Values (Null, 'Generic Key 1', 'dfaultVal1', 'DefaultVal2')
insert into Test Values ('Specific Key 1', 'Generic Key 1', Null, 'Specific Val 2')
You said that u use Specific Key in combination with Generic Key, if this is true you will not reach defaultval1 on Col1, so maybe you need to elaborate better. You could try to work with min/max or criteria like Like and keep selecting subsets till you filter the way you wanted.
select t.col1, t.col2, t.col3, t.col4
from test t,
(select max(col1) good1, max(col2) good2 from test) goodkey
where t.col1 = goodkey.good1 and t.col2 = goodkey.good2
Hope this helps.

Related

MySQL relations with restrictions

I have this table structure and simple relationships:
and sample data in the table:
Company
Company names are unique and should not be repeated:
+------------+---------------+
| Company_ID | Company_name |
+------------+---------------+
| 1 | Company_name1 |
+------------+---------------+
| 2 | Company_name2 |
+------------+---------------+
Location
(Locations should be assigned to a specific company):
+-------------+------------+-------------------------+
| Location_ID | Company_ID | Location_name |
+-------------+------------+-------------------------+
| 1 | 1 | Company1_Location_name1 |
+-------------+------------+-------------------------+
| 2 | 1 | Company1_Location_name2 |
+-------------+------------+-------------------------+
| 3 | 2 | Company2_Location_name1 |
+-------------+------------+-------------------------+
| 4 | 2 | Company2_Location_name2 |
+-------------+------------+-------------------------+
Data
The data in the table should depend on the selected company, and the locations should be only those that occur in the company:
+---------+-------------+------------+------------+------+
| Data_ID | Location_ID | Company_ID | data_value | date |
+---------+-------------+------------+------------+------+
| 1 | 1 | 1 | 5 | date |
+---------+-------------+------------+------------+------+
| 2 | 2 | 1 | 2 | date |
+---------+-------------+------------+------------+------+
| 3 | 3 | 2 | 3 | date |
+---------+-------------+------------+------------+------+
| 4 | 2 | 1 | 1 | date |
+---------+-------------+------------+------------+------+
| 5 | 4 | 2 | 6 | date |
+---------+-------------+------------+------------+------+
| 6 | 4 | 2 | 7 | date |
+---------+-------------+------------+------------+------+
The main dependencies that should be met:
Company names should be unique and attempts to add the same company should be blocked
Location names should be assigned to a specific company, but they may repeat and a location may appear in several companies but have a different Location_ID
Adding values to the date table should depend on:
company (we choose a specific company for which we add values)
locations (locations must depend on company)
For example:
When adding values for a company with Company_ID = 1, I should only be able to add Location_ID that occur under that company.
If I want to add a value in the data table for Company_name1 then the only available values for the Location_ID column in the data table, should be: Company1_Location_name1 and Company1_Location_name2 and I can't have values there from another company (i.e. Company2_Location_name1 and Company2_Location_name2)
At the moment it works badly:
when adding values to the data table I can select a company, but then I have locations available and I can add values that do not make sense - for Company_name1 I can add a location from Company_name2 but it should be blocked.
How can I solve such a problem? Add some additional table which will be responsible for particular pairing?
Depends what database you use.
A simpler way would be to just create a unique constraint on the table field, this will also enforce it for updates too and remove the need for a trigger. Just do:
Example for MSSQL:
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [Company_name] UNIQUE NONCLUSTERED
(
[CompanyID], [Company_name]
)
and then you'll be in business. You will be not able to add 2 company with the same name.
You can find another examples here : Trigger to prevent Insertion for duplicate data of two columns
This is exacly what you are looking for :)
#EDIT 1
OK so if you want example for MARIADB here we go :
Create unique Contraint - Using a CREATE TABLE statement
The syntax for creating a unique constraint using a CREATE TABLE statement in MariaDB is:
CREATE TABLE table_name
(
column1 datatype [ NULL | NOT NULL ],
column2 datatype [ NULL | NOT NULL ],
...
CONSTRAINT constraint_name UNIQUE (uc_col1, uc_col2, ... uc_col_n)
);
table_name
The name of the table that you wish to create.
column1, column2
The columns that you wish to create in the table.
constraint_name
The name of the unique constraint.
uc_col1, uc_col2, ... uc_col_n
The columns that make up the unique constraint.
In your example :
CREATE TABLE Company
( Company_ID INT(11) PRIMARY KEY AUTO_INCREMENT,
Company_name VARCHAR(250) NOT NULL,
CONSTRAINT company_name_unique UNIQUE (Company_name)
);
In this example, we've created a unique constraint on the Company table called company_name_unique. It consists of only one field - the Company_name field.

Multiple row retrieval with their latest inserted value using MySQL

I have two tables, first one is 'file_details':
+---------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| file_name | varchar(40) | YES | | NULL | |
| creation_date | date | YES | | NULL | |
+---------------+-------------+------+-----+---------+-------+
and second one is 'logs':
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| sl_no | varchar(20) | YES | | NULL | |
| file_name | varchar(40) | YES | | NULL | |
| status | varchar(100) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
values in the tables are:
file_details:
+-----------+---------------+
| file_name | creation_date |
+-----------+---------------+
| a1 | 2020-01-09 |
| a2 | 2020-01-08 |
+-----------+---------------+
logs:
+-------+-----------+---------+
| sl_no | file_name | status |
+-------+-----------+---------+
| 1 | a1 | created |
| 2 | a1 | step1 |
| 1 | a2 | created |
| 2 | a2 | step1 |
| 3 | a2 | step2 |
+-------+-----------+---------+
now I want to retrieve the following data:
+-----------+---------------+--------+
| file_name | creation_date | status |
+-----------+---------------+--------+
| a1 | 2020-01-09 | step1 |
| a2 | 2020-01-08 | step2 |
+-----------+---------------+--------+
using the below query:
select f.file_name,f.creation_date,
l.status
from file_details f
inner join logs l on f.file_name=l.file_name
and l.status=(select status
from logs
where sl_no=(
select max(convert(sl_no,unsigned))
from logs));
But the above query gives me the below output:
+-----------+---------------+--------+
| file_name | creation_date | status |
+-----------+---------------+--------+
| a2 | 2020-01-08 | step2 |
+-----------+---------------+--------+
which is not a required solution. So, please help me out.
So there is a couple things to discuss here, you mentioned in the comments that you are new to SQL, so I will provide some links to look at, first off being normalization, this is used to reduce data redundancy (which you have with your status descriptions).
Also what you are trying to do is essentially make the engine "guess" what status is the most up to date one, using the MAX like you have will only deal with alphabetical orders and as such is not scale-able for if you say want to add a status such as "completed", so what you would have to do is hard code the order in something like a case statement which gets really messy with multiple conditions.
And lastly here is a tutorial site on SELECT query basics with links to other data manipulation commands.
So the answer I came up with, I made a status table to store the description and then in the log table I store the status_id, doing this addresses the normalization issue I mentioned earlier. Creating this table also allows me to assign the statuses a rank to order with, which is another issue I discussed earlier.
SELECT t.file_name,
t.creation_date,
s.description
FROM status_details s
JOIN (SELECT f.file_name,
f.creation_date,
MAX(s2.rank_no) rank_no
FROM file_details f
JOIN logs l
ON l.file_name = f.file_name
JOIN status_details s2
ON s2.status_id = l.status_id
GROUP BY f.file_name,
f.creation_date) t
ON t.rank_no = s.rank_no
Now I don't want you so blindly copy this query without understanding what it is doing, so the general gist is that the inner select gets the file names and creation dates with the rank number of the status, note this only gets the status with the highest rank number, then the outer select takes the data already retrieved and joins back onto the status table to grab the status description from the rank number. Giving the output
file_name creation_date description
a1 2020-01-09 step1
a2 2020-01-08 step2
If you would like to see the query working I have created a fiddle for you to try.
These are the data scripts I used to create the environment:
create table file_details( file_name varchar(40), creation_date date)
create table logs (sl_no varchar(20), file_name varchar(40), status_id int)
create table status_details (status_id int, description varchar(100), rank_no int)
insert into file_details values ('a1', '2020-01-09')
insert into file_details values ('a2', '2020-01-08')
insert into status_details values (1, 'created', 1)
insert into status_details values (2, 'step1', 2)
insert into status_details values (3, 'step2', 3)
insert into logs values ('1', 'a1' , 1)
insert into logs values ('2', 'a1' , 2)
insert into logs values ('1', 'a2' , 1)
insert into logs values ('1', 'a2' , 2)
insert into logs values ('3', 'a2' , 3)
max(convert(sl_no,unsigned)) from logs) will return 3 in your example and therefore it only matches
+-------+-----------+---------+
| sl_no | file_name | status |
+-------+-----------+---------+
| 3 | a2 | step2 |
+-------+-----------+---------+

mysql copy many records with one change

Here is a table Evact:
+--------------+-----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------------+------+-----+---------+-------+
| EvActMas | char(10) | NO | PRI | | |
| EvActSub | char(10) | NO | PRI | | |
| EvActCode | char(10) | NO | PRI | | |
| EvActIncOutg | enum('I','O','B','N') | YES | | NULL | |
| EvActBudAct | enum('B','A','O') | YES | | NULL | |
...other columns ...
and here are some records:
EvActMas EvActSub EvActCode EvActIncOutg EvActBudAct ..other..
Bank-2017 Incoming mth01 I A
Bank-2017 Incoming mth02 I A
Bank-2017 Incoming mth03 I A
Bank-2017 Incoming mth04 I A
Bank-2017 Incoming mth05 I A
Bank-2017 Incoming mth06 I A
I want to add six new records to the table where 'Incoming' is changed to 'Outgoing' and 'I' is changed to 'O'.
I did it the hard way by creating a new table from the old one; updating the new table and then inserting back into Evact:
Create table btemp like Evact;
update btemp set Evact = 'Outgoing', EvActIncOutg = 'O';
insert into Evact select * from btemp;
That worked, but I want to get better at SQL. What I wish for is a way to do this in one step by joining Evact to itself in some way. Does anyone have a suggestion?
If you want to insert a bunch of rows that are part copies of existing rows:
INSERT INTO evact
SELECT evactmas, 'Outgoing', evactcode, 'O', evactbudact, ...other..
FROM evact
You make a Select statement that is the data you want to insert, some columns in the select are the values as-is, other columns are the new values
If you aren't specifying all the columns in the select you'll have to put a list of column names in brackets after the INTO so MySQL knows which columns are to get what data. You can only omit the columns list if your select query selects the same number of columns in the table (in which case the columns selected must be in the same order as the table columns to be inserted into)
If your table has a calculated primary key (auto increment for example) specify the value to insert as 0 or NULL to have MySQL calculate a new value for it, or name all the columns except that one after the INTO and omit it from the select list

mysql insert external data with join

I'm usually pretty resourceful, but I'm stuck on this one. Any help would be appreciated.
Say I've got a table for produce, like this, including counts of sold/in stock for each produce type.
+--------------+--------------+------+-----+
| Field | Type | Null | Key |
+--------------+--------------+------+-----+
| produce_type | varchar(100) | NO | PRI |
| sold_count | int(8) | YES | |
| stock_count | int(8) | YES | |
+--------------+--------------+------+-----+
I'm doing a separate insert using external data for each of the 'stock' and 'sold' counts, with hundreds to thousands of produce_types at a time. I may have data with a given produce_type existing only in the 'stock' or 'sold' data to be inserted, but want all to be present in the table.
So, e.g., doing one insert for sold_count ('potato', 3), ('onion', 5) and one for stock_count ('potato', 8), ('carrots', 6), I'd want to end up with this:
+--------------+------------+-------------+
| produce_type | sold_count | stock_count |
+--------------+------------+-------------+
| potato | 3 | 8 |
| onion | 5 | NULL |
| carrots | NULL | 6 |
+--------------+------------+-------------+
So I'd need to join to existing data upon the second column's insert statement, but all I see here or elsewhere on the web is instructions for joins when inserting from another table.
INSERT IGNORE doesn't do it, as one of the 'potato' columns wouldn't get written to.
INSERT ... ON DUPLICATE KEY UPDATE gets closer but I can't figure out how to set the update field to the value from the dataset I'm inserting.
Do I need to create a temp table for the 2nd insert (+ outer join)? Any structurally simpler way of doing this?
Thanks in advance.
Edit: I think I can probaly use this:
https://stackoverflow.com/a/3466/2540707
Does this work?
insert into produce ( produce_type, sold_count )
select produce_type, sold_count from sold_data
on duplicate key update sold_count = ( select sold_count from sold_data
where produce.produce_type = sold_data.produce_type
);

mysql update and insert statements based on another table

I need some help with the mysql statements for inserting and updating rows in a new table based on the contents of another table. I am going to use this in automated perl code, but the mysql statements themselves are what I am having trouble with.
My first table named PROFILE looks something like this:
+----------+---------------------------+
| ID | NAME |
+----------+---------------------------+
| 0 | Default profile |
| 04731470 | Development profile |
| 87645420 | Core Base |
| a41401a0 | Core Test |
| ba0e3000 | Development profile child |
| e37fe780 | Test2 |
+----------+---------------------------+
The second called DEPLOYMENT has these columns (and no rows yet):
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| PROF_ID | char(36) | NO | PRI | NULL | |
| NAME | varchar(60) | NO | | NULL | |
| ID | tinyint(4) | NO | MUL | NULL | |
+------------+-------------+------+-----+---------+-------+
ID.PROFILE is the foreign key for PROF_ID.DEPLOYMENT and I want all of the values for ID.PROFILE to go in PROF_ID.DEPLOYMENT. Then I want the NAME.DEPLOYMENT and ID.DEPLOYMENT fields to be set based on the words found in the NAME.PROFILE field.
The following shows what I want to do as far as the insert statements goes, but these failed due to "ERROR 1242 (21000): Subquery returns more than 1 row":
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID) VALUES((select ID from PROFILE where NAME like '%core%'),'Core','2');
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID) VALUES((select ID from PROFILE where NAME like '%development%'),'Dev','3');
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID) VALUES((select ID from PROFILE where NAME not like '%development%' and not like '%core%'),'Default','1');
I'm not sure where to start on the update part of this but the ID.DEPLOYMENT and NAME.DEPLOYMENT fields should change as above if the text in the NAME.PROFILE fields changes with any of the words above.
This is the resulting DEVELOPMENT table I am looking for.
+----------+---------------+----+
| PROF_ID | NAME | ID |
+----------+---------------+----+
| 0 | Default | 1 |
| 04731470 | Dev | 3 |
| 87645420 | Core | 2 |
| a41401a0 | Core | 2 |
| ba0e3000 | Dev | 3 |
| e37fe780 | Default | 1 |
+----------+---------------+----+
Then I want statements to update if any of the NAME.PROFILE information changes.
Sorry if this is confusing, I wasn't sure how to explain and I am still learning mysql. Any help is appreciated.
Just get rid of the values keyword, basically:
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID, 'Core','2'
from PROFILE
where NAME like '%core%';
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID, 'Dev', '3'
from PROFILE
where NAME like '%development%';
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID, 'Default', '1'
from PROFILE
where NAME not like '%development%' and not like '%core%';
By the way, you could combine these into one statement, using conditional expressions:
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID,
(case when NAME like '%core%' then 'Core'
when NAME like '%development%' then 'Dev'
else 'Default'
end)
(case when NAME like '%core%' then '2'
when NAME like '%development%' then '3'
else '1'
end)
from PROFILE;