T-SQL Select join with condition - sql-server

Lets say that i have a 3 tables:
Cars
Id
CarColorHistory
Id
CarID
ColorID
ModificationDate
Color:
Id
ColorName
I want to select all cars and their colors but the important thing is, that color for the car is the last modified color from CarColorHistory table.
I need to use join to do this.
Example:
Cars:
1
2
CarColorhistory:
1 1 1 26/03/2012 -> (actual color, can be take by date or id)
2 1 2 25/03/2012
3 2 2 25/03/2012
Color:
1 Blue
2 Red
I need to get result: (car id, colorName)
1 Blue
2 Red
I tried make it by joining Cars table and CarColorHistory table but I get cars for all colors. I need only actual color (last added).
Please help

Try this:
select c.id, colorname
from cars c
inner join CarColorHistory h on c.id = h.CarID
inner join Color c2 on h.colorid = c2.id
where h.ModificationDate = (select max(ModificationDate)
from CarColorHistory x where c.id = x.CarId)

There are several ways that you can get the result. You can use a subquery to get the max(modificationdate):
select c.id, r.colorname
from cars c
inner join CarColorhistory h1
on c.id = h1.carid
inner join
(
select max(modificationdate) MaxDate,
carid
from CarColorhistory
group by carid
) h2
on h1.carid = h2.carid
and h1.modificationdate = h2.maxdate
inner join color r
on h1.colorid = r.id
See SQL Fiddle with Demo
Or since you are using SQL Server you can use ranking functions:
select id, colorname
from
(
select c.id, r.colorname,
row_number() over(partition by c.id order by modificationdate desc) rn
from cars c
inner join CarColorhistory h1
on c.id = h1.carid
inner join color r
on h1.colorid = r.id
) src
where rn = 1;
See SQL Fiddle with Demo

This should do the trick for you:
SELECT c.id, (
SELECT co.ColorName FROM Color co
WHERE co.id = (
SELECT TOP 1 ColorID FROM CarColorHistory
WHERE CarID = c.id
ORDER BY ModificationDate DESC
)
) AS ColorName

One way to do this might be with just using a subquery, like posted before, since you are using t-sql you should also be able to do it with an apply:
SELECT
Cars.Id, LatestColors.ColorID, LatestColors.ModificationDate
FROM Cars
CROSS APPLY (
SELECT TOP 1
ColorID, ModificationDate
FROM CarColorHistory
WHERE CarID = Cars.ID
ORDER BY ModificationDate DESC
) AS LatestColors

You can try this if you have Sql Server 2005 or greater:
You can try here, how Common table expression is working: Sql Fiddle demo
;WITH CTE_Cars(CarID, MaxDate)
AS
(
SELECT CarID, MAX(ModificataionDate) AS MaxDate
FROM CarColorHistory
GROUP BY CarID
)
SELECT CTE_Cars.CarID, Color.ColorName
FROM
CTE_Cars
INNER JOIN CarColorHistory ON CarColorHistory.CarID = CTE_Cars.CarID AND
CarColorHistory.ModificataionDate = CTE_Cars.MaxDate
INNER JOIN Color ON Color.id = CarColorHistory.ColorId

I'm not sure if this is the best way, but this is the way I do it. First get the values you want out of the tables using the MAX and then use that result as a table to JOIN in order to eliminate unwanted values.
SELECT c.ID, Color.Color
From Cars c JOIN CarColorHistory h on c.id = h.CarID
JOIN Color on h.ColorID = Color.ID
JOIN
--Create a table with only the latest value and the car ID
(
SELECT c.ID, Max(h.TimeStamp) as time
FROM Cars c JOIN CarColorHistory h on c.id = h.CarID
JOIN Color on h.ColorID = Color.ID
Group by c.ID --Join the table on latest time to get rid of old timestamps
) Max on Max.ID = c.ID and h.TimeStamp=max.time

Related

Avoid select data from other tables

