convert cte to mysql - mysql

i have a recursive cte in mssql which finds all children of a certain node. in db terms:
create table nodes (
id int primary key autoincrement,
parent int
)
with this table i had a cte that i used to create a view:
create view (
node_id int,
child_id int
)
how do i do that in mysql? (I cannot change the table format to accomodate other methods such as with the nested set model)
worst case, could i make a function to do it?
thanks!

You may want to look at this discussion:
How do you use the "WITH" clause in MySQL?

Related

Create Table (new columns) and columns from different table

So For example. I have 1 table
and the name of the table is Suppliers
Contains :
1. SupplierName
2. SupplierID
I want to create another new table name Contracts
which contain new columns for
1. ContractID (new column)
2. SupplierID(from "Suppliers" table)
3. ContractValue (new column)
How do i do it?
I have researched and most of them told me to use Create table and then select, But it wont work and also ive tried alter table but still not working.
CREATE TABLE Contracts (
ContractID INT NOT NULL,
SELECT SupplierID
FROM Suppliers,
ContractValue INT NOT NULL,
ContractStart DATE NOT NULL)
These codes are not working so I'm not sure what is the solution.
CREATE TABLE Contracts (
ContractID INT NOT NULL,
(SELECT SupplierID
FROM Suppliers),
ContractValue INT NOT NULL,
ContractStart DATE NOT NULL)
I expect the result to be new table with ContractID (new column), SupplierID (from table Suppliers) and another new column named ContractValue
Think of Select query result set as a table or data grid.
So "SELECT [some fields] FROM [some table]" returns data grid where each row contains some fields from the table.
Therefore you can define table as select query with data OR alternatively specify the structure and create empty table. Most likely you don't want to mix those two approaches.
In your case, SupplierID field of contract table is a reference to SupplierID of Supplier table. In SQL it's called "foreign key". Theoretically you can use select statement in order to create new table and when you play a lot with database queries, you'll choose most convenient and faster way depending on your needs.
But when you start learning, it's better to create an empty table with structure and then insert data using new fields and existing data for the foreign key.
Therefore, the query will be something like:
CREATE TABLE Contracts (
ContractID INT NOT NULL,
SupplierID INT NOT NULL,
ContractValue INT,
ContractStart DATE
);
And then you can insert data using existing values from supplier table:
INSERT INTO Contracts (SupplierID)
SELECT SupplierID FROM Suppliers
Of course this is very simplified description
First, you have to specify ContractID as primary key. Then the query above will work only if you specify primary key as auto increment value, otherwise you have to use some logic and specify it explicitly.
In addition you have to specify default values if you want to use NOT NULL fields.
You can also specify SupplierID as foreign key, so only existing values will be added and some other integrity relationships will be supported.
See any MySQL or SQL documentation for details.
I don't know whether the below way could solve your problem
Make a copy of Suppliers table
Delete unnecessary column from the copied table
Add new column that you want to it.
You can use CTAS command.
CREATE TABLE Contracts as
SELECT
0 as ContractID,
SupplierID,
0 as ContractValue,
now() as ContractStart
FROM Suppliers;
This will create a table with all fields. The default value is to specify the dataType. You can update the table with relevant value or have a join in the select clause itself.
The basic syntax for creating a table from another table is as follows
CREATE TABLE NEW_TABLE_NAME AS
SELECT [ column1, column2...columnN ]
FROM EXISTING_TABLE_NAME
[ WHERE ]
Here, column1, column2... are the fields of the existing table and the same would be used to create fields of the new table.
Example
Following is an example, which would create a table SALARY using the CUSTOMERS table and having the fields customer ID and customer SALARY −
SQL> CREATE TABLE SALARY AS
SELECT ID, SALARY
FROM CUSTOMERS;
last week I did, as you want to do.
Only two steps I was followed:
Export existing table.
Open in notepad++ and change the existing table name, add my new columns and Import.
Thanks

insert into / select from ordered

