A Simple but complex SQL query - sql

I have a very simple MS SQL table with the following data(with column name and datatype):
TableId PersonName Attribute AttributeValue
(int) (nvarchar 50) (nvarchar 50) (bit)
----------- ----------------------- ------------------- --------------
1 A IsHuman 1
2 A CanSpeak 1
3 A CanSee 1
4 A CanWalk 0
5 B IsHuman 1
6 B CanSpeak 1
7 B CanSee 0
8 B CanWalk 0
9 C IsHuman 0
10 C CanSpeak 1
11 C CanSee 1
12 C CanWalk 0
Now, What I need as a result is the unique PersonName that have both Attribute IsHuman and CanSpeak with AttributeValue = 1.
The expected result should be (Must not include C as this one has IsHuman = 0)
PersonName
------------
A
B
Please can any expert help me in writting a SQL Query for this.

SELECT PersonName
FROM MyTable
WHERE AttributeName = 'IsHuman'
AND AttributeValue = 1
INTERSECT
SELECT PersonName
FROM MyTable
WHERE AttributeName = 'CanSpeak'
AND AttributeValue = 1;
Obviously this approach doesn't 'scale' if the criteria can vary. It could be that the relational operator you require is division, popularly known as "the supplier who supplies all parts", specifically division with remainder.

SELECT PersonName
FROM MyTable
WHERE (AttributeName = 'IsHuman' AND AttributeValue = 1) OR
(AttributeName = 'CanSpeak' AND AttributeValue = 1)
GROUP BY PersonName
HAVING COUNT(*) > 1
or
SELECT PersonName
FROM MyTable
WHERE AttributeValue = 1 AND AttributeName IN ('IsHuman', 'CanSpeak')
GROUP BY PersonName
HAVING COUNT(*) > 1

SELECT PersonName FROM MyTable
WHERE PersonName IN
(SELECT T1.PersonName FROM MyTable T1 WHERE T1.Attribute = 'IsHuman' and T1.AttributeValue='1')
AND (Attribute = 'CanSpeak' AND AttributeValue='1')

I think two inner joins may give you alright performance depending on indexing and table sizes.
SELECT t.PersonName FROM table t
INNER JOIN table t2 ON t.PersonName=t2.PersonName AND t3.Attribute = 'IsHuman' AND t2.AttributeValue = 1
INNER JOIN table t3 ON t2.PersonName=t3.PersonName AND t3.Attribute = 'CanSpeak' AND t3.AttributeValue = 1
or
SELECT t.PersonName FROM table t
INNER JOIN table t2 ON t.PersonName=t2.PersonName
INNER JOIN table t3 ON t2.PersonName=t3.PersonName
WHERE t2.Attribute = 'IsHuman' AND t2.AttributeValue = 1 AND t3.Attribute = 'CanSpeak' AND t3.AttributeValue = 1
This solution could be simplified significantly however should the properties IsHuman and CanSpeak were in separate tables with an linking ID table between them. Sounds like this table could possibly benefit from some normalization.
If you cant progress that, a view may assist in performance. I am at home without SQL installed so I cant verify any performance aspects.

select personname from yourtablename where personname in ('a','b') group by personname

