How do I have SQL JOIN done depending on some condition? - sql

I have two tables:
CREATE TABLE Users(
UserId UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
UserName NVARCHAR(200) NOT NULL,
-- some other columns
CurrentSubscriptionId UNIQUEIDENTIFIER NULL,
)
CREATE TABLE Subscriptions(
SubscriptionId UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
UserId UNIQUEIDENTIFIER NOT NULL,
-- some other columns
)
and I have a stored procedure that retrieves a list of "users" with their "current subscriptions":
SELECT List-Of-Columns FROM Users LEFT OUTER JOIN Subscriptions ON
Users.CurrentSubscriptionId=Subscriptions.SubscriptionId
WHERE SomeConditionOnUsers
The point is I only need data from "subscriptions" for some calls of the stored procedure, not the others. So I'd add a #includeSubscriptionData bit parameter to the stored procedure (this is pseudocode, I'd have to list each conditionally included column separately):
SELECT List-Of-Columns-FromUsers,
CASE WHEN #includeSubscriptionData THEN Columns-From-Subscriptions ELSE NULL END
FROM Users LEFT OUTER JOIN Subscriptions ON
Users.CurrentSubscriptionId=Subscriptions.SubscriptionId
WHERE SomeConditionOnUsers
to hopefully avoid the JOIN. So I tried this first:
DECLARE #includeSubscriptionData BIT;
SET #includeSubscriptionData=0;
SELECT List-Of-Columns-FromUsers,
CASE WHEN #includeSubscriptionData THEN SubscriptionId ELSE NULL END
FROM Users LEFT OUTER JOIN Subscriptions ON
Users.CurrentSubscriptionId=Subscriptions.SubscriptionId
WHERE SomeConditionOnUsers
and the plan shows that there're still accesses to "subscriptions".
How do I get rid of accesses to the second table when the bit is set to zero?

Have you tried putting it in the join criteria?
DECLARE #includeSubscriptionData BIT;
SET #includeSubscriptionData=0;
SELECT List-Of-Columns-FromUsers,
SubscriptionId
FROM Users LEFT OUTER JOIN Subscriptions ON
Users.CurrentSubscriptionId=Subscriptions.SubscriptionId and #includeSubscriptionData = 1
WHERE SomeConditionOnUsers
OPTION(RECOMPILE)

Method 1 - Control Flow
IF #includeSubscriptionData = 1
BEGIN
SELECT list
, of
, columns
FROM Users
INNER
JOIN Subscriptions
ON Users.CurrentSubscriptionId = Subscriptions.SubscriptionId
WHERE SomeConditionOnUsers
;
END
ELSE
BEGIN
SELECT list
, of
, columns
FROM Users
;
END
;
Method 2 - Modified Join
SELECT list
, of
, columns
FROM Users
LEFT
JOIN Subscriptions
ON Users.CurrentSubscriptionId = Subscriptions.SubscriptionId
AND #includeSubscriptionData = 1
WHERE SomeConditionOnUsers

You can use IF/ELSE for this.
DECLARE #includeSubscriptionData BIT;
SET #includeSubscriptionData=0;
IF #includeSubscriptionData == 0
BEGIN
SELECT List-Of-Columns-FromUsers
FROM Users WHERE SomeConditionOnUsers
END
ELSE
BEGIN
SELECT List-Of-Columns-FromUsers
FROM Users LEFT OUTER JOIN Subscriptions ON Users.CurrentSubscriptionId=Subscriptions.SubscriptionId
WHERE SomeConditionOnUsers
END

Related

How do I display the count value of the results from one column in a new column?

