Cursor is becoming an never ending loop - sql-server-2008

I want to create an SP to generate the metadata for all tables using cursors in SQL. Following is the code I have tried. but its becoming a never ending loop and same data is repeated. Thanks in advance.
--SELECT * FROM information_schema.columns
ALTER PROCEDURE p1
AS
SET NOCOUNT ON;
DECLARE #id INT
, #tablename VARCHAR(100)
, #columnname VARCHAR(100)
, #datatype VARCHAR(100)
, #isnullable VARCHAR(100)
BEGIN
DECLARE CURSOR_1 CURSOR FOR
SELECT TABLE_NAME
, COLUMN_NAME
, DATA_TYPE
, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Employee' -- group BY table_name
OPEN CURSOR_1
FETCH NEXT FROM CURSOR_1 INTO
#tablename,
#columnname,
#datatype,
#isnullable
WHILE ##fetch_status = 0
BEGIN
INSERT INTO table_schema_detail (TABLE_NAME, COLUMN_NAME, DATA_TYPE, isnullable)
VALUES (#tablename, #columnname, #datatype, #isnullable)
END
FETCH NEXT FROM CURSOR_1 INTO
#tablename,
#columnname,
#datatype,
#isnullable
CLOSE CURSOR_1
DEALLOCATE CURSOR_1
SET NOCOUNT OFF;
END
GO

I don't really understand why you need to store this information on a table, since it's already available on the system views (and if you still need to store this data, why are you using a cursor?). If, like your comment says, you need to store data from 3 tables, then you could simply do:
INSERT INTO table_schema_detail(table_name,column_name,data_type,isnullable)
SELECT table_name, column_name, data_type, is_nullable
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name IN ('Employee','OtherTable1','OtherTable2')
But again, I don't see the point. At least you could store the date when this was done:
INSERT INTO table_schema_detail(table_name,column_name,data_type,isnullable,DateInserted)
SELECT table_name, column_name, data_type, is_nullable, GETDATE()
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name IN ('Employee','OtherTable1','OtherTable2')

look where you have that end statement
insert INTO table_schema_detail(table_name,column_name,data_type,isnullable) VALUES(#tablename,#columnname,#datatype,#isnullable)
end
Suspect it never gets to this line
FETCH NEXT FROM CURSOR_1 into
And agree with other comments on is a cursor the proper approach but this is an answer to the question as stated.

I think get meta-data from sys.columns more preferable (in your case, cursor is not necessary):
INSERT INTO dbo.table_schema_detail
(
TABLE_NAME
, COLUMN_NAME
, DATA_TYPE
, IS_NULLABLE
)
SELECT
SCHEMA_NAME(o.[schema_id]) + '.' + o.name
, c.name
, TYPE_NAME(c.system_type_id)
, c.is_nullable
FROM sys.columns c
JOIN sys.objects o ON c.[object_id] = o.[object_id]
WHERE SCHEMA_NAME(o.[schema_id]) + '.' + o.name = 'dbo.Employee'
AND o.[type] = 'U'

Related

Truncate in multiple tables

I'm trying to create a way for me to apply truncate across multiple tables at the same time.
I tried the following code:
SELECT CONCAT('TRUNCATE TABLE ',table_schema,'.',TABLE_NAME, ';')
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema in ('mytable1','mytable2','mytable3');
More unsuccessfully, it is not truncating the tables.
Does anyone know of any way to do this?
DECLARE #CODE NVARCHAR(MAX) = '', #XML XML
SET #XML = STUFF((
SELECT ' ' + data.CODE_DELETE
FROM (
SELECT CONCAT ('TRUNCATE TABLE ', table_schema, '.', TABLE_NAME, '') AS CODE_DELETE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME IN ('CITY', 'WORKERS')
) [data]
FOR XML PATH('')
), 1, 1, '')
SET #CODE = CAST(#XML AS VARCHAR(MAX))
SELECT #CODE
EXECUTE sp_executesql #CODE;
the variable #XML is to be able to convert the rows into a single value, then we must convert the sql code to varchar in order to execute it with
EXECUTE sp_executesql #CODE;
you can see the code that will be executed if you do this:
SELECT #CODE
city ​​and workers are tables that I have in my database, if you look I change table_schema by TABLE_NAME in the WHERE clause.
Please use transaction to do it if you want to keep it atomic.
START TRANSACTION;
// Your truncate statements;
COMMIT;

