I have a procedure with select from 4 tables. In dependence on input variable, I want to join and show 2 or all 4. So if the #var = 1 only table1 and table2 will be output, if #var = 2 join from all 4 tables is output.
What I have tried is this:
SELECT * FROM t1
JOIN t2 ON t1.pk1 = t2.pk1
LEFT JOIN t3 ON t2.pk2 = t3.pk2 AND CASE #var = 2 THEN 1 ELSE = 0 END = 1
LEFT JOIN t4 ON t3.pk3 = t4.pk3 AND CASE #var = 2 THEN 1 ELSE = 0 END = 1
But the output is shown only for variable = 2 otherwise the output is empty.
In reality I have a lot more tables connected together and these 2 are needed only for WHERE part (more filtering). When I don't get any columns to filter from t3 and t4 I want to show everything without any connection with these tables. When I have data to filter from t3 and t4 I need to join them and use them in WHERE clause.
EDIT:
So the final solution is like this:
IF (#var = 2)
BEGIN
SELECT * FROM t1
JOIN t2 ON t1.pk1 = t2.pk1
LEFT JOIN t3 ON t2.pk2 = t3.pk2
LEFT JOIN t4 ON t3.pk3 = t4.pk3
END
ELSE
BEGIN
SELECT * FROM t1
JOIN t2 ON t1.pk1 = t2.pk1
END
Are you looking for this?
Let's create a mockup scenario to simulate your issue:
DECLARE #t1 TABLE(ID INT IDENTITY,SomeValue VARCHAR(100));
DECLARE #t2 TABLE(ID INT IDENTITY,t1ID INT /*FK to t1*/,SomeValue VARCHAR(100));
DECLARE #t3 TABLE(ID INT IDENTITY,t2ID INT /*FK to t2*/,SomeValue VARCHAR(100));
DECLARE #t4 TABLE(ID INT IDENTITY,t3ID INT /*FK to t3*/,SomeValue VARCHAR(100));
INSERT INTO #t1 VALUES('Row 1'),('Row 2');
INSERT INTO #t2 VALUES(1,'Row 1.1'),(1,'Row 1.2'),(2,'Row 2.1');
INSERT INTO #t3 VALUES(1,'Row 1.1.1'),(1,'Row 1.1.2'),(2,'Row 1.2.1'),(3,'Row 2.1.1');
INSERT INTO #t4 VALUES(1,'Row 1.1.1.1'),(1,'Row 1.1.1.2'),(4,'Row 2.1.1.1'),(4,'Row 2.1.1.2');
--Now we need your variable #var
DECLARE #var INT;
--We can set this to either 1 or 2 (or leave it unset, which is NULL)
--SET #var=1; --show all
SET #var=2; --just t1 and t2
--Check the query's result with various input:
SELECT *
FROM #t1 t1
INNER JOIN #t2 t2 ON t1.ID = t2.t1ID
LEFT JOIN #t3 t3 ON t2.ID = t3.t2ID AND ISNULL(#var,1)=1
LEFT JOIN #t4 t4 ON t3.ID = t4.t3ID AND ISNULL(#var,1)=1;
The idea in short:
The ON-clause will return FALSE due to the AND in any case, where #var is neiter NULL nor 1.
Both values 1 and NULL will be taken as 1. Any other value will not be equal to 1.
But I'm not fully sure, if I really got what you need...
Related
I am pretty new to the t-sql world and am trying to create a query that will change a value based on multiple criteria.
TSH1 is the main table that values will be changed in.
Freightview is the table that has the shipping amount I need to add into TSH1.
I want the query to look for matches between the tables and when there is one make a change to the FREIGHT line if it exists. If the FREIGHT line doesn't exist then it needs to add a line with the invoice amount from Freightview table.
My issue is the IF statement. It is returning two many values for the query to work. What do I need to change?
The last two queries are to return values that are not in each table.
SELECT *
FROM TSH1 T
JOIN Freightview FR on FR.[Shippers number] = T.sonum
IF
((SELECT [Shippers number] FROM Freightview) = (SELECT sonum FROM TSH1 T WHERE EXISTS(SELECT * FROM TSH1 T WHERE T.productnum = 'FRT-OUT' OR T.productnum = 'FRT-IN' OR T.productnum = 'FRT')))
BEGIN
UPDATE TSH1 SET tcost = FR.[Invoice Amount] FROM TSH1 T INNER JOIN Freightview FR on FR.[Shippers number] = T.sonum
WHERE T.productnum = 'FRT-OUT' OR T.productnum = 'FRT-IN' OR T.productnum = 'FRT';
END
ELSE IF
((SELECT [Shippers number] FROM Freightview) = (SELECT sonum FROM TSH1 T WHERE NOT EXISTS(SELECT * FROM TSH1 T WHERE T.productnum = 'FRT-OUT' OR T.productnum = 'FRT-IN' OR T.productnum = 'FRT')))
BEGIN
SELECT * INTO temp_table FROM TSH1 T INNER JOIN Freightview FR on FR.[Shippers number] = T.sonum
WHERE FR.[Shippers number] = T.sonum AND NOT EXISTS (SELECT productnum from TSH1 T where T.productnum = 'FRT-OUT' OR T.productnum = 'FRT-IN' OR T.productnum = 'FRT');
UPDATE temp_table SET temp_table.productnum = 'FRT', [Invoice Amount] = TT.tcost, temp_table.productid = '7240', temp_table.pd = 'FREIGHT', temp_table.qtyfulfilled = 1,
temp_table.tprice = 0, temp_table.stdcost = 0, temp_table.flag = 'D', temp_table.avgcost = NULL
FROM temp_table TT
INNER JOIN Freightview FR on TT.sonum = FR.[Shippers number];
UPDATE temp_table SET ID=NULL;
DELETE x FROM (
SELECT *, rn=row_number() over (partition by TT.sonum order by TT.soid)
FROM temp_table TT
) x
WHERE rn > 1;
INSERT INTO TSH1 SELECT * FROM temp_table;
DROP TABLE temp_table;
END
ELSE
BEGIN
SELECT *
FROM TSH1 T
LEFT JOIN Freightview FR on T.sonum = FR.[Shippers number]
WHERE FR.[Shippers number] IS NULL;
END
BEGIN
SELECT *
FROM Freightview FR
LEFT JOIN TSH1_Backup T on T.sonum = FR.[Shippers number]
WHERE T.sonum IS NULL;
END
END```
With SQL, you typically have to "think in sets". For example, a select statement returns a set of values, not just a single value1.
If I select * from T, the result might have multiple rows.
If I insert T1 select * from T2, multiple rows might be inserted into T1.
So, a statement like
if ((select c from T1) = (select c from T2))
Is sort of an odd construct. What exactly are we comparing here? On the left hand side we have zero or more rows from T1, and on the right hand side we zero or more rows from T2.
Now, you might be thinking to yourself...
Well the answer is obvious. If the two result sets are identical, then the equality comparison should return true, right?
Well... yes. It would be nice if we could do that. But that would require that SQL think of the result of a select statement as "an anonymous collection type with member-wise value equality semantics". And SQL is not that sophisticated as a language. In SQL, if you're comparing one thing to another with =, the left hand side and the right hand side should both be scalar types. "Single values", like an int, or a float, or a boolean. Not sets.
Fundamentally, it's the same reason why you can't do this:
create table T1(i int);
create table T2(j int);
if (T1 = T2) print 'tables had exactly the same content`;
So, how do you get the semantics "tell me if the contents of T1 and T2 exactly match?". There's no compact syntax to do this, you have to be verbose about it, there are lots of different ways you can "phrase" the question, and it's easy to make a mistake. Here's one correct way:
create table T1(i int);
create table T2(j int);
if not exists
(
select *
from T1
full join T2 on T1.i = T2.j
where T1.i is null or T2.j is null
) print 'tables had exactly the same content';
The logic is "match every row that you can, and tell me if there are any rows that couldn't be matched".
Now, interestingly enough SQL doesn't "validate" the comparison until it actually gets its results, so if your select statements each happen to return just a single row and single column, then the result of the select statement is treated as a scalar value, not a set, and then the equality comparison works. I sort of wish it didn't, because it's inconsistent and confuses people:
create table T1(i int);
create table T2(j int);
insert T1 values (1);
insert T2 values (1);
-- This will unfortunately succeed, and do what you intuitively "expect".
if ((select i from T1) = (select j from T2))
print 'tables both exactly one row with the same value';
But what if I put more rows into one of the tables?
create table T1(i int);
create table T2(j int);
insert T1 values (1), (2);
insert T2 values (1);
-- This will fail
if ((select i from T1) = (select j from T2))
print 'tables both exactly one row with the same value';
The error is:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
You have some SQL that makes this same mistake:
if ((select [shippers number] from Freightview) = -- ...
I hope this answers your specific question about why you're getting the error. But hang on, let's go back and look at your requirements:
I want the query to look for matches between the tables and when there is one make a change to the FREIGHT line if it exists. If the FREIGHT line doesn't exist then it needs to add a line with the invoice amount from Freightview table.
So, you want a combination of insert and update, depending on the data. An "upsert".
TSQL has a statement which can do exactly this: Merge. Here's a simplified example to demonstrate how to use it.
create table T1(i int, c char);
create table T2(j int, c char);
insert T1 values (1, 'a');
insert T2 values (1, 'b'), (2, 'c');
merge T1 -- T1 will be "target" in the rest of the merge statement
using T2 on t2.j = T1.i -- T2 will be "source" in the rest of the merge statment
when matched then
update
set T1.c = T2.c
-- "target" isn't an alias defined by me. It's defined by the structure of "merge"
-- So this condition translates to "if there is a row in T2 with no matching row in T1"
when not matched by target then
insert (i, c)
values (T2.j, T2.c);
select * from T1;
/* result:
i c
----
1 b
2 c
*/
Formatting merge statements is hard, I've never found a way to do it that I am totally happy with.
1 That's not really accurate. SQL allows duplicate rows to exist in tables, result sets, and so on. In mathematics sets cannot have duplicate members. So technically you have to "think in bags". But people tend to say "think in sets" despite this.
I have two tables with identical columns, in an identical order. I have a desire to join across one of the two tables, depending on a subquery condition. For example, assume I have the following schema:
CREATE TABLE b (
bid SERIAL PRIMARY KEY,
cid INT NOT NULL
);
CREATE TABLE a1 (
aid SERIAL PRIMARY KEY,
bid INT NOT NULL REFERENCES b
);
CREATE TABLE a2 (
aid SERIAL PRIMARY KEY,
bid INT NOT NULL REFERENCES b
);
I would like a query, that performs a join across either a1 or a2 based on some condition. Something like:
WITH z AS (
SELECT cid, someCondition FROM someTable
)
SELECT *
FROM CASE z.someCondition THEN a1 ELSE a2 END
JOIN b USING (bid)
WHERE cid = (SELECT cid FROM z);
However, the above doesn't work. Is there some way to conditionally join across a1 or a2, depending on some boolean condition stored in table z?
If the conditions are exclusive (I expect they are): just do both queries and UNION ALL them, with the smart union construct:
WITH z AS (
SELECT cid
, (cid %3) AS some_condition -- Fake ...
FROM b
)
SELECT *
FROM a1
JOIN b USING (bid)
WHERE EXISTS( SELECT * FROM z
WHERE some_condition = 1 AND cid = b.cid )
UNION ALL
SELECT *
FROM a2
JOIN b USING (bid)
WHERE EXISTS( SELECT * FROM z
WHERE some_condition = 2 AND cid = b.cid )
;
A somewhat different syntax to do the same:
WITH z AS (
SELECT cid
, (cid %3) AS some_condition
FROM b
)
SELECT *
FROM a1
JOIN b ON a1.bid = b.bid
AND EXISTS( SELECT * FROM z
WHERE some_condition = 1 AND cid = b.cid )
UNION ALL
SELECT *
FROM a2
JOIN b ON a2.bid = b.bid
AND EXISTS( SELECT * FROM z
WHERE some_condition = 2 AND cid = b.cid )
;
SQL syntax does not allow conditional joins.
Probably the simplest way to achieve a similar effect is to use a dynamic query in a plpgsql function, which may look like this:
create function conditional_select(acid int, some_condition boolean)
returns table (aid int, bid int, cid int)
language plpgsql as $$
declare
tname text;
begin
if some_condition then tname = 'a1';
else tname = 'a2';
end if;
return query execute format ($fmt$
select a.aid, b.bid, b.cid
from %s a
join b using(bid)
where cid = %s;
$fmt$, tname, acid);
end $$;
select * from conditional_select(1, true)
If, like in your example, you have only a few columns that you want to output, you can use the CASE statement for every column:
SELECT CASE z.someCondition THEN a1.aid ELSE a2.aid END AS aid,
CASE z.someCondition THEN a1.bid ELSE a2.bid END AS bid
FROM b
JOIN a1 ON a1.bid = b.bid
JOIN a2 ON a2.bid = b.bid
JOIN someTable z USING (cid);
Depending on the size of tables a1 and a2 and how many columns you have to output, this may or my not be faster than Klin's solution with a function, which is inherently slower than plain SQL and even more so because of the dynamic query. Given that z.someCondition is a boolean value already, the CASE evaluation will be very fast. Small tables + few columns = this solution; large tables + many columns = Klin's solution.
I have two tables Part and Service Entry Part. The first statement returns all parts and the second statement returns parts with a service type of 13.
What I need to do is if the second select returns a part then the record from second select should be inlcuded and that part from the first select should be be excluded
DECLARE #run_log TABLE(
[Instrument] nvarchar(max),
[SubSystem] NVARCHAR(max),
[AbPartNumber] NVARCHAR(max),
[RSLMSPartID] int,
[PartDescriptionWithParent] NVARCHAR(max),
[PartRevisionNumber] NVARCHAR(max),
[ServiceEntryID] int,
[Date] datetime,
[TSBNumber] nvarchar (max),
[CRNumber] nvarchar(max)
)
insert #run_log
-- parts with default revision number
select
System.SystemFullName as Instrument,
Part.System as SubSystem,
Part.AbPartNo as AbPartNumber,
Part.ID as RSLMSPartID,
Part.PartDescriptionWithParent,
Part.RevisionNumber as PartRevisionNumber,
NULL as ServiceEntryID,
NULL as Date,
NULL as TSBNumber,
NULL as CRNumber
from Part
inner join InstrumentType on Part.InstrumentTypeID = InstrumentType.ID
inner join SystemModule on SystemModule.InstrumentTypeID = Part.InstrumentTypeID
inner join System on System.ID = SystemModule.SystemID
WHERE ((#PlatformID =0) OR (System.PlatformID = #PlatformID) OR (#PlatformID = 12 AND System.PlatformID <= 2))
AND (#SelectedSystemID is null OR System.ID IN(select * from dbo.SplitInts_RBAR_1(#SelectedSystemID, ',')))
AND (#SelectedAbIDs is null OR Part.ID IN (select * from dbo.SplitInts_RBAR_1(#SelectedAbIDs,',')))
AND (#SelectedSubSystems is null OR Part.System IN (select * from dbo.SplitStrings_Moden(#SelectedSubSystems,',')))
AND System.Active = 1 and Part.Active = 1
ORDER BY SubSystem,RSLMSPartID
;WITH RunLogs AS(
SELECT *
FROM #run_log r
)
select * from RunLogs
UNION
select System.SystemFullName as Instrument,
Part.System as Subsystem,
Part.AbbottPartNo as AbPartNumber,
Part.ID as RSLMSPartID,
Part.PartDescriptionWithParent,
ServiceEntryPart.PartRevisionNumber AS PartRevisionNumber,
ServiceEntryPart.ServiceEntryID,
ServiceEntry.ServiceDateTime as Date,
ServiceEntry.TSBNumber,
ServiceEntry.CRNumber
from Part
inner join ServiceEntryPart on ServiceEntryPart.PartID = Part.ID
inner join ServiceEntry on ServiceEntry.ID = ServiceEntryPart.ServiceEntryID
inner join systemmodule on ServiceEntryPart.SystemModuleID = SystemModule.ID
inner join System on System.ID = SystemModule.SystemID
cross apply
dbo.SplitStrings_Moden(ServiceEntryPart.ServiceTypeIDs, N',') M2
JOIN dbo.SplitStrings_Moden('13', N',') P ON (M2.Item = P.Item)
WHERE System.Active = 1 AND Part.Active = 1
AND (#SelectedSystemID is null OR System.ID IN(select * from dbo.SplitInts_RBAR_1(#SelectedSystemID, ',')))
AND ((#PlatformID =0) OR (System.PlatformID = #PlatformID) OR (#PlatformID = 12 AND System.PlatformID <= 2))
AND (ServiceEntry.ServiceDateTime between #StartDate and #EndDate)
AND (#SelectedAbIDs is null OR Part.ID in (select * from dbo.SplitInts_RBAR_1(#SelectedAbIDs,',')))
AND (#SelectedSubSystems is null OR Part.System IN (select * from dbo.SplitStrings_Moden(#SelectedSubSystems,',')))
I have two tables of records that I need to find all of the matches. The tables are based on different Primary Key identifiers, but the data points are exactly the same. I need a fast query that can show me records that are duplicated from the first table to the second. Here is an example of what I am trying to do:
DECLARE #Table1 TABLE (ID INT, Value INT)
DECLARE #Table2 TABLE (ID INT, Value INT)
INSERT INTO #Table1 VALUES (1, 500)
INSERT INTO #Table1 VALUES (2, 500)
INSERT INTO #Table2 VALUES (3, 500)
INSERT INTO #Table2 VALUES (4, 500)
SELECT MAX(x.T1ID)
,MAX(x.T2ID)
FROM (
SELECT T1ID = t1.ID
,T2ID = 0
,t1.Value
FROM #Table1 t1
UNION ALL
SELECT T1ID = 0
,T2ID = t2.ID
,t2.Value
FROM #Table2 t2
) x
GROUP BY x.Value
HAVING COUNT(*) >= 2
The problem with this code is that it returns record 2 in table 1 correlated to record 4 in table 2. I really need it to return record 1 in table 1 correlated to record 3 in table 2. I tried the following:
SELECT MIN(x.T1ID)
,MIN(x.T2ID)
FROM (
SELECT T1ID = t1.ID
,T2ID = 0
,t1.Value
FROM #Table1 t1
UNION ALL
SELECT T1ID = 0
,T2ID = t2.ID
,t2.Value
FROM #Table2 t2
) x
GROUP BY x.Value
HAVING COUNT(*) >= 2
This code does not work either. It returns 0,0.
Is there a way to return the MIN value greater than 0 for both tables?
Might answer my own question. This seems to work. Are there any reasons why I would not do this?
SELECT MIN(t1.ID)
,MIN(t2.ID)
FROM #Table1 t1
INNER JOIN #Table2 t2 ON t1.Value = t2.Value
GROUP BY t1.Value
If you want to see the records in table1 that have matches in table2 then
select *
from #Table1 T1
where exists (select * from #Table2 T2
where T1.ID=T2.ID
-- you would put the complete join clause that defines a match here
)
I am trying to establish upper / lower bound in my stored procedure
below and am having some problems at the end (I am getting no results
where, without the temp table inner join i get the expected results).
I need some help where I am trying to join the columns in my temp table #PageIndexForUsers
to the rest of my join statement and I am mucking something up with
this statement:
INNER JOIN
#PageIndexForUsers ON ( dbo.aspnet_Users.UserId =
#PageIndexForUsers.UserId AND #PageIndexForUsers.IndexId >= #PageLowerBound AND
#PageIndexForUsers.IndexId <= #PageUpperBound )
I could use feedback at this point - and, any advice on how to improve
my procedure's logic (if you see anything else that needs improvement) is also appreciated.
Thanks in advance...
ALTER PROCEDURE dbo.wb_Membership_GetAllUsers
#ApplicationName nvarchar(256),
#sortOrderId smallint = 0,
#PageIndex int,
#PageSize int
AS
BEGIN
DECLARE #ApplicationId uniqueidentifier
SELECT #ApplicationId = NULL
SELECT #ApplicationId = ApplicationId FROM dbo.aspnet_Applications WHERE LOWER(#ApplicationName) = LoweredApplicationName
IF (#ApplicationId IS NULL)
RETURN 0
-- Set the page bounds
DECLARE #PageLowerBound int
DECLARE #PageUpperBound int
DECLARE #TotalRecords int
SET #PageLowerBound = #PageSize * #PageIndex
SET #PageUpperBound = #PageSize - 1 + #PageLowerBound
BEGIN TRY
-- Create a temp table TO store the select results
CREATE TABLE #PageIndexForUsers
(
IndexId int IDENTITY (0, 1) NOT NULL,
UserId uniqueidentifier
)
-- Insert into our temp table
INSERT INTO #PageIndexForUsers (UserId)
SELECT u.UserId
FROM dbo.aspnet_Membership m, dbo.aspnet_Users u
WHERE u.ApplicationId = #ApplicationId AND u.UserId = m.UserId
ORDER BY u.UserName
SELECT #TotalRecords = ##ROWCOUNT
SELECT dbo.wb_Profiles.profileid, dbo.wb_ProfileData.firstname, dbo.wb_ProfileData.lastname, dbo.wb_Email.emailaddress, dbo.wb_Email.isconfirmed, dbo.wb_Email.emaildomain, dbo.wb_Address.streetname, dbo.wb_Address.cityorprovince, dbo.wb_Address.state, dbo.wb_Address.postalorzip, dbo.wb_Address.country, dbo.wb_ProfileAddress.addresstype,dbo.wb_ProfileData.birthday, dbo.wb_ProfileData.gender, dbo.wb_Session.sessionid, dbo.wb_Session.lastactivitydate, dbo.aspnet_Membership.userid, dbo.aspnet_Membership.password, dbo.aspnet_Membership.passwordquestion, dbo.aspnet_Membership.passwordanswer, dbo.aspnet_Membership.createdate
FROM dbo.wb_Profiles
INNER JOIN dbo.wb_ProfileAddress
ON
(
dbo.wb_Profiles.profileid = dbo.wb_ProfileAddress.profileid
AND dbo.wb_ProfileAddress.addresstype = 'home'
)
INNER JOIN dbo.wb_Address
ON dbo.wb_ProfileAddress.addressid = dbo.wb_Address.addressid
INNER JOIN dbo.wb_ProfileData
ON dbo.wb_Profiles.profileid = dbo.wb_ProfileData.profileid
INNER JOIN dbo.wb_Email
ON
(
dbo.wb_Profiles.profileid = dbo.wb_Email.profileid
AND dbo.wb_Email.isprimary = 1
)
INNER JOIN dbo.wb_Session
ON dbo.wb_Profiles.profileid = dbo.wb_Session.profileid
INNER JOIN
dbo.aspnet_Membership
ON dbo.wb_Profiles.userid = dbo.aspnet_Membership.userid
INNER JOIN
dbo.aspnet_Users
ON dbo.aspnet_Membership.UserId = dbo.aspnet_Users.UserId
INNER JOIN
dbo.aspnet_Applications
ON dbo.aspnet_Users.ApplicationId = dbo.aspnet_Applications.ApplicationId
INNER JOIN
#PageIndexForUsers ON ( dbo.aspnet_Users.UserId =
#PageIndexForUsers.UserId AND #PageIndexForUsers.IndexId >= #PageLowerBound AND
#PageIndexForUsers.IndexId <= #PageUpperBound )
ORDER BY CASE #sortOrderId
WHEN 1 THEN dbo.wb_ProfileData.lastname
WHEN 2 THEN dbo.wb_Profiles.username
WHEN 3 THEN dbo.wb_Address.postalorzip
WHEN 4 THEN dbo.wb_Address.state
END
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRAN
EXEC wb_ErrorHandler
RETURN 55555
END CATCH
RETURN #TotalRecords
END
GO
You don't have enough rows in #PageIndexForUsers, no?
If #PageSize = 50 and you want #PageIndex 2, then you are looking for rows 100 to 149 from #PageIndexForUsers. Do you have this many rows?
The row filter should be applied over the larger dataset that starts FROM dbo.wb_Profiles