mySQL circular query - mysql

I have a two tables say table 1: Member and table 2: Info. ID is primary key and A & B are primary keys in second table.
A represents introduced from and B represents someone who introduces. So the entry 002 -> 001 means that 001 introduces 002.
I want to have a query to show those Name in which 001 is not involved, meaning that those people who 001 introduces AND those people who introduce 001 are NOT involved.
This is what i have so far.
SELECT DISTINCT Info.A
FROM Info
WHERE NOT (A="001" OR B="001")
UNION
SELECT DISTINCT Info.B
FROM Info
WHERE NOT (A="001" OR B="001)
The expected result should be 004 but my query is also including 003. Any suggestions?

You could get all the ids where 001 is involved, then the result is NOT IN those ids.
SELECT * FROM Member
WHERE ID NOT IN (
SELECT IF(A = '001', B, A)
FROM Info WHERE A = '001' OR B = '001'
UNION
SELECT '001'
)
THE SQLFIDDLE.

Related

Creating a column in a table based on aggregation (without creating another table)

I have a table called Sets for LEGO:
set_number (Primary Key)
set_name
other_fields
123
Firetruck
abc
234
Star Wars
abc
I have another table called Parts for LEGO:
part_number (Primary Key)
name
set_number (references set_number from Sets)
1
Truck Roof
123
2
Truck Body
123
3
Neimoidian Viceroy Robe
234
I want to create another column in the Sets table to indicate the number of unique parts the particular set has.
I was able to output the number of unique parts with the following:
SELECT s.set_number, COUNT(*) AS num_diff_parts
FROM Sets s, Parts p
WHERE p.set_number = s.set_number
GROUP BY s.set_number
This outputs the following table (let's call it results):
set_number
num_diff_parts
123
2
234
1
However, I wonder if I can put the column (num_diff_parts) into the Sets table as a new column, instead of having to run this query every time when I need this information, or create another table just to contain the content of the results table.
Ideally, the Sets table should look like this:
set_number (Primary Key)
set_name
other_fields
num_diff_parts
123
Firetruck
abc
2
234
Star Wars
abc
1
I've also tried to do GROUP BY on multiple fields, but I don't think that's safe to do as those fields can have repeats and will throw off the results.
select distinct
set_number
,set_name
,other_fields
,count(*) over(partition by set_number) as num_diff_parts
from Sets join Parts using(set_number)
set_number
set_name
other_fields
num_diff_parts
123
Firetruck
abc
2
234
Star Wars
abc
1
We can also count() before joining the tables.
with parts_cnt as (
select set_number
,count(*) as num_diff_parts
from Parts
group by set_number
)
select *
from Sets join parts_cnt using(set_number)
Fiddle
However, I wonder if I can put the column (num_diff_parts) into the Sets table as a new column, instead of having to run this query every time when I need this information
I would recommend using a view ; with this technique, the information is always available, and you don’t need to keep it up to date by yourself.
In MySQL, a correlated subquery comes handy to efficiently compute the count of parts per set :
create view v_sets as
select s.*,
(
select count(*)
from parts p
where p.set_number = s.set_number
) num_diff_parts
from sets s

Better solution to MySQL nested select in's

Currently I have two MySQL tables
Properties
id name
1 Grove house
2 howard house
3 sunny side
Advanced options
prop_id name
1 Wifi
1 Enclosed garden
1 Swimming pool
2 Swimming pool
As you can see table two contains specific features about the properties
When I only have max 3 options the query below worked just fine. (maybe a little slow but ok) now things have expanded somewhat and i have a max of 12 options that it is possible to search by and its causing me some major speed issues. The query below is for 8 options and as you can see its very messy. Is there a better way of doing what I'm trying to achieve?
SELECT * FROM properties WHERE id in (
select prop_id from advanced_options where name = 'Within 2 miles of sea or river' and prop_id in (
select prop_id from advanced_options where name = 'WiFi' and prop_id in (
select prop_id from advanced_options where name = 'Walking distance to pub' and prop_id in (
select prop_id from advanced_options where name = 'Swimming pool' and prop_id in (
select prop_id from advanced_options where name = 'Sea or River views' and prop_id in (
select prop_id from advanced_options where name = 'Pet friendly' and prop_id in (
select prop_id from advanced_options where name = 'Open fire, wood burning stove or a real flame fire-place' and prop_id in (
select prop_id from advanced_options where name='Off road parking')
)
)
)
)
)
)
)
Like Mike Brant suggest I would consider altering your datamodel to a limit to set and creating a column for each of these in your properties table. But some times the boss comes: "We also need 'flatscreen tv'" and then you have to go back to the DB and update the scheme and your data access layer.
A way to move this logic somehow out if the database it to use bitwise comparison. This allows you to make simple queries, but requires a bit of preprocessing before you make your query.
Judge for yourself.
I've put everything in a test suite for you here sqlfiddle
The basic idea is that each property in your table has an id that is the power of 2. Like this:
INSERT INTO `advanced_options` (id, name)
VALUES
(1, 'Wifi'),
(2, 'Enclosing Garden'),
(8, 'Swimming Pool'),
(16, 'Grill');
You can then store a single value in your properties table buy adding up the options:
Wifi + Swimming Pool = 1 + 8 = 9
If you want to find all properties with wifi and a swimming pool you then do like this:
SELECT * FROM `properties` WHERE `advanced_options` & 9 = 9
If you just wanted swimming pool this would be it:
SELECT * FROM `properties` WHERE `advanced_options` & 8 = 8
Go try out the fiddle
You really need to consider a schema change to your table. It seems that advanced options in and of themselves don't have any properties, so instead of an advanced_options table that is trying to be a many-to-many JOIN table, why not just have a property_options table with a field for each "options". Something like this
|prop_id | wifi | swimming_pool | etc..
-----------------------------------
| 1 | 0 | 1 |
| 2 | 1 | 0 |
Here each field is a simple TINYINT field with 0/1 boolean representation.
To where you could query like:
SELECT * FROM properties AS p
INNER JOIN property_options AS po ON p.id = po.prop.id
WHERE wifi = 1 AND swimming_pool = 1 ....
Here you would just build your WHERE clause based on which options you are querying for.
There actually wouldn't be any need to even have a separate table, as these records would have a one-to-one relationship with the properties, so you could normalize these fields onto you properties table if you like.
Join back to the advanced_options table multiple times. Here's a sample with 2 (lather, rinse, repeat).
select o1.prop_id
from advanced_options o1
inner join advanced_options o2 on o1.prop_id = o2.prop_id and o2.name = "WiFi"
where o1.name = 'Within 2 miles of sea or river'
Could you do something like this?:
select p.*,count(a.prop_id) as cnt
from properties p
inner join advanced_options a on a.prop_id = p.id
where a.name in ('Enclosed garden','Swimming pool')
group by p.name
having cnt = 2
That query would get all the properties that have ALL of those advanced_options...
I would also suggest normalizing your tables by creating a separate table Called Advanced_option (id,name) where you store your unique Option values and then create a junction entity table like Property_x_AdvancedOption (fk_PropertyID, FK_AdvancedOptionID) that way you use less resources and avoid data integrity issues.

MySQL Multiple Select Ambiguous Results

Using phpMyAdmin 5.1.44 to experiment with DML commands.
I've been following tutorials on-line.
SELECT book.b_isbn, publisher.p_name FROM 'book', 'publisher' WHERE book.b_title='DSA'
Table 1
book
b_id(PK) b_isbn b_title p_id(FK)
-----------------------------------------
1 12345 DSA 1
2 23456 SD 1
3 34567 CSP 2
Table 2
publisher
p_id(PK) p_name
--------------------
1 Fred
2 John
Expected Results
b_isbn p_name
---------------------
12345 Fred
Actual Results
b_isbn p_name
----------------------
12345 Fred
34567 John
Any ideas?
You need to tell MySQL how to join the tables together (without which it just matches every book to every publisher) - use any one of:
add AND publisher.p_id = book.p_id to your WHERE clause;
tell MySQL to join ON that condition / USING that column;
... FROM book JOIN publisher ON publisher.p_id = book.p_id WHERE ...
or
... FROM book JOIN publisher USING (p_id) WHERE ...
use a NATURAL JOIN to have MySQL guess that's what you want based on the column names.
... FROM book NATURAL JOIN publisher WHERE ...
I think you need to put the fk to the pk key in the where statement
SELECT
*
FROM
book, publisher
WHERE
book.p_id=publisher.p_id
AND book.b_title='DSA'
Or even better use JOINs:
SELECT
*
FROM
book
JOIN publisher
ON book.p_id=publisher.p_id
WHERE
book.b_title='DSA'
Or if you are not sure if there is a corresponding value then use a left join. Like this:
SELECT
*
FROM
book
LEFT JOIN publisher
ON book.p_id=publisher.p_id
WHERE
book.b_title='DSA'

Finding values from a table that are *not* in a grouping of another table and what group that value is missing from?

I hope I am not missing something very simple here. I have done a Google search(es) and searched through Stack Overflow.
Here is the situation: For simplicity's sake let's say I have a table called "PeoplesDocs", in a SQL Server 2008 DB, that holds a bunch of people and all the documents that they own. So one person can have several documents. I also have a table called "RequiredDocs" that simply holds all the documents that a person should have. Here is sort of what it looks like:
PeoplesDocs:
PersonID DocID
-------- -----
1 A
1 B
1 C
1 D
2 C
2 D
3 A
3 B
3 C
RequiredDocs:
DocID DocName
----- ---------
A DocumentA
B DocumentB
C DocumentC
D DocumentD
How do I write a SQL query that returns some variation of:
PersonID MissingDocs
-------- -----------
2 DocumentA
2 DocumentB
3 DocumentD
I have tried, and most of my searching has pointed to, something like:
SELECT DocID
FROM DocsRequired
WHERE NOT EXIST IN (
SELECT DocID FROM PeoplesDocs)
but obviously this will not return anything in this example because everyone has at least one of the documents.
Also, if a person does not have any documents then there will be one record in the PeoplesDocs table with the DocID set to NULL.
How about something like this:
Select ...
From RequiredDocs As RD
Cross Join People As P
Where Not Exists(
Select 1
From PeoplesDocs As PD1
Where PD1.PersonId = P.PersonId
And PD1.DocId = RD.DocId
)
SELECT
p.PersonID,
rd.DocName AS MissingDocs
FROM
dbo.People p, dbo.RequiredDocs rd
WHERE
rd.DocID NOT IN (SELECT pd.DocID FROM dbo.PeoplesDocs pd
WHERE pd.PersonID = p.PersonID)

MySQL - Duplicate elimination and Preserving Valuable Data?

Scenario : I have few duplicate contacts in a table. The duplicates are identified, I can just delete them but the problem is I don't want to lose the data the duplicate might have and the original don't. Any tips?
Sample data :
ID Name Email School Dupe_Flag Key
1 AAA a#a X 1
2 AAB JKL 1
3 BBB b#b MNO X 2
4 BBC 2
Desired output :
ID Name Email School Dupe_Flag Key
1 AAA a#a X 1
2 AAB a#a JKL 1
3 BBB b#b MNO X 2
4 BBC b#b MNO 2
How are 2 records related? : They both have the same Key Value with only one column having the Dupe_Flag SET which is the duplicate column.
In the above case ID 1 is going to be deleted but email info from ID 1 should be applied to ID 2.
What is the Data? : I have few hundred rows and few 100 duplicates. UPDATE statement for each row is cumbersome and is not feasible.
Business rules for determining what data takes priority :
If a column from the original/good record (Dupe_Flag is NOT set) has no data and if the corresponding Dupe record (has the same Key value) column has data then that original record column should be updated.
Any help/script is really appreciated! Thanks guys :)
Assuming empty values are null, something like this should output the desired data:
SELECT
a.ID,
IF(a.DupeFlag IS NULL, IF(a.Name IS NULL, b.Name, a.Name), a.Name) AS Name,
IF(a.DupeFlag IS NULL, IF(a.Email IS NULL, b.Email, a.Email), a.Email) AS Email,
IF(a.DupeFlag IS NULL, IF(a.School IS NULL, b.School, a.School), a.School) as School,
a.DupeFlag,
a.key
FROM
table a,
table b
WHERE
a.Key = b.Key AND
a.ID != b.ID
GROUP BY
a.ID
Note that turning this in an UPDATE statement is pretty straight-forward
I don't know the specifics of this problem but it is probably better to avoid this problem by setting the columns to "unique" so if a query tries to create a duplicate it will fail. I think the elegant solution to this problem is to avoid it at the point of data entry.
I like using this query for tracking down dupes:
select * from table group by `Email` having count(Email) > 1
While this uses a bunch of nested SELECTS, and isn't really a full solution, it should either spark something else, or possibly push in the right direction.
select * from
(select r1.ID,r1.Name,coalesce(r1.Email,r2.Email) as Email,
coalesce(r1.School,r2.School) as School,r1.Dupe_Flag,r1.Key from
(select * from test1 where Dupe_Flag IS NULL) as r1 left outer join
(select * from test1 where Dupe_Flag IS NOT NULL) as r2 on r1.KEY=r2.Key)
as results
Yields:
ID Name Email School Dupe_Flag Key
2 AAB a#a JKL NULL 1
4 BBC b#b MNO NULL 2
Based on your example data.
The rows are unique, so there's no problem. Please recheck your example data.