This stored procedure returns two values but it repeats multiple times.
I am trying to get the count value of PLTGRN so that it shows up in a new column like in this image:
My code:
ALTER PROCEDURE[dbo].[GreenTire_Fits_In_Press]
#Press varchar(10)
AS
BEGIN
SELECT FAC,PLTGRN
FROM [TireTrack].[dbo].[cos_work] cosw WITH (nolock)
INNER JOIN [DataWarehouse].[dbo].[v_Curing_Tooling] CURE WITH (Nolock) ON Cure.MLDNBR = Cosw.MOLD
WHERE Cosw.FAC = #Press
END
I tried adding COUNT(PLTGRN) in the select statement to the code above but that throws this error :
you need to use GROUP BY :
ALTER PROCEDURE[dbo].[GreenTire_Fits_In_Press]
#Press varchar(10)
AS
BEGIN
Select PLTGRN , COUNT(*) QTY
FROM [TireTrack].[dbo].[cos_work] cosw with (nolock)
Inner Join [DataWarehouse].[dbo].[v_Curing_Tooling] CURE with (Nolock)
On Cure.MLDNBR=Cosw.MOLD
Where Cosw.FAC=#Press
GROUP BY PLTGRN
End
SUM is used with a GROUP BY clause. The aggregate functions summarize the table data. Once the rows are divided into groups, the aggregate functions are applied in order to return just one value per group. It is better to identify each summary row by including the GROUP BY clause in the query result.
ALTER PROCEDURE[dbo].[GreenTire_Fits_In_Press]
#Press varchar(10)
AS
BEGIN
SELECT FAC,PLTGRN, COUNT(PLTGRN)
FROM [TireTrack].[dbo].[cos_work] cosw WITH (nolock)
INNER JOIN [DataWarehouse].[dbo].[v_Curing_Tooling] CURE WITH (Nolock) ON Cure.MLDNBR = Cosw.MOLD
WHERE Cosw.FAC = #Press
GROUP BY FAC, PLTGRN
END
-- you are missing group by and the part of the query to get count
ALTER PROCEDURE[dbo].[GreenTire_Fits_In_Press]
#Press varchar(10)
AS
BEGIN
Select FAC,PLTGRN, count(*)
FROM [TireTrack].[dbo].[cos_work] cosw with (nolock)
Inner Join [DataWarehouse].[dbo].[v_Curing_Tooling] CURE with (Nolock)
On Cure.MLDNBR=Cosw.MOLD
Where Cosw.FAC=#Press
Group By FAC,PLTGRN
End

Conversion failed while using ids "where ID in (...)", nvarchar to int

