Avoiding GROUP BY on specific columns - sql

I am looking at avoiding grouping by specific columns in a large query to reduce performance problems.
Example: http://sqlfiddle.com/#!18/cb98e/2
CREATE TABLE [OrderTable]
(
[id] INT,
[OrderGroupID] INT,
[Total] INT,
[fkPerson] INT,
[fkitem] INT,
PRIMARY KEY (id)
)
INSERT INTO [OrderTable] (id, OrderGroupID, Total, [fkPerson], [fkItem])
VALUES ('1', '1', '20', '1', '1'),
('2', '1', '45', '2', '2'),
('3', '2', '32', '1', '1'),
('4', '2', '30', '2', '2');
CREATE TABLE [Person]
(
[id] INT,
[Name] VARCHAR(32),
PRIMARY KEY (id)
)
INSERT INTO [Person] (id, Name)
VALUES ('1', 'Fred'),
('2', 'Sam');
CREATE TABLE [Item]
(
[id] INT,
[ItemNo] VARCHAR(32),
[Price] INT,
PRIMARY KEY (id)
)
INSERT INTO [Item] (id, ItemNo, Price)
VALUES ('1', '453', '23'),
('2', '657', '34');
Query:
WITH TABLE1 AS
(
SELECT
P.ID AS [PersonID],
P.Name,
SUM(OT.[Total]) AS [Total],
i.[id] AS [ItemID],
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rownum,
ot.fkperson
FROM
OrderTable OT
INNER JOIN
Person P ON P.ID = OT.fkperson
INNER JOIN
Item I ON I.[id] = OT.[fkItem]
GROUP BY
P.ID, P.Name, i.id, ot.fkperson
)
SELECT
*,
Totalrows = (SELECT MAX(rownum) FROM TABLE1)
FROM
TABLE1
Result:
| PersonID | Name | Total | ItemID | rownum | fkperson | Totalrows |
+----------+------+-------+--------+--------+----------+-----------+
| 1 | Fred | 52 | 1 | 1 | 1 | 2 |
| 2 | Sam | 75 | 2 | 2 | 2 | 2 |
Now, for example if i didn't want to GROUP BY a varchar column (Person Name) i could do this - remove person table and join it back later. e.g.
WITH TABLE1 AS
(
SELECT
-- P.ID AS [PersonID],
-- P.Name,
SUM(OT.[Total]) AS [Total],
i.[id] AS [ItemID],
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rownum,
ot.fkperson
FROM
OrderTable OT
-- INNER JOIN Person P ON P.ID = OT.fkperson
INNER JOIN
Item I ON I.[id] = OT.[fkItem]
GROUP BY
-- P.ID, P.Name,
i.id, ot.fkperson
)
SELECT
p.id as [PersonID],
p.Name,
t1.[total],
t1.[itemid],
t1.[rownum],
t1.fkperson
-- Totalrows = (SELECT MAX(rownum) FROM TABLE1 GROUP BY
-- i.id
-- ,ot.fkperson
-- )
FROM
TABLE1 T1
INNER JOIN
Person P ON P.ID = T1.fkperson
Result:
| PersonID | Name | total | itemid | rownum | fkperson |
+----------+------+-------+--------+--------+----------+
| 1 | Fred | 52 | 1 | 1 | 1 |
| 2 | Sam | 75 | 2 | 2 | 2 |
My issue is I also want to include the MAX(rownum) column but how can I do this in my last query without having to group by everything again? What's the best approach to this? Have I missed something really obvious? :)

You can also use CROSS APPLY. And count for the total you can use COUNT(*) OVER()
SELECT
P.id as [PersonID],
P.Name,
T1.[total],
I.id as [itemid],
ROW_NUMBER() OVER (
ORDER BY (SELECT NULL)
) AS [rownum],
T1.fkperson,
COUNT(*) OVER () Totalrows
FROM
Item I
CROSS APPLY (SELECT ot.fkperson, SUM(OT.[Total]) AS [total]
FROM OrderTable OT
WHERE I.[id] = OT.[fkItem]
GROUP BY ot.fkperson ) AS T1
INNER JOIN Person P ON P.id = t1.fkperson
Result:
PersonID Name total itemid rownum fkperson Totalrows
----------- ----------- ----------- ----------- --------- ----------- -----------
1 Fred 52 1 1 1 2
2 Sam 75 2 2 2 2

