Aggregate rows inside new columns - sql

I would like to join the two first tables (Product and ProductProperties) to get the result in the bottom.
How do I do this?

There are penalties for EAV. I'm not saying EAV is evil, but should be deployed carefully and with great forethought.
Here are two examples. The first is a PIVOT, and the second is a conditional aggregation.
I tend to lean towards the conditional aggregation, it offers more flexibility and often a performance bump
Untested for you did not supply sample data and desired results as text
Select *
From (
Select A.product_id
,A.product_name
,B.product_property
,B.product_property_value
From Product A
Join ProductProperties B on A.product_id=B.product_di
) src
Pivot (max( product_property_value ) for product_property in ([Price],[Category],[Status] ) ) pvt
Select A.product_id
,A.product_name
,Price = max( case when product_propery='Price' then product_propery_value end)
,Category = max( case when product_propery='Category' then product_propery_value end)
,Status = max( case when product_propery='Status' then product_propery_value end)
From Product A
Join ProductProperties B on A.product_id=B.product_di
Group By A.product_id,A.product_name

SELECT
b.product_id
,b.product_name
,Price = MAX(IIF(p.product_property = 'Price',p.product_property_value,NULL))
,Category = MAX(IIF(p.product_property = 'Category',p.product_property_value,NULL))
,Status = MAX(IIF(p.product_property = 'Status',p.product_property_value,NULL))
FROM books b (nolock)
JOIN prodprop p (nolock)
ON b.product_id = p.product_id
GROUP BY b.product_id,b.product_name

Related

Left outer join and necessary subquery with date in Oracle

I'm struggling with a left outer join where the right portion, the one that could contain nulls, doesn't show me values. I think the situation is called an implicit inner join. The reason is that the subquery doesn't return nulls but rather nothing for what I'd like to have on my right part.
(This question differs from Left Outer Join Not Working? in that a subquery to find the maximum date is needed.)
Here is a simplified example:
Table Contracts:
customer_id, status
Table Values:
customer_id, value_code, value, begin_date
I want to display all customers with status = 'active' and the latest value for a certain value_code, lets say 'volume'. There are more value_codes and the values have a certain date from which they are valid. There can also be no value_code BUT then I want a NULL on the right side.
So here is what I tried to do:
SELECT * FROM CONTRACTS C
LEFT JOIN VALUES V ON C.CUSTOMER_ID = V.CUSTOMER_ID
AND VALUE_CODE = 'VOLUME'
WHERE C.STATUS = 'ACTIVE'
AND V.BEGIN_DATE = (
SELECT MAX(BEGIN_DATE) FROM VALUES V2
WHERE V2.CUSTOMER_ID = V.CUSTOMER_ID
AND V2.VALUE_CODE = 'VOLUME'
)
I can't put the subquery in the join clause, Oracle won't accept that. On the other hand my subquery makes it so that all the rows that have no entry for a value with code 'volume' are omitted. What I want is to have value = NULL instead with all the customers on the left.
Thanks for helping!
Filter the VALUE rows first and then LEFT JOIN:
SELECT *
FROM CONTRACTS C
LEFT JOIN
( SELECT *
FROM (
SELECT V.*,
ROW_NUMBER() OVER ( PARTITION BY CUSTOMER_ID ORDER BY BEGIN_DATE DESC )
AS rn
FROM VALUES V
)
WHERE rn = 1
) V
ON ( C.CUSTOMER_ID = V.CUSTOMER_ID
AND VALUE_CODE = 'VOLUME' )
WHERE C.STATUS = 'ACTIVE'
As MT0 suggested using partitioning and sorting by row number helped. Only had to include value_code in the partitioning for my purpose.
So this query finally did what I wanted:
SELECT *
FROM CONTRACTS C
LEFT JOIN
( SELECT *
FROM (
SELECT V.*,
ROW_NUMBER() OVER ( PARTITION BY CUSTOMER_ID, VALUE_CODE ORDER BY BEGIN_DATE DESC )
AS rn
FROM VALUES V
)
WHERE rn = 1
) V
ON ( C.CUSTOMER_ID = V.CUSTOMER_ID
AND VALUE_CODE = 'VOLUME' )
WHERE C.STATUS = 'ACTIVE'

Easy Left Join SQL Syntax

New to SQL and want to complete a LEFT JOIN.
I have two seperate tables with the below code:
SELECT
StockCode, SalesOrder, SUM(OrderQty)
FROM
dbo.IopSalesPerf
WHERE
dbo.IopSalesPerf.CustRequestDate BETWEEN '2017-07-01' AND '2017-07-31'
AND EntrySystemTime = 1
AND Warehouse = '01'
AND StockCode = '001013'
GROUP BY
StockCode,SalesOrder
ORDER BY
StockCode ASC
SELECT
SalesOrder, SUM(NetSalesValue), SUM(QtyInvoiced)
FROM
ArTrnDetail
GROUP BY
SalesOrder
I would like to LEFT JOIN the last table onto the first using SalesOrder as the joining column. Can anyone assist with the syntax?
Simpliest way would be:
SELECT * FROM
(
SELECT StockCode,SalesOrder,sum(OrderQty)
FROM dbo.IopSalesPerf
WHERE dbo.IopSalesPerf.CustRequestDate between '2017-07-01' and '2017-07-31'
and EntrySystemTime = 1 and Warehouse = '01' and StockCode = '001013'
GROUP BY StockCode,SalesOrder
Order BY StockCode ASc
) AS A
LEFT JOIN
(
SELECT SalesOrder,sum(NetSalesValue),sum(QtyInvoiced)
FROM ArTrnDetail
Group by SalesOrder
) AS B
ON A.SalesOrder = B.SalesOrder

