SELECT/NOT EXISTS unique records with a 'condition' [duplicate] - sql

This question already has answers here:
sql query to extract unique records
(4 answers)
Closed 7 years ago.
My table contains only 2 columns ID and product and looks like this:
ID Product
-----------------
1 microsoft
0 cisco
2 cisco
7 cisco
3 vmware
0 adobe
0 microsoft
I need to write a query that lists records having id=0 only,
ID Product
---------------
0 adobe
The reason I do not want to list
ID Product
----------------
0 micrsoft
0 cisco
is because they have one or more records which does have a product ID.
Tried this:
SELECT
[ProductID], [Product]
FROM [table] AS t1
WHERE ProductID = 0
AND NOT EXISTS (SELECT 1
FROM [table] AS t2
WHERE t1.ProductID = t2.ProductID
AND t2.ProductID <> 0)
seems to take a long time to query. (table has 2 000 000 records)

Use a simple group by with a having clause:
select 0 as id, product
from [table] t
group by product
having min(id) = 0 and max(id) = 0;

You just need to join on Product instead of ProductID:
SELECT [ProductID],[Product]
FROM [table] as t1
where ProductID=0
AND NOT EXISTS (
SELECT 1 FROM [table] AS t2
where t1.Product=t2.Product
AND t2.ProductID<>0
)
This translates to "where there exists no other row where the product has the same name, with an ID other than 0".

Related

How to use multiple counts in where clause to compare data of a table in sql?

I want to compare data of a table with its other records. The count of rows with a specific condition has to match the count of rows without the where clause but on the same grouping.
Below is the table
-------------
id name time status
1 John 10 C
2 Alex 10 R
3 Dan 10 C
4 Tim 11 C
5 Tom 11 C
Output should be time = 11 as the count for grouping on time column is different when a where clause is added on status = 'C'
SELECT q1.time
FROM (SELECT time,
Count(id)
FROM table
GROUP BY time) AS q1
INNER JOIN (SELECT time,
Count(id)
FROM table
WHERE status = 'C'
GROUP BY time) AS q2
ON q1.time = q2.time
WHERE q1.count = q2.count
This is giving the desired output but is there a better and efficient way to get the desired result?
Are you looking for this :
select t.*
from table t
where not exists (select 1 from table t1 where t1.time = t.time and t1.status <> 'C');
However you can do :
select time
from table t
group by time
having sum (case when status <> 'c' then 1 else 0 end ) = 0;
If you want the times where the rows all satisfy the where clause, then in Postgres, you can express this as:
select time
from t
group by time
having count(*) = count(*) filter (where status = 'C');

Oracle SQL: Limiting multiple where clauses

Apologies if this seems like a duplicate to this question but I believe my use case is slightly different.
I have two tables.
Table1
ID INTCODE
-----------------------------
000019827364 1
000019829201 2
890418392101 3
890418390395 4
890418398677 5
505586578932 6
505586578914 7
505586578933 8
505586578012 9
490201827383 10
490201827466 11
001952046578 12
Table2
INTCODE Category
-------------------------
1 Display
2 Display
3 Display
4 Display
5 Display
6 Audio
7 Audio
8 Audio
9 Audio
10 Audio
11 Audio
12 Audio
My expected query results are all possible 5 digit prefixes of each category and in each of these prefixes - I want to extract at least 2 full IDs. Below is an example if I had a where clause for category as 'Display'.
ID PREFIX Category ID
-----------------------------------------------
00001 Display 000019827364
00001 Display 000019829201
89041 Display 890418392101
89041 Display 890418390395
The query I currently have is
SELECT
SUBSTR(t1.ID, 1, 5)
FROM
table1 t1
,table2 t2
WHERE
AND UPPER(t2.category) = 'DISPLAY'
AND t2.REGION_ID = 1
AND t2.ZONE_ID = 2
AND t1.REGION_ID = 1
AND t1.ZONE_ID = 2
AND t1.INTCODE = t2.INTCODE
GROUP BY
SUBSTR(t1.ID, 1, 5)
I am now kind of lost. Should I be running another query where I say
t1.ID LIKE '00001%'
OR LIKE '89041%'
This list will go on to be huge cause some of the categories have 400-500 prefixes. Is there a better way to go about this? Possibly in a single query?
I'm using Oracle SQL.
Many thanks!
You can use row_number() for this:
select Category, ID, IDPrefix
from (select Category, ID, SUBSTR(ID, 1, 5) as IDPREFIX,
ROW_NUMBER() OVER (PARTITION BY SUBSTR(ID, 1, 5) ORDER BY ID) as seqnum
FROM table1 JOIN
table2 t2
ON t1.INTCODE = t2.INTCODE ANd
t1.Region_id = t2.Region_id and
t1.zone_id = t2.zone_id
WHERE UPPER(t2.category) = 'DISPLAY'
) t
WHERE seqnum <= 2;
Assuming you can to display two rows with different Id, without any more constraint, you could simply use an union where the first query would select the max id, and the second query the min id.
So your query would look something like this
select id_prefix, category, max(id)
from yourTable
union
select id_prefix, category, min(id)
from yourTable
Now simply add to this algorithm your where conditions.

