SQL One-to-many or NONE to many? - sql-server

I have a one-to-many join. Works flawlessly, and quickly. The one-to-many may have zero records in the many table. If this is the case, I want to join it to ALL records in the many table.
USERS
-----
UserID | Name
CATEGORIES
------------------
CategoryID | CategoryName
USER_CATEGORIES
---------------
UserID | CategoryID
IF there are NO categories assigned to the user, I would like to join ALL categories. The reasoning behind this is, some users may manage all categories. If a category is added, it's automatically assigned to those users.
1 | Michael
2 | Bob
100 | Billing
101 | Email
102 | Technical
1 | 101
I would like the result to be:
1 | Michael | 101 | Email
2 | Bob | 100 | Billing
2 | Bob | 101 | Email
2 | Bob | 102 | Technical
So far, the way it works is:
DECLARE #USERS TABLE (UserID INT, UserName VARCHAR(10));
DECLARE #USER_CATEGORIES TABLE (UserID INT, CategoryID INT);
DECLARE #CATEGORIES TABLE (CategoryID INT, CategoryName VARCHAR(10));
INSERT INTO #USERS (UserID, UserName)
(
SELECT 1, 'Michael' UNION ALL
SELECT 2, 'Bob'
)
INSERT INTO #CATEGORIES (CategoryID, CategoryName)
(
SELECT 100, 'Billing' UNION ALL
SELECT 101, 'Email' UNION ALL
SELECT 102, 'Technical'
)
INSERT INTO #USER_CATEGORIES (UserID, CategoryID)
(
SELECT 1, 101
)
SELECT
U.UserID
, U.UserName
, C2.CategoryID
, C2.CategoryName
FROM
#USERS U LEFT JOIN
#USER_CATEGORIES UC ON U.UserID = UC.UserID LEFT JOIN
#CATEGORIES C ON UC.CategoryID = C.CategoryID LEFT JOIN
#CATEGORIES C2 ON C.CategoryID = C2.CategoryID OR (C.CategoryID IS NULL)
Is this the correct way to handle this? It seems to be a little too much overhead when I have many users/category combinations.

You can remove one of the joins to #CATEGORIES:
SELECT
U.UserID
, U.UserName
, C2.CategoryID
, C2.CategoryName
FROM #USERS U
LEFT JOIN #USER_CATEGORIES UC
ON U.UserID = UC.UserID
LEFT JOIN #CATEGORIES C2
ON UC.CategoryID = C2.CategoryID
OR (UC.CategoryID IS NULL)

Erm, if you were querying for one category or one user, then while I find it aesthetically offensive, okay.
For all users and categories? Not for me that, no
Can't say which way you should go. because I haven't a clue why you want this data
But me I'd either return say 'ALL' the result of the inner join was null, or I'd just use it returning no records for that user, to trigger a select * from categories if I needed it and hadn't already cached it.

Related

How to show a specific view from a table in MS-SQL

I'm new to database concepts. So, i need some help in solving a particular problem.
Say i have a table named emp whose data are as below:
id | dept | doj
100 | FS | 02-04-13
100 | HST | 02-04-14
100 | ETA | 02-04-15
What i want to display is:
id | from | to | doj
100 | FS | HST | 02-04-14
100 | HST | ETA | 02-04-15
Any help would be appreciated. Thank you.
The database i'm using is MS-SQL server 13v.
You can use apply :
select t.id, t.dept as [from], t1.dept as [to], t1.doj
from table t cross apply
(select top (1) t1.*
from table t1
where t1.id = t.id and t1.doj > t.doj
order by t1.doj
) t1
With a self join to the table:
select t.id, tt.dept [from], t.dept [to], t.doj
from tablename t inner join tablename tt
on tt.id = t.id and
tt.doj = (select max(doj) from tablename where id = t.id and doj < t.doj)
See the demo
If I understand your question correctly and you want to get data from current and subsequent row in the same result set, you may use LEAD() function:
Input:
CREATE TABLE #Table (
id int,
dept varchar(10),
doj date
)
INSERT INTO #Table
(id, dept, doj)
VALUES
(100, 'FS', '2013-04-02'),
(100, 'HST', '2014-04-02'),
(100, 'ETA', '2015-04-02'),
(101, 'XTA', '2015-04-02'),
(101, 'YTA', '2015-04-02')
Statement:
SELECT *
FROM
(
SELECT
id,
dept [from],
LEAD(dept) OVER (PARTITION BY id ORDER BY id, doj) AS [to],
LEAD(doj) OVER (PARTITION BY id ORDER BY id, doj) AS [doj]
FROM #Table
) t
WHERE t.[to] IS NOT NULL
Output:
id from to doj
100 FS HST 2014-04-02
100 HST ETA 2015-04-02
101 XTA YTA 2015-04-02

JOIN SUM with WHERE