Inner join that ignore singlets

I have to do an self join on a table. I am trying to return a list of several columns to see how many of each type of drug test was performed on same day (MM/DD/YYYY) in which there were at least two tests done and at least one of which resulted in a result code of 'UN'.
I am joining other tables to get the information as below. The problem is I do not quite understand how to exclude someone who has a single result row in which they did have a 'UN' result on a day but did not have any other tests that day.
Query Results (Columns)
County, DrugTestID, ID, Name, CollectionDate, DrugTestType, Results, Count(DrugTestType)
I have several rows for ID 12345 which are correct. But ID 12346 is a single row of which is showing they had a row result of count (1). They had a result of 'UN' on this day but they did not have any other tests that day. I want to exclude this.
I tried the following query
select
c.desc as 'County',
dt.pid as 'PID',
dt.id as 'DrugTestID',
p.id as 'ID',
bio.FullName as 'Participant',
CONVERT(varchar, dt.CollectionDate, 101) as 'CollectionDate',
dtt.desc as 'Drug Test Type',
dt.result as Result,
COUNT(dt.dru_drug_test_type) as 'Count Of Test Type'
from
dbo.Test as dt with (nolock)
join dbo.History as h on dt.pid = h.id
join dbo.Participant as p on h.pid = p.id
join BioData as bio on bio.id = p.id
join County as c with (nolock) on p.CountyCode = c.code
join DrugTestType as dtt with (nolock) on dt.DrugTestType = dtt.code
inner join
(
select distinct
dt2.pid,
CONVERT(varchar, dt2.CollectionDate, 101) as 'CollectionDate'
from
dbo.DrugTest as dt2 with (nolock)
join dbo.History as h2 on dt2.pid = h2.id
join dbo.Participant as p2 on h2.pid = p2.id
where
dt2.result = 'UN'
and dt2.CollectionDate between '11-01-2011' and '10-31-2012'
and p2.DrugCourtType = 'AD'
) as derived
on dt.pid = derived.pid
and convert(varchar, dt.CollectionDate, 101) = convert(varchar, derived.CollectionDate, 101)
group by
c.desc, dt.pid, p.id, dt.id, bio.fullname, dt.CollectionDate, dtt.desc, dt.result
order by
c.desc ASC, Participant ASC, dt.CollectionDate ASC
This is a little complicated because the your query has a separate row for each test. You need to use window/analytic functions to get the information you want. These allow you to do calculate aggregation functions, but to put the values on each line.
The following query starts with your query. It then calculates the number of UN results on each date for each participant and the total number of tests. It applies the appropriate filter to get what you want:
with base as (<your query here>)
select b.*
from (select b.*,
sum(isUN) over (partition by Participant, CollectionDate) as NumUNs,
count(*) over (partition by Partitipant, CollectionDate) as NumTests
from (select b.*,
(case when result = 'UN' then 1 else 0 end) as IsUN
from base
) b
) b
where NumUNs <> 1 or NumTests <> 1
Without the with clause or window functions, you can create a particularly ugly query to do the same thing:
select b.*
from (<your query>) b join
(select Participant, CollectionDate, count(*) as NumTests,
sum(case when result = 'UN' then 1 else 0 end) as NumUNs
from (<your query>) b
group by Participant, CollectionDate
) bsum
on b.Participant = bsum.Participant and
b.CollectionDate = bsum.CollectionDate
where NumUNs <> 1 or NumTests <> 1
If I understand the problem, the basic pattern for this sort of query is simply to include negating or exclusionary conditions in your join. I.E., self-join where columnA matches, but columns B and C do not:
select
[columns]
from
table t1
join table t2 on (
t1.NonPkId = t2.NonPkId
and t1.PkId != t2.PkId
and t1.category != t2.category
)
Put the conditions in the WHERE clause if it benchmarks better:
select
[columns]
from
table t1
join table t2 on (
t1.NonPkId = t2.NonPkId
)
where
t1.PkId != t2.PkId
and t1.category != t2.category
And it's often easiest to start with the self-join, treating it as a "base table" on which to join all related information:
select
[columns]
from
(select
[columns]
from
table t1
join table t2 on (
t1.NonPkId = t2.NonPkId
)
where
t1.PkId != t2.PkId
and t1.category != t2.category
) bt
join [othertable] on (<whatever>)
join [othertable] on (<whatever>)
join [othertable] on (<whatever>)
This can allow you to focus on getting that self-join right, without interference from other tables.

