How to use a view name stored in a field for an sql query? - sql

I have a table with a view_name field (varchar(256)) and I would like to use that field in an sql query.
Example :
TABLE university_members
id | type | view_name | count
1 | professors | view_professors | 0
2 | students | view_students2 | 0
3 | staff | view_staff4 | 0
And I would like to update all rows with some aggregate calculated on the corresponding view (for instance ..SET count = SELECT count(*) FROM view_professors).
This is probably a newbie question, I'm guessing it's either obviously impossible or trivial. Comments on the design, i.e. the way one handle meta-data here (explicity storing DB object names as strings) would be appreciated. Although I have no control over that design (so I'll have to find out the answer anyway), I'm guessing it's not so clean although some external constraints dictated it so I would really appreciate the community's view on this for my personal benefit.
I use SQL Server 2005 but cross-platform answers are welcome.

To do this you would have to do it as a bit of dynamic SQL, something like this might work, obviously you would need to edit to actually match what you are trying to do.
DECLARE #ViewName VARCHAR(500)
SELECT #ViewName = view_name
FROM University_Members
WHERE Id = 1
DECLARE #SQL VARCHAR(MAX)
SET #SQL = '
UPDATE YOURTABLE
SET YOURVALUE = SELECT COUNT(*) FROM ' + #ViewName + '
WHERE yourCriteria = YourValue'
EXEC(#SQL)

The way I see it, you could generate SQL code in a VARCHAR(MAX) variable and then execute it using EXEC keyword. I don't know of any way to do it directly, as you tried.
Example:
DECLARE #SQL VARCHAR(MAX)
SET #SQL = ''
SELECT #SQL = #SQL + 'UPDATE university_members SET count = (SELECT COUNT(*) FROM ' + view_name + ') WHERE id = ' + id + CHAR(10) + CHAR(13) FROM university_members
EXEC #SQL
Warning! This code is not tested. It's just a hint...

Dynamic SQl is the only way to do this which is why this is a bad design choice. Please read the following article if you must be using dynamic SQl in order to protect your data.
http://www.sommarskog.se/dynamic_sql.html

As HLGEM wrote, the fact that you're being forced to use dynamic SQL is a sign that there is a problem with the design itself. I'll also point out that storing an aggregate in a table like that is most likely another bad design choice.
If you need to determine a value at some point, then do that when you need it. Trying to keep a calculated value like that synchronized with your data is almost always fraught with problems - inaccuracy, extra overhead, etc.
There are very rarely situations where storing a value like that is necessary or gives an advantage and those are typically in very large data warehouses or systems with EXTREMELY high throughput. It's nothing that a school or university is likely to encounter.

Related

Processing SQL Updates

I have a table with Table name, Id, Column Name and Value
I need to run these into a Azure SQL Database as updates.
The tables are all different with different columns for the update.
The only way I could think of is RBAR, but there has to be an easier way to process this, either SQL or maybe even data factory?
Example of the data is:
Table Name Id Column Name Value
Table 1 1234 column1 1
Table 1 1234 column2 2022-01-02
Table 2 4321 column6 2144
Table 2 4321 column12 2022-01-02
The column name could be any column in the table with a update value.
As mentioned I have tried Row By Agonising Row but, as you would expect, WAY too painful as I have approx 161K rows that need processing.
You'll need to build an SQL string for each row and run that string as it's own command.
One thing I would do as part of this is pull the table/column information from Information_Schema.Columns. This will give you the same text you already have, but it ensures (in a safe way) that the data in your source table really is a valid object in your database, and not an SQL injection attempt. I would also use sp_executesql for the final query, to include the new value in a safe way.
So the main query would like looks this:
SELECT d.ID, d.Value,
'UPDATE ' + QUOTENAME(c.Table_Name) + ' SET ' + QUOTENAME(c.Column_Name) + ' = #new_value WHERE ID= #RowID' as SQL
FROM [MyData] d
INNER JOIN Information_Schema.Columns c ON c.[Table Name] = d.Table_Name
AND c.[Column Name] = d.Column_Name
And the loop would then take each row and do something like this:
-- #SQL, #ID, and #Value from the row
Execute sp_executeSql #SQL, '#new_value varchar(50), #id int', #new_value = #Value, #RowID = #ID
I skipped over the part where you loop through the rows because you have many options here, and which is best depends on your specific situation: do you want a cursor? Client code? Hard to know.
The trick here is we don't know the data type of the new value... and it certainly looks like you're mixing data types in there. You know that, for example, storing dates in varchar columns is really bad, right?

create dynamic temp table in sql

Is there a way to create a dynamic temp table. Below sql code is declaring a variable #tic. I am planning to insert contents from table1 to temp table #df. So instead of giving directly as #df, I am passing as a variable. But below is code is not successful. Can anyone help me here?
declare #tic as varchar(100) = 'df'
select *
into '#' + #tic from (
select * from [dbo].[table1])
select * from #df
Is there a way? Well, I think of the answer as "yes and no and maybe".
As far as I know, there is no way to do this using a local temporary table. As Stu explains in the comment, you would need dynamic SQL to define the table name and then the table would not be visible in the outer scope, because it is a local temporary table.
The "yes" is because one type of temporary table are global temporary tables. These are tables that persist across different scopes. And they are defined using ## instead of # as the prefix. So this works:
declare #tic as varchar(100) = 'df'
declare #sql nvarchar(max);
set #sql = 'select * into ##' + #tic + ' from table1';
select #sql;
exec sp_executesql #sql;
select * from ##df;
(Here is a db<>fiddle.)
The "maybe" is because I'm quite skeptical that you really need this. Dynamic table names are rarely useful in SQL systems, precisely because they depend on dynamic SQL. Introducing dynamic names into SQL (whether columns or tables) is dangerous, both because of the danger of SQL injection and also because it can introduce hard-to-debug syntax errors.
If you are trying to solve a real problem, there might be alternative approaches that are better suited to SQL Server.

Alter table to add dynamic columns based off some previously selected query

Is it possible to alter a table and add columns with a dynamic name/data type based off some previously select query?
The pseudo equivalent for what I'm looking to do in SQL would be:
foreach row in tableA
{
alter tableB add row.name row.datatype
}
This is for SQL Server.
As mentioned, you can do this with dynamic sql. Something along these lines:
Declare #SQL1 nvarchar(4000)
SELECT #SQL1=N'ALTER TABLE mytable'+NCHAR(13)+NCHAR(10)
+N' ADD COLUMN '+ my_new_column_name + ' varchar(25)'+NCHAR(13)+NCHAR(10)
-- SELECT LEN(#SQL1), #SQL1
EXECUTE (#SQL1)
Apart from the fact that this is messy, error prone, a security risk, requires high authorization to execute and needs multiple variables for batches bigger than 4000 characters, it is usually also a bad idea from a design point of view (depending on when/why you are doing this).
Sure, you can do this with dynamic sql.

SQL JOIN based on table contents

I have a single table that contains questions with corresponding references to another table and field that contain the answers. Something like:
I would like to query the questions table and return QID, QuestionText and the value contained in the [ResponseTable].[ResponseField] for each QID. The design seamed flexible at the time. However the app developer is expecting a stored procedure and the SQL developer was counting on an in app solution for this issue.
I am at the end of my rope trying to build this query. How would you suggest accomplishing this task?
I don't think you'll like hearing this answer because it will likely mean some major rework, but I think it's the right answer. Get rid of the questions table and put the questions into new Question fields in the Client1, Client9, and Jobs tables; one for each response.
For example the Client1 table will have these fields:
ColorPref
ColorPrefQuestion
Rating
RatingQuestion
...and so on
Working around that design will be manageable where working around the design you have now will be a headache.
It sounds like a redesign should be considered (storing all responses in one table, for example), but if that's not a possibility then dynamic SQL (using sp_executesql) can be used. However, it can be dangerous to use as it is vulnerable to SQL injection. There are some precautions that can be taken, such as using QUOTENAME on table and column names. This is also a good read before using dynamic SQL: The Curse and Blessings of Dynamic SQL.
DECLARE #tableName NVARCHAR(50)
DECLARE #columnName NVARCHAR(50)
DECLARE #query NVARCHAR(MAX)
SET #tableName = 'Client1'
SET #columnName = 'ColorPref'
SET #query = 'SELECT ' + QUOTENAME(#columnName) + ' FROM ' + QUOTENAME(#tableName)
EXEC sp_executesql #query
Until you get to the rewrite you mentioned, consider the idea of using a view to bring these response tables together.
CREATE VIEW ClientResponses AS
SELECT QID, ResponseField FROM [Client1]
UNION
SELECT QID, ResponseField FROM [Jobs]
UNION
SELECT QID, ResponseField FROM [Client9]
-- ..... add the new tables as they are created
This will
Avoid dynamic SQL
Give you a single place to maintain querying
Provide a pretty simple, readable way to hobble this together

SQL clone record with a unique index

Is there a clean way of cloning a record in SQL that has an index(auto increment). I want to clone all the fields except the index. I currently have to enumerate every field, and use that in an insert select, and I would rather not explicitly list all of the fields, as they may change over time.
Not unless you want to get into dynamic SQL. Since you wrote "clean", I'll assume not.
Edit: Since he asked for a dynamic SQL example, I'll take a stab at it. I'm not connected to any databases at the moment, so this is off the top of my head and will almost certainly need revision. But hopefully it captures the spirit of things:
-- Get list of columns in table
SELECT INTO #t
EXEC sp_columns #table_name = N'TargetTable'
-- Create a comma-delimited string excluding the identity column
DECLARE #cols varchar(MAX)
SELECT #cols = COALESCE(#cols+',' ,'') + COLUMN_NAME FROM #t WHERE COLUMN_NAME <> 'id'
-- Construct dynamic SQL statement
DECLARE #sql varchar(MAX)
SET #sql = 'INSERT INTO TargetTable (' + #cols + ') ' +
'SELECT ' + #cols + ' FROM TargetTable WHERE SomeCondition'
PRINT #sql -- for debugging
EXEC(#sql)
There's no easy and clean way that I can think of off the top of my head, but from a few items in your question I'd be concerned about your underlying architecture. Maybe you have an absolutely legitimate reason for wanting to do this, but usually you want to try to avoid duplicates in a database, not make them easier to cause. Also, explicitly naming columns is usually a good idea. If you're linking to outside code, it makes sure that you don't break that link when you add a new column. If you're not (and it sounds like you probably aren't in this scenario) I still prefer to have the columns listed out because it forces me to review the effects of the change/new column - even if it's just to look at the code and decide that adding the new column is not a problem.
DROP TABLE #tmp_MyTable
SELECT * INTO #tmp_MyTable
FROM MyTable
WHERE MyIndentID = 165
ALTER TABLE #tmp_MyTable
DROP Column MyIndentID
INSERT INTO MyTable
SELECT *
FROM #tmp_MyTable
This also deals with a unique key projectnum as well as the primary key.
CREATE TEMPORARY TABLE projecttemp SELECT * FROM project WHERE projectid='6';
ALTER TABLE projecttemp DROP COLUMN projectid;
UPDATE projecttemp SET projectnum = CONCAT(projectnum, ' CLONED');
INSERT INTO project SELECT NULL,projecttemp.* FROM projecttemp;
You could create an insert trigger to do this, however, you would lose the ability to do an insert with an explicit ID. It would, instead, always use the value from the sequence.
You could create a trigger to do it for you. To make sure that trigger only works for cloning, you could create a separate username CLONE and log in with it. Or, even better, if your DBMS supports it, create a role named CLONE and any user can log in using that role and do the cloning. The trigger code would be something like:
if (CURRENT_ROLE = 'CLONE') then
new.ID = assign new id from generator/sequence
Of course, you would grant that role only to the users who are allowed to clone records.