Select column based on whether a specific row in another table exists - sql-server

Question is similar to this one How to write a MySQL query that returns a temporary column containing flags for whether or not an item related to that row exists in another table
Except that I need to be more specific about which rows exists
I have two tables: 'competitions' and 'competition_entries'
Competitions:
ID | NAME | TYPE
--------------------------------
1 | Example | example type
2 | Another | example type
Competition Entries
ID | USERID | COMPETITIONID
---------------------------------
1 | 100 | 1
2 | 110 | 1
3 | 110 | 2
4 | 120 | 1
I want to select the competitions but add an additional column which specifies whether the user has entered the competition or not. This is my current SELECT statement
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
(CASE
WHEN e.ID IS NOT NULL AND e.USERID = #userid THEN 1
ELSE 0
END
) AS 'ENTERED'
FROM competitions AS c
LEFT OUTER JOIN competition_entries AS e
ON e.COMPETITIONID = c.ID
My desired result set from setting the #userid parameter to 110 is this
ID | NAME | TYPE | ENTERED
-------------------------------------
1 | Example | example type | 1
2 | Another | example type | 1
But instead I get this
ID | NAME | TYPE | ENTERED
-------------------------------------
1 | Example | example type | 0
1 | Example | example type | 1
1 | Example | example type | 0
2 | Another | example type | 1
Because it's counting the entries for all user ids

