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
Related
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
I need to fetch all our product categories from Magento 1.9 database and present it in one row, showing 'product ID', 'product type', 'sku' and 'category path' (IDs and values) I managed to get to the point where all my categories for each product are returned in multiple rows. My current query is below:
SELECT
p.entity_id AS 'product_id',
p.type_id AS 'product_type',
p.sku,
c.category_id,
l.level AS 'category_level',
l.path AS 'category_path',
n.name AS 'category_name'
FROM
mage_catalog_product_entity AS p
JOIN
mage_catalog_category_product_index AS c ON p.entity_id = c.product_id
JOIN
mage_catalog_category_entity AS l ON l.entity_id = c.category_id
JOIN
mage_catalog_category_flat_store_6 AS n ON n.path = l.path
WHERE
c.store_id = '6'
AND l.path LIKE '1/14%'
AND l.path NOT LIKE '1/14/207%'
AND l.path NOT LIKE '1/14/12/25%'
-- AND l.level > 1
ORDER BY p.sku , length(l.path)
It returns the following:
What I am trying to achieve is something like that:
Or, ideally, CONCAT these columns into one
Any suggestions?
You can pivot your resultset with conditional aggregation.
Starting from your existing query, here is on approach that groups by product id, product type and sku, and uses the category level to spread category names across columns:
SELECT
p.entity_id AS product_id,
p.type_id AS product_type,
p.sku,
max(l.level) AS category_level,
max(l.path) AS category_path,
max(case when l.level = 1 then n.name end) AS level1_category_name,
max(case when l.level = 2 then n.name end) AS level2_category_name,
max(case when l.level = 3 then n.name end) AS level3_category_name,
max(case when l.level = 4 then n.name end) AS level4_category_name
FROM
mage_catalog_product_entity AS p
JOIN
mage_catalog_category_product_index AS c ON p.entity_id = c.product_id
JOIN
mage_catalog_category_entity AS l ON l.entity_id = c.category_id
JOIN
mage_catalog_category_flat_store_6 AS n ON n.path = l.path
WHERE
c.store_id = '6'
AND l.path LIKE '1/14%'
AND l.path NOT LIKE '1/14/207%'
AND l.path NOT LIKE '1/14/12/25%'
GROUP BY product_id, p.entity_id, p.sku
ORDER BY p.sku
Side note: do not use single quotes for column aliases; while some databases may support that, the SQL standard states that single quotes are meant for literal strings. Use the proper quoting character for your database, or better yet use aliases that do not require quoting.
I have Projects - Issues, one to many relationship.
I want Pending issues and Completed issues for each project.
So, what I have done
SELECT
a.id ,
a.Name,
SUM(CASE WHEN b.StatusId = 3 THEN 1 ELSE NULL END) AS CompletedIssues,
SUM(CASE WHEN b.StatusId != 3 THEN 1 ELSE NULL END) AS PendingIssues
FROM
Projects a
JOIN Issues b
ON a.ID = b.ProjectId
GROUP BY
a.name,
b.StatusId,
a.ID
But it's not giving proper output. see below snap.
There are two separate rows for Completed and pending issues and sometimes more then 2 rows based upon Issues Status ID (See BT5).
Is case when is wrong for this scenario?
what is the proper way to achieve this?
Fix your group by:
select p.id, p.Name,
sum(case when i.StatusId = 3 then 1 else null end) as CompletedIssues,
sum(case when i.StatusId <> 3 then 1 else null end) as PendingIssues
from Projects p join
Issues i
on p.ID = i.ProjectId
group by p.name, p.id;
Note: You may not want else NULL. Normally, you want counts to be zero rather than NULL:
select p.id, p.Name,
sum(case when i.StatusId = 3 then 1 else 0 end) as CompletedIssues,
sum(case when i.StatusId <> 3 then 1 else 0 end) as PendingIssues
from Projects p join
Issues i
on p.ID = i.ProjectId
group by p.name, p.id;
Also, I changed the table aliases to something more meaningful. Don't use meaningless letters such as a and b. Use table abbreviations.
I have the following sql query and trying to optimize it using execution plan. In execution plan it says Estimated subtree cost is 36.89. There are several table spools(Eager Spool). can anyone help me to optimize this query. Thanks in advance.
SELECT
COUNT(DISTINCT bp.P_ID) AS total,
COUNT(DISTINCT CASE WHEN bc.Description != 'S' THEN bp.P_ID END) AS m_count,
COUNT(DISTINCT CASE WHEN bc.Description = 'S' THEN bp.P_ID END) AS s_count,
COUNT(DISTINCT CASE WHEN bc.Description IS NULL THEN bp.P_ID END) AS n_count
FROM
progress_tbl AS progress
INNER JOIN Person_tbl AS bp ON bp.P_ID = progress.person_id
LEFT OUTER JOIN Status_tbl AS bm ON bm.MS_ID = bp.MembershipStatusID
LEFT OUTER JOIN Membership_tbl AS m ON m.M_ID = bp.CurrentMembershipID
LEFT OUTER JOIN Category_tbl AS bc ON bc.MC_ID = m.MembershipCategoryID
WHERE
logged_when BETWEEN '2017-01-01' AND '2017-01-31'
Here's a technique you can use.
WITH T AS
(
SELECT DISTINCT CASE
WHEN bc.Description != 'S' THEN 'M'
WHEN bc.Description = 'S' THEN 'S'
WHEN bc.Description IS NULL THEN 'N'
END AS type,
bp.P_ID
FROM progress_tbl AS progress
INNER JOIN Person_tbl AS bp
ON bp.P_ID = progress.person_id
LEFT OUTER JOIN Status_tbl AS bm
ON bm.MS_ID = bp.MembershipStatusID
LEFT OUTER JOIN Membership_tbl AS m
ON m.M_ID = bp.CurrentMembershipID
LEFT OUTER JOIN Category_tbl AS bc
ON bc.MC_ID = m.MembershipCategoryID
WHERE logged_when BETWEEN '2017-01-01' AND '2017-01-31'
)
SELECT COUNT(DISTINCT P_ID) AS total,
COUNT(CASE WHEN type= 'M' THEN P_ID END) AS m_count,
COUNT(CASE WHEN type= 'S' THEN P_ID END) AS s_count,
COUNT(CASE WHEN type= 'N' THEN P_ID END) AS n_count
FROM T
I will demonstrate it on a simpler example.
Suppose your existing query is
SELECT
COUNT(DISTINCT number) AS total,
COUNT(DISTINCT CASE WHEN name != 'S' THEN number END) AS m_count,
COUNT(DISTINCT CASE WHEN name = 'S' THEN number END) AS s_count,
COUNT(DISTINCT CASE WHEN name IS NULL THEN number END) AS n_count
FROM master..spt_values;
You can rewrite it as follows
WITH T AS
(
SELECT DISTINCT CASE
WHEN name != 'S'
THEN 'M'
WHEN name = 'S'
THEN 'S'
ELSE 'N'
END AS type,
number
FROM master..spt_values
)
SELECT COUNT(DISTINCT number) AS total,
COUNT(CASE WHEN type= 'M' THEN number END) AS m_count,
COUNT(CASE WHEN type= 'S' THEN number END) AS s_count,
COUNT(CASE WHEN type= 'N' THEN number END) AS n_count
FROM T
Note the rewrite is costed as considerably cheaper and the plan is much simpler.
As already pointed out, there seems to be some typo/copy paste issues with your query. This makes it rather difficult for us to figure out what's going on.
The table-spools probably are what's going on in the CASE WHEN b.description etc... constructions. MSSQL first creates a (memory) table with all the resulting values and then that one gets sorted and streamed through the COUNT(DISTINCT ...) operator. I don't think there is much you can do about that as the work needs to be done somewhere.
Anyway, some remarks and wild guesses:
I'm guessing that logged_when is in the progress_tbl table?
If so, do you really need to LEFT OUTER JOIN all the other tables? From what I can tell they aren't being used?
You're trying to count the number of P_IDs that match the criteria and you want to split up that number between those that have b.Description either 'S', something else, or NULL.
for this you could calculate the total as the sum of the m_count, s_count and n_count. This would save you 1 COUNT() operation, not sure it helps a lot in the bigger picture but all bits help I guess.
Something like this:
;WITH counts AS (
SELECT
COUNT(DISTINCT CASE WHEN b.Description != 'S' THEN b_p.P_ID END) AS m_count,
COUNT(DISTINCT CASE WHEN b.Description = 'S' THEN b_p.P_ID END) AS s_count,
COUNT(DISTINCT CASE WHEN b.Description IS NULL THEN b_p.P_ID END) AS n_count
FROM
progress_tbl AS progress
INNER JOIN Person_tbl AS bp ON bp.P_ID = progress.person_id
LEFT OUTER JOIN Status_tbl AS bm ON bm.MS_ID = bp.MembershipStatusID -- really needed?
LEFT OUTER JOIN Membership_tbl AS m ON m.M_ID = bp.CurrentMembershipID -- really needed?
LEFT OUTER JOIN Category_tbl AS bc ON bc.MC_ID = m.MembershipCategoryID -- really needed?
WHERE
logged_when BETWEEN '2017-01-01' AND '2017-01-31' -- what table does logged_when column come from????
)
SELECT total = m_count + s_count + n_count,
*
FROM counts
UPDATE
BEWARE: Using the answer/example code of Martin Smith I came to realize that total isn't necessarily the sum of the other fields. It could be a given P_ID shows up with different description which then might fall into different categories. Depending on your data it might thus be that my answer is plain wrong.
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