I've a table like so:
create linkedList (
id int identity(1, 1),
parentId int not null, -- foreign key to table.id, always < this row's id
name nvarchar(200)
)
I want to insert the rows from this table into:
create reportTable (
id int,
parentId int not null, -- foreign key to reportTable.id
name nvarchar(200)
)
... but I only want to insert some rows (the exact where condition is not relevant), and I only want to to insert a row when its parent row was also inserted.
I can do this by using a cursor, but is there a better way? Basically I need an insert into / select from where I can control the insert order. If I understand ORDER BY on insert into it doesn't guarantee the order in which records are inserted (so I couldn't use an exists clause because I can't guarantee a parent row will be inserted before the child).
Basically I want something like:
INSERT INTO reportTable
SELECT id, parentId, name
FROM linkedList
WHERE
name = 'foo'
and exists (select 1 from reportTable where id = linkedList.parentId)
OREDER BY id asc
... is there a shorthand way to do this that will guarantee the inserts are done from the lowest linkedList.Id to the highest (which is important due to the exists condition)?
Because the parent record may have a parent record, and that a parent record also, I can't just check in the exists clause if the parent record's name is foo, because any of the ancestors might fail to meet this requirement).
You could try using a recursive CTE to expand the hierarchy from the child perspective (the anchor query in this case might include all records in the table, while the recursive portion would climb up the hierarchy to the root). This would give you the full ancestor hierarchy for every child. In theory, you could then write your INSERT using an EXISTS in the WHERE clause to filter the child records where one or more parents match your conditions.
https://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/
Keep in mind that expanding the hierarchy in this way can generate a very large amount of data. Depending on the size of your table, this might not be feasible.

Tagging query with group_concat