Fixing your query
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
MAX(CASE
WHEN e.ID IS NOT NULL AND e.USERID = #userid THEN 1
ELSE 0
END
) AS 'ENTERED'
FROM competitions AS c
LEFT OUTER JOIN competition_entries AS e ON e.COMPETITIONID = c.ID
GROUP BY
c.[ID],
c.[NAME],
c.[TYPE]
An alternative is to rewrite it using EXISTS which is pretty much the same but may be easier to understand.
BTW, using single quotes on the column name is deprecated. Use square brackets.
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
CASE WHEN EXISTS (
SELECT *
FROM competition_entries AS e
WHERE e.COMPETITIONID = c.ID
AND e.USERID = #userid) THEN 1 ELSE 0 END [ENTERED]
FROM competitions AS c

Related

Update hierarchy after deletion of row

I have a table that contains tree-like data (hierarchic design). Here is a small sample:
+----+----------+-----------+-------+----------+---------+
| ID | ParentID | Hierarchy | Order | FullPath | Project |
+----+----------+-----------+-------+----------+---------+
| 1 | null | 1 | 1 | 1 | 1 |
| 2 | null | 2 | 2 | 2 | 1 |
| 3 | 1 | 1.1 | 1 | 1-3 | 1 |
| 4 | 1 | 1.2 | 2 | 1-4 | 1 |
| 5 | 4 | 1.2.1 | 1 | 1-4-5 | 1 |
| 6 | 2 | 2.1 | 1 | 2-6 | 1 |
| 7 | null | 3 | 1 | 1 | 2 |
+----+----------+-----------+-------+----------+---------+
Project indicates which project owns the hierarchic dataset
ParentID is the ID of the parent node, it has a foreign key on ID.
Order is the rank of the element in one branch. For example, IDs 1, 2 and 7 are on the same node while 3 and 4 are in another.
FullPath shows the order using the ID (it's for system use and performance reasons).
Hierarchy is the column displayed to the user, which displays the hierarchy to the UI. It auto calculates after every insert, update and delete, and it's the one I'm having issues.
I created a procedure for deletion elements in the table. It receives as input the ID of the element to delete and deletes it, along with it's children if any. Then, it recalculates the FullPath and the Order Column .That works.
Problems is when I try to update the Hierarchy column. I use this procedure:
SELECT T.ID,
T.ParentID,
CASE WHEN T.ParentID IS NOT NULL THEN
CONCAT(T1.Hierarchy, '.', CAST(T.Order AS NVARCHAR(255)))
ELSE
CAST(T.Order AS NVARCHAR(255))
END AS Hierarchy
INTO #tmp
FROM t_HierarchyTable T
LEFT JOIN t_HierarchyTable T1
ON T1.ID = T.ParentID
WHERE Project = #Project --Variable to only update the current project for performance
ORDER BY T.FullPath
--Update the table with ID as key on tmp table
This fails when I delete items that have lower order than others and they have children.
For example, if I delete the item 3, item 4 Hierachy will be corrected (1.1), BUT its child won't (it will stay at 1.2.1, while it should be 1.1.1). I added the order by to make sure parents where updated first, but no change.
What is my error, I really don't know how to fix this.
I managed to update the hierarchy with a CTE. Since I have the order, I can append it to Hierarchy, based on the previous branch (parent) who is already updated.
;WITH CODES(ID, sCode, iLevel) AS
(
SELECT
T.[ID] AS [ID],
CONVERT(VARCHAR(8000), T.[Order]) AS [Hierarchy],
1 AS [iLevel]
FROM
[dbo].[data] AS T
WHERE
T.[ParentID] IS NULL
UNION ALL
SELECT
T.[ID] AS [ID],
P.[Hierarchy] + IIF(RIGHT(P.[Hierarchy], 1) <> '-', '-', '') + CONVERT(VARCHAR(8000), T.[Order]) AS [Hierarchy],
P.[iLevel] + 1 AS [iLevel]
FROM
[dbo].[data] AS T
INNER JOIN CODES AS P ON
P.[ID] = T.[ParentID]
WHERE
P.[iLevel] < 100
)
SELECT
[ID], [Hierarchy], [iLevel]
INTO
#CODES
FROM
CODES

Check if records exists at least once in LEFT JOINED Table

I have an Images, Orders and OrderItems table, I want to match for any images, if any has already been bought by the User passed as parameters by displaying true or false in an IsBought column.
Select Images.Id,
Images.Title,
Images.Description,
Images.Location,
Images.PriceIT,
Images.PostedAt,
CASE WHEN OrderItems.ImageId = Images.Id THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT) END
AS 'IsBought'
FROM Images
INNER JOIN Users as u on Images.UserId = u.Id
LEFT JOIN Orders on Orders.UserId = #userId
LEFT JOIN OrderItems on Orders.Id = OrderItems.OrderId and OrderItems.ImageId = Images.Id
Group By Images.Id,
Images.Title,
Images.Description,
Images.Location,
Images.PriceIT,
Images.PostedAt,
OrderItems.ImageId,
Orders.UserId
When I use this CASE WHEN I have duplicates when the item has been bought where IsBought is True and the duplicate is False.
In the case where the Item has never been bought, there is no duplicates, IsBought is just equal to False
----------------------------------
| User | type |
----------------------------------
| Id | nvarchar(450) |
----------------------------------
| .......|
----------------------------------
----------------------------------
| Orders | type |
----------------------------------
| Id | nvarchar(255) |
----------------------------------
| UserId | nvarchar(450) |
----------------------------------
| ........................... |
----------------------------------
----------------------------------
| OrderItems | type |
----------------------------------
| Id | nvarchar(255) |
----------------------------------
| OrderId | nvarchar(255) |
----------------------------------
| ImageId | int |
----------------------------------
----------------------------------
| Images | type |
----------------------------------
| Id | int |
----------------------------------
| UserId | nvarchar(450) |
----------------------------------
| Title | nvarchar(MAX) |
----------------------------------
| Description| nvarhar(MAX) |
----------------------------------
| ......................... |
----------------------------------
Any ideas on how I could just have one row per Images with IsBought set to true or false but not duplicates?
I would like something like this:
----------------------------------------------------------------------------
| Id | Title | Description | Location | PriceIT | Location | IsBought |
----------------------------------------------------------------------------
| 1 | Eiffel Tower | .... | ...... | 20.0 | Paris | true |
----------------------------------------------------------------------------
| 2 | Tore di Pisa | .... | ...... | 20.0 | Italia | false |
---------------------------------------------------------------------------
| etc ......
---------------------------------------------------------------------------
Your query logic looks suspicious. It is unusual to see a join that consists only of a comparison of a column from the unpreserved table to a parameter. I suspect that you don't need a join to users at all since you seem to be focused on things "bought" by a person and not things "created" (which is implied by the name "author") by that same person. And a group by clause with no aggregate is often a cover-up for a logically flawed query.
So start over. You want to see all images apparently. For each, you simply want to know if that image is associated with any order of a given person.
select img.*, -- you would, or course, only select the columns needed
(select count(*) from Sales.SalesOrderDetail as orddet
where orddet.ProductID = img.ProductID) as [Order Count],
(select count(*) from Sales.SalesOrderDetail as orddet
inner join Sales.SalesOrderHeader as ord
on orddet.SalesOrderID = ord.SalesOrderID
where orddet.ProductID = img.ProductID
and ord.CustomerID = 29620
) as [User Order Count],
case when exists(select * from Sales.SalesOrderDetail as orddet
inner join Sales.SalesOrderHeader as ord
on orddet.SalesOrderID = ord.SalesOrderID
where orddet.ProductID = img.ProductID
and ord.CustomerID = 29620) then 1 else 0 end as [Has Ordered]
from Production.ProductProductPhoto as img
where img.ProductID between 770 and 779
order by <something useful>;
Notice the aliases - it is much easier to read a long query when you use aliases that are shorter but still understandable (i.e., not single letters). I've included 3 different subqueries to help you understand correlation and how you can build your logic to achieve your goal and help debug any issues you find.
This is based on AdventureWorks sample database - which you should install and use as a learning tool (and to help facilitate discussions with others using a common data source). Note that I simply picked a random customer ID value - you would use your parameter. I filtered the query to a range of images to simplify debugging. Those are very simple but effective methods to help write and debug sql.

Cross join, display table 2 column value base on table1

I have two tables in SQL Server, Say in table1 I have two columns Key1Display and Key2Display, they are of datatype bit and used to control whether to display the values in table2, and table 2 will have 2 columns Key1 and Key2.
What I am trying to achieve is a sort of cross join, say if table 1 has 3 rows:
| Key1Display | Key2Display |
+---------------------+------------------+
| 0 | 1 |
| 1 | 0 |
| 1 | 1 |
Say in table 2 there are 2 rows
| Key1 | Key2 |
+---------------------+------------------+
| Row1Key1value | Row1Key2value |
| Row2Key1value | Row2Key2value |
Then based on these two tables, I want to have a query to display 6 (2*3) rows and 1 column of results like this:
null:Row1Key2value
Row1Key1Value:null
Row1Key1Value:Row1Key2value
null:Row2Key2value
Row1Key2Value:null
Row1Key2Value:Row2Key2value
So something like:
select
case when t1.Key1Display = 1 then coalesce(t2.Key1,'??') else 'null' end
+ ':' + case when t1.Key2Display = 1 then coalesce(t2.Key2,'??') else 'null' end
-- And so on for as many keys as you have
from table1 t1
cross join table2 t2

SQL Server - How to check if two items have the same relations to another set of items?

I have table with Employees (tblEmployee):
| ID | Name |
| 1 | Smith |
| 2 | Black |
| 3 | Thompson |
And a table with Roles (tblRoles):
| ID | Name |
| 1 | Submitter |
| 2 | Receiver |
| 3 | Analyzer |
I have also a table with relations of Employees to their Roles with many to many relation type (tblEmployeeRoleRel):
| EmployeeID | RoleID |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 3 |
I need to select ID, Name from tblEmployee that have exaclty the same set of roles from tblEmployeeRoleRel as has the Employee with ID = 1. How can I do it?
Use a where clause to limit the roles you're looking at to those of employeeID of 1 and use a having clause to make sure that the employee's role count matches that of employee1.
SELECT A.EmployeeID
FROM tblEmployeeRoleRel A
WHERE Exists (SELECT 1
FROM tblEmployeeRoleRel B
WHERE B.EmployeeID = 1
and B.RoleID = A.RoleID)
GROUP BY A.EmployeeID
HAVING count(A.RoleID) = (SELECT count(C.RoleID)
FROM tblEmployeeRoleRel C
WHERE EmployeeID = 1)
This assumes that employeeID and roleID are unique in tblEmployeeRoleRel otherwise we may have to distinct the roleID fields above.
Declare #EmployeeID int = 1 -- change this to whatever employee ID you like, or perhaps you'd pass an Employee ID to it in a stored procedure.
Select Distinct e.EmployeeID -- normally distinct would incur extra overhead, but in this case you only want the employee IDs. not using Distinct when an employee has multiple roles will give you multiple employee IDs.
from tblEmployeeRoleRel as E
where E.EmployeeID not in
(Select EmployeeID from tblEmployeeRoleRel where RoleID not in (Select RoleID from tblEmployeeRoleRel where Employee_ID = #EmployeeID))
and exists (Select EmployeeID from tblEmployeeRoleRel where EmployeeID = e.EmployeeID) -- removes any "null" matches.
and E.Employee_ID <> #Employee_ID -- this keeps the employee ID itself from matching.

SQL Query : Obtaining Rows where Columns A in Table A = Column A in Table B

I am probably being completely thick here.
I have a tables with a list of reason codes in, the 'access' to these reasons is limited by whether or not columns have 1 or 0 in.
ID | Category | Reason | Brand1 | Brand2 | Brand3
1 | Test | That | 1 | 1 | 0
2 | Test2 | This | 0 | 1 | 0
3 | Test3 | Mine | 0 | 0 | 1
The user table
Username | Brand1 | Brand2 | Brand3
User1 | 1 | 1 | 0
Basically User1 should only be able to see the reasons whereby they have a 1 in the Users Table in the Brand Columns they have a 1 in the Brand column in the Reasons Table.
User1 should only be able to see the reason ID 1 & 2 because they have a 1 in Brand1 and 1 in Brand2 in the User Table.
I only wish to return the rows from the Reasons Table, whereby the User in the Users Table has a match 1 in the corresponding 'Brand' column.
i.e.
User 1 should return
ID | Category | Reason | Brand1 | Brand2 | Brand3
1 | Test | That | 1 | 1 | 0
2 | Test2 | This | 0 | 1 | 0
How would I get this in SQL, I tried writing it with OR's in the WHERE statement but it was always returning every row. There are 7 Brand columns in total.
I think I'm overlooking something simple here from over complicating the matter.
Cheers
If you post your SQL then I could get something closer.
Otherwise:
SELECT *
FROM Reasons AS R
INNER JOIN UserTable AS U
ON (U.Brand1 = 1 AND U.Brand1 = R.Brand1)
OR (U.Brand2 = 1 AND U.Brand2 = R.Brand2)
OR (U.Brand3 = 1 AND U.Brand3 = R.Brand3);
This will get only the rows where the user has a brand that the reason also has. Technically you could change the U.Brand# = R.Brand# to just R.Brand# = 1, but that might be hard for others to read later.
SELECT *
FROM Reasons AS R
INNER JOIN UserTable AS U
ON (U.Brand1 = 1 AND R.Brand1 = 1)
OR (U.Brand2 = 1 AND R.Brand2 = 1)
OR (U.Brand3 = 1 AND R.Brand3 = 1);
This would probably be faster than the second query, but not enough to notice in most situations.
If I Understood Correctly try below script
SELECT t.ID , t.Category , t.Reason , t.Brand1 , t.Brand2 , t.Brand3
FROM TableName t,UserTable U
WHERE (t.Brand1 =U.Brand1 AND t.Brand1 = 1 ) OR
(t.Brand2 = U.Brand2 AND t.Brand2 = 1 ) OR
(t.Brand3 =U.Brand3 AND t.Brand3 = 1 )
SELECT
rt.*
FROM ReasonTable AS rt
INNER JOIN UserTable AS ut ON (rt.Id = 1 AND ut.Brand1 = 1)
OR (rt.Id = 2 AND ut.Brand2 = 1) OR (rt.Id = 3 AND ut.Brand2 = 1)
WHERE ut.UserId = #userId

Resources