Proper use of NOT EXISTS - sql

I have two tables -
member_master
-----------------------------
member_id, ( PK )
branch_id, ( PK )
name member_id,
member_photo
-----------------------------
member_id, ( FK )
branch_id, ( FK )
photo_index,
photo_file
Each entry in member_master has zero or more corresponding entries in member_photo table.
I have two requirements :
Get all the entries from member_master which have at least one entry in member_photo table. I am getting the correct result using the following SQL command
SELECT DISTINCT member_master.member_id,member_master.branch_id,name
FROM member_master, member_photo
WHERE member_master.branch_id=1
AND EXISTS
(
SELECT member_photo.member_id
WHERE member_master.member_id = member_photo.member_id
AND member_master.branch_id = member_photo.branch_id
)
;
Get all the entries from member_master which DO NOT have any entry in member_photo table. I am using the following SQL command
SELECT DISTINCT member_master.member_id,member_master.branch_id,name FROM member_master, member_photo
WHERE member_master.branch_id=1
AND NOT EXISTS
(
SELECT member_photo.member_id
WHERE member_master.member_id = member_photo.member_id
AND member_master.branch_id = member_photo.branch_id
)
;
The only difference is I have added a NOT before EXISTS command.
But unfortunately it does not give me the correct result. It simply returns all the rows in the table.
Please note that I am using SQL Server Express 2005.

You should do it without joining the tables, just select from the master table and add check for the photo table, like this:
SELECT
m.member_id,
m.branch_id,
m.name
FROM
member_master m
WHERE
m.branch_id=1 AND
EXISTS (SELECT 1 from member_photo p where
m.member_id = p.member_id AND m.branch_id = p.branch_id)
And similarly the other case:
SELECT
m.member_id,
m.branch_id,
m.name
FROM
member_master m
WHERE
m.branch_id=1 AND
not EXISTS (SELECT 1 from member_photo p where
m.member_id = p.member_id AND m.branch_id = p.branch_id)

Required SQL query is:
SELECT
member_id,
branch_id,
name
FROM member_master
WHERE member_master.branch_id=1
AND NOT EXISTS(SELECT member_photo.member_id
WHERE member_master.member_id = member_photo.member_id
AND member_master.branch_id = member_photo.branch_id)

Related

How to create cases for multiple purposes and just showing the count of unique IDs for multiple cases

I wrote a CTE which helps determine a flag based off a certain client's ID and what kind of client they are. I am looking to test the counts of the Flags, and the counts are totaling out amazingly! However, I am looking to add additional columns to the very last section of my code to show the amount of IDs who have belonged in all 3 cases, or both PPP and R, PPP and RR, RR and PPP, or RR and R. Is there a way I could do this? I know SUM won't work. I'm thinking using CASE or an IF, however I am a novice to SQL and am unsure what to do.
WITH ids AS (
SELECT DISTINCT LOWER(r.entry_id) AS ID
FROM id_user AS r
UNION
SELECT DISTINCT LOWER(identifiervalue) AS ID
FROM account AS a
)
,PPP as (
SELECT DISTINCT
LOWER(accountid) as "ID"
FROM ppp
WHERE (date >= '2022-11-21')
)
,R as (
SELECT DISTINCT
LOWER(account_id) as "ID"
FROM user
)
, RR as (
SELECT DISTINCT
LOWER(id) AS "ID"
FROM program_member
)
, Joint as (
SELECT
r.ID
,CASE WHEN p.ID IS NULL THEN 0 ELSE 1 END AS "PPP Flag"
,CASE WHEN r.ID IS NULL THEN 0 ELSE 1 END AS "R Flag"
,CASE WHEN rr.raid IS NULL THEN 0 ELSE 1 END AS "RR Flag"
FROM ids i
LEFT JOIN PPP ppp ON i.RAID = ppp.RAID
LEFT JOIN R r ON i.RAID = r.RAID
LEFT JOIN RR rr on i.RAID = rr.RAID
----TESTING COUNTS
SELECT
COUNT(ID) AS "ID Count"
,sum("PPP Flag") AS "PPP Users"
,sum("R") AS "R Accounts"
,sum("RR Flag") AS "RR Users"
FROM Joint
if all you are trying to do is get distinct counts, and how many are within each of those given flag categories, dont try to repeat then join. They are either in the PPP, User or Program_Member table. Just get that
select
UnionCnt.IDAndRaidCnt,
P.PPPCnt,
R.RCnt,
RR.RRCnt
from
-- this outer query returns only 1 record
( select
count(*) IDAndRaidCnt
from
-- this inner query returns all distinct based on the UNION result
(SELECT DISTINCT
LOWER(r.entry_id) ID
FROM
id_user r
UNION
SELECT DISTINCT
LOWER(identifiervalue) ID
FROM
account a)
) UnionCnt
JOIN
(SELECT
count( DISTINCT LOWER(accountid)) PPPCnt
FROM
ppp
WHERE
date >= '2022-11-21' ) P
-- above will only return a single record anyhow
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(account_id) RCnt
FROM
user ) R
-- also returns single row with total qualifying distinct ID count
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(id)) RRCnt
FROM
program_member ) RR
-- same here, single record
on 1=1