I couldn't figure this out from existing kinda similar threads. I've tried left join and different sort of subqueries but no luck.
I got left join work with group by but couldn't figure out how to add where clauses, then wen't to subqueries and broke everything.
I have two tables storage and orders.
Storage has list of unique id and name
id | name
1234 | product1
1235 | product2
A123 | product3
Orders have multiple instances code, quantity and type
code | qty| type
1234 | 10 | order
1234 | 10 | quote
1234 | 10 | order
A123 | 15 | order
1235 | 13 | order
I wan't to join these tables so that I get filtered (with where) results from storage and join with summed qty where type is order.
For example filter storage where id in (1234, A123) should result:
id | name | sum qty
1234 | product1 | 20
A123 | product3 | 15
Any help appreciated!
--
Going forward, storage has products and cols. There is table prod_to_col that has productid and col_id to tie them together.
I would need to grab product codes from table prod_to_col and show total quantity for cols according to order quantity.
I tried this according to #iSR5 example:
SELECT st.id, st.name, SUM(order.qty) AS SumQty
FROM storage
JOIN prod_to_col ON st.id=prod_to_col.col_id
JOIN orders ON order.id IN (SELECT prod_id FROM prod_to_col WHERE col_id=st.id) AND type='order'
WHERE id IN (1234, A123)
GROUP BY st.id, st.name
This almost works but quantities are multiplied in some rows some are fine, can someone point where it goes wrong?
In addition to tables storage and orders above, here's example of prod_to_col and cols:
Prod_to_col
prod_id | col_id | col_qty (per product)
1235 | C101 | 2
1236 | C102 | 1
Cols
col_id | name | other data
C101 | cname1 | --
C102 | cname2 | --
Orders
prod_id | qty | type
1235 | 10 | order
1235 | 10 | order
1236 | 2 | quote
1236 | 5 | order
Storage
st.id | st.name| SumQty
C101 | cname1 | 40
C102 | cname2 | 5
I understand I need to use two different sentence to populate storage list, one for products and one for cols. The one for products works fine.
Is this is what you need ?
DECLARE #Storage TABLE(ID VARCHAR(50), name VARCHAR(250) )
DECLARE #Orders TABLE(code VARCHAR(50), qty INT, type VARCHAR(50))
INSERT INTO #Storage VALUES
('1234','product1')
, ('1235','product2')
, ('A123','product3')
INSERT INTO #Orders VALUES
('1234',10,'order')
, ('1234',10,'quote')
, ('1234',10,'order')
, ('A123',15,'order')
, ('1235',13,'order')
SELECT
s.ID
, s.name
, SUM(o.qty) TotalQty
FROM
#Storage s
JOIN #Orders o ON o.code = s.ID AND o.type = 'order'
WHERE
s.ID IN('1234','A123')
GROUP BY
s.ID
, s.name
UPDATE
You've updated your post with more logic to cover, which wasn't provided before, however, I've update it the query for you ..
DECLARE #Storage TABLE(ID VARCHAR(50), name VARCHAR(250) )
DECLARE #Orders TABLE(code VARCHAR(50), qty INT, type VARCHAR(50))
DECLARE #Prod_to_col TABLE(prod_id VARCHAR(50), col_id VARCHAR(50), col_qty INT)
DECLARE #Cols TABLE(col_id VARCHAR(50), name VARCHAR(250))
INSERT INTO #Storage VALUES
('1234','product1')
, ('1235','product2')
, ('A123','product3')
, ('1236','product3')
INSERT INTO #Orders VALUES
('1234',10,'order')
, ('1234',10,'quote')
, ('1234',10,'order')
, ('A123',15,'order')
, ('1235',10,'order')
, ('1235',10,'order')
, ('1236',2,'quote')
, ('1236',5,'order')
INSERT INTO #Prod_to_col VALUES
('1235','C101',2)
, ('1236','C102',1)
INSERT INTO #Cols VALUES
('C101','cname1')
, ('C102','cname2')
SELECT
c.col_id
, c.name
, SUM(o.qty) * MAX(ptc.col_qty) TotalQty
FROM
#Storage s
JOIN #Orders o ON o.code = s.ID AND o.type = 'order'
JOIN #Prod_to_col ptc ON ptc.prod_id = o.code
JOIN #Cols c ON c.col_id = ptc.col_id
--WHERE
-- s.ID IN('1234','A123')
GROUP BY
c.col_id
, c.name
Try to use Left Join combined with a group by
SELECT OD.CODE
,ST.PRODUCT
,SUM(quantity) as qnt
FROM ORDERS OD
LEFT JOIN STORAGE ST ON(OD.CODE = SG.CODE)
WHERE OD.type like 'order'
GROUP BY
OD.CODE
,ST.PRODUCT
You can use Having to filter
Having id in (1234, A123)
Greetings

SQL Server Select count > than 1