Related

Concatenate from rows in SQL server

I want to concatenate from multiple rows
Table:
|id |Attribute |Value |
|--------|------------|---------|
|101 |Manager |Rudolf |
|101 |Account |456 |
|101 |Code |B |
|102 |Manager |Anna |
|102 |Cardno |123 |
|102 |Code |B |
|102 |Code |C |
The result I’m looking for is:
|id |Manager|Account|Cardno|Code |
|--------|-------|-------|------|----------|
|101 |Rudolf |456 | |B |
|102 |Anna | |123 |B,C |
I have the following code from a related question:
select
p.*,
a.value as Manager,
b.value as Account,
c.value as Cardno
from table1 p
left join table2 a on a.id = p.id and a.attribute = 'Manager'
left join table2 b on b.id = p.id and b.attribute = 'Account'
left join table2 c on c.id = p.id and b.attribute = 'Cardno'
However, it fails for the Code attribute with ID# 102, where both B and C values are present.
How can I update this to include both of those values in the same result?
If you are using SQL SERVER 2017 or above then string_agg() with PIVOT() will be easy to use but much faster in performance solution (Query#1).
If you are using older version of SQL Server then go for Query#2 with STUFF() and XML PATH FOR() for concatenating value along with PIVOT()
Schema:
create table table1 (id int, Attribute varchar(50) , Value varchar(50));
insert into table1 values(101 ,'Manager' ,'Rudolf');
insert into table1 values(101 ,'Account' ,'456');
insert into table1 values(101 ,'Code' ,'B');
insert into table1 values(102 ,'Manager' ,'Anna');
insert into table1 values(102 ,'Cardno' ,'123');
insert into table1 values(102 ,'Code' ,'B');
insert into table1 values(102 ,'Code' ,'C');
GO
Query#1 PIVOT() with STRING_AGG():
select *
from
(
select t1.id,t1.attribute,
string_agg(value,',') AS value
from table1 t1
group by t1.id,t1.attribute
) d
pivot
(
max(value)
for attribute in (manager,account,cardno,code)
) piv
Output:
id
manager
account
cardno
code
101
Rudolf
456
<emnull</em
B
102
Anna
<emnull</em
123
B,C
Query#2 PIVOT() WITH STUFF() AND XML PATH FOR():
select *
from
(
select distinct t1.id,t1.attribute,
STUFF(
(SELECT ', ' + convert(varchar(10), t2.value, 120)
FROM table1 t2
where t1.id = t2.id and t1.attribute=t2.attribute
FOR XML PATH (''))
, 1, 1, '') AS value
from table1 t1
) d
pivot
(
max(value)
for attribute in (manager,account,cardno,code)
) piv
Output:
id
manager
account
cardno
code
101
Rudolf
456
<emnull</em
B
102
Anna
<emnull</em
123
B, C
db<fiddle here
Another method via XML and XQuery.
It is for SQL Server 2008 onwards.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT, attribute VARCHAR(20), [Value] VARCHAR(30));
INSERT INTO #tbl (ID, attribute, Value) VALUES
(101,'Manager','Rudolf'),
(101,'Account','456'),
(101,'Code','B'),
(102,'Manager','Anna'),
(102,'Cardno','123'),
(102,'Code','B'),
(102,'Code','C');
-- DDL and sample data population, end
;WITH rs AS
(
SELECT ID, (
SELECT *
FROM #tbl AS c
WHERE c.id = p.id
FOR XML PATH('r'), TYPE, ROOT('root')
) AS xmldata
FROM #tbl AS p
GROUP BY id
)
SELECT ID
, COALESCE(xmldata.value('(/root/r[attribute="Manager"]/Value/text())[1]','VARCHAR(30)'),'') AS Manager
, COALESCE(xmldata.value('(/root/r[attribute="Account"]/Value/text())[1]','VARCHAR(30)'),'') AS Account
, COALESCE(xmldata.value('(/root/r[attribute="Cardno"]/Value/text())[1]','VARCHAR(30)'),'') AS Cardno
, COALESCE(REPLACE(xmldata.query('data(/root/r[attribute="Code"]/Value)').value('.', 'VARCHAR(MAX)'), SPACE(1), ','),'') AS Code
FROM rs
ORDER BY ID;
Output
+-----+---------+---------+--------+------+
| ID | Manager | Account | Cardno | Code |
+-----+---------+---------+--------+------+
| 101 | Rudolf | 456 | | B |
| 102 | Anna | | 123 | B,C |
+-----+---------+---------+--------+------+
UPD: "STRING_AGG only Server 2017+"
You can solve this task using CTE and STRING_AGG function, for example:
declare
#t table (id int, Attribute varchar (100), [Value] varchar (100) )
insert into #t
values
(101, 'Manager', 'Rudolf'),
(101, 'Account', '456'),
(101, 'Code', 'B'),
(102, 'Manager', 'Anna'),
(102, 'Cardno', '123'),
(102, 'Code', 'B'),
(102, 'Code', 'C')
;with cte as
(
select id, Attribute
,STRING_AGG([Value], ', ') WITHIN GROUP (ORDER BY ID ASC) AS [Value]
from #t
group by ID, Attribute
)
select
max(p.ID) ID
,a.Value Manager
,isnull(b.Value, '') Account
,isnull(c.Value, '') Cardno
,isnull(e.Value, '') Code
from cte p
left join cte a on a.id =p.ID and a.attribute = 'Manager'
left join cte b on b.id = p.id and b.attribute = 'Account'
left join cte c on c.id = p.id and c.attribute = 'Cardno'
left join cte e on e.id = p.id and e.attribute = 'Code'
group by p.ID, a.Value,b.Value,c.Value,e.Value