delete duplicates and retain MAX(id) mysql

I have a code where it list all the duplicates of the data on database
SELECT MAX(id) id
FROM el_student_class_relation
GROUP BY student_id, class_id
HAVING COUNT(*) > 1
Now, I'm trying to retain the MAX(id), then the rest of the duplicates should be deleted
I tried the code
DELETE us
FROM el_student_class_relation us
INNER JOIN(SELECT MAX(id) id
FROM el_student_class_relation
GROUP BY student_id, class_id HAVING COUNT(*) > 1) t ON t.id = us.id
But it deletes the MAX(ID) and it is retaining the the other duplicates and it is the opposite of what I want.
Try this
DELETE FROM el_student_class_relation
WHERE id not in
(
SELECT * from
(SELECT MAX(id) id
FROM el_student_class_relation
GROUP BY student_id, class_id) temp_tbl
)
Please note:
do not use the HAVING COUNT(*) > 1 in inner query.
it will create issue when there is only single record with same id.
You might try the following query that deletes all elements for which another one with a higher ID (and same class and student) exists:
DELETE
FROM el_student_class_relation el1
WHERE EXISTS (SELECT el2.id
FROM el_student_class_relation el2
WHERE el1.student_id = el2.student_id
AND el1.class_id = el2.class_id
AND el2.id > el1.id);
The direct fix for your query is to use an "anti-join", where NOT joining is the important feature. This can be done with LEFT JOIN.
DELETE
us
FROM
el_student_class_relation us
LEFT JOIN
(
SELECT student_id, class_id, MAX(id) id
FROM el_student_class_relation
GROUP BY student_id, class_id
-- HAVING COUNT(*) > 1 [Don't do this, you need to return ALL the rows you want to keep]
)
gr
ON gr.id = us.id
WHERE
gr.id IS NULL -- WHERE there wasn't a match in the "good rows" table
EDIT MariaDB and MySQL aren't the same thing. MariaDB DOES allow self joins on the table being deleted from.
in mysql(lower version) in case of delete sub-query work a little bit different way, you have to use a layer more than required
DELETE FROM el_student_class_relation us
WHERE us.id not in
(
select * from (
SELECT MAX(id) id
FROM el_student_class_relation
GROUP BY student_id, class_id
) t1
)

Update data in table1 where duplicates in table2

I have 2 tables: Users and Results.
The usertable contains duplicate data which is reflected in the results table. The user below is created 3 times. I need to update the results table where UserId 2 and 3 to 1 so that all the results can be viewed on this user only.
This is easy if I have only have a few users and a few results for them, but in my case I have 500 duplicated users and 30000 results.
I am using SQL Server Express 2014
I will really appreciate any help with this!
Edit: misstyped column names in resultTable. Im sorry if you got confused by it.
UserTable
UserId---Fname---LName
1-----Georg-----Smith
2-----Georg-----Smith
3-----Georg-----Smith
ResultsTable
ResultId---UserRefId
1-----1
2-----2
3-----3
4-----1
I have manage to select duplicates from usertable, but i don't know how to proceed further.
;WITH T AS
(
SELECT *, COUNT(*) OVER (PARTITION BY Fname + Lname) as Cnt
FROM TestDatabase.Users
)
SELECT Id, Fname, Lname
FROM T
WHERE Cnt > 2
Your ResultTable has 2 columns with the same UserId name. I changed the second to UserId2 for the query below:
;WITH cte As
(
SELECT R.UserId, R.UserId2,
MIN(U.UserId) OVER (PARTITION BY U.FName, U.LName) As OriginalUserId
FROM ResultTable R
INNER JOIN UserTable U ON R.UserId = U.UserId
)
UPDATE cte
SET UserId2 = OriginalUserId
You are on the right track with the cte. The ROW_NUMBER() function can be used to flag duplicate UserIds, then you can join the cte into the from clause of your update statement to find the UserIds you want to replace, and join again to find the UserIds you want to replace them with.
;WITH cteDedup AS(
SELECT
UserId
,FName
,LName
,ROW_NUMBER() OVER(PARTITION BY FName, LName ORDER BY UserID ASC) AS row_num
FROM UserTable
)
UPDATE rt
SET UserId = original.UserId
FROM ResultsTable rt
JOIN cteDedup dupe
ON rt.UserId = dupe.UserId
JOIN cteDedup original
ON dupe.FName = original.FName
AND dupe.LName = original.LName
WHERE dupe.row_num <> 1
AND original.row_num = 1
See the SQLFiddle
A little tricky query looks like this:
;with t as (
select fname+lname name,id,
ROW_NUMBER() over(partition by fname+lname order by id) rn
from #users
)
--for test purpose comment next 2 lines
update #results
set userid=t1.id
--and uncomment the next one
--select t.name,t.id,userid,res,t1.id id1--,(select top 1 id from t t1 where t1.name=t.name and t.rn=1) id1
from t
inner join #results r on t.id=r.userid
inner join t t1 on t.name=t1.name and t1.rn=1
And then you can delete duplicate users
;with t as (
select name,id,
ROW_NUMBER() over(partition by name order by id) rn
from #users
)
delete t where rn>1

SELECT Statement for cocktail db

This is probably pretty simple and dumb to ask but Im just not getting there right now. I have a db for cocktails and want to check which cocktails I can make with the available ingredients:
Get the names of all cocktails where every ingredient is in stock
These are my tables:
create table cocktails
(
name TEXT PRIMARY KEY
)
create table ingredients
(
name TEXT PRIMARY KEY
)
create table cocktail_ingredients
(
cocktail_name TEXT ,
ingredient_name TEXT ,
amount INTEGER ,
FOREIGN KEY ( cocktail_name ) REFERENCES cocktails( name ) ,
FOREIGN KEY ( ingredient_name ) REFERENCES ingredients( name )
)
create table ingredients_in_stock
(
ingredient_name TEXT ,
FOREIGN KEY ( ingredient_name ) REFERENCES ingredients ( name )
)
And this is my code so far:
SELECT ci.cocktail_name
FROM cocktail_ingredients ci
WHERE ci.ingredient_name IN ( SELECT iis.ingredient_name
FROM ingredients_in_stock iis
)
GROUP BY ci.cocktail_name
HAVING COUNT(*) = ( SELECT COUNT(*)
FROM ingredients_in_stock
)
;
You can use a LEFT JOIN and a IN clause for this. Something like this:
SELECT name FROM cocktails WHERE Name NOT IN(
SELECT DISTINCT ci.cocktail_name FROM cocktail_ingredients ci LEFT JOIN ingredients_in_stock istk
ON ci.ingredient_name=istk.ingredient_name WHERE istk.ingredient_name IS NULL)
This query inverts the logic: List the cocktails where none of it's ingredients are missing on the ingredients_in_stock table. Hope the idea helps you
A correlated subquery should work:
select cocktail_name as all_ingredients_in_stock
from cocktail_ingredients ci
inner join ingredients_in_stock iis
on ci.ingredient_name = iis.ingredient_name
group by cocktail_name
having count(*) =
(select count(*)
from cocktail_ingredients
where cocktail_name = ci.cocktail_name
)
Sample SQL Fiddle
You could just say something like this:
select ci.name
from cocktail_ingredients ci
left join ingredients_in_stock iis on iis.ingredient_name = ci.ingredient_name
group by ci.name
having count(ci.ingredient_name) = sum( case
when iis.ingredient_name is not null
then 1
else 0
end
)
In the having clause,
The count(ci.ingredient_name) gives you the total number of ingredients required for the cocktail
The sum() expression gives you the count of in-stock ingredients used by the cocktail.

Recursive query in SQL Server

I have a table with following structure
Table name: matches
That basically stores which product is matching which product. I need to process this table
And store in a groups table like below.
Table Name: groups
group_ID stores the MIN Product_ID of the Product_IDS that form a group. To give an example let's say
If A is matching B and B is Matching C then three rows should go to group table in format (A, A), (A, B), (A, C)
I have tried looking into co-related subqueries and CTE, but not getting this to implement.
I need to do this all in SQL.
Thanks for the help .
Try this:
;WITH CTE
AS
(
SELECT DISTINCT
M1.Product_ID Group_ID,
M1.Product_ID
FROM matches M1
LEFT JOIN matches M2
ON M1.Product_Id = M2.matching_Product_Id
WHERE M2.matching_Product_Id IS NULL
UNION ALL
SELECT
C.Group_ID,
M.matching_Product_Id
FROM CTE C
JOIN matches M
ON C.Product_ID = M.Product_ID
)
SELECT * FROM CTE ORDER BY Group_ID
You can use OPTION(MAXRECURSION n) to control recursion depth.
SQL FIDDLE DEMO
Something like this (not tested)
with match_groups as (
select product_id,
matching_product_id,
product_id as group_id
from matches
where product_id not in (select matching_product_id from matches)
union all
select m.product_id, m.matching_product_id, p.group_id
from matches m
join match_groups p on m.product_id = p.matching_product_id
)
select group_id, product_id
from match_groups
order by group_id;
Sample of the Recursive Level:
DECLARE #VALUE_CODE AS VARCHAR(5);
--SET #VALUE_CODE = 'A' -- Specify a level
WITH ViewValue AS
(
SELECT ValueCode
, ValueDesc
, PrecedingValueCode
FROM ValuesTable
WHERE PrecedingValueCode IS NULL
UNION ALL
SELECT A.ValueCode
, A.ValueDesc
, A.PrecedingValueCode
FROM ValuesTable A
INNER JOIN ViewValue V ON
V.ValueCode = A.PrecedingValueCode
)
SELECT ValueCode, ValueDesc, PrecedingValueCode
FROM ViewValue
--WHERE PrecedingValueCode = #VALUE_CODE -- Specific level
--WHERE PrecedingValueCode IS NULL -- Root