Project: Classic ASP VBScript / SQL Server 2012
I have 2 tables, Products and Categories.
I want to build a SQL query that will return ONLY the categories that have MORE THAN ONE product.
The common key between the 2 tables is the column Category_id (exists in the Products table and also in the Categories table).
You try this:
SELECT *
FROM Categories C
WHERE C.Category_id IN ( SELECT P.Category_id FROM Products P GROUP BY P.Category_id HAVING COUNT(*) > 1)
You can use the having clause for this type of query, which filters your dataset after doing all the aggregates:
declare #Categories table(CategoryID int);
declare #Products table(ProdID int, CategoryID int);
insert into #Categories values(1),(2),(3);
insert into #Products values(10,1),(20,1),(30,2),(40,2),(50,3);
select c.CategoryID
,count(1) as ProductCount
from #Categories c
left join #Products p
on(c.CategoryID = p.CategoryID)
group by c.CategoryID
having count(1) > 1;
Output:
+------------+--------------+
| CategoryID | ProductCount |
+------------+--------------+
| 1 | 2 |
| 2 | 2 |
+------------+--------------+
Use Simple Group by with Having as next demo:-
create table Products (ProductID int, name varchar(50), Category_id int )
insert into Products
values
(1,'aaaa',1),
(2,'bbbb',1),
(3,'ccccc',2),
(4,'11111',2),
(5,'11111ccc',3)
create table Categories (Category_id int, name varchar(50))
insert into Categories
values
(1,'Letters'),
(2,'Numbers'),
(3,'NumbersAndLetters')
-- CategoryID = 3 has only one product ..
select p.Category_id ,count(p.ProductID) CountProducts from
Products p inner join Categories c
on p.Category_id = c.Category_id
group by p.Category_id
having count(p.ProductID) > 1
Result:-
Category_id CountProducts
1 2
2 2

Referencing outer table in an aggregate function in a subquery

I'm looking for a solution to particular query problem. I have a table Departments and table Employees designed like that:
Departments Employees
===================== ============================
ID | Name ID | Name | Surname | DeptID
--------------------- ----------------------------
1 | ADMINISTRATION 1 | X | Y | 2
2 | IT 2 | Z | Z | 1
3 | ADVERTISEMENT 3 | O | O | 1
4 | A | B | 3
I'd like to get list of all departments whose number of employees is smaller than number of employees working in Administration.
That was one of my ideas, but it did not work:
select * from Departments as Depts where Depts.ID in
(select Employees.ID from Employees group by Employees.ID
having count(Employees.ID) < count(case when Depts.Name='ADMINISTRATION' then 1 end));
Using GROUP BY and HAVING:
SELECT
d.ID, d.Name
FROM Departments d
LEFT JOIN Employees e
ON e.DeptID = d.ID
GROUP BY d.ID, d.Name
HAVING
COUNT(e.ID) < (SELECT COUNT(*) FROM Employees WHERE DeptID = 1)
Try this,
declare #Departments table (ID int, Name varchar(50))
insert into #Departments
values
(1 ,'ADMINISTRATION')
,(2 ,'IT')
,(3 ,'ADVERTISEMENT')
declare #Employees table (ID int, Name varchar(50)
,Surname varchar(50),DeptID int)
insert into #Employees
values
(1 ,'X','Y',2)
,(2 ,'Z','Z',1)
,(3 ,'O','O',1)
,(4 ,'A','B',3)
;
WITH CTE
AS (
SELECT *
,row_number() OVER (
PARTITION BY deptid ORDER BY id
) rn
FROM #Employees
WHERE deptid <> 1
)
SELECT *
FROM cte
WHERE rn < (
SELECT count(id) admincount
FROM #Employees
WHERE DeptID = 1
)

INTERSECT & EXCEPT [SQL Server 2012]

I have one problem that i can't find nice solution for.
I have relational table Groups_Members with columns GroupId and MemberId.
I have a stored procedure that creates a new group and receives an array of memberId as parameter (user defined type). What I want is to make sure that there is not a group with exactly the same members in the database already.
I'm trying to figure out how EXCEPT operator might help me but I can't. I need condition that would return the group that has exactly the same set of members as those in my memberId parameter (or null or 0 if such group doesn't exist).
Any help would be highly appreciated!
thanks!
Table Groups_Members
GroupId|MemberId
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 4
3 | 1
3 | 3
3 | 4
Declare #Members table(id int)
insert into #Members
values(1), (3), (4)
Declare #MemberCount int
Select #MemberCount = count(id) From #Members
Select GroupId from
(Select distinct groupId, memberid from Groups_Members) gm
Inner Join #Members On MemberId = id
group by GroupId
Having COUNT(MemberId) = #MemberCount
Result would be 3
Description can be provided on demand.
Declare #Members table(id int)
insert into #Members
values(1), (3), (4)
Declare #MemberCount int
Select #MemberCount = count(id) From #Members
--Select GroupId from
--(Select distinct groupId, memberid from Groups_Members) gm
--Inner Join #Members On MemberId = id
--group by GroupId
--Having COUNT(MemberId) = #MemberCount
When values(1), (3), (4) (it works correctly).
But when values(1), (2) (it does not work correctly).
Nice try but it does not provide the exact matching with group members.
Try this for better solution
Select Gm.GroupId from
(Select distinct GroupId, memberid from Groups_Members) gm
Inner Join #Members On MemberId = id
Inner join (Select COUNT(MemberId) as Totalmember,GroupId
from Groups_Members group by GroupId) tgm on tgm.GroupId = gm.GroupId
where Totalmember = #MemberCount
group by gm.GroupId
Having COUNT(MemberId) = #MemberCount
Description can be provided on demand.

Resources