Using the database schema for tagging from this question's accepted answer is it possible to have a query using group_concat that works with a large amount of data? I need to get items with their tags for all items tagged with tag x. Using a query with group_concat having ~ .5 million tags is very slow at > 15 seconds. Without group_concat (items without tags) it is ~ 0.05 seconds.
As a side question, how does SO solve this problem?
This is probably a case of a poor indexing strategy. Adapting the schema shown in the accepted answer of the question to which you linked:
CREATE Table Items (
Item_ID SERIAL,
Item_Title VARCHAR(255),
Content TEXT
) ENGINE=InnoDB;
CREATE TABLE Tags (
Tag_ID SERIAL,
Tag_Title VARCHAR(255)
) ENGINE=InnoDB;
CREATE TABLE Items_Tags (
Item_ID BIGINT UNSIGNED REFERENCES Items (Item_ID),
Tag_ID BIGINT UNSIGNED REFERENCES Tags ( Tag_ID),
PRIMARY KEY (Item_ID, Tag_ID)
) ENGINE=InnoDB;
Note that:
MySQL's SERIAL data type is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE and, as such, is indexed;
defining the foreign key constraints in Items_Tags creates indexes on the foreign key columns.
I would propose to have a hybrid between normalized data and denormalized data .
So using the normalized structure provided by eggyal i would do the following denormalized structure :
CREATE TABLE Items_Tags_Denormalized (
Item_ID BIGINT UNSIGNED REFERENCES Items (Item_ID),
Tags BLOB,
PRIMARY KEY (Item_ID)
) ENGINE=InnoDB;
In column Tags you would have all the tags (Tag_Title) for the corresponding Item_ID.
Now you have 2 ways to achieve this:
create a cron that runs periodically which will build this table Items_Tags_Denormalized using GROUP_CONCAT or whatever suits you (advantage: doesn't put additional load when you insert or delete in Items_Tags table; disadvantage: the denormalized table will not always be up to date (depending on how often do you run the cron))
create triggers for Items_Tags table on insert and delete in order to keep up to date the Items_Tags_Denormalized table (advantage: the denormalized table will always be up to date;disadvantage: additional load when you insert or delete in Items_Tags table)
Choose whatever solution suits your needs best considering the advantages and disadvantages.
So in the end you will have the Items_Tags_Denormalized table from which you will only read without doing additional operations.
Why would you use group_concat for that? For a given tag x you said that selecting the list of items is fast. For a given list of items getting all the tags should be fast, too. And is there not normally some kind of restriction, I mean normal websites don't show 100000 entries on one page.
I would suggest:
drop temporary table if exists lookup_item;
create temporary table lookup_item (item_id serial, primary key(item_id));
insert into lookup_item select i.id as item_id
from items i
where exists (select * from items_tags where item_id = i.id and tag_id = <tag_id>)
and <other conditions or limits>;
select * from lookup_item
inner join items_tags it on it.item_id = i.id
inner join tags t on t.id = it.tag_id
order by i.<priority>, t.<priority>
priority could be last-modified for items and some kind of importance for tags.
Then you get every item with it's tags. The only work in the code is to see when the result-line has the next item.
If I understand correctly, GROUP_CONCAT isn't the only thing you are removing that makes the query faster without tags. Inside the GROUP_CONCAT you're selecting Tags.Tag_Title and forcing the Tags table to be accessed.
You could try running GROUP_CONCAT with Items_Tags.Tag_ID to test my theory.

SQL Queries on Table with "Forest" structure

I'm using MySQL 5.5. Suppose I have an SQL table that has an "N-forest" structure as follows:
create table foo
(
id int not null primary key,
parent_id int,
bar varchar(255),
foreign key (parent_id) references foo (id)
)
If parent_id is null it signifies a root element. Also we guarantee it is acyclic.
I now want to select the row with id #x and all of its descendants (that is the set containing row with id #x and recursively any rows that have a parent_id in this set) sorted in pre-order:
select * from foo where parent_id=DescendantOf(#x) sort by Preorder
What SQL statement can I use? (modifications to table structure allowed if needed)
Also I would like to find the root id of a given element:
select RootOf(#x) from foo
How can I do that?
Can MySQL handle this efficiently or do I need to maintain my own index or auxillary table?
AFAIK there is no such functonality in MySQL built-in... Oracle has a feature called "hierarchical query" which does what you want...
In MySQL You can implement/simulate it although the "how" is rather complex - for a very good walkthrough see this article.

How to fill in the "holes" in auto-increment fields?

I've read some posts about this but none cover this issue.
I guess its not possible, but I'll ask anyway.
I have a table with more than 50.000 registers. It's an old table where various insert/delete operations have taken place.
That said, there are various 'holes' some of about 300 registers. I.e.: ..., 1340, 1341, 1660, 1661, 1662,...
The question is. Is there a simple/easy way to make new inserts fill these 'holes'?
I agree with #Aaron Digulla and #Shane N. The gaps are meaningless. If they DO mean something, that is a flawed database design. Period.
That being said, if you absolutely NEED to fill these holes, AND you are running at least MySQL 3.23, you can utilize a TEMPORARY TABLE to create a new set of IDs. The idea here being that you are going to select all of your current IDs, in order, into a temporary table as such:
CREATE TEMPORARY TABLE NewIDs
(
NewID INT UNSIGNED AUTO INCREMENT,
OldID INT UNSIGNED
)
INSERT INTO NewIDs (OldId)
SELECT
Id
FROM
OldTable
ORDER BY
Id ASC
This will give you a table mapping your old Id to a brand new Id that is going to be sequential in nature, due to the AUTO INCREMENT property of the NewId column.
Once this is done, you need to update any other reference to the Id in "OldTable" and any foreign key it utilizes. To do this, you will probably need to DROP any foreign key constraints you have, update any reference in tables from the OldId to the NewId, and then re-institute your foreign key constraints.
However, I would argue that you should not do ANY of this, and just understand that your Id field exists for the sole purpose of referencing a record, and should NOT have any specific relevance.
UPDATE: Adding an example of updating the Ids
For example:
Let's say you have the following 2 table schemas:
CREATE TABLE Parent
(
ParentId INT UNSIGNED AUTO INCREMENT,
Value INT UNSIGNED,
PRIMARY KEY (ParentId)
)
CREATE TABLE Child
(
ChildId INT UNSIGNED AUTO INCREMENT,
ParentId INT UNSIGNED,
PRIMARY KEY(ChildId),
FOREIGN KEY(ParentId) REFERENCES Parent(ParentId)
)
Now, the gaps are appearing in your Parent table.
In order to update your values in Parent and Child, you first create a temporary table with the mappings:
CREATE TEMPORARY TABLE NewIDs
(
Id INT UNSIGNED AUTO INCREMENT,
ParentID INT UNSIGNED
)
INSERT INTO NewIDs (ParentId)
SELECT
ParentId
FROM
Parent
ORDER BY
ParentId ASC
Next, we need to tell MySQL to ignore the foreign key constraint so we can correctly UPDATE our values. We will use this syntax:
SET foreign_key_checks = 0;
This causes MySQL to ignore foreign key checks when updating the values, but it will still enforce the correct value type is used (see MySQL reference for details).
Next, we need to update our Parent and Child tables with the new values. We will use the following UPDATE statement for this:
UPDATE
Parent,
Child,
NewIds
SET
Parent.ParentId = NewIds.Id,
Child.ParentId = NewIds.Id
WHERE
Parent.ParentId = NewIds.ParentId AND
Child.ParentId = NewIds.ParentId
We now have updated all of our ParentId values correctly to the new, ordered Ids from our temporary table. Once this is complete, we can re-institute our foreign key checks to maintain referential integrity:
SET foreign_key_checks = 1;
Finally, we will drop our temporary table to clean up resources:
DROP TABLE NewIds
And that is that.
What is the reason you need this functionality? Your db should be fine with the gaps, and if you're approaching the max size of your key, just make it unsigned or change the field type.
You generally don't need to care about gaps. If you're getting to the end of the datatype for the ID it should be relatively easy to ALTER the table to upgrade to the next biggest int type.
If you absolutely must start filling gaps, here's a query to return the lowest available ID (hopefully not too slowly):
SELECT MIN(table0.id)+1 AS newid
FROM table AS table0
LEFT JOIN table AS table1 ON table1.id=table0.id+1
WHERE table1.id IS NULL
(remember to use a transaction and/or catch duplicate key inserts if you need concurrent inserts to work.)
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
IFNULL for skip null on zero rows count
add target for skip error mysql "error clause FROM)
There is a simple way but it doesn't perform well: Just try to insert with an id and when that fails, try the next one.
Alternatively, select an ID and when you don't get a result, use it.
If you're looking for a way to tell the DB to automatically fill the gaps, then that's not possible. Moreover, it should never be necessary. If you feel you need it, then you're abusing an internal technical key for something but the single purpose it has: To allow you to join tables.
[EDIT] If this is not a primary key, then you can use this update statement:
update (
select *
from table
order by reg_id -- this makes sure that the order stays the same
)
set reg_id = x.nextval
where x is a new sequence which you must create. This will renumber all existing elements preserving the order. This will fail if you have foreign key constraints. And it will corrupt your database if you reference these IDs anywhere without foreign key constraints.
Note that during the next insert, the database will create a huge gap unless you reset the identity column.
As others have said, it doesn't matter, and if it does then something is wrong in your database design. But personally I just like them to be in order anyway!
Here is some SQL that will recreate your IDs in the same order, but without the gaps.
It is done first in a temp_id field (which you will need to create), so you can see that it is all good before overwriting your old IDs. Replace Tbl and id as appropriate.
SELECT #i:=0;
UPDATE Tbl
JOIN
(
SELECT id
FROM Tbl
ORDER BY id
) t2
ON Tbl.id = t2.id
SET temp_id = #i:=#i+1;
You will now have a temp_id field with all of your shiny new IDs. You can make them live by simply:
UPDATE Tbl SET id = temp_id;
And then dropping your temp_id column.
I must admit I'm not quite sure why it works, since I would have expected the engine to complain about duplicate IDs, but it didn't when I ran it.
You might wanna clean up gaps in a priority column.
The way below will give an auto increment field for the priority.
The extra left join on the same tabel will make sure it is added in the same order as (in this case) the priority
SET #a:=0;
REPLACE INTO footable
(id,priority)
(
SELECT tbl2.id, #a
FROM footable as tbl
LEFT JOIN footable as tbl2 ON tbl2.id = tbl.id
WHERE (select #a:=#a+1)
ORDER BY tbl.priority
)