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
Related
I have temp table in my SP and a table in my DB and I need to update the table in the DB, and as of now I am able to update the table using select statement.
But I am having multiple records in my temp table and I am able to update only my last row of the table in DB.
Below is the query I am having,
UPDATE
Table_A
SET
Table_A.col2 = Table_B.col2,
Table_A.col3 = Table_B.col3
FROM
Some_Table AS Table_A
INNER JOIN temp_Table AS Table_B ON Table_A.col1 = Table_B.col1
And DB table structures
col1 | col2 | col3
1 | India | Delhi
2 | US | NewYork
3 | UK | London
And temp table structure as below
col1 | col2 | col3
1 | US | NewYork
2 | UK | London
3 | India | Delhi
So, I need to update my table for multiple rows.
As far as I am understanding your post I think this should be the solution for that which will update properly. Hope this helps
WITH CTE1
AS ( SELECT Col1 ,
Col2 ,
ROW_NUMBER() OVER ( PARTITION BY COl1 ORDER BY Col2 ) AS rn
FROM table_1 t1
),
CTE2
AS ( SELECT Col1 ,
Col2 ,
ROW_NUMBER() OVER ( PARTITION BY COl1 ORDER BY Col2 ) AS rn
FROM table_2 t2
)
UPDATE br
SET ....
FROM Cte1 c1
INNER JOIN cte2 c2 br ON c1.Col1 = c2.Col1
AND c1.rn = c2.rn;
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
)
Following structure exists:
CREATE TABLE rel(
entry_id int,
parent_id int
)
CREATE TABLE entries(
entry_id int,
name varchar(44)
)
Following data exists:
INSERT INTO entries VALUES (1,'user 1');
INSERT INTO entries VALUES (2,'group 2');
INSERT INTO entries VALUES (3,'group 3');
INSERT INTO entries VALUES (4,'user 4');
INSERT INTO entries VALUES (5,'user 5');
INSERT INTO rel VALUES (3,2);
INSERT INTO rel VALUES (4,2);
INSERT INTO rel VALUES (1,3);
INSERT INTO rel VALUES (5,3);
INSERT INTO rel VALUES (2,NULL);
Result should look like:
group_id| group_name | member_id | member_name | level
2 | group 2 | 4 | user 4 | 0
2 | group 2 | 1 | user 1 | 1
2 | group 2 | 5 | user 5 | 1
3 | group 3 | 1 | user 1 | 0
3 | group 3 | 5 | user 5 | 0
I already tried stuff like the following but it's not returning the results I need:
SELECT
entries.entry_id,
entries.name,
rel.parent_id,
(SELECT name FROM entries WHERE entry_id=parent_id) AS parent_name
INTO
#tmpEntries
FROM
entries, rel
WHERE
rel.entry_id = entries.entry_id
;
SELECT * FROM #tmpEntries;
WITH MyCTE
AS (
SELECT
entry_id,
name,
parent_id,
--CAST('' AS VARCHAR(44)) AS
parent_name
FROM #tmpEntries
--WHERE parent_id IS NULL
UNION ALL
SELECT
#tmpEntries.entry_id,
#tmpEntries.name,
#tmpEntries.parent_id,
--MyCTE.name AS
#tmpEntries.parent_name
FROM #tmpEntries
INNER JOIN MyCTE ON #tmpEntries.parent_id = MyCTE.entry_id
--WHERE #tmpEntries.parent_id IS NOT NULL
-- WHERE NOT EXISTS (SELECT entry_id FROM rel WHERE parent_id=#tmpEntries.entry_id)
)
SELECT DISTINCT *
FROM MyCTE
ORDER BY parent_id
;
WITH MyCTE2
AS (
SELECT
entry_id,
name,
parent_id,
--CAST('' AS VARCHAR(44)) AS
parent_name
FROM #tmpEntries
--WHERE parent_id IS NULL
UNION ALL
SELECT
#tmpEntries.entry_id,
#tmpEntries.name,
#tmpEntries.parent_id,
--MyCTE.name AS
#tmpEntries.parent_name
FROM #tmpEntries
INNER JOIN MyCTE2 ON #tmpEntries.parent_id = MyCTE2.entry_id
--WHERE #tmpEntries.parent_id IS NOT NULL
WHERE NOT EXISTS (SELECT entry_id FROM rel WHERE parent_id=#tmpEntries.entry_id)
)
SELECT DISTINCT *
FROM MyCTE2
ORDER BY parent_id
This will work under assumption that group is anything that contains one or more members i.e. empty group will be considered a simple member.
with cte_hierarchy as
(
select entry_id, parent_id, 0 as level
from rel
where parent_id is not null
union all
select h.entry_id, rel.parent_id, h.level + 1 as level
from cte_hierarchy h
inner join rel on h.parent_id = rel.entry_id
)
select
g.entry_id as group_id,
g.name as group_name,
e.entry_id as member_id,
e.name as member_name,
h.level
from cte_hierarchy h
inner join entries e on e.entry_id = h.entry_id
inner join entries g on g.entry_id = h.parent_id
where not exists (select * from rel where parent_id = h.entry_id)
order by g.entry_id, h.level, e.entry_id
The where clause excludes nested groups that would otherwise appear as members.
I am trying to identify duplicate records and then delete one of the duplicate record using PARTITION and RANK() n SQL Server 2008. I condition to delete duplicate record is that it should not be referenced in another table.
I have a Language table that has some duplicate languages. Employee table has employees and mapping to language. I have to delete one of the duplicate records if that Language id is not being mapped in Employee table.
CREATE TABLE MY_LANGUAGE (LANGUAGEID INT, LANGUAGENAME VARCHAR(20))
CREATE TABLE MY_EMPLOYEE (EMPID INT, NAME VARCHAR(20), LANGUAGEID INT)
INSERT INTO MY_LANGUAGE VALUES(1, 'ENGLISH')
INSERT INTO MY_LANGUAGE VALUES(2, 'FRENCH')
INSERT INTO MY_LANGUAGE VALUES(3, 'ITALIAN')
INSERT INTO MY_LANGUAGE VALUES(4, 'GERMAN')
INSERT INTO MY_LANGUAGE VALUES(5, 'ITALIAN')
INSERT INTO MY_LANGUAGE VALUES(6, 'GERMAN')
INSERT INTO MY_LANGUAGE VALUES(7, 'SPANISH')
INSERT INTO MY_EMPLOYEE VALUES (10, 'GLEN', 1)
INSERT INTO MY_EMPLOYEE VALUES (20, 'PETER', 2)
INSERT INTO MY_EMPLOYEE VALUES (30, 'MARIA', 3)
If you see, I have two languages that are duplicate and one of them is being used by an employee. I want to delete language ids 4 and 5.
LANGUAGENAME LANGUAGEID EMPNAME
GERMAN 4
GERMAN 6
ITALIAN 3 MARIA
ITALIAN 5
I have tried to create a select statement to return what I want to delete:
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
SELECT * FROM CTE WHERE RANKING = 1
This return the following
LANGUAGENAME LANGUAGEID RANKING
GERMAN 4 1
ITALIAN 5 1
When I try to delete I get an error:
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
DELETE FROM CTE WHERE RANKING = 1
Error that I get is:
Msg 4405, Level 16, State 1, Line 1
View or function 'CTE' is not updatable because the modification affects multiple base tables.
Any ideas how to fix this or may be it can be simplified. Thanks to #Szymon for showing a temp table solution but I am hoping to get a solution without temp tables (if possible).
Query:
DELETE ll
FROM MY_LANGUAGE ll
JOIN (SELECT L.LANGUAGENAME,
L.LANGUAGEID,
ROW_NUMBER()OVER(PARTITION BY L.LANGUAGENAME, e.EMPID
ORDER BY L.LANGUAGEID ASC) rnk,
COUNT(*)OVER(PARTITION BY L.LANGUAGENAME) cnt,
e.EMPID
FROM MY_LANGUAGE l
LEFT JOIN MY_EMPLOYEE e ON e.LANGUAGEID = l.LANGUAGEID) a
ON ll.LANGUAGEID = a.LANGUAGEID
AND a.rnk = 1
AND a.cnt > 1
and a.EMPID IS NULL
Result:
| LANGUAGEID | LANGUAGENAME |
|------------|--------------|
| 1 | ENGLISH |
| 2 | FRENCH |
| 3 | ITALIAN |
| 6 | GERMAN |
| 7 | SPANISH |
You can get the records from your CTE query into a temporary table and then delete based on that table.
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
SELECT * INTO #temp FROM CTE WHERE RANKING = 1
And then use records from #temp to delete from the original table.
Try this..
;WITH CTE AS (
SELECT t.LANGUAGEID, LANGUAGENAME, RANK() OVER(PARTITION BY T.LANGUAGENAME ORDER BY T.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE T
WHERE T.LANGUAGEID IN (
SELECT L.LANGUAGEID
FROM MY_LANGUAGE L LEFT JOIN MY_EMPLOYEE E
ON L.LANGUAGEID = E.LANGUAGEID
WHERE E.LANGUAGEID IS NULL)
)
DELETE FROM CTE
WHERE RANKING = 1 AND (CTE.LANGUAGENAME IN (SELECT LANGUAGENAME
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(LANGUAGENAME) > 1))
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.