SQL select parent-child recursively based on a reference table

I saw many questions related to a recursive query but couldn't find any that shows how to use it based on a reference table.
I have a MasterTable where Id, ParentId columns are establishing the parent/child relation.
I have a SubTable where I have a bunch of Ids which could be a parent Id or child Id.
I would like to retrieve all related records (parent or child, recursively) from the MasterTable based on given SubTable
Current output:
id parentId
----------- -----------
1 NULL
2 1
3 1
4 NULL
5 4
6 5
7 6
Expected output
id parentId
----------- -----------
1 NULL
2 1
3 1
4 NULL
5 4
6 5
7 6
8 9
9 NULL
10 NULL
11 10
13 11
14 10
15 16
16 NULL
Comparison of actual vs expected:
Code:
DECLARE #MasterTable TABLE
(
id INT NOT NULL,
parentId INT NULL
);
DECLARE #SubTable TABLE
(
id INT NOT NULL
);
INSERT INTO #MasterTable (id, parentId)
VALUES (1, NULL), (2, 1), (3, 1), (4, NULL), (5, 4), (6, 5),
(7, 6), (8, 9), (9, NULL), (10, NULL), (11, 10), (12, NULL),
(13, 11), (13, 11), (14, 10), (15, 16), (16, NULL);
INSERT INTO #SubTable (id)
VALUES (1), (2), (3), (4), (6), (5), (7),
(8), -- it does not show
(13), -- it does not show
(15); -- it does not show
/* beside 8,13,15 it should add 9,11,14 and 10,16 */
;WITH cte AS
(
SELECT
mt1.id,
mt1.parentId
FROM
#MasterTable AS mt1
WHERE
mt1.parentId IS NULL
AND EXISTS (SELECT NULL AS empty
FROM #SubTable AS st
WHERE st.Id = mt1.id)
UNION ALL
SELECT
mt2.id,
mt2.parentId
FROM
#MasterTable AS mt2
INNER JOIN
cte AS c1 ON c1.id = mt2.parentId
)
SELECT DISTINCT
c2.id,
c2.parentId
FROM
cte AS c2
ORDER BY
id;
Is the following query suitable for the issue in question?
with
r as(
select
m.*, iif(m.parentid is null, 1, 0) p_flag
from #MasterTable m
join #SubTable s
on s.id = m.id
union all
select
m.*, iif(m.parentid is null, 1, r.p_flag)
from r
join #MasterTable m
on (r.p_flag = 1 and m.parentid = r.id) or
(r.p_flag = 0 and r.parentid = m.id)
)
select distinct
id, parentid
from r
order by id;
Output:
| id | parentid |
+----+----------+
| 1 | NULL |
| 2 | 1 |
| 3 | 1 |
| 4 | NULL |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
| 8 | 9 |
| 9 | NULL |
| 10 | NULL |
| 11 | 10 |
| 13 | 11 |
| 14 | 10 |
| 15 | 16 |
| 16 | NULL |
Test it online with rextester.com.
;WITH cte
AS (
SELECT mt1.id,
mt1.parentId
FROM #MasterTable AS mt1
WHERE mt1.parentId IS NULL
UNION ALL
SELECT mt2.id,
mt2.parentId
FROM #MasterTable AS mt2
INNER JOIN cte AS c1
ON c1.id = mt2.parentId
)
SELECT DISTINCT c2.id,
c2.parentId
FROM cte AS c2
where
EXISTS (
SELECT 1 AS empty FROM #SubTable AS st
WHERE ( st.Id = c2.id or st.Id = c2.parentId)
)
or
EXISTS (
SELECT 1 AS empty FROM #MasterTable AS mt
WHERE ( c2.Id = mt.parentId or c2.parentId = mt.parentId)
)
ORDER BY id;
You may try this....
; with cte as(
select distinct mas.id, mas.parentId, iif(mas.parentid is null, 1, 0) PId
from #MasterTable mas inner join #SubTable sub
on sub.id in(mas.id, mas.parentid) ----- create top node having parentid is null
union all
select mas.id, mas.parentId, ct.PId
from cte ct inner join #MasterTable mas
on (ct.PId = 1 and mas.parentid = ct.id) or
(ct.PId = 0 and ct.parentid = mas.id) ----- create child node for correspoding parentid created above
)
select distinct id, parentid from cte order by id
option (MAXRECURSION 100); ---- Add Maxrecursion to prevent the infinite loop
You can find this link for more info on recursive query in SQL link. In this link see Example E or above.

