Update using having and MIN function non key-preserved table error - sql

I have below data in two different tables. I am able to retrieve required records using select query, however I am unable to update ROW_IND of these records. The update statement I used gives me error. Any pointers will be much appreciated.
Table CLP :
ID KEY EFF_DT ROW_IND
28420000000006 4599 1/1/2000 1
28420000000006 21164 10/16/2019 1
28420000000011 58429 1/1/2000 1
28420000000011 68434 10/16/2019 1
Table CPI :
KEY2 ID2
21164 28420000000006
68434 28420000000011
The Query :
SELECT p.id , p.key, i.key AS KEY2, i.id AS ID2, p.EFF_DT, p.row_ind
FROM CLP P, CLI I
WHERE p.id = i.id
AND P.KEY <> I.KEY
AND p.row_ind = 1
AND P.id IN
(
SELECT id
FROM CLP
WHERE row_ind = 1
GROUP BY id
HAVING count(*) > 1
);
ID KEY KEY2 ID2 EFF_DT ROW_IND
28420000000006 4599 21164 28420000000006 1/1/2000 1
28420000000011 58429 68434 28420000000011 1/1/2000 1
The Update Query:
UPDATE
(
< The Above SELECT Query >
) A
SET A.row_ind = 0
Error: ORA-01779: cannot modify a column which maps to a non key-preserved table

This syntax for an UPDATE statement cannot be used within Oracle while might be used within MySQL. Alternatively, you can try to use a MERGE statement :
MERGE INTO CLP t
USING
(
SELECT p.id , p.key, i.key AS KEY2, i.id AS ID2, p.EFF_DT, p.row_ind
FROM CLP p
JOIN CPI i
ON p.id = i.id
WHERE p.key <> i.key
AND p.row_ind = 1
AND P.id in
(
SELECT id
FROM CLP
WHERE row_ind = p.row_ind
GROUP BY id
HAVING count(*) > 1
)
) tt
ON (tt.key2 = t.key)
WHEN MATCHED THEN UPDATE SET t.row_ind = 0;
where
you'd better converting the old-style SELECT statement to the
syntax containing explicit JOIN keywords instead of comma-seperated
JOINs
no need to repeat the condition row_ind = 1 twice, replacing the
second one with row_ind = p.row_ind is more preferable
Demo

Related

SQL Group by colum1 "keep" by datetime

I have the follow table:
ID -- timestamps -- uid
1 -- 12:00 -- 1
2 -- 12:15 -- 1
3 -- 12:30 -- 2
4 -- 12:45 -- 2
Now I need unique uid's by the last timestamps, result have to look like this:
ID -- timestamps -- uid
2 -- 12:15 -- 1
4 -- 12:45 -- 2
If there is any problem with joins, tell me please.
Thats my current SQL Statmant:
SELECT DISTINCT "lists".* FROM "lists" INNER JOIN identifys i WHERE (i.ip
= '1' OR i.session = '2')
And that my ActiveRecord:
List.all.joins("INNER JOIN identifys i")
.where("i.ip = ? OR i.session = ?", ip, session)
.distinct
How about adding Group by to your query? Something along these lines:
select Max(l.ID) as ID, Max(l.timestamps) as timestamps, l.uid
from Lists l INNER JOIN identifys i
WHERE (i.ip = '1' OR i.session = '2')
Group By l.uid
Another possible solution:
SELECT l2.id , a.uid, a.timestamps
FROM
(
SELECT Max(l.timestamps) as timestamps, l.uid
FROM Lists l INNER JOIN identifys i
WHERE (i.ip = '1' OR i.session = '2')
Group By l.uid
) a
Inner Join Lists l2 on a.timestamps = l2.timestamps
In MySQL or SQLite you could use a filtering join, like:
select *
from YourTable yt
join (
select UID
, max(timestamp) as max_timestamp
from YourTable
group by
UID
) filter
on yt.UID = filter.UID
and yt.timestamp = filter.max_timestamp
In a datase that supports windowing functions, like PostgreSQL, Oracle or SQL Server, you could:
select *
from (
select row_number() (
partition by UID
order by timestamp desc) as rn
, *
from YourTable
) SubQueryAlias
where rn = 1 -- Only latest row per UID

How to find rows that have one equal value and one different value from the table