I actually use this as a screening question for interviews. None of you people would get the job.
OK, maybe you would, but while the strategies you use might or might not work, they aren't generalizable and they miss a basic notion of relational algebra, to wit, aliasing.
The right answer (in the sense that it would make me more likely to employ you as well as the less importance senses that the RDMS's optimizer understands it and it can be extended to other, arbitrarily complex, cases) is
SELECT t1.PersonName
FROM MyTable t1, MyTable t2
WHERE t2.AttributeName = 'CanSpeak'
AND t2.AttributeValue = 1
AND t1.AttributeName = 'IsHuman'
AND t1.AttributeValue = 1
AND t1.PersonName = t2.PersonName;

Related

SQL query to check if user assigned with a specific value and does not a different value assigned to the same user?

We can have users with multiple valueIDs. In SQL Server I am trying to pull all users that have valueID of 3 but do not have valueID of 1 and 2. So Table A would have a user column and a valueID column.
How do I write a SQL query to do this?
Also, any good resources to get my SQL query skills up to par?
Much appreciated.
Try:
SELECT user
FROM A outer
WHERE valueID = 3
AND NOT EXISTS
(
SELECT 1
FROM A inner
WHERE
(
valueID = 1
OR valueID = 2
)
AND inner.user = outer.user
)
Here's one more.... (all users with valueid = 3 and nothing else)
SELECT [USER]
FROM A
GROUP BY [USER]
HAVING MAX(CASE WHEN valueid = 3 THEN 0 when valueid IN (1,2) THEN 1 END) = 0;
This query will return all users that have values (3 and 1) or (3 and 2) or just 3
select user
from mytable t1
where valueId = 3
and (select count(distinct valueId)
from mytable t2 where t2.user = t1.user
and valueId IN (1,2)) < 2
A simple way to get what you asked if you meant users with a value of 3 except those which also have values of 1 and 2
Select s.UserID From SomeTable s
Where s.UserID NOT In
(select s1s.UserID From SomeTable s1s
inner join SomeTable s2s On s1s.UserID = s2s.UserID
Where s1s.ValueId = 1 And s2s.ValueId = 2)
Or
Select s.UserID From SomeTable s
Left Join
(select s1s.UserID From SomeTable s1s
inner join SomeTable s2s On s1s.UserID = s2s.UserID
Where s1s.ValueId = 1 And s2s.ValueId = 2) both
Where both.userID is null
I'm not really sure what your table structure is, but it's likely the below query should work depending on column names...
SELECT user, valueID
FROM users
WHERE valueID = 3

Is there a simpler way to write this query? [MS SQL Server]

I'm wondering if there is a simpler way to accomplish my goal than what I've come up with.
I am returning a specific attribute that applies to an object. The objects go through multiple iterations and the attributes might change slightly from iteration to iteration. The iteration will only be added to the table if the attribute changes. So the most recent iteration might not be in the table.
Each attribute is uniquely identified by a combination of the Attribute ID (AttribId) and Generation ID (GenId).
Object_Table
ObjectId | AttribId | GenId
32 | 2 | 3
33 | 3 | 1
Attribute_Table
AttribId | GenId | AttribDesc
1 | 1 | Text
2 | 1 | Some Text
2 | 2 | Some Different Text
3 | 1 | Other Text
When I query on a specific object I would like it to return an exact match if possible. For example, Object ID 33 would return "Other Text".
But if there is no exact match, I would like for the most recent generation (largest Gen ID) to be returned. For example, Object ID 32 would return "Some Different Text". Since there is no Attribute ID 2 from Gen 3, it uses the description from the most recent iteration of the Attribute which is Gen ID 2.
This is what I've come up with to accomplish that goal:
SELECT attr.AttribDesc
FROM Attribute_Table AS attr
JOIN Object_Table AS obj
ON obj.AttribId = obj.AttribId
WHERE attr.GenId = (SELECT MIN(GenId)
FROM(SELECT CASE obj2.GenId
WHEN attr2.GenId THEN attr2.GenId
ELSE(SELECT MAX(attr3.GenId)
FROM Attribute_Table AS attr3
JOIN Object_Table AS obj3
ON obj3.AttribId = attr3.AttribId
WHERE obj3.AttribId = 2
)
END AS GenId
FROM Attribute_Table AS attr2
JOIN Object_Table AS obj2
ON attr2.AttribId = obj2.AttribId
WHERE obj2.AttribId = 2
) AS ListOfGens
)
Is there a simpler way to accomplish this? I feel that there should be, but I'm relatively new to SQL and can't think of anything else.
Thanks!
The following query will return the matching value, if found, otherwise use a correlated subquery to return the value with the highest GenId and matching AttribId:
SELECT obj.Object_Id,
CASE WHEN attr1.AttribDesc IS NOT NULL THEN attr1.AttribDesc ELSE attr2.AttribDesc END AS AttribDesc
FROM Object_Table AS obj
LEFT JOIN Attribute_Table AS attr1
ON attr1.AttribId = obj.AttribId AND attr1.GenId = obj.GenId
LEFT JOIN Attribute_Table AS attr2
ON attr2.AttribId = obj.AttribId AND attr2.GenId = (
SELECT max(GenId)
FROM Attribute_Table AS attr3
WHERE attr3.AttribId = obj.AttribId)
In the case where there is no matching record at all with the given AttribId, it will return NULL. If you want to get no record at all in this case, make the second JOIN an INNER JOIN rather than a LEFT JOIN.
Try this...
Incase the logic doesn't find a match for the Object_table GENID it maps it to the next highest GENID in the ON clause of the JOIN.
SELECT AttribDesc
FROM object_TABLE A
INNER JOIN Attribute_Table B
ON A.AttrbId = B.AttrbId
AND (
CASE
WHEN A.Genid <> B.Genid
THEN (
SELECT MAX(C.Genid)
FROM Attribute_Table C
WHERE A.AttrbId = C.AttrbId
)
ELSE A.Genid
END
) -- Selecting the right GENID in the join clause should do the job
= B.Genid
This should work:
with x as (
select *, row_number() over (partition by AttribId order by GenId desc) as rn
from Attribute_Table
)
select isnull(a.attribdesc, x.attribdesc)
from Object_Table o
left join Attribute_Table a
on o.AttribId = a.AttribId and o.GenId = a.GenId
left join x on o.AttribId = x.AttribId and rn = 1

How to exclude records with certain values in sql select

How do I only select the stores that don't have client 5?
StoreId ClientId
------- ---------
1 4
1 5
2 5
2 6
2 7
3 8
I'm trying something like this:
SELECT SC.StoreId FROM StoreClients
INNER JOIN StoreClients SC
ON StoreClients.StoreId = SC.StoreId
WHERE SC.ClientId = 5
GROUP BY StoreClients.StoreId
That seems to get me all the stores that have that client but I can't do the opposite because if I do <> 5 ill still get Store 1 and 2 which I don't want.
I'm basically trying to use this result in another query's EXISTS IN clause
One way:
SELECT DISTINCT sc.StoreId
FROM StoreClients sc
WHERE NOT EXISTS(
SELECT * FROM StoreClients sc2
WHERE sc2.StoreId = sc.StoreId AND sc2.ClientId = 5)
SELECT SC.StoreId
FROM StoreClients SC
WHERE SC.StoreId NOT IN (SELECT StoreId FROM StoreClients WHERE ClientId = 5)
In this way neither JOIN nor GROUP BY is necessary.
SELECT DISTINCT a.StoreID
FROM tableName a
LEFT JOIN tableName b
ON a.StoreID = b.StoreID AND b.ClientID = 5
WHERE b.StoreID IS NULL
SQLFiddle Demo
OUTPUT
╔═════════╗
║ STOREID ║
╠═════════╣
║ 3 ║
╚═════════╝
SELECT StoreId
FROM StoreClients
WHERE StoreId NOT IN (
SELECT StoreId
FROM StoreClients
Where ClientId=5
)
SQL Fiddle
You can use EXCEPT syntax, for example:
SELECT var FROM table1
EXCEPT
SELECT var FROM table2
<> will surely give you all values not equal to 5.
If you have more than one record in table it will give you all except 5.
If on the other hand you have only one, you will get surely one.
Give the table schema so that one can help you properly

SQL SELECT criteria in another table

I have 2 related tables:
messages
--------
mid subject
--- -----------------
1 Hello world
2 Bye world
3 The third message
4 Last one
properties
----------
pid mid name value
--- --- ---------------- -----------
1 1 read false
2 1 importance high
3 2 read false
4 2 importance low
5 3 read true
6 3 importance low
7 4 read false
8 4 importance high
And I need to get from messages using the criteria on the properties table.
Eg: if I have a criteria like return unread (read=false) high prio (importance=high) messages it should return
mid subject
--- -----------------
1 Hello world
4 Last one
How could I get this with a SELECT clause (MySQL dialect)?
In SQL, any expression in a WHERE clause can only reference one row at a time. So you need some way of getting multiple rows from your properties table onto one row of result. You do this with self-joins:
SELECT ...
FROM messages AS m
JOIN properties AS pRead
ON m.mid = pRead.mid AND pRead.name = 'read'
JOIN properties AS pImportance
ON m.mid = pImportance.mid AND pImportance.name = 'importance'
WHERE pRead.value = 'false' AND pImportance.value = 'high';
This shows how awkward it is to use the EAV antipattern. Compare with using conventional attributes, where one attribute belongs in one column:
SELECT ...
FROM messages AS m
WHERE m.read = 'false' AND m.importance = 'high';
By the way, both answers from #Abe Miessler and #Thomas match more mid's than you want. They match all mid's where read=false OR where importance=high. You need to combine these properties with the equivalent of AND.
I believe the query below will work.
UPDATE: #Gratzy is right, this query won't work, take a look at the structure changes I suggested.
SELECT DISTINCT m.id as mid, m.subject
FROM message as m
INNER JOIN properties as p
ON m.mid = p.mid
where (p.name = 'read' and p.value = 'false') or (p.name = 'importance' AND p.value = 'high')
The structure of your properties table seems a little off to me though...
Would it be possible to structure the table like this:
messages
--------
mid subject Read Importance
--- ----------------- --------- ------------
1 Hello world false 3
2 Bye world false 1
3 The third message true 1
4 Last one false 3
importance
----------
iid importanceName
--- --------------
1 low
2 medium
3 high
and use this query:
SELECT m.id as mid, m.subject
FROM message as m
where m.read = false AND m.importance = 3
Clearly, you are using an EAV (Entity-Attribute-Value) schema. One of the many reasons for avoiding such a structure is that it makes queries more difficult. However, for the example you gave, you could do something like:
Select ...
From messages As M
Where Exists (
Select 1
From Properties As P1
Where P1.mid = M.mid
And P1.name = 'unread' And P1.value = 'false'
)
And Exists (
Select 1
From Properties As P2
Where P2.mid = M.mid
And P2.name = 'importance' And P2.value = 'high'
)
A more succinct solution would be:
Select ...
From messages As M
Where Exists (
Select 1
From Properties As P1
Where P1.mid = M.mid
And ((P1.name = 'unread' And P1.value = 'false')
Or (P1.name = 'importance' And P1.value = 'high'))
Having Count(*) = 2
)
Select m.mid, m.subject
from properties p
inner join properties p1 on p.mid = p1.mid
inner join messages m on p.mid = m.mid
where
p.name = 'read'
and p.value = 'false'
and p1.name = 'importance'
and p2.value = 'high'
I prefer to put my filter criteria in the where clause and leave my join's to elements that are in both tables and are the actual criteria for the join.
Another way might be (untested) to use a derived table to hold the criteria that all messages must meet then use the standard relational division technique of double NOT EXISTS
SELECT mid,
subject
FROM messages m
WHERE NOT EXISTS
( SELECT *
FROM ( SELECT 'read' AS name,
'false' AS value
UNION ALL
SELECT 'importance' AS name,
'high' AS value
)
c
WHERE NOT EXISTS
(SELECT *
FROM properties P
WHERE p.mid = m.mid
AND p.name =c.name
AND p.value=c.value
)
)
If you want to keep your existing data model, then go with Bill Karwin's first suggestion. Run it with this select clause to understand what it's doing:
select m.*, r.value as read, i.value as importance
from message m
join properties r
on r.mid = m.mid and r.name = 'read'
join properties i
on i.mid = m.mid and i.name = 'importance'
where r.value = 'false' and i.value = 'high';
But if you go this way, there are a few constraints you should put in place to avoid storing and retrieving bad data:
A unique index on message(mid) and a unique index on properties(pid), both of which I'm sure you have already.
A unique index on properties(mid, name) so that each property can only be defined once for a message -- otherwise you may get duplicate results from your query. This will also help your query performance by allowing an index access for both joins.

Using (IN operator) OR condition in Where clause as AND condition

Please look at following image, I have explained my requirements in the image.
alt text http://img30.imageshack.us/img30/5668/shippment.png
I can't use here WHERE UsageTypeid IN(1,2,3,4) because this will behave as an OR condition and fetch all records.
I just want those records, of first table, which are attached with all 4 ShipmentToID .
All others which are attached with 3 or less ShipmentToIDs are not needed in result set.
Thanks.
if (EntityId, UsageTypeId) is unique:
select s.PrimaryKeyField, s.ShipmentId from shipment s, item a
where s.PrimaryKeyField = a.EntityId and a.UsageTypeId in (1,2,3,4)
group by s.PrimaryKeyField, s.ShipmentId having count(*) = 4
otherwise, 4-way join for the 4 fields,
select distinct s.* from shipment s, item a, item b, item c, item d where
s.PrimaryKeyField = a.EntityId = b.EntityId = c.EntityId = d.EntityId and
a.UsageTypeId = 1 and b.UsageTypeId = 2 and c.UsageTypeId = 3 and
d.UsageTypeId = 4
you'll want appropriate index on (EntityId, UsageTypeId) so it doesn't hang...
If there will never be duplicates of the UsageTypeId-EntityId combo in the 2nd table, so you'll never see:
EntityUsageTypeId | EntityId | UsageTypeId
22685 | 4477 | 1
22687 | 4477 | 1
You can count matching EntityIds in that table.
WHERE (count(*) in <tablename> WHERE EntityId = 4477) = 4
DECLARE #numShippingMethods int;
SELECT #numShippingMethods = COUNT(*)
FROM shippedToTable;
SELECT tbl1.shipmentID, COUNT(UsageTypeId) as Usages
FROM tbl2 JOIN tbl1 ON tbl2.EntityId = tbl1.EntityId
GROUP BY tbl1.EntityID
HAVING COUNT(UsageTypeId) = #numShippingMethods
This way is preferred to the multiple join against same table method, as you can simply modify the IN clause and the COUNT without needing to add or subtract more tables to the query when your list of IDs changes:
select EntityId, ShipmentId
from (
select EntityId
from (
select EntityId
from EntityUsage eu
where UsageTypeId in (1,2,3,4)
group by EntityId, UsageTypeId
) b
group by EntityId
having count(*) = 4
) a
inner join Shipment s on a.EntityId = s.EntityId