I'm coding a movies web app, for showing some tags I'm trying to write a query that returns the movie name and the count of how many categorys has assigned it. I'm trying to add a filter that for example: if X movie contains a "Comedy" category this movie doesn't even need to be consider in my query.
At the moment this is the query that I have:
SELECT A.*, B.*, C.* -- A.name, count(C.name) [Categories]
FROM movies A
INNER JOIN moviesGenres B ON A.id = B.movieId
INNER JOIN genres C ON B.genreId = C.id
WHERE C.name <> 'Comedy'
-- group by A.name, C.name
-- having count(C.name) > 2
At the moment this query is working to return the expected output. But if you run that query with the data in this SQL Fiddle you'll see that is considering the movie "Bad Boys" but this movie has assigned one "Comedy" category so any data from this movie should't be considered.
You need to put that condition in the having clause wich is the where clause of a group.
SELECT m.name, count(g.name) [Genres]
FROM movies m
INNER JOIN moviesGenres mg ON m.id = mg.movieId
INNER JOIN genres g ON mg.genreId = g.id
GROUP BY m.name
HAVING sum(case when g.name = 'Comedy' then 1 else 0 end) = 0
If you really want to select columns from [moviesGenres] and [genres], go for juergen's answer. If you don't need them, anti join them:
SELECT A.*, B.*, C.* -- A.name, count(C.name) [Categories]
FROM
movies A
WHERE NOT EXISTS
(
SELECT 1
FROM
moviesGenres Bneg
inner join genres Cneg ON Bneg.genreId = Cneg.id
WHERE
A.id = Bneg.movieId
and Cneg.name = 'Comedy'
)

Query performances by evaluating Execution Plan (Join First Row)

