How to select or skip to the next row if meets certain criteria - sql

I really am not even sure which direction to go with this...
I'm trying to select a list of customers based on the following rules:
Select all rows from Customer where Ranking = 1,
OR if Ranking = 1 AND Type = Store then Rank 1 and return the row with Rank 2.
OR if the customer only has 1 row, return it even if the type = Store.
The Ranking is not assigned with a Rank statement in the query. Rather it is an actual column in the Customer table (populated by a stored proc that does the ranking).
Using the example below I'd want rows 1, 4, 6, and 10 returned.
Customer Table
RowID CustID Type Ranking
----- ------ ---- -------
1 9 Web 1
2 9 Catalog 2
3 9 Store 3
4 10 Store 1
5 11 Store 1
6 11 Web 2
7 12 Store 1
8 12 Web 2
9 12 Catalog 3
10 13 Web 1
I feel like this task is more difficult BECAUSE the Ranking is already done when the table is created! Any suggestions are most welcome!

You could try something like this (I haven't tested it!):
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE (c.Ranking = 1 AND c.Type != 'Store')
OR (c.Type = 'Store' AND Ranking = 2)
OR (c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2))
If the customer table is large, you might find that the query is a bit slow and something like this would be faster:
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Ranking = 1 AND c.Type != 'Store'
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 2
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2)

As with the other answer, I haven't done a lot of thorough testing, but here's what I'd look at. The idea here is to build a row_number over the data set prioritizing type:store to the top, and then using rank as the secondary sort condition.
select *
from (
select
rid = row_number() over (partition by CustID, order by case when type = 'Store' then 0 else 1 end, Rank desc),
rowid,
CustID,
Type,
Ranking
from customer)
where RID = 1

Try:
SELECT *
FROM Customer c
WHERE
-- There's only one row for this customer
(
SELECT COUNT(*)
FROM Customer
WHERE CustID = c.CustID
) = 1
-- There's a row with Ranking = 1 and Type = 'Store', so select Ranking = 2
OR (Ranking = 2 AND EXISTS (SELECT 1 FROM Customer WHERE CustID = c.CustID AND Ranking = 1 AND Type = 'Store'))
-- There's a row with Ranking = 1 that's not Type = 'Store'
OR (Ranking = 1 AND Type <> 'Store')

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');

Select if then case with first record

Can you do something like this in SQL Server?
I want to select from a table which has some records with the same product_id in one column and a Y or N in another (in stock), and take the first one which has a Y where the product_id is the same, while matching the product_id_set from another table.
... ,
SELECT
(SELECT TOP 1
(product_name),
CASE
WHEN in_stock = 'Y' THEN product_name
ELSE product_name
END
FROM
Products
WHERE
Products.product_set = Parent_Table.product_set) AS 'Product Name',
...
Sample data would be
product_set in_stock product_id product_name
---------------------------------------------------
1 N 12 Orange
1 Y 12 Pear
2 N 12 Apple
2 N 12 Lemon
Output from product_set = 1 would be 'Pear' for example.
So there's kind of two solutions depending on the answer to the following question. If there are no records for a product id with an in_stock value of 'Y', should anything return? Secondly, if there are multiple rows with in_stock 'Y', do you care which one it picks?
The first solution assumes you want the first row, whether or not there is ANY "Y" value.
select *
from (select RID = row_number() over (partition by product_set order by in_stock desc) -- i.e. sort Y before N
from Products) a
where a.RID = 1
The second will only return a value if there is at least one row with a 'Y' for in_stock. Note that the order by (select null) is essentially saying you don't care which one it picks if there are multiple in_stock items. If you DO care the order, replace it with the appropriate sort condition.
select *
from (select RID = row_number() over (partition by product_set order by (select null)) -- i.e. sort Y before N
from Products
where in_stock = 'Y') a
where a.RID = 1
I don't know what the structure of the "parent table" in your query is, so I've simplified it to assume you have what you need in Products alone.
SELECT ISNULL(
(
SELECT TOP 1 product_name
FROM Products
WHERE Products.product_set = Parent_Table.product_set
AND Products.in_stock = 'Y'
), 'Not in the stock') AS 'Product Name'

T-SQL, need help manipulating the data into a form I can use