I have the following table:
ID Number Revision
x y 0
x y 1
z w 0
a w 0
a w 1
b m 0
b m 0
I need to return rows that for the same Number thare are more then one ID with the same Revision.Number can be "Null" and I don't need those values.
The output should be:
z w 0
a w 0
I have tried the following query:
SELECT a.id,a.number,a.revision,
FROM table a INNER JOIN
(SELECT id, number, revision FROM table where number > '0'
GROUP BY number HAVING COUNT(*) > 1
) b ON a.revision = b.revision AND a.id != b.id
A little addition- I have rows in my table with the same Number, ID and Revision- I don't need those rows in my query to be displayed!
It is not working! Please help me to figure out how to fix it.
Thanks.
Select t.Id,s.number,t.revision
from (Select number,count(*) 'c'
from table t1
where revision=0
group by number
having count(*) > 1
) s join table t on t.number= s.number
where revision = 0
Another simple approach:
SELECT DISTINCT b.id, b.Number, b.Revision
FROM tbl a
INNER JOIN tbl b
ON a.ID != b.ID AND a.Number = b.Number AND a.Revision = b.Revision;
This is tested in MySql 5, syntax might differ slightly.
You are not that far away with your query:
SELECT a.id,a.number,a.revision
FROM table a
JOIN (
-- multiple id for the same number and revision
SELECT number, revision
FROM table
GROUP BY number, revision
HAVING COUNT(*) > 1
) b
ON a.revision = b.revision
AND a.number = b.number
Untested, but you should get the idea. If your sql-server is a resent version you can solve this with OLAP functions as well.
To filter out rows where the whole row is duplicated we can select only unique rows via group by and having:
SELECT a.id,a.number,a.revision
FROM table a
JOIN (
-- multiple id for the same number and revision
SELECT number, revision
FROM table
GROUP BY number, revision
HAVING COUNT(*) > 1
) b
ON a.revision = b.revision
AND a.number = b.number
GROUP BY a.id,a.number,a.revision
HAVING COUNT(1) = 1

A Simple but complex SQL query

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;

Find records with exact matches on a many to many relationship

I have three tables that look like these:
PROD
Prod_ID|Desc
------------
P1|Foo1
P2|Foo2
P3|Foo3
P4|Foo4
...
RAM
Ram_ID|Desc
------------
R1|Bar1
R2|Bar2
R3|Bar3
R4|Bar4
...
PROD_RAM
Prod_ID|Ram_ID
------------
P1|R1
P2|R2
P3|R1
P3|R2
P3|R3
P4|R3
P5|R1
P5|R2
...
Between PROD and RAM there's a Many-To-Many relationship described by the PROD_RAM table.
Given a Ram_ID set like (R1,R3) I would like to find all the PROD that has exactly ONE or ALL of the RAM of the given set.
Given (R1,R3) should return for example P1,P4 and P5; P3 should not be returned because has R1 and R3 but also R2.
What's the fastest query to get all the PROD that has exactly ONE or ALL of the Ram_ID of a given RAM set?
EDIT:
The PROD_RAM table could contain relationship bigger than 1->3 so, "hardcoded" checks for count = 1 OR = 2 are not a viable solution.
Another solution you could try for speed would be like this
;WITH CANDIDATES AS (
SELECT pr1.Prod_ID
, pr2.Ram_ID
FROM PROD_RAM pr1
INNER JOIN PROD_RAM pr2 ON pr2.Prod_ID = pr1.Prod_ID
WHERE pr1.Ram_ID IN ('R1', 'R3')
)
SELECT *
FROM CANDIDATES
WHERE CANDIDATES.Prod_ID NOT IN (
SELECT Prod_ID
FROM CANDIDATES
WHERE Ram_ID NOT IN ('R1', 'R3')
)
or if you don't like repeating the set conditions
;WITH SUBSET (Ram_ID) AS (
SELECT 'R1'
UNION ALL SELECT 'R3'
)
, CANDIDATES AS (
SELECT pr1.Prod_ID
, pr2.Ram_ID
FROM PROD_RAM pr1
INNER JOIN PROD_RAM pr2 ON pr2.Prod_ID = pr1.Prod_ID
INNER JOIN SUBSET s ON s.Ram_ID = pr1.Ram_ID
)
, EXCLUDES AS (
SELECT Prod_ID
FROM CANDIDATES
LEFT OUTER JOIN SUBSET s ON s.Ram_ID = CANDIDATES.Ram_ID
WHERE s.Ram_ID IS NULL
)
SELECT *
FROM CANDIDATES
LEFT OUTER JOIN EXCLUDES ON EXCLUDES.Prod_ID = CANDIDATES.Prod_ID
WHERE EXCLUDES.Prod_ID IS NULL
One way to do this would be something like the following:
SELECT PROD.Prod_ID FROM PROD WHERE
(SELECT COUNT(*) FROM PROD_RAM WHERE PROD_RAM.Prod_ID = PROD.Prod_ID) > 0 AND
(SELECT COUNT(*) FROM PROD_RAM WHERE PROD_RAM.Prod_ID = PROD.Prod_ID AND PROD.Ram_ID <>
IFNULL((SELECT TOP 1 Ram_ID FROM PROD_RAM WHERE PROD_RAM.Prod_ID = PROD.Prod_ID),0)) = 0
SELECT Prod_ID
FROM
( SELECT Prod_ID
, COUNT(*) AS cntAll
, COUNT( CASE WHEN Ram_ID IN (1,3)
THEN 1
ELSE NULL
END
) AS cntGood
FROM PROD_RAM
GROUP BY Prod_ID
) AS grp
WHERE cntAll = cntGood
AND ( cntGood = 1
OR cntGood = 2 --- number of items in list (1,3)
)
Not at all sure if it's the fastest way. You'll have to try different ways to write this query (using JOINs and NOT EXISTS ) and test for speed.

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