Multiple SELECTs in one query using same WITH - sql

This query filters some IDs from a master table, and returns all data abpout that IDs from two other tables as two record sets:
WITH filteredIds (fid) AS
(
SELECT id
FROM MasterTable
WHERE <somecondition>
)
SELECT *
FROM Table1
RIGHT JOIN filteredIds ON (Table1.id = filteredIds.fid);
WITH filteredIds (fid) AS
(
SELECT id
FROM MasterTable
WHERE <somecondition>
)
SELECT *
FROM Table2
RIGHT JOIN filteredIds ON (Table2.id = filteredIds.fid);
This works so far, but it would be great to have just one WITH clause, since the condition is always the same. Further more, the condition is often written manually, since this is used to collect some diagnostics data.
But this
WITH filteredIds (fid) AS
(
SELECT id
FROM MasterTable
WHERE <somecondition>
)
SELECT *
FROM Table1
RIGHT JOIN filteredIds ON (Table1.id = filteredIds.fid);
SELECT *
FROM Table2
RIGHT JOIN filteredIds ON (Table2.id = filteredIds.fid);
does not work, SQL Server claims it does not know object filteredIds in the last query.
Have I overseen something, or doesn't it work that way? I guess the alternative would be a temporary table.

Once a CTE has been consumed in a query, it cannot be reused in another query. There is no direct workaround as far as I know. The closest thing perhaps to want you want would be to create a bona fide temporary table:
CREATE TABLE #temp (fid int);
INSERT INTO #temp (fid)
SELECT id FROM MasterTable WHERE <somecondition>;
Then, you may reuse the temporary table directly:
SELECT * FROM Table1 a RIGHT JOIN #temp b ON a.id = b.fid;
SELECT * FROM Table2 a RIGHT JOIN #temp b ON a.id = b.fid;

You can't do what you want with CTE. CTE doesn't exist outside the query where it was defined.
I think that the closest thing to what you want is called a "view".
CREATE VIEW filteredIds
AS
SELECT id AS fid
FROM MasterTable
WHERE <somecondition>
;
And now you can use that view in many other queries.
SELECT *
FROM Table1
RIGHT JOIN filteredIds ON (Table1.id = filteredIds.fid);
SELECT *
FROM Table2
RIGHT JOIN filteredIds ON (Table2.id = filteredIds.fid);
Furthermore, in SQL Server it will work exactly like a CTE in a sense that the list of filtered IDs is not materialized and the engine will optimize the whole query that references the view. It will inline the view definition into the outer / main query as if you wrote everything in one big query. Just like it would do with CTE.
With a temporary table you explicitly write this temporary result to disk and read it back.
In some cases it may be better, in some not.

Related

Pass values as parameter from select query

I want to pass values from output of select query to another query. Basically both queries will be part of a stored procedure. e.g.
select Id, RelId
from tables
There will be multiple rows returned by above query and I want to pass them to the following query
select name
from table2
where Id = #Id and MgId = #RelId
Please suggest
You cannot pass multiple values in SQL.
But maybe you can just join your 2 tables, that would be far more efficient.
Not knowing your table schemes I suggest something like this. You might have to adapt this to your actual table schemas off course
select name
from table2 t2
inner join tables t on t2.Id = t.Id
and t2.MgId = t.RelId
EDIT
As Gordon mentioned in his answer, this approach can show double rows in your result.
If you don't want that than here are 2 ways of getting rid of the doubles
select distinct name
from ...
or by grouping by adding this at the end of the statement
group by name
Though this will work, avoiding the doubles like in Gordon's answer is better
I would suggest using exists:
select t2.name
from table2 t2
where exists (select 1
from tables t
where t2.Id = t.Id and t2.MgId = t.RelId
);
The difference between exists and join is that this will not generate duplicates, if there are multiple matches between the tables.
Or...
SELECT *
INTO #Table1
FROM ...
SELECT *
INTO #Table2
FROM ...
SELECT *
FROM #Table1 T1
JOIN #Table2 T2
DROP TABLE #Table1, #Table2

SQL subquery to joins -

