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

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.

Related

Sql Query with Temp tables in Visual Studio 2010 data grid

I apologize up front if this has been answered, i read several other posts and didn't see a clear answer to my question. I am a beginner at VS2010. Basically i have the below query and i want it to display in the data grid view when i run my program.
I can use VS2010 to join two actual tables but as you can see below the temp tables are very important.
IF OBJECT_ID('tempdb..#tempbatch') IS NOT NULL DROP TABLE #tempbatch
IF OBJECT_ID('tempdb..#tempbatch2') IS NOT NULL DROP TABLE #tempbatch2
IF OBJECT_ID('tempdb..#tempbatch1') IS NOT NULL DROP TABLE #tempbatch1
IF OBJECT_ID('tempdb..#tempbatch3') IS NOT NULL DROP TABLE #tempbatch3
IF OBJECT_ID('tempdb..#tempbatch4') IS NOT NULL DROP TABLE #tempbatch4
IF OBJECT_ID('tempdb..#tempbatch5') IS NOT NULL DROP TABLE #tempbatch5
IF OBJECT_ID('tempdb..#tempbatch6') IS NOT NULL DROP TABLE #tempbatch6
IF OBJECT_ID('tempdb..#tempbatch7') IS NOT NULL DROP TABLE #tempbatch7
IF OBJECT_ID('tempdb..#tempbatch8') IS NOT NULL DROP TABLE #tempbatch8
IF OBJECT_ID('tempdb..#tempbatch9') IS NOT NULL DROP TABLE #tempbatch9
IF OBJECT_ID('tempdb..#tempbatch10') IS NOT NULL DROP TABLE #tempbatch10
create table #tempbatch (rowid bigint primary key identity(1,1), shipmentno varchar(64))
insert into #tempbatch select * from #unitnames
select distinct b.dcsID, a.BoxType, b.BoxNO, b.shipmentno, b.PaletteWithinShipment into #tempbatch1 from #tempbatch c
join dva..Boxmapping as a
on c.shipmentno = a.shipmentno
join dva..Boxmapping as b
on a.boxno = b.BoxNO
--where b.shipmentno = '#rmn'
group by b.dcsID, a.BoxType, b.BoxNO, b.shipmentno, b.PaletteWithinShipment
order by b.PaletteWithinShipment, b.BoxNO
--select dcsid,boxtype,boxno,shipmentno from #tempbatch1
select distinct a.dcsid,b.dcsid as manifestDCS,b.rmn into #tempbatch3 from #tempbatch1 a
left outer join dva..manifestDCS b
on a.dcsid = b.dcsid
select distinct b.dcsid,a.rmn into #tempbatch5 from #tempbatch3 a
left outer join dva..manifestdcs b
on a.rmn = b.rmn
select b.dcsid as deliverexDCSID,a.dcsid,a.rmn,pbatch into #tempbatch4 from #tempbatch5 a
left outer join #tempbatch1 b
on a.dcsid = b.dcsid
join dva..document c
on a.dcsid = c.dcsid
where a.dcsid not in (select dcsid from dva..document where ftpstime is null) and a.dcsid not in (select dcsid from dva..boxmapping)
delete from #tempbatch4 where deliverexdcsid is not null
----- ******************************** START OF SECOND QUERY *********************************-------------
select * into #tempbatch6 from dva..Boxmapping
select distinct c.rmn,c.dcsid,b.dcsid as BoxDCSID,a.pbatch into #tempbatch8 from #tempbatch4 a
left outer join dva..manifestDCS b
on a.dcsid = b.dcsid
left outer join dva..manifestdcs c
on b.rmn = c.rmn
select distinct c.rmn,c.dcsid as Missing,a.dcsid,d.BoxNO,d.boxtype,e.palette,e.PaletteWithinShipment,e.shipmentno into #tempbatch9 from #tempbatch8 a
left outer join #tempbatch4 c
on a.rmn = c.rmn
left outer join dva..boxmapping d
on b.dcsid = d.dcsid
left outer join dva..boxmapping e
on d.boxno = e.boxno
delete from #tempbatch9 where dcsID is null
delete from #tempbatch9 where boxno is null
delete from #tempbatch9 where palette is null
select distinct rmn,missing,boxno,boxtype,PaletteWithinShipment,shipmentno from #tempbatch9
order by rmn,PaletteWithinShipment
Wrap the whole of your query in a stored procedure, i.e.
CREATE PROCEDURE dbo.MyData
AS
SET NOCOUNT ON;
BEGIN
<insert your SQL here>
END;
Back in Visual Studio you will need to open a connection to your database, then open a command that you will use in conjunction with a data reader. There should be plenty of examples of how to do this, but I will give you a very short sample (sorry it's in C#, I don't touch VB):
using (var con = new SqlConnection("Server=WHATEVERSERVER;Database=WHATEVERDBS;Trusted_Connection=True;"))
{
con.Open();
using (var com = con.CreateCommand())
{
com.CommantText = "EXECUTE <MyDatabase>.dbo.MyData;";
using (var dr = com.ExecuteReader())
{
while (dr.Read())
{
var row = new string[7];
row[0] = dr["rmn"].ToString();
row[1] = dr["missing"].ToString();
etc...
MyDataGrid.Rows.Add(row);
}
}
}
}
Finally, you can just pull back a row count from your data grid, to get the number of rows of data that you want to display in a window.
If you can't create a stored procedure then you could type the entire SQL script into the command text bit, but you would have to be very careful with the carriage returns, spacing, literals, quotes, etc.
Again, apologies that my example code is in C#, I was just wanting to give you a very basic framework, so at the very least you know what to go and type into Google...