I have a query in MSSQL 2008 like:
IF OBJECT_Id('tempdb..#AccessibleFacilities') IS NOT NULL DROP TABLE #AccessibleFacilities
SELECT u.Userid
, AccesibleFacilityIds = dbo.GetCommaDelimitedString(upf.Facility_Id)
INTO #AccessibleFacilities
FROM Users u
INNER join UserProfileFacilities upf on upf.UserProfile_Id = up.Id
WHERE LOWER(u.Userid) = LOWER(#userId)
GROUP BY u.Userid
This query returns AccessibleFacilityIds like ",1,2,3,4,5,6,". Please note that I am not able to modify GetCommaDelimitedString function.
What I actually need to do is that using those facility ids to reach provs like below:
INSERT INTO #AccessibleProvs
SELECT Userid = #userId
, AccessibleProvIds = dbo.GetCommaDelimitedString(distinct p.Id)
FROM Provs p
inner join ProvFacs pf on p.Id = pf.Provider_Id
WHERE pf.Facility_Id in
(select a.AccesibleFacilityIds from #AccessibleFacilities a)
However, it gives me an error like:
Conversion failed when converting the nvarchar value ',1,2,3,4,5,6,'
to data type int.
I tried removing the comma signs at the start and end like below to fix it, but it did not help:
...
where pf.Facility_Id in (
select SUBSTRING(a.AccesibleFacilityIds,2,LEN(a.AccesibleFacilityIds)-2)
from #AccessibleFacilities a
)
Any advice would be appreciated. Thanks.
Instead of converting Facility_Id into a comma delimited string, why not keep it as a usable column in your temp table?
if object_Id('tempdb..#AccessibleFacilities') is not null drop table #AccessibleFacilities;
select
u.UserId
, upf.Facility_Id
into #AccessibleFacilities
from Users u
inner join UserProfileFacilities upf
on upf.UserProfile_Id = up.Id
Then use it as you did with in() or with exists():
insert into #AccessibleProvs
select
UserId = #userId
, AccessibleProvIds = dbo.GetCommaDelimitedString(distinct p.Id)
from Provs p
inner join ProvFacs pf
on p.Id = pf.Provider_Id
where exists (
select 1
from #AccessibleFacilities a
where a.Facility_Id = pf.Facility_Id
--and a.UserId = #UserId -- Do you need to check Facility_Id by User?
)
If you have the value for #UserId in the beginning, you could limit your temp table usage to just the user you need. Hopefully this code is not meant for use in some sort of cursor or other loop.

SQL query inputs needed

I have UI where there is an search functionality. There are 2 parameters in search.
User can search of either of the two params to get the result (ie either of it can be null)
I am having difficulties in getting the to write the exact sql (SP) when one of the values is null.
For example as below:
declare #param1, #param2
create table #Ids
(
Id bigint,
Name varchar(10)
)
--Inserting multiple ids in my temp table
IF #param1 IS NOT NULL
BEGIN
INSERT INTO #Ids (Id, Name)
SELECT DISTINCT Id, Name
FROM MainIdTable I
WHERE (I.Name = #param1)
END
SELECT
I.Name, S.ID, S.Value
FROM
SecondTable S
JOIN
#Ids I ON S.Id = P.Id
WHERE
S.Date = '2012-01-06'
AND s.tempId = ISNULL(#param2, p.tempId)
If user enters param1 value, the search works fine
If the user enters both param1 and param2 values, then it also works fine
But if the user doesn't enter a param1 value, but just enters a param2 value, the search doesn't work.
I believe that's because my temp table is empty and when we make a join it returns nothing.
Any workaround to get this working?
Thanks
Select I.Name, S.ID, S.Value from
SecondTable S
LEFT JOIN #Ids I on S.Id = P.Id
where S.Date = '2012-01-06'
and s.tempId = isnull(#param2, p.tempId)
You should use a left join here.

Using a Cursor inside a Stored Procedure to Write to a Table from Multiple Tables

I'm struggling to write my first procedure to pull data from multiple tables together and write it to another table using a cursor to loop through all of the data. Was hoping to find some help here.
I'm joining 6 tables back to the main table, Accounts, to displayed the required data. With the first cursor, I was joining five of the tables to get the needed information and then I wanted to add two more cursors to get phone numbers (primary and secondary) from a Phone Detail table added to the table.
Hopefully this makes sense. I'm sure I"m missing some thing in the SQL but basically I'd like to loop through the Accounts table and write the data into a new table and also loop through the Phone Detail table and get the primary phone for each account and then the secondary phone (while accounting for a NULL value) writing this to the new table as well.
CREATE PROCEDURE [dbo].[CRM_Account_Info]
#AccountID int,
#AccountName nvarchar(128),
#Bus_Type nvarchar(50),
#AccountAddr1 nvarchar(128),
#AccountAddr2 nvarchar(128),
#AccountCity nvarchar(32),
#AccountState nvarchar(10),
#AccountZip nvarchar(10),
#Account_Coll_Area_CodeID int,
#Account_Coll_Area nvarchar(50),
#Account_CRC_ID int,
#Account_CRC_Name nvarchar(100),
#Account_Prime_Number nvarchar(120),
#Account_2nd_Number nvarchar(120)
AS
BEGIN
-- Truncate Accounts table
Execute Immediate 'Truncate DBO.CRM_Accounts';
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
-- Grab Account ID, Account Name, Account Type, Address, City, State, Zip, Collection Area ID, Collection Area Description,
-- Recruiter ID, Recruiter Full Name from the Accounts Table
Declare Acct_Info cursor for
Select
Acct.AccountID, Acct.Internalname,
QC.Descshort,
AD.Addr1, AD.Addr2, AD.City, AD.State, AD.Zip,
Sub.CodeID, Sub.Description,
Peo.PersonID, Peo.Fullname
from
Accounts as Acct
inner join
AddressDetail AD on Acct.AccountID = AD.AccountID
inner join
CenterDetail CD on Acct.Centerid = CD.CenterID
inner join
People Peo on Acct.LeaderID = Peo.PersonID
inner join
IDViewOrgSubCenter SUB on CD.OrgSubCenter = SUB.CodeID
inner join
QuickCodes QC on Acct.AccountType = QC.CodeID
Open Acct_Info -- Open cursor
Fetch Next from Acct_Info into #AccountID, #AccountName, #Bus_Type, #AccountAddr1,
#AccountAddr2, #AccountCity, #AccountState, #AccountZip,
#Account_Coll_Area_CodeID, #Account_Coll_Area,
#Account_CRC_ID, #Account_CRC_Name, #Account_Prime_Number,
#Account_2nd_Number
Close Acct_Info -- Close cursor
-- Grab the Primary Phone for the Account
Declare Primary_Phone cursor for
Select top 1 Acct.AccountID, PD.FormattedNumber
From PhoneDetail PD
inner join Accounts Acct on PD.AccountID=Acct.AccountID
Where PD.PrimaryPhone=1
And PD.AccountID=#AccountID
Close Primary_Phone -- Close cursor
-- Grab the second phone for an account
Declare Secondary_Phone cursor for
Select top 1 Acct.AccountID, PD.FormattedNumber
From PhoneDetail PD
inner join Accounts Acct on PD.AccountID=Acct.AccountID
Where PD.PrimaryPhone<>1
And PD.AccountID=#AccountID
Close Secondary_Phone -- Close cursor
-- Insert the values into the CRM table
Insert CRM_Accounts (
AccountID,
AccountName,
Bus_Type,
AccountAddr1,
AccountAddr2,
AccountCity,
AccountState,
AccountZip,
Account_Coll_Area_CodeID,
Account_Coll_Area,
Account_CRC_ID,
Account_CRC_Name,
Account_Prime_Number,
Account_2nd_Number
)
Values (
#AccountID,
#AccountName,
#Bus_Type,
#AccountAddr1,
#AccountAddr2,
#AccountCity,
#AccountState,
#AccountZip,
#Account_Coll_Area_CodeID,
#Account_Coll_Area,
#Account_CRC_ID,
#Account_CRC_Name,
#Account_Prime_Number,
#Account_2nd_Number
)
END
GO
Try this to get it all in one query. I did 2 derived tables one of primary and one of secondary and gave them row numbers partitioned by accountid. Then in the join I only get the rownumber = 1 so that it only returns one number per accountid.
SELECT Acct.accountid,
Acct.internalname,
QC.descshort,
AD.addr1,
AD.addr2,
AD.city,
AD.state,
AD.zip,
Sub.codeid,
Sub.description,
Peo.personid,
Peo.fullname,
pd1.formattedNumber,
pd2.formattedNumber
FROM accounts AS Acct
INNER JOIN addressdetail AD
ON Acct.accountid = AD.accountid
INNER JOIN centerdetail CD
ON Acct.centerid = CD.centerid
INNER JOIN people Peo
ON Acct.leaderid = Peo.personid
INNER JOIN idvieworgsubcenter SUB
ON CD.orgsubcenter = SUB.codeid
INNER JOIN quickcodes QC
ON Acct.accounttype = QC.codeid
LEFT JOIN (select accountid, formattednumber, primaryphone, row_number() over(partition by accountid order by formattednumber) as rNum
from phonedetail where primaryphone = 1) pd1
ON acct.accountid = pd1.accountid and pd1.rNum = 1
LEFT JOIN (select accountid, formattednumber, primaryphone, row_number() over(partition by accountid order by formattednumber) as rNum
from phonedetail where primaryphone <> 1) pd2
ON acct.accountid = pd2.accountid and pd2.rNum = 1
#JChao, I'm going to mark your answer correct as the SQL worked brilliantly but I'd like to post this here based on the recommendations received and for additional eyes to look over this procedure now. It seems to work but I'd just like more experienced users to check it out to see if they spot any glaring mistakes:
The first step is to drop (or truncate) all data currently in the table and then write the select statement into my new table:
Create Procedure Update_CRM_Accts
--Alter Procedure Update_CRM_Accts
As
Begin
Truncate Table CRM_Accounts
SET NOCOUNT ON;
Insert into CRM_Accounts
Select Acct.AccountID,
Acct.InternalName,
QC.DescShort,
AD.Addr1,
AD.Addr2,
AD.City,
AD.State,
AD.Zip,
Sub.CodeID,
Sub.Description,
Peo.PersonID,
Peo.FullName,
PD1.FormattedNumber as 'Primary_Number',
PD2.FormattedNumber as 'Secondary_Number'
From Accounts As Acct
INNER JOIN addressdetail AD ON Acct.AccountID = AD.AccountID
INNER JOIN CenterDetail CD ON Acct.CenterID = CD.CenterID
INNER JOIN People Peo ON Acct.LeaderID = Peo.PersonID
INNER JOIN IDViewOrgSubCenter SUB ON CD.OrgSubcenter = SUB.CodeID
INNER JOIN quickcodes QC ON Acct.AccountType = QC.CodeID
LEFT OUTER JOIN (Select AccountID, FormattedNumber, PrimaryPhone, row_number() over(partition by AccountID order by FormattedNumber) as rNum
From PhoneDetail where PrimaryPhone = 1) PD1
ON Acct.accountid = PD1.AccountID and PD1.rNum = 1
LEFT OUTER JOIN (Select AccountID, FormattedNumber, PrimaryPhone, row_number() over(partition by AccountID order by FormattedNumber) as rNum
From PhoneDetail where PrimaryPhone <> 1) PD2
ON Acct.AccountID = PD2.AccountID and PD2.rNum = 1
End
Go

How can I perform the Count function with a where clause?

I have my database setup to allow a user to "Like" or "Dislike" a post. If it is liked, the column isliked = true, false otherwise (null if nothing.)
The problem is, I am trying to create a view that shows all Posts, and also shows a column with how many 'likes' and 'dislikes' each post has. Here is my SQL; I'm not sure where to go from here. It's been a while since I've worked with SQL and everything I've tried so far has not given me what I want.
Perhaps my DB isn't setup properly for this. Here is the SQL:
Select trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname, Count(trippin.LikesDislikesData.liked)
as TimesLiked from trippin.PostData
inner join trippin.AccountData on trippin.PostData.accountid = trippin.AccountData.id
inner join trippin.CategoryData on trippin.CategoryData.id = trippin.PostData.categoryid
full outer join trippin.LikesDislikesData on trippin.LikesDislikesData.postid =
trippin.PostData.id
full outer join trippin.LikesDislikesData likes2 on trippin.LikesDislikesData.accountid =
trippin.AccountData.id
Group By (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.categorydata.categoryname);
Here's my table setup (I've only included relevant columns):
LikesDislikesData
isliked(bit) || accountid(string) || postid(string
PostData
id(string) || posttext || accountid(string)
AccountData
id(string) || username(string)
CategoryData
categoryname(string)
Problem 1: FULL OUTER JOIN versus LEFT OUTER JOIN. Full outer joins are seldom what you want, it means you want all data specified on the "left" and all data specified on the "right", that are matched and unmatched. What you want is all the PostData on the "left" and any matching Likes data on the "right". If some right hand side rows don't match something on the left, then you don't care about it. Almost always work from left to right and join results that are relevant.
Problem 2: table alias. Where ever you alias a table name - such as Likes2 - then every instance of that table within the query needs to use that alias. Straight after you declare the alias Likes2, your join condition refers back to trippin.LikesDislikesData, which is the first instance of the table. Given the second one in joining on a different field I suspect that the postid and accountid are being matched on the same row, therefore it should be AND together, not a separate table instance. EDIT reading your schema closer, it seems this wouldn't be needed at all.
Problem 3: to solve you Counts problem separate them using CASE statements. Count will add the number of non NULL values returned for each CASE. If the likes.liked = 1, then return 1 otherwise return NULL. The NULL will be returned if the columns contains a 0 or a NULL.
SELECT trippin.PostData.Id, trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname,
SUM(CASE WHEN likes.liked = 1 THEN 1 ELSE 0 END) as TimesLiked,
SUM(CASE WHEN likes.liked = 0 THEN 1 ELSE 0 END) as TimesDisLiked
FROM trippin.PostData
INNER JOIN trippin.AccountData ON trippin.PostData.accountid = trippin.AccountData.id
INNER JOIN trippin.CategoryData ON trippin.CategoryData.id = trippin.PostData.categoryid
LEFT OUTER JOIN trippin.LikesDislikesData likes ON likes.postid = trippin.PostData.id
-- remove AND likes.accountid = trippin.AccountData.id
GROUP BY trippin.PostData.Id, (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.categorydata.categoryname);
Then "hide" the PostId column in the User Interface.
Instead of selecting Count(trippin.LikesDislikesData.liked) you could put in a select statement:
Select AccountData.username, PostData.posttext, CategoryData.categoryname,
(select Count(*)
from LikesDislikesData as likes2
where likes2.postid = postdata.id
and likes2.liked = 'like' ) as TimesLiked
from PostData
inner join AccountData on PostData.accountid = AccountData.id
inner join CategoryData on CategoryData.id = PostData.categoryid
USE AdventureWorksDW2008R2
GO
SET NOCOUNT ON
GO
/*
Default
*/
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRAN
IF OBJECT_ID('tempdb.dbo.#LikesDislikesData') IS NOT NULL
BEGIN
DROP TABLE #LikesDislikesData
END
CREATE TABLE #LikesDislikesData(
isLiked bit
,accountid VARCHAR(50)
,postid VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#PostData') IS NOT NULL
BEGIN
DROP TABLE #PostData
END
CREATE TABLE #PostData(
postid INT IDENTITY(1,1) NOT NULL
,accountid VARCHAR(50)
,posttext VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#AccountData') IS NOT NULL
BEGIN
DROP TABLE #AccountData
END
CREATE TABLE #AccountData(
accountid INT
,username VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#CategoryData') IS NOT NULL
BEGIN
DROP TABLE #CategoryData
END
CREATE TABLE #CategoryData(
categoryname VARCHAR(50)
);
INSERT INTO #AccountData VALUES ('1', 'user1')
INSERT INTO #PostData VALUES('1','this is a post')
INSERT INTO #LikesDislikesData (isLiked ,accountid, postid)
SELECT '1', P.accountid, P.postid
FROM #PostData P
WHERE P.posttext = 'this is a post'
SELECT *
FROM #PostData
SELECT *
FROM #LikesDislikesData
SELECT *
FROM #AccountData
SELECT COUNT(L.isLiked) 'Likes'
,P.posttext
,A.username
FROM #PostData P
JOIN #LikesDislikesData L
ON P.accountid = L.accountid
AND L.IsLiked = 1
JOIN #AccountData A
ON P.accountid = A.accountid
GROUP BY P.posttext, A.username
SELECT X.likes, Y.dislikes
FROM (
(SELECT COUNT(isliked)as 'likes', accountid
FROM #LikesDislikesData
WHERE isLiked = 1
GROUP BY accountid
) X
JOIN
(SELECT COUNT(isliked)as 'dislikes', accountid
FROM #LikesDislikesData
WHERE isLiked = 0
GROUP BY accountid) Y
ON x.accountid = y.accountid)
IF (XACT_STATE() = 1 AND ERROR_STATE() = 0)
BEGIN
COMMIT TRAN
END
ELSE IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN
END
How do you think about the solution? We create a new table SummaryReport(PostID,AccountID,NumberOfLikedTime,NumberOfDislikedTimes).
An user clicks on LIKE or DISLIKE button we update the table. After that, you can query as you desire. Another advantage, the table can be served reporting purpose.