Is it possible to remove the subquery from this SQL?
Table has 2 attributes "id" and "field"
Many field could have the same Id.
These table has many registers with the same Id and different Value
In need get all same Id values using one of them like filter.
select *
from Table
where id = (select id from Table where value = 'someValue')
I think it could be really easy but I don't know how to do.
Self Join can be done
select T.Id,T.Field
from Table T
INNER JOIN Table TT
ON T.ID = TT.ID
AND TT.Value = 'someValue'
Not sure if you over simplified your example too much but you could make this a little simpler.
select *
from Table
where value = 'someValue'
This should work
select T1.* from Table T1 JOIN Table T2 ON T1.id = T2.id AND T2.value = 'someValue'
Edited (Correct Answer):
What I assume your problem is:
You have a value. Let´s pretend it´s "testValue". Now you want to get the id of this value and find all other datasets with the same id.
What has to be cleared is that, "ID" is not the Primary Key and is not Unique.
You should be able to solve this by a simple self join:
select t.* from Table t right join Table tt on tt.id = t.id where tt.value = 'someValue';
So because of the join you will get a result that returns simply the table. With the where clause you shrink the result to your value. You should get the set of ids.
Old Answer:
This should do the trick:
select * from Table a inner join Table2 b on a.id = b.id where b.value = 'someValue';
You mentioned only one table in your question. I think this must be a mistake. If not, you have to change only the Table2 in my query. But that would have no sense as you could do a simple query, too:
select * from Table where value = 'someValue';
this would be the result of the first query with a self join.

Slow performance query

I'm having a slow query performance and sometimes it came to an error of "Can't allocate space for object 'temp work table'"
I have 2 tables and 1 view. The first two tables have an left join and the last view will do a sub query. Below is the sample query.
SELECT a.*
FROM Table1 a LEFT JOIN Table2 b ON a.ID = b.ID
WHERE a.ID (SELECT ID
FROM View1).
The above query is very slow. BUT when I used a #temp table it becomes faster.
SELECT ID
INTO #Temp
FROM View1
SELECT a.*
FROM Table1 a LEFT JOIN Table2 b ON a.ID = b.ID
WHERE a.ID IN (SELECT ID
FROM #Temp)
Could someone explain why the first sql statement is very slow? and kindly give me an advise like adding new index?
Note: The first query statement cannot be altered or modified. I used only the second query statement to show to my team that if we put the 3rd table into temporary table and used it, makes faster.
Basically in the first query you are accessing the view for each and every row, and in turn the view is executing it's query.
In the second one you are executing the view's query just once and using the returned results through the temp table.
Try:
SELECT a.*
FROM Table1 a LEFT JOIN Table2 b ON a.ID = b.ID,
(SELECT ID
FROM View1) c
WHERE a.ID = c.ID;

How to join a table when the to be joined table is a variable in column

I have a table called Table1.
The table contains columns like:
The sourceID (ID of the join)
The sourceTable (the table to be joined table, this is variable)
My query:
SELECT T.Category, J.Tariff
FROM Table1 as T
INNER JOIN T.SourceTable J ---- This needs to be changed
ON T.sourceID = J.id
Is this possible?
Basically the source table can be different for every row
I'd say that's generally a bad idea. Even if possible, it basically prevents the optimiser from doing much (which can cause the query to slow down a lot).
I suggest changing your database design to make what you're trying to do easier (think having a base table which the tables you want to join on have a 1-to-1 mapping to) rather than trying to get this to work.
You can use Union, and filter by the column with the table pointer. Something like this:
SELECT T.Category, J.Tariff
FROM Table1 as T
INNER JOIN T.SourceTable1 J ON T.sourceID = J.id
where T.sourceTable = '1'
union
SELECT T.Category, J.Tariff
FROM Table1 as T
INNER JOIN T.SourceTable2 J ON T.sourceID = J.id
where T.sourceTable = '2'
Table names cannot be dynamic in an SQL Query. You have to find a different way to do this. Use a Stored Procedure and/or EXEC().
In principle it's possible using left joins, but only if you have a fixed set of lookup tables (otherwise, you'll need to build your SQL statement dynamically):
SELECT T.Category, coalesce(source1.tariff, source2.tariff) as tariff
FROM Table1 as T
LEFT OUTER JOIN T.source1
ON T.sourceID = source1.id and t.sourcetable = 'source1'
LEFT OUTER JOIN T.source2
ON T.sourceID = source2.id and t.sourcetable = 'source2'
but as mentioned in the other answers, that's usually a sign that your database design is flawed.
Try something like this:
1) Create a VIEW with all source tables:
CREATE VIEW source_tables AS
(SELECT 'table_1' as table_name,
id,
value
FROM table_1
UNION ALL
SELECT 'table_2' as table_name,
id,
value
FROM table_2
UNION ALL
..........
SELECT 'table_n' as table_name,
id,
value
FROM table_n);
2) Use the VIEW to get the values from source table:
SELECT T.category, S.value
FROM base_table as T
JOIN source_tables S ON T.sourceID = S.id AND T.SourceTable = S.table_name

An issue possibly related to Cursor/Join