is there a way to update a column using multiple tables

I'm trying to update a column called Specialty in a table called #LIST. I need to pull information from 3 different tables incase the specialty in 1 table is NULL. I prefer to use the specialty from the #Segment table, if that's NULL then I want to use the specialty from the #SUBMARKET table, and if that's NULL I want to use the specialty from the #MARKET table.
When I run the below query, it doesn't update any rows. I don't want to use more than 1 Update statement. What can I do?
UPDATE #LIST
SET Specialty = CASE WHEN (l.Segment__c is not NULL) THEN s.Specialty
WHEN (l.Segment__c is NULL) THEN b.Specialty
WHEN (l.Submarket__c is NULL) THEN m.Specialty
ELSE s.Specialty
END
FROM #LIST l
join #MARKET m on m.Market = l.Market__c
join #SUBMARKET b on b.Submarket = l.Submarket__c
join #Segment s on s.Segment = l.Segment__c
You need to use the table alias in the UPDATE. Also, I think you need left join:
UPDATE l
SET Specialty = CASE WHEN (l.Segment__c is not NULL) THEN s.Specialty
WHEN (l.Segment__c is NULL) THEN b.Specialty
WHEN (l.Submarket__c is NULL) THEN m.Specialty
ELSE s.Specialty
END
FROM #LIST l
left join #MARKET m on m.Market = l.Market__c
left join #SUBMARKET b on b.Submarket = l.Submarket__c
left #Segment s on s.Segment = l.Segment__c;
Otherwise, the NULL values will filter out rows, because they do not match in the join.

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

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

SQL: Want to alter the conditions on a join depending on values in table

I have a table called Member_Id which has a column in it called Member_ID_Type. The select statement below returns the value of another column, id_value from the same table. The join on the tables in the select statement is on the universal id column. There may be several entries in that table with this same universal id.
I want to adjust the select statement so that it will return the id_values for entries that have member_id_type equal to '7'. However if this is null then I want to return records that have member_id_type equal to '1'
So previously I had a condition on the join (commented out below) but that just returned records that had member_id_type equal to '7' and otherwise returned null.
I think I may have to use a case statement here but I'm not 100% sure how to use it in this scenario
SELECT TOP 1 cm.Contact_Relation_Gid,
mc.Universal_ID,
mi.ID_Value,
cm.First_Name,
cm.Last_Name,
cm.Middle_Name,
cm.Name_Suffix,
cm.Email_Address,
cm.Disability_Type_PKID,
cm.Race_Type_PKID,
cm.Citizenship_Type_PKID,
cm.Marital_Status_Type_PKID,
cm.Actual_SSN,
cm.Birth_Date,
cm.Gender,
mc.Person_Code,
mc.Relationship_Code,
mc.Member_Coverage_PKID,
sc.Subscriber_Coverage_PKID,
FROM Contact_Member cm (NOLOCK)
INNER JOIN Member_Coverage mc (NOLOCK)
ON cm.contact_relation_gid = mc.contact_relation_gid
AND mc.Record_Status = 'A'
INNER JOIN Subscriber_Coverage sc (NOLOCK)
ON mc.Subscriber_Coverage_PKID = sc.Subscriber_Coverage_PKID
AND mc.Record_Status = 'A'
LEFT outer JOIN Member_ID mi ON mi.Universal_ID = cm.Contact_Gid
--AND mi.Member_ID_Type_PKID='7'
WHERE cm.Contact_Relation_Gid = #Contact_Relation_Gid
AND cm.Record_Status = 'A'
Join them both, and use one if the other is not present:
select bt.name
, coalesce(eav1.value, eav2.value) as Value1OrValue2
from BaseTable bt
left join EavTable eav1
on eav1.id = bt.id
and eav1.type = 1
left join EavTable eav2
on eav2.id = bt.id
and eav2.type = 2
This query assumes that there is never more than one record with the same ID and Type.