shifting some columns one record back or forward

I have a table with about 8000 rows and 15 columns. After I have inserted the data I saw that my data was wrong after a number of records (let's say 1000) some column values belong to the previous record some thing like this:
A B C (A+B)
==================================
1 1 2
2 2 4
3 3 6
4 4 8
5 5
6 6 10
7 7 12
8 8 14
9 9 16
Now I have to either move some column values a record back or forward and I don't actually have much option testing it I'm afraid I may overwrite some data and ruin the whole table
I should do something like this but for about 7000 records:
update table1
set B = (select B from table1 where id = 1000)
where id = 999
Any ideas?
If you know the ids are sequential with no gaps, you can use a join to look up the value you want:
update t1
set c = tt1.c
from table1 t1 join
table1 t2
on t1.id = t2.id - 1
where t1.id > 1000;
If you cannot trust the ids, you can create the appropriate sequential number without gaps using row_number():
with toupdate as (
select t.*, row_number() over (order by id) as seqnum
from table1
)
update t1
set c = tt1.c
from toupdate t1 join
toupdate t2
on t1.seqnum = t2.seqnum - 1
where t1.id > 1000;
Create another table with the same fields as the table in question. Insert the bad records. Fix the data in the new table. Update the real table from the new one.
First, you should always test your statements before making definate changes to your data. You could start a transaction and only commit when certain it went well or make a copy of your table (select * into x from y) and test on that.
To answer your question, try something like this;
WITH dataToUpdate AS(
SELECT RowNr ,
DATA,
DataFromPreviousRow = FIRST_VALUE(data) OVER (ORDER BY RowNr ROWS 1 PRECEDING)
FROM dbo.test
)
UPDATE dataToUpdate
SET data = dataToUpdate.DataFromPreviousRow;

How do I fix this SQL query returning improper values?

I am writing an SQL query which will return a list of auctions a certain user is losing, like on eBay.
This is my table:
bid_id bid_belongs_to_auction bid_from_user bid_price
6 7 1 15.00
8 7 2 19.00
13 7 1 25.00
The problematic area is this (taken from my full query, placed at the end of the question):
AND EXISTS (
SELECT 1
FROM bids x
WHERE x.bid_belongs_to_auction = bids.bid_belongs_to_auction
AND x.bid_price > bids.bid_price
AND x.bid_from_user <> bids.bid_from_user
)
The problem is that the query returns all the auctions on which there are higher bids, but ignoring the user's even higher bids.
So, an example when the above query works:
bid_id bid_belongs_to_auction bid_from_user bid_price
6 7 1 15.00
7 7 2 18.00
In this case, user 1 is returned as losing the auction, because there is another bid higher than the users bid.
But, here is when the query doesn't work:
bid_id bid_belongs_to_auction bid_from_user bid_price
6 7 1 15.00
8 7 2 19.00
13 7 1 25.00
In this case, user 1 is incorrectly returned as losing the auction, because there is another bid higher than one of his previous bids, but the user has already placed a higher bid over that.
If it's important, here's my full query, but I think it won't be necessary to solve the aforementioned problem, but I'm posting it here anyway:
$query = "
SELECT
`bid_belongs_to_auction`,
`auction_unixtime_expiration`,
`auction_belongs_to_hotel`,
`auction_seo_title`,
`auction_title`,
`auction_description_1`
FROM (
SELECT
`bid_belongs_to_auction`,
`bid_from_user`,
MAX(`bid_price`) AS `bid_price`,
`auctions`.`auction_enabled`,
`auctions`.`auction_unixtime_expiration`,
`auctions`.`auction_belongs_to_hotel`,
`auctions`.`auction_seo_title`,
`auctions`.`auction_title`,
`auctions`.`auction_description_1`
FROM `bids`
LEFT JOIN `auctions` ON `auctions`.`auction_id`=`bids`.`bid_belongs_to_auction`
WHERE `auction_enabled`='1' AND `auction_unixtime_expiration` > '$time' AND `bid_from_user`='$userId'
AND EXISTS (
SELECT 1
FROM bids x
WHERE x.bid_belongs_to_auction = bids.bid_belongs_to_auction
AND x.bid_price > bids.bid_price
AND x.bid_from_user <> bids.bid_from_user
)
GROUP BY `bid_belongs_to_auction`
) AS X
WHERE `bid_from_user`='$userId'
";
Here's a different approach:
$query = "
SELECT
`max_bids`.`bid_belongs_to_auction`,
`auctions`.`auction_unixtime_expiration`,
`auctions`.`auction_belongs_to_hotel`,
`auctions`.`auction_seo_title`,
`auctions`.`auction_title`,
`auctions`.`auction_description_1`
FROM `auctions`
INNER JOIN (
SELECT
`bid_belongs_to_auction`,
MAX(`bid_price`) AS `auction_max_bid`,
MAX(CASE `bid_from_user` WHEN '$userId' THEN `bid_price` END) AS `user_max_bid`
FROM `bids`
GROUP BY `bid_belongs_to_auction`
) AS `max_bids` ON `auctions`.`auction_id` = `max_bids`.`bid_belongs_to_auction`
WHERE `auctions`.`auction_enabled`='1'
AND `auctions`.`auction_unixtime_expiration` > '$time'
AND `max_bids`.`user_max_bid` IS NOT NULL
AND `max_bids`.`user_max_bid` <> `max_bids`.`auction_max_bid`
";
Basically, when you are retrieving the max bids for all the auctions, you are also retrieving the specific user's max bids along. Next step is to join the obtained list to the auctions table and apply an additional filter on the user's max bid being not equal to the auction's max bid.
Note: the `max_bids`.`user_max_bid` IS NOT NULL condition might be unnecessary. It would definitely be so in SQL Server, because the non-nullness would be implied by the `max_bids`.`user_max_bid` <> `max_bids`.`auction_max_bid` condition. I'm not sure if it's the same in MySQL.
Untested, but this is how I would approach it. Ought to perform OK if there's an index on userid and also one on auctionid.
select OurUserInfo.auctionid, OurUserInfo.userid,
OurUserInfo.ourusersmaxbid, Winningbids.TopPrice
from
(
select A.auctionid, A.userid, max(A.price) as OurUsersMaxBid
from auctions A where userid = ?
group by A.auctionid, A.userid
) as OurUserInfo
inner join
(
-- get the current winning bids for all auctions in which our user is bidding
select RelevantAuctions.auctionid, max(auctions.price) as TopPrice
from auctions inner join
(
select distinct auctionid from auctions where userid = ? -- get our user's auctions
) as RelevantAuctions
on auctions.auctionid = RelevantAuctions.auctionid
group by RelevantAuctions.auctionid
) as WinninBids
on OurUserInfo.auctionid = winningbids.auctionid
where WinninBids.TopPrice > OurUserInfo.ourusersmaxbid
Instead of
SELECT 1
FROM bids x
WHERE x.bid_belongs_to_auction = bids.bid_belongs_to_auction
AND x.bid_price > bids.bid_price
AND x.bid_from_user <> bids.bid_from_user
try this:
SELECT 1
FROM (SELECT BID_ID,
BID_BELONGS_TO_AUCTION,
BID_FROM_USER,
BID_PRICE
FROM (SELECT BID_ID,
BID_BELONGS_TO_AUCTION,
BID_FROM_USER,
BID_PRICE,
RANK ()
OVER (
PARTITION BY BID_BELONGS_TO_AUCTION, BID_FROM_USER
ORDER BY BID_PRICE DESC)
MY_RANK
FROM BIDS)
WHERE MY_RANK = 1) x
WHERE x.bid_belongs_to_auction = bids.bid_belongs_to_auction
AND x.bid_price > bids.bid_price
AND x.bid_from_user <> bids.bid_from_user;

Add Column values in sql server query

I have result of two queries like:
Result of query 1
ID Value
1 4
2 0
3 6
4 9
Result of query 2
ID Value
1 6
2 4
3 0
4 1
I want to add values column "Value" and show final result:
Result of Both queries
ID Value
1 10
2 4
3 6
4 10
plz guide me...
select id, sum(value) as value
from (
select id, value from query1
uninon all
select id, value from query2
) x
group by id
Try using a JOIN:
SELECT
T1.ID,
T1.Value + T2.Value AS Value
FROM (...query1...) AS T1
JOIN (...query2...) AS T2
ON T1.Id = T2.Id
You may also need to consider what should happen if there is an Id present in one result but not in the other. The current query will omit it from the results. You may want to investigate OUTER JOIN as an alternative.
A not particularly nice but fairly easy to comprehend way would be:
SELECT ID,SUM(Value) FROM
(
(SELECT IDColumn AS ID,ValueColumn AS Value FROM TableA) t1
OUTER JOIN
(SELECT IDColumn AS ID,ValueColumn AS Value FROM TableB) t2
) a GROUP BY a.ID
It has the benefits of
a) I don't know your actual table structure so you should be able to work out how to get the two 'SELECT's working from your original queries
b) If ID doesn't appear in either table, that's fine