I'm not sure if this is possible in T-SQL alone or if I should move this into c# and do it there. I have a table setup like so:
Tracking
--------
ID int
Hash varchar
pageID int
The table is tracking how a user travels though a website. Every page load a new line is added. Each session gets a new hash. Example data:
1 3pm6Qav1Vdf 4
2 3pm6Qav1Vdf 7
3 3pm6Qav1Vdf 41
4 3pm6Qav1Vdf 2
So doing the following will get the trail (first page to last page) for a session:
SELECT * from Tracking WHERE hash='3pm6Qav1Vdf' ORDER BY ID;
What I would like to get if possible would be:
1 3pm6Qav1Vdf NULL 4
2 3pm6Qav1Vdf 4 7
3 3pm6Qav1Vdf 7 41
4 3pm6Qav1Vdf 41 2
It would add a column listing the previous pageID as well as the current pageID. Is something like this possible in T-SQL or should I move it to c#?
DECLARE #hash VARCHAR(32);
;WITH x AS
(
SELECT ID, pageID, rn = ROW_NUMBER() OVER (ORDER BY ID)
FROM dbo.Tracking
WHERE Hash = #hash
)
SELECT x.ID, Hash = #hash, PreviousPage = y.pageID, x.pageID
FROM x LEFT OUTER JOIN x AS y
ON x.rn = y.rn + 1
ORDER BY x.ID;
This CTE will give you access to the previous row. You could move the WHERE clause to the CTE portion if you'd like, or you could leave it out entirely to get your entire rowset with previous values.
with cte as (
select *, row_number() over (partition by hash order by id) as RN
from Tracking
)
select c.id, c.hash, cprev.id as previd, c.id
from cte c
left join cte cprev on c.hash = cprev.hash and c.rn = cprev.rn + 1
where
c.hash = 'yourhash'

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;

Multiple row SQL Where clause

This is probably a simple SQL statement, but it's been a while since I've done SQL and I'm having issues with it. I have this table design:
ID PositionId Qty LeagueId
1 1 1 5
2 3 2 5
3 8 5 2
4 1 6 4
What I need to get are all the rows that have specific PositionId's and Qty's. Something like:
SELECT ID, PositionId, LeagueId, Qty
FROM Lineups
WHERE (PositionId = 1 AND Qty = 1) AND (PositionId = 3 AND Qty = 2)
What I'm trying to get is LeagueId 5 returned since it has both PositionId of 1 and Qty 1 and PositionId of 3 and Qty 2. I don't want to use an OR statement because if I change the WHERE to:
WHERE (PositionId = 1 AND Qty = 1) OR (PositionId = 3 AND Qty = 1)
Then LeagueId of 5 will still get returned.
A general way of performing this would be:
SELECT LeagueId
FROM Lineups
WHERE (PositionId = 1 AND Qty = 1) OR (PositionId = 3 AND Qty = 2) OR ...
GROUP BY LeagueId
HAVING COUNT(*) = <number of OR'ed together clauses>
Try this:
Select Distinct LeagueId
From LineUps L
Where Exists (Select * From LineUps
Where LeagueId = L.LeagueId
And PositionId = 1
And Qty = 1)
And Exists (Select * From LineUps
Where LeagueId = L.LeagueId
And PositionId = 3
And Qty = 2)
This more closely semantically represents your intent
This should return 5:
SELECT DISTINCT lineups1.leagueid
FROM lineups AS lineups1 INNER JOIN lineups AS LINEUPS2
ON lineups1.LeagueId=lineups2.LeagueId
WHERE lineups1.PositionId=1 AND lineups2.Qty = 1
AND lineups2.PositionId=3 AND lineups2.Qty = 2
Since you can only select single rows, you have to JOIN another table if you want to consider more than one. In this case, the table you're "self-joining" lineups, retrieving the value from one row based on conditions from another row (of course it doesn't matter whose leagueid you take because they're identical).
Update You can of course extend this to
SELECT lineups1.ID, ..., lineupts2.ID, ...
to retrieve whichever fields you want to retrieve.
SELECT DISTINCT LeagueId /*to display non-repeating record*/
FROM Lineups
WHERE PositionId in (1,3) AND Qty in (1,2) /*OR*/
The First statement will return 2 records which the League ID's are 5, but if your intention is to get the league ID's containing those positions and QTY, replace the 'AND' with 'OR' then it will return league Id 4 and 5.
You can also try:
SELECT ID, PositionId, LeagueId, Qty
FROM Lineups
WHERE (PositionId = 1 AND Qty = 1)
AND ID IN (SELECT ID FROM Lineups WHERE PositionId=3 AND Qty=2)