Slow stored procedure

I currently have a stored procedure that copies content from one table to another.
However when it is trying to only insert 27 new rows it continues on for over 12 minutes (after which point I stopped it) it said Affected 27 rows 4 times, however changes were not made.
Can you spot any reason the following SP would be slow?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Procedure [dbo].[sp_CopyCompanyContent]
(
#intCopyFromCompanyID Int,
#intNewCompanyID Int
)
As
Begin
/*
RaisError If any of Odl/New Company ID's are 0
*/
If (#intCopyFromCompanyID = 0 Or #intNewCompanyID = 0)
Begin
RaisError('New Company ID or Old Company ID can not be 0', 16, 1)
Return
End
/*
Create Temp Table For the Content Sections
*/
If Object_ID('tempdb..#ContentSections') IS Not Null
Begin
Drop Table dbo.#ContentSections
End
/*
Have to get all the existing data for the Old company we are copying from.
Have to also add the Max(ContentSectionID) From ContentSection. Max(ContentSectionID) +
The Identity (Added further down due to laziness) will be our seed for the ContentID
*/
Select CS.ContentID,
CS.SectionID,
CS.MenuOrder,
#intNewCompanyID NewCompanyID,
CS.CompanyID OldCompanyID,
CS.SubMenu,
CS.Link,
CS.HeaderMenu,
CS.ParentContentID,
CRS.*
Into dbo.#ContentSections
From dbo.Company COMP
Join dbo.ContentSection CS
On COMP.Company_id = CS.CompanyID
Join dbo.Content CONT
On CONT.ContentID = CS.ContentID
Cross Join (
Select MAx(ContentSectionID) MaxContentSectionID
From dbo.ContentSection CONT
) crs
Where COMP.Company_id = #intCopyFromCompanyID
Order By COMP.Company_id
/*
We now need to create a table for the existing content for the old company.
Also have to create the seed. Same principle as above.
*/
If Object_ID('tempdb..#Content') IS Not Null
Begin
Drop Table dbo.#Content
End
Select CONT.*,
CRS.*
Into dbo.#Content
From dbo.Company COMP
Join dbo.ContentSection CS
On COMP.Company_id = CS.CompanyID
Join dbo.Content CONT
On CONT.ContentID = CS.ContentID
Cross Join (
Select MAx(ContentID) MaxContentID
From dbo.Content CONT
) crs
Where COMP.Company_id = #intCopyFromCompanyID
Order By COMP.Company_id
/*
Add Identity to each of the tables we have created above. The ID fields will add to
the Max of each table to mimic what the future seeds will be.
*/
exec('Alter table #ContentSections Add ID Int Identity(1,1)')
exec('Alter table #Content Add ID Int Identity(1,1)')
/*
Add content data from the temp table.
*/
Insert Into dbo.Content
(
Title,
Content
)
Select Title,
Content
From dbo.#Content
/*
Have to the Content table up to the content sections table
as this contains what ID has been add to the Content Table.
*/
Insert Into dbo.ContentSection
(
ContentID,
SectionID,
MenuOrder,
CompanyID,
SubMenu,
Link,
HeaderMenu,
ParentContentID
)
Select C.MaxContentID + C.ID,
CS.SectionID,
CS.MenuOrder,
CS.NewCompanyID,
CS.Submenu,
CS.Link,
CS.HEaderMEnu,
CS.ParentContentID
From dbo.#Content C
Join dbo.#ContentSections CS
On C.ID = CS.ID
End
First thing to do is check the query plan for the selects since cross joins are dangerous beasts, if i'm not reading it wrong you'd display the same value in CRS.* in every record right?
If so, make that query before the select and stored the result in a variable and display it in the select, something like this.
DECLATE #maxValue INTEGER
Select #maxValue=MAX(ContentID) MaxContentID
From dbo.Content CONT
Select CONT.*,
#maxValue as MaxContentID
Into dbo.#Content
From dbo.Company COMP
Join dbo.ContentSection CS
On COMP.Company_id = CS.CompanyID
Join dbo.Content CONT
On CONT.ContentID = CS.ContentID
Where COMP.Company_id = #intCopyFromCompanyID
Order By COMP.Company_id
It is probably because of the identity (it is 2 times from the 4 times*27 rows)
exec('Alter table #ContentSections Add ID Int Identity(1,1)')
exec('Alter table #Content Add ID Int Identity(1,1)')
Instead, if you don't want to create the table, try to use ROW_NUMBER() OVER()... clause as ID, and then you don't need to create the identity later.
And as I see, you don't even need to use the Temp Tables, because then you can simply use the two select as INSERT INTO .... SELECT ... With the ROW_NUMBER().
It seems you don't make any changes on the Temp Tables, so you probably don't need them. (Only if you want to use them out of the SP's scope)