Consider a simple join:
Select TableB.*
From TableA
Inner Join TableB
On TableB.ID = TableA.ID
What I want to do is decide which table to join to depending on a parameter. Although the following syntax is not valid, I wrote it just to illustrate what I am after:
Select TableD.*
From TableA
Inner Join
[If #useTableC = 1 Then Join to TableC Else Join to TableB] As TableD
Both TableB and TableC have identical columns.
How can I create this kind of join. Please be aware that this example is actually a small portion of a much larger query so you cannot just use If...Else statements.
Thanks a lot!
Usually I'd prefer to rewrite in more than one statement, but if you really need it:
SELECT td.*
FROM TableA ta
JOIN (
SELECT tc.* FROM TableC Where #useTableC = 1
UNION ALL
SELECT tb.* FROM TableB Where #useTableC = 0
) td ON ( /* JOIN CONDITION MISSING */)
Unfortunately, T-SQL has no syntax to support this. I tinkered with this some time ago and couldn't come up with a solution, until I found this article that helped me (by using Left Outer joins) and it might help you. It is located here.
#Gerardo Lima's answer is excellent, but as an alternative you could build up your query with dynamic SQL. This would let you use a series of if statements to add, or not, as many joins as you wish using as many if statements as needed. As a coarse and rough example:
declare #sql varchar(8000)
set #sql = 'Select TableD.* From TableA '
if #usertableC = 1
then select #sql = #sql + ' innner join tableC'
if #usertableC != 1
then select #sql = #sql + ' innner join tableB
exec #sql
There is a good discussion of dynamic sql at "the curse and blessing of dynamic sql"
Nice solution, Geraldo! There is not be a performance hit on using the union. I can't tell you why (perhaps someone can fill in the gaps here), but the execution plan shows 0 I/O cost for the constant scan.
However, this does approach depend on the schemas for TableB and TableC being identical. To do this with multiple statements, supporting disparate schemas in TableB and TableC, I'd consider the following approach:
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'SELECT td.* FROM TableA ta INNER JOIN Table' +
CASE WHEN #useTableC = 1 THEN 'C' ELSE 'B' END + ' td ON ta.ID = td.ID'
EXEC sp_executesql #sql
Note that it would then be up to your application code to determine the corresponding schema and read the data accordingly.
Related
I have a query that is merging 2 tables. Table 1 has many columns, and may eventually expand. Table 2 also has several columns, but I will be performing aggregate functions on 90% of its columns. Table 1 has 300 + rows, Table 2 has 84K + rows.
SELECT
t1.*
,t2.c2
,SUM(t2.c3)
,SUM(t2.c4)
FROM
Table1 AS t1
LEFT JOIN Table2 AS t2 ON t1.c10 = t2.c1
GROUP BY
t1.*
,t2.c2
I'm getting an error Incorrect Syntax near '*' and it points to the line containing the GROUP BY statement.
I am aware that the SELECT t1.* works as I ran this portion prior to trying to aggregate T2 columns and it worked as expected.
Is there a way to quickly GROUP BY all the columns in T1? I know normally we would select only needed columns, but in this case, I need all the T1 columns.
Previous research has led me to only find instances where 1 table was used, and mostly people were looking to get or remove duplicate values. I'm looking to specifically combine the 300 records of T1 to the 84K records of T2 without having to name off all the columns from T1 in the GROUP BY section.
This method is slightly unconventional, but you can pass it into a variable by using dynamic sql. Below is an example of how you can do it:
declare #test nvarchar(max)
set #test = ''
select #test += Column_name +',' from information_schema.columns where table_name='Table1'
DECLARE #sql nvarchar(max)
SELECT #sql = N'SELECT top 10 ' +#test+ 'NULL as a FROM Table1;'
EXEC sp_executesql #sql
You can apply the same principle and rewrite your query to use the group by function. Hope this helps.
Based on the article posted by #wosi, https://dba.stackexchange.com/questions/21226/why-do-wildcards-in-group-by-statements-not-work, I was able to modify the code and get the expected results. Please note I went from 80K to 70K because I was joining the tables on 1 column. The way my data was structured I had to join on 2 columns. Final code looks something like this:
SELECT
t1.*
,t2.c2
,t2.c3
,t2.c4
FROM
Table1 AS t1
LEFT JOIN
(SELECT c2, SUM(c3), SUM(c4)
FROM Table2
GROUP BY c2) AS t2
ON t1.c10 = t2.c1 AND t1.c15 = t2.c2
You can't use * in GroupBy Statement. Of course, there are some Dynamic SQL to prevent typing all columns in the SP but if you are using T-SQL in a view you should type all columns.
Like we had query to find no of columns in a Table, is there any similar query to find total no of columns in a Sql Server View ?
Even simpler is to use sys.columns.
select count(*)
from sys.columns
where OBJECT_ID = OBJECT_ID('YourView')
Similar to Larnu's comment, I tend to prefer the Table-Valued-Function
Example
Declare #tsql nvarchar(max) = N'Select * from YourView_Table_Or_Query'
Select column_ordinal
,name
,system_type_name
From sys.dm_exec_describe_first_result_set(#tsql,null,null )
-- Or for the Count
Select ColumnCnt=count(*)
From sys.dm_exec_describe_first_result_set(#tsql,null,null)
Besides the rather clumsy procedure sp_describe_first_reuslt_set you can use the generic abilities of XML:
SELECT (SELECT TOP 1 * FROM YourView FOR XML RAW, ELEMENTS XSINIL ,TYPE).value('count(/row/*)','int');
Edit: Forgot to add ELEMENTS XSNIL which would omit columns with a value of NULL otherwise...
--this might help
SELECT v.name, count(1) ColumnCount
FROM SYS.VIEWS v
INNER JOIN SYS.all_columns c ON v.object_id = c.object_id
WHERE v.name IN (SELECT name FROM sys.VIEWS)
GROUP BY v.name
ORDER BY v.name
SELECT name
FROM sys.VIEWS, can be replaced by viewname/s or query which returns viewname/s
I have two tables that I have joined together. I'd like to join the result of the joined table with the results of a stored procedure that has two variables.
I'm not sure whether or not I should create two temporary tables or another function, so I'm a little lost on where I should even start and what the easiest method would be.
Below is my first join.
SELECT *
FROM dbo.Users a WITH (NOLOCK)
JOIN Company b ON a.email = b.email
Below is my stored procedure, all it does is split one column into more rows. Split is another function. I would like to use an inner join.
SELECT a.*, b.*
FROM [dbo].[Menu] a
CROSS APPLY dbo.Split(SalesPersons, ',') b
WHERE ID = #ID AND Date = #Date
The easiest way to do this, assuming that the output from the stored procedure is deterministic would be to populate the output of the stored procedure into a temp table and then join to it.
CREATE TABLE #tmp
(
COL1 INT NOT NULL,
COL2 INT NOT NULL
)
INSERT INTO #tmp
Exec sproc_YourSproc 'Params'
SELECT *
FROM dbo.Users u
INNER JOIN dbo.Company c ON u.email = c.email
INNER JOIN #tmp t ON t.ID = c.ID
That being said, as Martin Smith said above, you probably want to move that logic into the stored procedure if possible.
Also, please don't use (NOLOCK) it doesn't really help the way most people think that it does, and it can cause some really nasty results. (Double reading rows, ghost records, ect)
If you need to be able to perform reads without causing read/write contention, I would investigate using more optimistic isolation levels, find ways to optimize the read performance to reduce possible congestion, or find indexing strategies that would make it possible to satisfy reads without locking the table itself.
As per my requirement, I have to find if some words like xyz#test.com value exists in which tables of columns. The database size is very huge and more than 2500 tables.
Can anyone please provide an optimal way to find this type of value from the database. I've created a loop query which took around almost more than 9 hrs to run.
9 hours is clearly a long time. Furthermore, 2,500 tables seems close to insanity for me.
Here is one approach that will run 1 query per table, not one per column. Now I have no idea how this will perform against 2,500 tables. I suspect it may be horrible. That said I would strongly suggest a test filter first like Table_Name like 'OD%'
Example
Declare #Search varchar(max) = 'cappelletti' -- Exact match '"cappelletti"'
Create Table #Temp (TableName varchar(500),RecordData xml)
Declare #SQL varchar(max) = ''
Select #SQL = #SQL+ ';Insert Into #Temp Select TableName='''+concat(quotename(Table_Schema),'.',quotename(table_name))+''',RecordData = (Select A.* for XML RAW) From '+concat(quotename(Table_Schema),'.',quotename(table_name))+' A Where (Select A.* for XML RAW) like ''%'+#Search+'%'''+char(10)
From INFORMATION_SCHEMA.Tables
Where Table_Type ='BASE TABLE'
and Table_Name like 'OD%' -- **** Would REALLY Recommend a REASONABLE Filter *** --
Exec(#SQL)
Select A.TableName
,B.*
,A.RecordData
From #Temp A
Cross Apply (
Select ColumnName = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)')
From A.RecordData.nodes('/row') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Where a.value('.','varchar(max)') Like '%'+#Search+'%'
) B
Drop Table #Temp
Returns
If it Helps, the individual queries would look like this
Select TableName='[dbo].[OD]'
,RecordData= (Select A.* for XML RAW)
From [dbo].[OD] A
Where (Select A.* for XML RAW) like '%cappelletti%'
On a side-note, you can search numeric data and even dates.
Make a procedure with VARCHAR datatype of column with table name and store into the temp table from system tables.
Now make one dynamic Query with executing a LOOP on each record with = condition with input parameter of email address.
If condition is matched in any statement using IF EXISTS statement, then store that table name and column name in another temp table. and retrieve the list of those records from temp table at end of the execution.
I can easily create a stored procedure in SQL Server with parameters that I use with =, LIKE and most operators. But when it comes to using IN, I don't really understand what to do, and I can't find a good site to teach me.
Example
CREATE PROCEDURE TEST
#Ids --- What type should go here?
AS BEGIN
SELECT * FROM TableA WHERE ID IN ( #Ids )
END
Is this possible and if so how ?
With SQL Server 2008 and above, you can use Table Valued Parameters.
You declare a table type and can use that as a parameter (read only) for stored procedures that can be used in IN clauses.
For the different options, I suggest reading the relevant article for your version of the excellent Arrays and Lists in SQL Server, by Erland Sommarskog.
I've done this in the past using a Split function that I add to my schema functions as described here
Then you can do the following:
CREATE PROCEDURE TEST
#Ids --- What type should go here?
AS BEGIN
SELECT * FROM TableA WHERE ID IN ( dbo.Split(#Ids, ',') )
END
Just remember that the IN function always expects a table of values as a result. SQL Server is smart enough to convert strings to this table format, so long as they are specifically written in the procedure.
Another option in your specific example though, could be to use a join. This will have a performance improvement, but often does not really meet a real-world example you need. The join version would be:
SELECT *
FROM TableA AS ta
INNER JOIN dbo.Split(#Ids, ',') AS ids
ON ta.Id = ids.items
If your asking what I think your asking, I do this every day..
WITH myData(FileNames)
AS
(
SELECT '0608751970%'
UNION ALL SELECT '1000098846%'
UNION ALL SELECT '1000101277%'
UNION ALL SELECT '1000108488%'
)
SELECT DISTINCT f.*
FROM tblFiles (nolock) f
INNER JOIN myData md
ON b.FileNames LIKE md.FileNames
Or if your doing this based on another table:
WITH myData(FileNames)
AS
(
SELECT RTRIM(FileNames) + '%'
FROM tblOldFiles
WHERE Active=1
)
SELECT DISTINCT f.*
FROM tblFiles (nolock) f
INNER JOIN myData md
ON b.FileNames LIKE md.FileNames