I have the following tables:
Customers
ID Name
============
1 John
2 Alice
3 Bob
Orders
ID CustomerID Status
==========================
1001 1 1
1002 2 1
1003 2 2
1004 3 2
I'd like to join tables showing one entry per customer only (the one with lowest Status) i.e.
ID Name OrderID
======================
1 John 1001
2 Alice 1002
3 Bob 1004
Thanks to the answer to this question, I chose 2 solutions which produce the same output:
Solution 1
SELECT c.id, c.name, o.id FROM customers AS c
INNER JOIN orders AS o ON
c.id = o.customerid
WHERE o.status = (SELECT MIN(status) FROM orders WHERE customerid = c.id)
Solution 2
SELECT c.id, c.name, o.id FROM customers as c
INNER JOIN orders AS o ON
o.id = (SELECT TOP 1 id FROM orders WHERE customerid = c.id ORDER BY status)
Trying to understand which one runs faster, I used SQL Fiddle View Execution Plan which gave the following:
Solution 1
Solution 2
How to interpret those diagrams and which one performs faster?
Using MS SQL Server 2016.
Here's my breakdown and the last one is my suggestion to you.
Query Cost 67%
SELECT c.id, c.name, o.id FROM #Customers AS c
INNER JOIN #Orders AS o ON
c.id = o.customerid
WHERE o.status = (SELECT MIN(status) FROM #Orders WHERE customerid = c.id)
Query Cost 66%
SELECT c.id, c.name, o.id FROM #Customers as c
INNER JOIN #Orders AS o ON
o.id = (SELECT TOP 1 id FROM #Orders WHERE customerid = c.id ORDER BY status)
Query Cost 47%
SELECT
x.CustID,
x.Name,
x.OrderID
FROM (SELECT
C.id CustID,
c.Name,
o.ID OrderID,
o.status,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY o.status) rn
FROM #Customers c
INNER JOIN #Orders o
ON o.CustomerID = c.ID) x
WHERE x.rn = 1

Is there a way to UPDATE TOP (N) with inner join where N is a field of such inner join?

I'm trying to create a script that synchronizes Sales and Inventory tables. For that I wrote an UPDATE on the Inventory table (which has 1 record per item of inventory present) like this:
UPDATE TOP (q.QuantitySold) i
SET i.Converted = 1,
i.CartID = q.CartID,
i.ReservedDate = GETDATE()
FROM Inventory i
INNER JOIN
(
SELECT product.ProductID, sales.CartID, COUNT(sales.ID) AS QuantitySold
FROM Products product
INNER JOIN Sales sales ON sales.ProductID = product.ProductID
WHERE <conditions>
GROUP BY product.ProductID, sales.CartID
) q ON q.ProductID = i.ProductID
WHERE i.Converted = 0 AND i.CartID IS NULL
But it's not working, error says q.QuantitySold couldn't be bound.
Is there a way to update N records of inventory (equal to the quantity sold) without using a cursor? I refuse to give up like that.
Note: this is a simplified version of the actual query.
You could use ROW_NUMBER to enumerate the inventory items that you need to update.
WITH cteProducts AS(
SELECT product.ProductID, sales.CartID, COUNT(sales.ID) AS QuantitySold
FROM Products product
INNER JOIN Sales sales ON sales.ProductID = product.ProductID
WHERE <conditions>
GROUP BY product.ProductID, sales.CartID
),
cteInventory AS(
SELECT *,
ROW_NUMBER() OVER( PARTITION BY ProductID ORDER BY (SELECT NULL)) AS rn /*Change the ORDER BY for an actual column if needed, probably for FIFO*/
FROM Inventory
WHERE i.Converted = 0
AND i.CartID IS NULL
)
UPDATE i
SET i.Converted = 1,
i.CartID = q.CartID,
i.ReservedDate = GETDATE()
FROM cteInventory i
INNER JOIN cteProducts q ON q.ProductID = i.ProductID
WHERE i.rn <= q.QuantitySold;

Group nested selected query returning all rows

I'm trying to display the data so that only one line is displayed per customer, i'm having trouble with trying to achieve that with my code as its returning all records, can anyone help
SELECT customerOrdrs.NAME AS 'Name',
customerOrdrs.currentbalance -
Sum(COALESCE(customerOrdrs.revisedbalance, 0)) AS 'RevisedBalance',
sold AS 'NumberOfItemsSold'
FROM customers,
(SELECT c.NAME AS NAME,
c.balance AS CurrentBalance,
i.qty AS RevisedBalance,
( Min(s.price) * i.qty ) AS Sold
FROM customers c
INNER JOIN sales o
ON c.NAME = o.custname
INNER JOIN purchases i
ON i.orderno = o.orderno
INNER JOIN contracters s
ON i.item = s.item
GROUP BY c.NAME,
c.balance,
i.qty) customerOrdrs
GROUP BY customerOrdrs.NAME,
customerOrdrs.currentbalance,
sold
I'm not sure how your data looks but I have reformatted the query and there are a few things I've noticed off the bat.
I have removed the subquery as I don't believe it is necessary - in addition your original query is referring to customer table twice without defining a join
Select [C].[Name] As [Name]
, [CurrentBalance] = [C].[Balance]
, [RevisedBalance] = [C].[Balance] - Sum([P].[Qty])
, [Sold] = ( Min([CO].[Price]) * sum([P].[Qty]) )
From [CUSTOMERS] [C]
Inner Join [Sales] [s]
On [C].[Name] = [s].[custName]
Inner Join [Purchases] [P]
On [P].[OrderNo] = [s].[OrderNo]
Inner Join [Contracters] [CO]
On [P].[Item] = [CO].[Item]
Group By [C].[Name]
, [C].[Balance];

one user many phones in a temp table

I need to show in a temporary table one user a many phone of that user, but I'm stuck
in the select, I need something like this:
user1 phone1 phone2 phone3 phone4 phone5
11816116-5 8555588 77877888 33254477 224474 45777885
this is the code that I'm trying:
select
phone As phonenum
Into #Tmp_phonenumber
From
clients_has_phones
where
user_number='11816116-5'
thanks in advance.
I can not think of a good way of doing the select statement other than by self joining on how ever many phone numbers your user may have.. With that being said you can try this for your select statement:
;With CTE_Main as (
Select
id
,Fono
,row_number()
Over(Partition by ID order by Fono) as RN
From sucursales
), CTE_Users as (
Select
id as id_num
from sucursales
group by id
)
Select
id_num
,a.Fono as Phone_1
,b.Fono as Phone_2
,c.Fono as Phone_3
,d.Fono as Phone_4
,e.Fono as Phone_5
From CTE_Users as realz
Left Join [CTE_Main] as a on a.id = realz.id_num and a.RN = 1
Left Join [CTE_Main] as b on b.id = realz.id_num and b.RN = 2
Left Join [CTE_Main] as c on c.id = realz.id_num and c.RN = 3
Left Join [CTE_Main] as d on d.id = realz.id_num and d.RN = 4
Left Join [CTE_Main] as e on e.id = realz.id_num and e.RN = 5
I know its kind of lengthy but it will display the results in the way that you want them.. My example only uses 5 rows but it should be pretty self explanatory.
Sql Fiddle: http://sqlfiddle.com/#!3/496f6/1

Resources