Inject array of columns to COUNT in SELECT MSSQL - dynamically - sql

Currently I have prepared a sting of columns which can be added (hence the need for the dynamic query.
I have #cols which can be printed with an output like "Color","Size","Width"
I then have a SELECT/COUNT statement which needs to look like as follows...
SELECT
Product_code,
count(distinct [Color]),
count(distinct [Size]),
count(distinct [Width])
FROM.....
I need of the columns that I have in my string of columns to be counted with distinct..
Also would be even better if I could add a AS with the name of each of these in here too!
Many help is much appreciated - my SQL are OK but the dynamic bit turns me blue!
Cheers.

Convert your comma seperated list to a table first. See this
Assuming that table name to be ListOfColumns
DECLARE #Query VARCHAR( 1000 ) = '';
SELECT #Query+=#Query + ', COUNT(DISTINCT ' + COLUMN_NAME + ') AS ' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS c
INNER JOIN ListOfColumns d ON c.COLUMN_NAME = d.ColName
WHERE TABLE_NAME = 'MyTable';
SET #Query = 'SELECT ' + STUFF( #query,1,3,'' ) + ' FROM Tbl';
EXEC ( #Query );

Related

SQL Search for Data in Multiple Columns

Dears,
I have a table as shown below as a sample, and I want to run one query by which i can find all the yellow highlighted ones by using %AAA%.
Instead of running the Where command on each column one by one, I can do one general find option and it will list all the rows.
Thank you in advance!!
You can include all the conditions in one where clause using or:
where col1 like '%aaa%' or
col2 like '%aaa%' or
. . . -- and so on for all the columns
Unpivot the columns and do a WHERE based on that:
select *
from Table
where exists (select 1
from (values (col1), (col2), (col3) ) AS v (allCols) -- etc
where v.allCols like '%aaa%'
);
If you can't be bothered to type them out, try this little query:
select STRING_AGG('(' + c.name + ')', ', ')
from sys.columns c
where c.object_id = OBJECT_ID('Name_Of_Table_Here');
If you are using sql server then you can write dynamic query to do so. Please try below query:
declare #sql as varchar(max);
select #sql = 'select * from [TableName] where '
+ stuff((
select ' or [' + [column_name] + '] like ''%AAA%'''
from information_schema.columns
where table_name = 'TableName'
for xml path('')
)
, 1, 5, ''
);
exec(#sql);
This query will return every row in which at least one column contains AAA.
If you are using PostgreSQL, you can use its JSON functionality:
select t.*
from the_table t
where exists (select *
from jsonb_each(to_jsonb(t)) as x(col,val)
where val like '%AAA%');
If you are using Postgres 12 or later you can use a SQL/JSON path expression:
select t.*
from the_table t
where to_jsonb(t) ## '$.* like_regex "AAA" flag "i"'

Find all tables with a specific column name + where value is lower than X

I need help in searching our database. So we have this problem that we need to know all tables, with the column_name "sysmodified" and see if there are any entries before a specific date (25-sep-2019).
I tried to find the answer on google and stackoverflow, but I either get an answer how to get the results before 25-sep within 1 table Example1, or results how to get all tables, which has this column_name Example2.
Using the code I have so far (see below), we know that there are 325 tables, which contain the column_name "sysmodified". I could manually use example 1 to get my information, but I was hoping for a way to get the results that I need with just one query.
This is what I have so far:
USE [database2]
GO
SELECT t.name AS table_name,
SCHEMA_NAME(schema_id) AS schema_name,
c.name AS column_name
FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
WHERE c.name LIKE '%sysmodified%'
ORDER BY schema_name, table_name;
However if I try to enter anything like sysmodified < '20190925'. I get errors
WHERE c.name LIKE '%sysmodified%'
AND t.sysmodified < '20190925'
or this approach, which also results in errors
SELECT t.name AS table_name, sysmodified,
based on (but I cannot add 325 columnnames in FROM?)
SELECT
title,
primary_author,
published_date
FROM
books
WHERE
title LIKE 'The%'
Hopefully someone could help me with an approach how to tackle this problem. We use Microsoft SQL Server Management Studio 17 (if that might be relevant).
This is fairly simple dynamic sql to put together. This should produce the results you are looking for as I understand your requirements.
declare #SQL nvarchar(MAX) = ''
select #SQL = #SQL + 'select distinct TableName = ''' + object_name(object_ID) + ''' from ' + quotename(object_name(object_ID)) + ' where ' + quotename(c.name) + ' < ''20190925'' UNION ALL '
from sys.columns c
where name like '%sysmodified%'
set #SQL = left(#SQL, len(#SQL) - 10) --removes the final UNION ALL
select #SQL
--once you are comfortable that the dynamic sql is correct just uncomment the next line
--exec sp_executesql #SQL
The comment from GMB is right. Fastest way I can think of an answer is using Dynamic SQL.
I would build a query to loop through or create a union select statement across all tables that have that column. Something like:
(skeleton)
DECLARE #N_SQL NVARCHAR(MAX)
Find all tables that have a column with '%sysmodified%'
Build a dynamic query of (union style) from above like:
SET #N_SQL = ''
SELECT #N_SQL = #NSQL + 'UNION SELECT ' [SCHEMA] + '.' + [TABLENAME] + ' AS TABLENAME FROM ' + [SCHEMA] + '.' + [TABLENAME] + ' WHERE ' + [COLUMN] + ' >= '''<DATE>'''
SELECT #N_SQL --just to see what that string looks like
EXEC SP_EXECUTESQL RIGHT(#N_SQL, LEN(#N_SQL) - 5) --Trimming out the first word "UNION"
So, the above might work. Might need a bit of clean up, but its a skeleton idea.

SQL loop for each column in a table

Say I have a table called:
TableA
The following columns exist in the table are:
Column1, Column2, Column3
what I am trying to accomplish is to see how many records are not null.
to do this I have the following case statement:
sum(Case when Column1 is not null then 1 else 0 end)
What I want is the above case statement for every table that exists from a list provided and to be run for each columns that exists in the table.
So for the above example the case statment will run for Column1, Column2 and Column3 as there are 3 columns in that particular table etc
But I want to specfiy a list of tables to loop through executing the logic above
create procedure tab_cols (#tab nvarchar(255))
as
begin
declare #col_count nvarchar(max) = ''
,#col nvarchar(max) = ''
select #col_count += case ORDINAL_POSITION when 1 then '' else ',' end + 'count(' + QUOTENAME(COLUMN_NAME,']') + ') as ' + QUOTENAME(COLUMN_NAME,']')
,#col += case ORDINAL_POSITION when 1 then '' else ',' end + QUOTENAME(COLUMN_NAME,']')
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #tab
order by ORDINAL_POSITION
declare #stmt nvarchar(max) = 'select * from (select ' + #col_count + ' from ' + #tab + ') t unpivot (val for col in (' + #col + ')) u'
exec sp_executesql #stmt
end
Wouldn't it be easy as this?
SELECT AccountID
,SUM(Total) AS SumTotal
,SUM(Profit) AS SumProfit
,SUM(Loss) AS SumLoss
FROM tblAccount
GROUP BY AccountID
If I understand this correctly you want to get the sums, but not for all rows in one go but for each accountID separately. This is what GROUP BY is for...
If ever possible try to avoid loops, cursors and other procedural approaches...
UPDATE: Generic approach for different tables
With different tables you will - probably - need exactly the statement I show above, but you'll have to generate it dynamically and use EXEC to execute it. You can go through INFORMATION_SCHEMA.COLUMNS to get the columns names...
But:
How should this script know generically, which columns should be summed up? You might head for data_type like 'decimal%' or similar...
What about the other columns and their usage in GROUP BY?
How would you want to place aliases
How do you want to continue with a table of unknown structure?
To be honest: I think, there is no real-generic-one-for-all approach for this...

Dynamic Pivot in MS SQL Server

I am trying to do a dynamic pivot on the last two columns that i take from one table and am joining onto the contents of another table. I need the Name values to pivot to the header fields and the Value values to fill in correspondingly underneath. This is my current query:
USE Innovate
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(NAME)
FROM (SELECT DISTINCT NAME FROM Innovate.dbo.Table1 WHERE Name IS NOT NULL) AS ATTRIBUTE_NAME
WHERE Name LIKE 'Suture_-_2nd_Needle_Code'
OR Name LIKE 'Suture_-_Absorbable'
OR Name LIKE 'Suture_-_Antibacterial'
OR Name LIKE 'Suture_-_Armed'
OR Name LIKE 'Suture_-_Barbed'
OR Name LIKE 'Suture_-_Brand_Name'
OR Name LIKE 'Suture_-_C/R_2nd_Needle_Code'
OR Name LIKE 'Suture_-_C/R_Brand_Name'
OR Name LIKE 'Suture_-_C/R_length'
OR Name LIKE 'Suture_-_C/R_Needle_Code'
OR Name LIKE 'Suture_-_Coating'
OR Name LIKE 'Suture_-_Dyed'
OR Name LIKE 'Suture_-_Filament'
OR Name LIKE 'Suture_-_length_inches'
OR Name LIKE 'Suture_-_Looped'
OR Name LIKE 'Suture_-_Material'
OR Name LIKE 'Suture_-_Needle_Code'
OR Name LIKE 'Suture_-_Needle_Shape'
OR Name LIKE 'Suture_-_Needle_Style'
OR Name LIKE 'Suture_-_Noun'
OR Name LIKE 'Suture_-_pleget'
OR Name LIKE 'Suture_-_Popoff'
OR Name LIKE 'Suture_-_Suture_count'
OR Name LIKE 'Suture_-_Suture_size'
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT Table1.Primary_Key, Company_Name, Part_Number, Product_Desc, Innovate_Description, ' + #ColumnName + '
FROM Table1 AS P
LEFT JOIN Table2 AS A ON P.Primary_Key = A.Primary_Key
PIVOT(MAX(A.VALUE)
FOR A.NAME IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXECUTE sp_executesql #DynamicPivotQuery
;
And this is the Result for the Query that I keep getting: Msg 8156,
Level 16, State 1, Line 5 The column 'Primary_Key' was specified
multiple times for 'PVTTable'. Msg 4104, Level 16, State 1, Line 1 The
multi-part identifier "Table1.Primary_Key" could not be bound.
Can anyone help me pivot these columns without the error message? I only specified the Primary_Key in the code once so I do not know how I specified it multiple times and how it is unbound.
Try with the below script..
SET #DynamicPivotQuery =
N'SELECT P.Primary_Key, Company_Name, Part_Number, Product_Desc, Innovate_Description, ' + #ColumnName + '
FROM Table1 AS P
LEFT JOIN Table2 AS A ON P.Primary_Key = A.Primary_Key
PIVOT(MAX(A.VALUE)
FOR A.NAME IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXECUTE sp_executesql #DynamicPivotQuery
you have a couple of things going on with your PIVOT statement that are problematic. First you are attempting to reference table aliases for Table1 and Table2 but those aliases are not available in the final select of a PIVOT the Pivot command is kind of like an outer select and the only table alias that is then available is the pivot alias.
Next pivot's documenation states "You can use the PIVOT and UNPIVOT relational operators to change a table-valued expression into another table" (https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx). Basically that means that you need a single table expression of only the uniquely named columns you want to be involved pivot being passed to the PIVOT command. The later part is likely the issue as Table1 and Table2 probably both have a column Primary_Key so pivot doesn't understand the reference.
To fix you can either move your Table1 & 2. join to an inner select and alias the table or build a cte and use the cte in your command. Here is the former way:
SET #DynamicPivotQuery =
N'SELECT * FROM
(
SELECT Table1.Primary_Key, Company_Name, Part_Number, Product_Desc, Innovate_Description, ' + #ColumnName + '
FROM Table1 AS P
LEFT JOIN Table2 AS A ON P.Primary_Key = A.Primary_Key
) t
PIVOT(MAX(A.VALUE)
FOR NAME IN (' + #ColumnName + ')) AS PVTTable'
Those Name values get hardcoded to calculate that #ColumnName variable.
If the values are hardcoded anyway, then you might as well run the pivot with a join without building a SQL statement to execute.
SELECT A.Company_Name, A.Part_Number, A.Product_Desc, A.Innovate_Description, P.*
FROM (select Primary_Key, Name, Value from Innovate.dbo.Table1) T1
PIVOT(MAX(VALUE) FOR NAME IN (
[Suture_-_2nd_Needle_Code],
[Suture_-_Absorbable],
[Suture_-_Antibacterial],
[Suture_-_Armed],
[Suture_-_Barbed],
[Suture_-_Brand_Name],
[Suture_-_C/R_2nd_Needle_Code],
[Suture_-_C/R_Brand_Name],
[Suture_-_C/R_length],
[Suture_-_C/R_Needle_Code],
[Suture_-_Coating],
[Suture_-_Dyed],
[Suture_-_Filament],
[Suture_-_length_inches],
[Suture_-_Looped],
[Suture_-_Material],
[Suture_-_Needle_Code],
[Suture_-_Needle_Shape],
[Suture_-_Needle_Style],
[Suture_-_Noun],
[Suture_-_pleget],
[Suture_-_Popoff],
[Suture_-_Suture_count],
[Suture_-_Suture_size]
)
) P
LEFT JOIN Innovate.dbo.Table2 AS A ON (A.Primary_Key = P.Primary_Key);
Fair enough, this has a disadvantage that if that [Primary_Key] needs to be the first column, that the P.* should be replaced by those literal column values. Or use the EXEC approach after all.
Anyway, to build name list for that #ColumnName variable, it can be done without all the OR's :
DECLARE #ColumnName NVARCHAR(MAX);
--Get distinct values of the PIVOT Column
SELECT #ColumnName = ISNULL(#ColumnName + ',','') + QUOTENAME(NAME)
FROM Innovate.dbo.Table1
WHERE Name Like 'Suture_-_%'
AND SUBSTRING(Name,10,30) IN (
'2nd_Needle_Code',
'Absorbable',
'Antibacterial',
'Armed',
'Barbed',
'Brand_Name',
'C/R_2nd_Needle_Code',
'C/R_Brand_Name',
'C/R_length',
'C/R_Needle_Code',
'Coating',
'Dyed',
'Filament',
'length_inches',
'Looped',
'Material',
'Needle_Code',
'Needle_Shape',
'Needle_Style',
'Noun',
'pleget',
'Popoff',
'Suture_count',
'Suture_size')
GROUP BY Name;

Select from tables, where the table names are stored in another table

I'm trying to write a query in which I can select data from a series of tables. I want to be able to pull those table names FROM ANOTHER TABLE; I don't want to just write
select * from tableA union select * from tableB etc.
A further restriction that's complicating the issue is that my query MUST start with select.
I've tried to use OPENQUERY within the select statement but the server I'm trying to access is 'not configured for DATA ACCESS.'
You can do something like this:
DECLARE #SQL AS VARCHAR(MAX);
SELECT #SQL = COALESCE(#SQL + ' ', '') +
'SELECT * FROM ' + TableName +
CASE
WHEN TableName = MAX(TableName) OVER () THEN ''
ELSE ' UNION ALL '
END
FROM TableNames;
EXEC(#SQL);