Here is my situation:
Table one contains a set of data that uses an id for an unique identifier. This table has a one to many relationship with about 6 other tables such that.
Given Table 1 with Id of 001:
Table 2 might have 3 rows with foreign key: 001
Table 3 might have 12 rows with foreign key: 001
Table 4 might have 0 rows with foreign key: 001
Table 5 might have 28 rows with foreign key: 001
I need to write a report that lists all of the rows from Table 1 for a specified time frame followed by all of the data contained in the handful of tables that reference it.
My current approach in pseudo code would look like this:
select * from table 1
foreach(result) {
print result;
select * from table 2 where id = result.id;
foreach(result2) {
print result2;
}
select * from table 3 where id = result.id
foreach(result3) {
print result3;
}
//continued for each table
}
This means that the single report can run in the neighbor hood of 1000 queries. I know this is excessive however my sql-fu is a little weak and I could use some help.
LEFT OUTER JOIN Tables2-N on Table1
SELECT Table1.*, Table2.*, Table3.*, Table4.*, Table5.*
FROM Table1
LEFT OUTER JOIN Table2 ON Table1.ID = Table2.ID
LEFT OUTER JOIN Table3 ON Table1.ID = Table3.ID
LEFT OUTER JOIN Table4 ON Table1.ID = Table4.ID
LEFT OUTER JOIN Table5 ON Table1.ID = Table5.ID
WHERE (CRITERIA)
Join doesn't do it for me. I hate having to de-tangle the data on the client side. All those nulls from left-joining.
Here's a set-based solution that doesn't use Joins.
INSERT INTO #LocalCollection (theKey)
SELECT id
FROM Table1
WHERE ...
SELECT * FROM Table1 WHERE id in (SELECT theKey FROM #LocalCollection)
SELECT * FROM Table2 WHERE id in (SELECT theKey FROM #LocalCollection)
SELECT * FROM Table3 WHERE id in (SELECT theKey FROM #LocalCollection)
SELECT * FROM Table4 WHERE id in (SELECT theKey FROM #LocalCollection)
SELECT * FROM Table5 WHERE id in (SELECT theKey FROM #LocalCollection)
Ah! Procedural! My SQL would look like this, if you needed to order the results from the other tables after the results from the first table.
Insert Into #rows Select id from Table1 where date between '12/30' and '12/31'
Select * from Table1 t join #rows r on t.id = r.id
Select * from Table2 t join #rows r on t.id = r.id
--etc
If you wanted to group the results by the initial ID, use a Left Outer Join, as mentioned previously.
You may be best off to use a reporting tool like Crystal or Jasper, or even XSL-FO if you are feeling bold. They have things built in to handle specifically this. This is not something the would work well in raw SQL.
If the format of all of the rows (the headers as well as all of the details) is the same, it would also be pretty easy to do it as a stored procedure.
What I would do: Do it as a join, so you will have the header data on every row, then use a reporting tool to do the grouping.
SELECT * FROM table1 t1
INNER JOIN table2 t2 ON t1.id = t2.resultid -- this could be a left join if the table is not guaranteed to have entries for t1.id
INNER JOIN table2 t3 ON t1.id = t3.resultid -- etc
OR if the data is all in the same format you could do.
SELECT cola,colb FROM table1 WHERE id = #id
UNION ALL
SELECT cola,colb FROM table2 WHERE resultid = #id
UNION ALL
SELECT cola,colb FROM table3 WHERE resultid = #id
It really depends on the format you require the data in for output to the report.
If you can give a sample of how you would like the output I could probably help more.
Join all of the tables together.
select * from table_1 left join table_2 using(id) left join table_3 using(id);
Then, you'll want to roll up the columns in code to format your report as you see fit.
What I would do is open up cursors on the following queries:
SELECT * from table1 order by id
SELECT * from table1 r, table2 t where t.table1_id = r.id order by r.id
SELECT * from table1 r, table3 t where t.table1_id = r.id order by r.id
And then I would walk those cursors in parallel, printing your results. You can do this because all appear in the same order. (Note that I would suggest that while the primary ID for table1 might be named id, it won't have that name in the other tables.)
Do all the tables have the same format? If not, then if you have to have a report that can display the n different types of rows. If you are only interested in the same columns then it is easier.
Most databases have some form of dynamic SQL. In that case you can do the following:
create temporary table from
select * from table1 where rows within time frame
x integer
sql varchar(something)
x = 1
while x <= numresults {
sql = 'SELECT * from table' + CAST(X as varchar) + ' where id in (select id from temporary table'
execute sql
x = x + 1
}
But I mean basically here you are running one query on your main table to get the rows that you need, then running one query for each sub table to get rows that match your main table.
If the report requires the same 2 or 3 columns for each table you could change the select * from tablex to be an insert into and get a single result set at the end...