Improve performance on SELECT CASE with Partition Plus COUNT

I have revised the code based on suggestions but now performance has dwindled. Any suggestions are welcome
select *
from (
SELECT
d.ID,
d.HeaderId,
CASE WHEN h.MyType = 'C' THEN
RANK() OVER (PARTITION BY l.Work ORDER BY l.Address1 DESC)
ELSE
RANK() OVER (PARTITION BY l.Home ORDER BY l.Address1 DESC)
END
AS 'RANK',
CASE WHEN h.MyType = 'C' THEN
COUNT(l.Work) OVER (PARTITION BY l.Work)
ELSE
COUNT(l.Home) OVER (PARTITION BY l.Home)
END
AS 'MAXCOUNT'
FROM schema1.Details AS d
JOIN schema1.BatchHeader AS h
ON d.HeaderId = h.ID
JOIN schema2.Details AS l
ON d.LeadBatchDetailId = l.Id
LEFT JOIN LDCs AS ldcElec
ON l.LDC_Elec = schema3.Code
LEFT JOIN LDCs AS ldcGas
ON l.ldc_gas = ldcGas.Code
LEFT JOIN schema2.Accounts ag
ON (l.Work = ag.Phone AND 'G' = ag.Business AND h.MyType = 'C')
OR (l.Home = ag.Phone AND 'G' = ag.Business AND h.MyType = 'R')
LEFT JOIN schema2.Accounts ae
ON (l.Work = ae.Phone AND 'E' = ae.Business AND h.MyType = 'C')
OR (l.Home = ae.Phone AND 'E' = ae.Business AND h.MyType = 'R')
WHERE d.HeaderId = #Id)
) a
WHERE [RANK] = [MAXCOUNT]
ORDER BY LdcGasName, LdcElecName
I don’t believe SQL does that, supporting AND-type logic embedded within the ranking function that way. You might need to break it into further subqueries. Some extreme psuedo-code:
Select rank over work for ‘C’
Full outer join select rank over home for “R”
Make that a subquery, with the outer query performing the coalesce and all the outer joins
But then you have to deal with the COUNT... OVER..., and that count is not dependant upon h.MyType = C or R, which makes me wonder if that MAXCOUNT will end up equallying the ranking values which are so constrained.
This all assumes I'm reading it right... but it's a complex query, and hard to understand without knowledge of the underlying tables and business logic.

Pivot using SQL Server 2000

I put together a sample scenario of my issue and I hope its enough for someone to point me in the right direction.
I have two tables
Products
Product Meta
I need a result set of the following
I realize this is two years old, but it bugs me that the accepted answer calls for using dynamic SQL and the most upvoted answer won't work:
Select P.ProductId, P.Name
, Min( Case When PM.MetaKey = 'A' Then PM.MetaValue End ) As A
, Min( Case When PM.MetaKey = 'B' Then PM.MetaValue End ) As B
, Min( Case When PM.MetaKey = 'C' Then PM.MetaValue End ) As C
From Products As P
Join ProductMeta As PM
On PM.ProductId = P.ProductId
Group By P.ProductId, P.Name
You must use a Group By or you will get a staggered result. If you are using a Group By, you must wrap each column that is not in the Group By clause in an aggregate function (or a subquery).
We've successfully used the following approach in the past...
SELECT [p].ProductID,
[p].Name,
MAX(CASE [m].MetaKey
WHEN 'A'
THEN [m].MetaValue
END) AS A,
MAX(CASE [m].MetaKey
WHEN 'B'
THEN [m].MetaValue
END) AS B,
MAX(CASE [m].MetaKey
WHEN 'C'
THEN [m].MetaValue
END) AS C
FROM Products [p]
INNER JOIN ProductMeta [m]
ON [p].ProductId = [m].ProductId
GROUP BY [p].ProductID,
[p].Name
It can also be useful transposing aggregations with the use of...
SUM(CASE x WHEN 'y' THEN yVal ELSE 0 END) AS SUMYVal
EDIT
Also worth noting this is using ANSI standard SQL and so it will work across platforms :)
If your database engine is 2005 and your database is in 2000 compatibility mode, you can work around the lower compatibility mode by running your query from a 2005 database. Target the 2000 database by using 3 part naming convention for your tables in the query such as DatabaseNameHere.dbo.TableNameHere
Select a.ProductId
,a.Name
,(Select c.MetaValue
From [Product Meta] c
Where c.ProductId = a.ProductId
And c.MetaKey = 'A') As 'A'
,(Select d.MetaValue
From [Product Meta] d
Where d.ProductId = a.ProductId
And d.MetaKey = 'B') As 'B'
,(Select e.MetaValue
From [Product Meta] e
Where e.ProductId = a.ProductId
And e.MetaKey = 'C') As 'C'
From Products a
Order By a.ProductId Asc