modify format of multiple database columns at once

I have imported a csv table into sql db using phpmyadmin. I guess the default format is decimal(8,5), or at least that is how it comes out. Seems overkill and I thought I could reduce to 4,1. Problem is there are around 470 fields. I know how to change one at a time but this would take a long time. Is there a faster way?
I only have Sql Server 2008 R2, but it seems the MySQL solution may be similar. I've tried to align it as closely with MySQL as I can, but without a MySQL engine I can't check it ... So for what it's worth, here's my script (tested in Sql Server):
-- I believe this is 'CREATE TEMPORARY TABLE' in MySql
create table #tbl
(
ownerName sysname -- `sysname` is nvarchar(128) the max identifier length
, tableName sysname
, colName sysname
, colType sysname
, colPrecision int
, colScale int
)
insert #tbl
select table_schema, table_name, column_name, data_type, numeric_precision, numeric_scale
FROM information_schema.columns
where data_type = 'decimal' and numeric_precision = 8 and numeric_scale = 5
-- Note: It is 'MODIFY COLUMN' in MySQL
declare #newPrecision int = 4, #newScale int = 1
DECLARE #sql varchar(max) = ''
select #sql = #sql + CHAR(13) + CHAR(10) + 'ALTER TABLE ' + ownerName + '.' + tableName + ' ALTER COLUMN '
+ colName + ' decimal(' + cast(#newPrecision as varchar(max)) + ',' + cast(#newScale as varchar(max)) + ')'
from #tbl
/**
-- In MySql, GROUP_CONCAT() may work
select #sql = GROUP_CONCAT( 'ALTER TABLE ' + ownerName + '.' + tableName + ' MODIFY COLUMN '
+ colName + ' decimal(' + cast(#newPrecision as varchar(max)) + ',' + cast(#newScale as varchar(max)) + ')' SEPARATOR ' ' )
from #tbl
**/
print #sql
execute ( #sql )
I'd never used SqlFiddle before so thought it was a good time to start, to give a complete answer specifically for MySQL 5.6. Please see http://sqlfiddle.com/#!9/19f46/1
At the bottom of Build Schema panel, select Delimiter = [ // ]. SqlFiddle uses this to decide when to send off a chunk of code to MySQL. It is necessary because the CREATE PROCEDURE has to be sent off as one chunk. If you use the default Delimiter = [ ; ], then it will send off only part of the CREATE PROCEDURE up to the first ; it finds.
The table cols selects columns of type decimal with the precision and scale you want to change. Currently hardcoded as 8 and 5 as the OP requested, but change as required to identify only the columns you want to modify. It would be a good idea before you do the table modifications to run this select to verify which columns you will be modifying.
The stored procedure exec_multiple uses the table cols to generate ALTER TABLE statements which are then dynamically executed.
EXECUTE only handles one statement at a time, so you need to iterate through the rows of cols and apply each ALTER TABLE individually. The auto_increment column id in cols allows you to select each row in turn without the use of a cursor.
The table test_log captures any debugging information you may want to examine after the Build Schema is complete.
The following goes in the Build Schema panel on the left.
All of the logic needs to be in this panel because SqlFiddle does not allow Data Definition Language or table insert/update/delete statements in the Run SQL panel.
create table if not exists cols
(
id int auto_increment primary key
, ownerName varchar(128)
, tblName varchar(128)
, colName varchar(128)
, colType varchar(128)
, colPrecision int
, colScale int
) //
create table if not exists test_table
(
testDecimal1 decimal(8,5)
, testDecimal2 decimal(8,5)
) //
create table if not exists test_table2
(
testDecimal3 decimal(8,5)
, testDecimal4 decimal(8,5)
) //
create table if not exists test_log
(
msg varchar(1024)
) //
INSERT INTO cols
( ownerName, tblName, colName, colType, colPrecision, colScale )
SELECT TABLE_SCHEMA, `TABLE_NAME`, COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE = 'decimal' AND NUMERIC_PRECISION = 8 AND NUMERIC_SCALE = 5 //
insert test_log( msg) select database() //
CREATE PROCEDURE `exec_multiple` ( newPrecision int, newScale int)
BEGIN
declare n int;
declare nrows int;
declare sql_stmt varchar(1024);
set n = 1;
select count(*) from cols into nrows;
while n <= nrows do
select CONCAT('ALTER TABLE '
, ownerName, '.', tblName, ' MODIFY COLUMN '
, colName, ' decimal(', newPrecision, ','
, newScale, ')') into sql_stmt from `cols` where id = n;
SET #sql_stmt := sql_stmt; -- not sure why this is necessary
insert test_log( msg ) select #sql_stmt;
PREPARE dynamic_statement FROM #sql_stmt;
EXECUTE dynamic_statement;
DEALLOCATE PREPARE dynamic_statement;
set n = n + 1;
end while;
END //
call exec_multiple(4, 1) //
This goes in the Run SQL panel on the right
select * from test_log;
SELECT TABLE_SCHEMA, `TABLE_NAME`, COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE = 'decimal'
;
select * from cols;

SQL delete all from all if column A exists and equals B

How can I delete all records from all tables in a database, where the table has a column called systemid where systemid does not equal 1 or 2?
So I need to see if the table contains a certain column name, and if yes, check value of that column for all records, if not 1 or 2, delete. On all tables in the db.
Trying to clean-up a development db.
--- UPDATE ---
I found this SO thread: SQL Server : check if table column exists and remove rows
Which details the following:
IF EXISTS( SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TAB1')
IF EXISTS( SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TAB1' AND COLUMN_NAME = 'COL1')
delete TAB1 where COL1 not in (select COL2 from TAB2);
but I can't for the life of me correctly from a SQL query that can do what I wan to achieve due to both lack of knowledge and experience. Could anyone please provide a sample code with an explanation?
Thank you overflowers!
DECLARE #TableName VARCHAR(128);
DECLARE #MyColumn VARCHAR(128);
SET #MyColumn = 'MyColumnName'
DECLARE MyCursor CURSOR FOR
(SELECT OBJECT_NAME(c.id) as ObjectName
FROM dbo.syscolumns c
WHERE
OBJECTPROPERTY(c.id,'ISTABLE') = 1 --Search for tables only
AND c.name = #MyColumn)
OPEN MyCursor
FETCH NEXT FROM MyCursor into #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC
(
'DELETE ' + #MyColumn
+' FROM ' + #TableName
+' WHERE ' + #MyColumn + ' not in (1,2)'
)
FETCH NEXT FROM MyCursor into #TableName
END
CLOSE MyCursor
DEALLOCATE MyCursor

Alter all tables in database

I would like to run a "Alter Table" on ALL the tables in my SQL databse:
ALTER TABLE test ADD CONSTRAINT [COLLUM_NAME] DEFAULT ((0)) FOR [COLLUM_NAME]
I know how to get all of the existing tables from the database:
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
or
USE DATABASE_NAME
GO
SELECT name
FROM sys.Tables
GO
But I don’t know how to combine these two.
In my database (50+ tables) all of the tables have 1 row in common.
and I would like to set a default value to all of these rows.
You can try to generate a command and execute it after.
You can do something like this:
SELECT CONCAT("Alter Table `", TABLE_SCHEMA,"`.`", TABLE_NAME, "` this is my default value change on the column") as MySQLCMD
FROM TABLES
And execute the retrieving.
If this is a one-off process that doesn't need to be automated then you could probably do worse than running something like the following and just copy/pasting the output:
select 'alter table ' + t.name + ' add constraint ' + c.name + ' default ((0)) for ' + c.name
from sysobjects t join syscolumns c on c.id = t.id
where t.xtype = 'U'
If u want use INFORMATION_SCHEMA
SELECT 'ALTER TABLE ' +t.TABLE_NAME+ ' ADD CONSTRAINT '
+c.COLUMN_NAME +' DEFAULT ((0)) FOR '+c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLES t
INNER JOIN INFORMATION_SCHEMA.COLUMNS c on t.TABLE_NAME=c.TABLE_NAME
WHERE TABLE_TYPE = 'BASE TABLE'
set the 'COLUMN NAME' and execute, it will add a default constraint to setted column.
DECLARE #sql NVARCHAR(MAX) ;
DECLARE #LINEBREAK AS VARCHAR(2)
SET #LINEBREAK = CHAR(13) + CHAR(10)
SELECT #sql = COALESCE(#sql + ';' + #LINEBREAK, '') + 'ALTER TABLE '
+ QUOTENAME([TABLES].TABLE_NAME) + ' ADD CONSTRAINT ' + QUOTENAME([COLUMNS].COLUMN_NAME)
+ ' DEFAULT ((0)) FOR ' + QUOTENAME([COLUMNS].COLUMN_NAME)
FROM INFORMATION_SCHEMA.TABLES [TABLES]
INNER JOIN INFORMATION_SCHEMA.COLUMNS AS [COLUMNS] ON [TABLES].TABLE_NAME = [COLUMNS].TABLE_NAME
WHERE TABLE_TYPE = 'BASE TABLE'
AND [COLUMNS].[COLUMN_NAME] = 'COLUMN NAME'
PRINT #sql
EXEC sp_executesql #sql
My preferred way is to write a SP for this. I have some of these utility SPs in my databases and I alter them as needed to update things. Here's an example that changes the collation. You can see that by modifying the SET #S=... statement you can do any table alteration you like.
DECLARE table_name VARCHAR(255);
DECLARE end_of_tables INT DEFAULT 0;
DECLARE num_tables INT DEFAULT 0;
DECLARE cur CURSOR FOR
SELECT t.table_name
FROM information_schema.tables t
WHERE t.table_schema = DATABASE() AND t.table_type='BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_of_tables = 1;
OPEN cur;
tables_loop: LOOP
FETCH cur INTO table_name;
IF end_of_tables = 1 THEN
LEAVE tables_loop;
END IF;
SET num_tables = num_tables + 1;
SET #s = CONCAT('ALTER TABLE ' , table_name , ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci');
PREPARE stmt FROM #s;
EXECUTE stmt;
END LOOP;
CLOSE cur;

MySQL sorting table by column names

I have already built a table with field names in arbitrary order. I want those field names to be in alphabetical order so that I can use them in my dropdown list. Is it possible with a query?
Select columns from a specific table using INFORMATION_SCHEMA.COLUMNS and sort alphabetically with ORDER BY:
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = '[schemaname]'
AND table_name = '[tablename]'
ORDER BY column_name
Note: The following code will alter the specified table and reorder the columns in alphabetical order
This should do the trick. It's a bit messy and lengthy, and you'll have to change the database name and table name, but for this one, the only requirement is that there is a database named "test" and that you are running these commands in it:
Let's create the tables we need:
-- CREATE TESTING TABLE IN A DATABASE NAMED "test"
DROP TABLE IF EXISTS alphabet;
CREATE TABLE alphabet (
d varchar(10) default 'dee' not null
, f varchar(21)
, e tinyint
, b int NOT NULL
, a varchar(1)
, c int default '3'
);
-- USE A COMMAND STORAGE TABLE
DROP TABLE IF EXISTS loadcommands;
CREATE TABLE loadcommands (
id INT NOT NULL AUTO_INCREMENT
, sqlcmd VARCHAR(1000)
, PRIMARY KEY (id)
);
Now let's create the two stored procedures required for this to work:
Separating them since one will be responsible for loading the commands, and including a cursor to immediately work with it isn't plausible (at least for me and my mysql version):
-- PROCEDURE TO LOAD COMMANDS FOR REORDERING
DELIMITER //
CREATE PROCEDURE reorder_loadcommands ()
BEGIN
DECLARE limitoffset INT;
SET #rank = 0;
SET #rankmain = 0;
SET #rankalter = 0;
SELECT COUNT(column_name) INTO limitoffset
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'test'
AND table_name = 'alphabet';
INSERT INTO loadcommands (sqlcmd)
SELECT CONCAT(t1.cmd, t2.position) AS commander FROM (
SELECT #rankalter:=#rankalter+1 AS rankalter, CONCAT('ALTER TABLE '
, table_name, ' '
, 'MODIFY COLUMN ', column_name, ' '
, column_type, ' '
, CASE
WHEN character_set_name IS NOT NULL
THEN CONCAT('CHARACTER SET ', character_set_name, ' COLLATE ', collation_name, ' ')
ELSE ' '
END
, CASE
WHEN is_nullable = 'NO' AND column_default IS NULL
THEN 'NOT NULL '
WHEN is_nullable = 'NO' AND column_default IS NOT NULL
THEN CONCAT('DEFAULT \'', column_default, '\' NOT NULL ')
WHEN is_nullable = 'YES' THEN 'DEFAULT NULL '
END
) AS cmd
, column_name AS columnname
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'test'
AND table_name = 'alphabet'
ORDER BY columnname
) t1
INNER JOIN (
SELECT #rankmain:=#rankmain+1 AS rownum, position FROM (
SELECT 0 AS rownum, 'FIRST' AS position
, '' AS columnname
UNION
SELECT #rank:=#rank+1 AS rownum, CONCAT('AFTER ', column_name) AS position
, column_name AS columnname
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'test'
AND table_name = 'alphabet'
ORDER BY columnname
LIMIT limitoffset
) inner_table
) t2 ON t1.rankalter = t2.rownum
;
END//
DELIMITER ;
If anyone thinks/sees that I'm missing to include any important column attributes in the ALTER command, please hesitate not and mention it! Now to the next procedure. This one just executes the commands following the order of column id from the loadcommands table. :
-- PROCEDURE TO RUN EACH REORDERING COMMAND
DELIMITER //
CREATE PROCEDURE reorder_executecommands ()
BEGIN
DECLARE sqlcommand VARCHAR(1000);
DECLARE isdone INT DEFAULT FALSE;
DECLARE reorderCursor CURSOR FOR
SELECT sqlcmd FROM loadcommands ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET isdone = TRUE;
OPEN reorderCursor;
read_loop:LOOP
FETCH reorderCursor INTO sqlcommand;
IF isdone THEN
LEAVE read_loop;
END IF;
SET #sqlcmd = sqlcommand;
PREPARE stmt FROM #sqlcmd;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP read_loop;
CLOSE reorderCursor;
END//
DELIMITER ;
The SQL is long, so if someone can point out ways (and has tested them) to make this shorter I'd gladly do it, but for now, this at least works on my end. I also didn't need to put dummy data in the alphabet table. Checking the results can be done using the SHOW... command.
The last part:
-- TO TEST; AFTER RUNNING DDL COMMANDS:
SHOW CREATE TABLE alphabet; -- SEE ORIGINAL ORDER
CALL reorder_loadcommands(); -- PREPARE COMMANDS
CALL reorder_executecommands(); -- RUN COMMANDS
SHOW CREATE TABLE alphabet; -- SEE NEW ORDER
Perhaps later on I could make reorder_loadcommands dynamic and accept table and schema parameters, but I guess this is all for now..