remove duplicate in double left join

I know there are several questions about this already.
But i cant find a answer from them about my case.
I need to produce a report with some data combines from three tables.
There is relations between those tables but some data may not match the related table, so i cant use a inner join.
So I tried with left join. This kinda worked but now I get back to many rows due to the left join.
I have set up a test case here -> http://sqlfiddle.com/#!18/637b1/1
CREATE TABLE [xOrder](
[ID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[SomeText] [nvarchar](255) NOT NULL DEFAULT (''))
GO
CREATE TABLE [Order_Time](
[ID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[OrderId] [uniqueidentifier] NOT NULL,
[SomeText] [nvarchar](255) NOT NULL DEFAULT (''),
[TimeTypeId] [int] NOT NULL DEFAULT ((-1)),
[TimeType2Id] [int] NOT NULL DEFAULT ((-1)))
GO
CREATE TABLE [Terms](
[ID] [int] NOT NULL,
[CategoryId] [int] NOT NULL,
[SomeText] [nvarchar](255) NOT NULL DEFAULT (''))
GO
Insert into [xOrder] (ID, SomeText) VALUES ('db63ddb9-40d9-4d41-9dfc-5335c400dbd8','aaa')
Insert into [xOrder] (ID, SomeText) VALUES ('ef19af2d-66e9-4de1-a9a2-178b61dfe958','bbb')
Insert into [xOrder] (ID, SomeText) VALUES (newid(),'ccc')
GO
Insert into Order_Time (ID, OrderId, SomeText, TimeTypeId, TimeType2Id) VALUES (newid(),'db63ddb9-40d9-4d41-9dfc-5335c400dbd8', 'time1.1', 1, 1)
Insert into Order_Time (ID, OrderId, SomeText, TimeTypeId, TimeType2Id) VALUES (newid(),'db63ddb9-40d9-4d41-9dfc-5335c400dbd8', 'time1.2', 1, -1)
Insert into Order_Time (ID, OrderId, SomeText, TimeTypeId, TimeType2Id) VALUES (newid(),'db63ddb9-40d9-4d41-9dfc-5335c400dbd8', 'time1.3', -1, -1)
GO
Insert into Order_Time (ID, OrderId, SomeText, TimeTypeId, TimeType2Id) VALUES (newid(),'ef19af2d-66e9-4de1-a9a2-178b61dfe958', 'time2.1', 2, 2)
Insert into Order_Time (ID, OrderId, SomeText, TimeTypeId, TimeType2Id) VALUES (newid(),'ef19af2d-66e9-4de1-a9a2-178b61dfe958', 'time2.2', 2, -1)
Insert into Order_Time (ID, OrderId, SomeText, TimeTypeId, TimeType2Id) VALUES (newid(),'ef19af2d-66e9-4de1-a9a2-178b61dfe958', 'time2.3', -1, -1)
GO
Insert into Terms (ID, CategoryId, SomeText) VALUES (1, 1, 'Term1')
Insert into Terms (ID, CategoryId, SomeText) VALUES (2, 1, 'Term2')
Insert into Terms (ID, CategoryId, SomeText) VALUES (3, 1, 'Term3')
Insert into Terms (ID, CategoryId, SomeText) VALUES (1, 2, 'Category1')
Insert into Terms (ID, CategoryId, SomeText) VALUES (2, 2, 'Category2')
Insert into Terms (ID, CategoryId, SomeText) VALUES (3, 2, 'Category3')
GO
And this i the query i have tried.
select o.SomeText as OrderText, ot.SomeText as TimeText1, coalesce(t.SomeText, 'NotFound') as TermText, coalesce(tt.SomeText, 'NotFound') as CategoryText from xOrder o
inner join order_time ot on o.id = ot.OrderId
left join terms t on ot.TimeTypeId = t.Id
left join terms tt on (ot.TimeType2Id = t.Id and t.ID = 2)
The result i expect is 6 row containing this:
----------------------------------------
| aaa | Time1.1 | Term1 | Category1 |
| aaa | Time1.2 | Term1 | NotFound |
| aaa | Time1.2 | NotFound | NotFound |
| bbb | Time2.1 | Term2 | Category2 |
| bbb | Time2.2 | Term2 | NotFound |
| bbb | Time2.2 | NotFound | NotFound |
----------------------------------------
But that isnt happening. So how do i remove the extra rows from the left joins?
I think this is what you want:
select o.SomeText as OrderText, ot.SomeText as TimeText1,
coalesce(t.SomeText, 'NotFound') as TermText,
coalesce(tt.SomeText, 'NotFound') as CategoryText
from xOrder o inner join
order_time ot
on o.id = ot.OrderId left join
terms t
on ot.TimeTypeId = t.Id and t.CategoryId = 1 left join
terms tt
on ot.TimeType2Id = tt.Id and tt.CategoryId = 2;
Here is a db<>fiddle.
Your query has some problem with table aliases, which is why you get duplicates.
You need to change your join condition
DEMO
select distinct o.SomeText as OrderText, ot.SomeText as TimeText1,
coalesce(t.SomeText, 'NotFound') as TermText,
coalesce(tt.SomeText, 'NotFound') as CategoryText
from xOrder o
inner join order_time ot on o.id = ot.OrderId
left join terms t on ot.TimeTypeId = t.id and t.CategoryId=1
left join terms tt on ot.TimeTypeId = tt.id and tt.CategoryId=2
OUTPUT:
OrderText TimeText1 TermText CategoryText
aaa time1.1 Term1 Category1
aaa time1.2 Term1 Category1
aaa time1.3 NotFound NotFound
bbb time2.1 Term2 Category2
bbb time2.2 Term2 Category2
bbb time2.3 NotFound NotFound
Try below query:
select o.SomeText, ot.sometext,
coalesce((select someText from terms where id = ot.TimetypeId and categoryid = 1), 'NotFound') TimeType,
coalesce((select someText from terms where id = ot.Timetype2Id and categoryid = 2), 'NotFound') CategroyId
from xOrder o
join order_time ot on o.id = ot.OrderId

Grouping values in order to sort by minimum of a value, then other fields then by that value itself

I would like to sort my data by an aggregate value, then by other fields, then by the unaggregated value.
I have the following schema:
CREATE TABLE priority_t (
id NUMERIC(10),
priority NUMERIC(10),
CONSTRAINT priority_t_pk PRIMARY KEY (id)
);
CREATE TABLE value_t (
id NUMERIC(10),
value NUMERIC(10),
CONSTRAINT value_t_pk PRIMARY KEY (id)
);
CREATE TABLE file_t (
id NUMERIC(10),
CONSTRAINT file_t_pk PRIMARY KEY (id)
);
CREATE TABLE main_t (
id NUMERIC(10),
priority_id NUMERIC(10),
value_id NUMERIC(10),
file_id NUMERIC(10),
CONSTRAINT main_t_pk PRIMARY KEY (id),
CONSTRAINT priority_t_fk FOREIGN KEY (priority_id) REFERENCES priority_t(id),
CONSTRAINT value_t_fk FOREIGN KEY (value_id) REFERENCES value_t(id),
CONSTRAINT file_t_fk FOREIGN KEY (file_id) REFERENCES file_t(id)
);
Then I insert the following data:
INSERT INTO priority_t (id, priority) VALUES (1, 10);
INSERT INTO priority_t (id, priority) VALUES (2, 20);
INSERT INTO value_t (id, value) VALUES (1, 987);
INSERT INTO value_t (id, value) VALUES (2, 876);
INSERT INTO value_t (id, value) VALUES (3, 765);
INSERT INTO value_t (id, value) VALUES (4, 654);
INSERT INTO file_t (id) VALUES (111);
INSERT INTO file_t (id) VALUES (222);
INSERT INTO file_t (id) VALUES (333);
INSERT INTO file_t (id) VALUES (444);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (1, 1, 1, 111);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (2, 2, 1, 111);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (3, 2, 2, 222);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (4, 1, 2, 333);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (5, 2, 3, 111);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (6, 1, 4, 444);
INSERT INTO main_t (id, priority_id, value_id, file_id) VALUES (7, 2, 4, 444);
COMMIT;
And I want to get the following result:
min_priority | priority | value | value_id | file_id
(hidden) | | | (hidden) |
--------------+----------+-------+----------+---------
10 | 10 | 654 | 4 | 444
10 | 20 | 654 | 4 | 444
10 | 10 | 876 | 2 | 333
10 | 10 | 987 | 1 | 111
10 | 20 | 987 | 1 | 111
20 | 20 | 765 | 3 | 111
20 | 20 | 876 | 2 | 222
I know how to sort them:
ORDER BY min_value ASC, value ASC, value_id ASC, priority ASC
But my problem is that I don't know how to group the values themselves: I keep getting duplicates in my rows, and/or incorrect values.
My closest attempt is the following:
WITH listing AS (
SELECT m.id AS main_id,
p.id AS priority_id,
p.priority AS priority,
v.id AS value_id,
v.value AS value,
f.id AS file_id
FROM main_t m
INNER JOIN priority_t p ON m.priority_id = p.id
INNER JOIN value_t v ON m.value_id = v.id
INNER JOIN file_t f ON m.file_id = f.id
)
SELECT min_p.min_priority AS min_priority,
listing.priority AS priority,
listing.value AS value,
listing.file_id AS file_id
FROM listing,
(
SELECT min(min_p_value.min_priority) AS min_priority,
min_p_value.value_id AS min_value_id,
listing.file_id AS file_id
FROM listing,
(
SELECT min(listing.priority) AS min_priority,
listing.value AS value,
listing.value_id AS value_id
FROM listing
GROUP BY listing.value, listing.value_id
) min_p_value
WHERE listing.value = min_p_value.value
AND listing.value_id = min_p_value.value_id
AND min_p_value.min_priority = min_priority
GROUP BY min_p_value.value_id, listing.file_id
) min_p
WHERE min_p.min_value_id = listing.value_id
AND min_p.file_id = listing.file_id
ORDER BY min_p.min_priority ASC,
listing.value ASC,
listing.value_id ASC,
listing.priority;
And this returns the following incorrect result:
MIN_PRIORITY PRIORITY VALUE FILE_ID
------------- ---------- ---------- ----------
10 10 654 444
10 20 654 444
10 10 876 333
10 20 876 222 <-- incorrect, should have a min_priority of 20, and therefore be the last
10 10 987 111
10 20 987 111
20 20 765 111
How can I achieve what I expect?
this should work:
select (select min(priority)
from main_t mm
join priority_t tt
on tt.id = mm.priority_id
where mm.value_id = m.value_id) as min_priority,
p.priority as priority,
v.value as value,
m.value_id,
m.file_id
from main_t m
join priority_t p
on p.id = m.priority_id
join value_t v
on v.id = m.value_id
order by 1, 3, 4, 2;
It determines the minimum priority by value_id.
You can order the result by column number as shown.
I have written this code in MySQL (not Oracle) and was not sure about the CTE syntax, so I replaced the WITH clause with a temp table called min_priority, but you can of course replace the temp table with a CTE. When I ran this on MySQl, I got the same result as you wanted.
Also was not sure whether you need a left join or inner join, both would work in this example.
-- find min priority for each value
create temporary table if not exists min_priority as (
select m.value_id, min(p.priority) as min_pri
from main_t m
inner join priority_t p on m.priority_id = p.id
group by m.value_id
);
-- just join all the tables, including min_priority, and order the result
select mp.min_pri, p.priority, v.value, v.id, f.id
from main_t m
left join priority_t p on m.priority_id = p.id
left join value_t v on m.value_id = v.id
left join file_t f on m.file_id = f.id
left join min_priority mp on mp.value_id = v.id
order by mp.min_pri asc, v.value asc, v.id asc, p.priority asc;
Just as a side note, I wouldn't use multiple levels of inner queries, as your example in the question, as they would affect the performance.
When you need aggregated and non-aggregated values use analytic functions. I think, that would be something like this:
select *
from (select min(p.priority) over (partition by v.id, v.value, f.id) min_priority,
p.priority, v.value, v.id value_id, f.id file_id
from main_t m
join priority_t p on m.priority_id = p.id
join value_t v on m.value_id = v.id
join file_t f on m.file_id = f.id)
order by min_priority, value, value_id, priority
Result:
MIN_PRIORITY PRIORITY VALUE VALUE_ID FILE_ID
------------ ----------- ----------- ----------- -----------
10 10 654 4 444
10 20 654 4 444
10 10 876 2 333
10 10 987 1 111
10 20 987 1 111
20 20 765 3 111
20 20 876 2 222

Inner Join + select the most recent

I have been trying to do the bellow query but honestly it's driving me crazy.
I have 2 Tables on MS SQL CE 4.0
Table 1 Name: Items
ID
Item_Code
Logged_by
Description
ID | Item_Code | Logged_by | Description
1 | A | Pete | just an A
2 | B | Mary | Seams like a B
3 | C | Joe | Obviously this is a C
4 | D | Pete | This is another A
Table 2 Name: Item_Comments
ID
Item_Code
Comment
Date
ID | Item_Code | Comment | Date
1 | B | Done | 2014/08/08
2 | A | Nice A | 2014/08/08
3 | B | Send 1 More | 2014/08/09
4 | C | Done | 2014/08/10
5 | D | This is an A | 2014/08/10
6 | D | Opps Sorry | 2014/08/11
The wanted result: I'm looking to join the most recent comment from Item_Comments to the Items Table
ID | Item_Code | Logged_by | Description | Comment
1 | A | Pete | just an A | Nice A
2 | B | Mary | Seams like a B | Send 1 More
3 | C | Joe | Obviously this is a C | Done
4 | D | Pete | This is another A | Opps Sorry
I did this query but I'm getting all the information =( mixed.
SELECT *
FROM Items t1
JOIN
(SELECT Item_Code, Comment, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code, Comment, Date
) t2
ON Item_Code= Item_Code
ORDER BY t1.Item_Code;
Do you know any way to do this ?
Try:
select x.*, z.comment
from items x
join (select item_code, max(date) as latest_dt
from item_comments
group by item_code) y
on x.item_code = y.item_code
join item_comments z
on y.item_code = z.item_code
and y.latest_dt = z.date
Fiddle test: http://sqlfiddle.com/#!6/d387f/8/0
You were close with your query but in your inline view aliased as t2 you were grouping by comment, leaving the max function to not actually aggregate anything at all. In t2 you should have just selected item_code and max(date) and grouped only by item_code, then you can use that to join into item_comments (y and z in my query above).
This is a second way of doing this using a subquery, however I would stick to the above (a join w/ an inline view):
select i.*, c.comment
from items i
join item_comments c
on i.item_code = c.item_code
where c.date = (select max(x.date)
from item_comments x
where x.item_code = c.item_code)
order by i.id
Fiddle test: http://sqlfiddle.com/#!6/d387f/11/0
Note if you run this inside piece you get every single record:
SELECT Item_Code, Comment, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code, Comment, Date
You want only the most recent comment. Assuming this is SQL Server 2008 or earlier, this get's you the most recent date for each Item_Code:
SELECT Item_Code, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code
Now you need to join that back and look up the comment on that date:
SELECT C.*
FROM Item_Comments C
INNER JOIN
(SELECT Item_Code, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code
) t2
ON C.Item_Code= t2.Item_Code
AND C.date = t2.MyDate
Now you can use that to join back to your original table:
SELECT t1.*, LatestComment.*
FROM Items t1
INNER JOIN
(
SELECT C.*
FROM Item_Comments C
INNER JOIN
(SELECT Item_Code, MAX(date) as MyDate
FROM Item_Comments
Group By Item_Code
) t2
ON C.Item_Code= t2.Item_Code
AND C.date = t2.MyDate
) LatestComment
On LatestComment.Item_Code = t1.Item_Code
Depending on the actual database you are using, this can get much simpler. Thats why you need to tag your database and version.
Try this,
create table items (id int, item_code char(1), logged_by varchar(10), description varchar(30));
insert into items values (1, 'A', 'Pete', 'just an A');
insert into items values (2, 'B', 'Mary', 'Seams like a B');
insert into items values (3, 'C', 'Joe', 'Obviously this is a C');
insert into items values (4, 'D', 'Pete', 'This is another A');
create table item_comments (id int, item_code char(1), comment varchar(20), date date);
insert into item_comments values (1, 'B', 'Done', '2014/08/08');
insert into item_comments values (2, 'A', 'Nice A', '2014/08/08');
insert into item_comments values (3, 'B', 'Send 1 More', '2014/08/09');
insert into item_comments values (4, 'C', 'Done', '2014/08/10');
insert into item_comments values (5, 'D', 'This is an A', '2014/08/10');
insert into item_comments values (6, 'D', 'Opps Sorry', '2014/08/11');
select * from items;
select * from item_comments;
select * from (select i.logged_by,i.id,i.item_code,i.description,ic.comment
,row_number() over(partition by i.id order by i.id )as Rnk
from items i inner join item_comments ic
on i.item_code=ic.item_code and i.id in(1,3)) x
where x.Rnk=1
union
select * from (select i.logged_by,i.id,i.item_code,i.description,ic.comment
,row_number() over(partition by i.id order by i.id )as Rnk
from items i inner join item_comments ic
on i.item_code=ic.item_code and i.id in(2,4)
) x where x.Rnk=2 order by item_code