How does this SQL query return results with same id_product? - sql

I am facing a complex SQL query in some code, which is suppose to return products without duplicates (by the use of DISTINCT keywork at the beginning), here is the query:
SELECT DISTINCT p.`id_product`, p.*, product_shop.*, pl.* , m.`name` AS manufacturer_name, x.`id_feature` , x.`id_feature_value` , s.`name` AS supplier_name
FROM `ps_product` p
INNER JOIN ps_product_shop product_shop
ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 1)
LEFT JOIN `ps_product_attribute` y ON (y.`id_product` = p.`id_product`)
LEFT JOIN `ps_product_attribute_combination` ac ON (y.`id_product_attribute` = ac.`id_product_attribute`)
LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.id_shop = 1 )
LEFT JOIN `ps_manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `ps_feature_product` x ON (x.`id_product` = p.`id_product`)
LEFT JOIN `ps_supplier` s ON (s.`id_supplier` = p.`id_supplier`)
LEFT JOIN `ps_category_product` c ON (c.`id_product` = p.`id_product`)
WHERE pl.`id_lang` = 1 AND c.`id_category` = 18 AND p.`price` between 0 and 1000
AND product_shop.`visibility` IN ("both", "catalog") AND product_shop.`active` = 1
ORDER BY p.`id_product` ASC LIMIT 1,4
But it returns 4 product with 2 products with same "id_product" (11941)
What I need is to return 4 products but of different ids each.
Anyone ?
Thanks a lot
Aymeric
[EDIT]
The result of this query shows 4 rows, with 2 having the same exact columns values EXCEPT for the id_feature_value column which 36 for one and 38 for the other.

SELECT DISTINCT gets all the distinct combinations of all selected fields in your query, not just the first field.
Now, you could solve that by using GROUP BY to select only distinct values of id_product specifically, like:
SELECT p.`id_product`, p.*, product_shop.*, pl.* , m.`name` AS manufacturer_name, x.`id_feature` , x.`id_feature_value` , s.`name` AS supplier_name
FROM `ps_product` p
INNER JOIN ps_product_shop product_shop
ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 1)
LEFT JOIN `ps_product_attribute` y ON (y.`id_product` = p.`id_product`)
LEFT JOIN `ps_product_attribute_combination` ac ON (y.`id_product_attribute` = ac.`id_product_attribute`)
LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.id_shop = 1 )
LEFT JOIN `ps_manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `ps_feature_product` x ON (x.`id_product` = p.`id_product`)
LEFT JOIN `ps_supplier` s ON (s.`id_supplier` = p.`id_supplier`)
LEFT JOIN `ps_category_product` c ON (c.`id_product` = p.`id_product`)
WHERE pl.`id_lang` = 1 AND c.`id_category` = 18 AND p.`price` between 0 and 1000
AND product_shop.`visibility` IN ("both", "catalog") AND product_shop.`active` = 1
GROUP BY p.`id_product`
ORDER BY p.`id_product` ASC LIMIT 1,4
However, the problem now is that your query has multiple different values of all the other fields you are selected to choose from, and no deterministic way to pick from them. Even though the id_product is unique in it's table, it's not unique in the result set because in at least one of your JOINs there is a one-to-many relationship, meaning there are several rows that match the JOIN conditions.
On older versions of MySQL, it will just pick the first value it finds in this case, but on SQL Server it will actually error out and tell you that the remaining fields either have to be mentioned in the GROUP BY clause, or they have to be aggregated. So, you've got a few ways you can go from here:
You are on an old version of MySQL and you don't particularly care which values are returned for the rest of the fields, so leave the query as I've posted and use that. I wouldn't recommend this, as it's undefined behaviour so in theory it could change at MySQL's whim. All the values returned will be from the same result row though.
Add aggregate functions, such as MIN() or MAX() to the rest of the remaining fields in the select clause. This will reduce the possible values for the fields down to one, but you will probably end up with a mixture of values from different rows.
Remove any one-to-many JOINs from your query so that you only ever get one row back in the result set for each individual id_product. Then, fetch the remaining data you need in a separate query.
There may be other alternative solutions, but it depends a lot on which values you want returned for the rest of the rows and what RDBMS you are using. For example, on SQL Server you could potentially make use of PARTITION BY to select the first row for each distinct id_product deterministically.

Related

How to Display Two Query Side by Side using SQL

I have the following 2 queries that are almost identical except the second query contains a table join, where clause and has less FAXDEPTs(the commented out portion is included in Query 2 just shared the one query to make easier to read).
I want to join the two queries by FAXDEPT and have the results look something like this,
FAXDEPT| TOTAL DOCUMENTS(Query1)|TOTAL PAGES (Query1)|TOTAL DOCUMENTS(Query2)|TOTAL PAGES (Query2)
Query 1 contains more FAXDEPTs than Query2. Is there a way I can display "0"s for Query 2 TOTAL PAGES and TOTAL DOCS
I'm assumed I would do some sort of FUll OUTER JOIN but can't seem to get it to work. Not sure if using alias is part of my issue or not. I appreciate all the help in advance!
select sq.faxdept 'Fax Department', count(sq.docid)'Total Documents', sum(sq.pages)'Total Pages'
from
(
select idp.id as docid, (MAX(idp.pagenum)+1) as pages, ki178.keyvaluechar as faxdept
from DOCDATA dd
left join FAXDEPT ki178 on id.id = ki178.id
left join IMPORTSOURCE ki228 on id.id = ki228.id
left join BATCHINFO kgd105 on dd.id = kgd105.id
left join PAGEDATA idp on id.id = idp.id
--left join QUEUE ilc on id.id = ilc.id
where
id.datestored > '10/07/2021' and id.status = 0
--and ilc.num = 252
and (kgd105.kg128 like 'DISC DIP%SJIN%' or ki228.keyvaluechar like 'SJIN' )
group by idp.id, ki178.keyvaluechar
)
as sq
group by sq.faxdept

Merge A nested Query with another result in SQLite

I have two sets of queries:
SELECT
t.series_name,
ti.num_views_per_telecast
FROM
(
SELECT
ti.telecast_id,
ti.network_id,
count(*) as num_views_per_telecast
FROM
tunein AS ti
INNER JOIN affiliates AS a ON ti.network_id = a.network_id
WHERE
ti.dvr_time_shift = 'L+SD'
and a.network_name = 'ABC'
group by
ti.telecast_id,
ti.network_id
) ti
inner join telecast AS t On t.telecast_id = ti.telecast_id
ORDER BY
ti.num_views_per_telecast DESC
And
select
distinct *
from
telecast
where
episode_name = 'friday night dinner'
and series_name = 'A Million Little Things'
and date(program_start_local) = '2018-10-17'
I want to be able to combine the two so that I can get the num_views_per_telecast for the episodes in the bottom query. Not quite sure how I would inner join these though so I could keep the results from the first set of queries.
How the tables are connected are below:
How would I combine these???
At a guess:
SELECT
t.*,
ti.num_views_per_telecast
FROM
(
SELECT
ti.telecast_id,
ti.network_id,
count(*) as num_views_per_telecast
FROM
tunein AS ti
INNER JOIN
affiliates AS a
ON
ti.network_id = a.network_id
WHERE
ti.dvr_time_shift = 'L+SD' and
a.network_name = 'ABC'
group by
ti.telecast_id,
ti.network_id
)ti
inner join telecast AS t
On
t.telecast_id = ti.telecast_id
--from query2
where
t.episode_name = 'friday night dinner'
and t.series_name = 'A Million Little Things'
and t.date(program_start_local) = '2018-10-17'
ORDER BY ti.num_views_per_telecast DESC
For reasons given in the comment, the DISTINCT is redundant so you seem to want all rows from telecast that match some criteria. Given that your first query contains telecast but without any criteria and you only select one column from it, merging the two is a case of boosting the number of columns selected to (all from telecast) plus anything else, and adding the where clause from query 2 to restrict the rows in query 1

Pulling cost field from a sub-query

I wrote the following query to pull a unit cost from another table COSreport into my profitability query ProfitabilityReport and am having a problem with my sub-query.
select
i.tranid
, it.item_id
, it.displayname
, tl.Item_Count * -1 Unit_Qty
, case when tl.Item_Count=0 then 0
else ((tl.GROSS_AMOUNT * -1)/ Item_Count) * -1
end as PricePerUnit,
**(select sum(c.tranamt) from ns.COSreport c
inner join ns.ProfitabilityReport d
on c.InvoiceID = d.tranid
and c.item_id = d.item_id) as 'True Cost'**
, '0' 'Cost Per M'
from ns.tinvoice i
join ns.transaction_lines tl on i.transaction_id = tl.transaction_id
join ns.Customers cust on c.customer_id = i.ENTITY_ID
join ns.items it on it.item_id = tl.item_id
left join ns.ITEM_CLASSIFICATION it_class on it_class.list_id =
it.ITEM_CLASSIFICATION_ID
where list_item_name IS NOT NULL
and i.tranid = '1262INV'
I'm joining on both the invoice id and item id so that the proper cost is pulled across for the given invoice and item from COSReport.
However, the true cost is not coming up with the unit cost but instead is summing up the cost field for the entire table.
See below for example using invoice # 1262INV specified in the query above. The cost should be 1.04, .26, and 4 respectively vs 138m.
Any help getting this cleared up would be appreciated
I actually prefer using CTEs for readability. You can take your subquery, put it into a CTE, and then join it in your main query, but you'll want to add the tranid and item_id fields to the CTE so you can use them in your join.
EDIT: since you're using Azure SQL Server, you don't need the semicolon before the WITH.
WITH TrueCosts AS
(
SELECT
d.tranid
,d.item_id
,TrueCost = SUM(c.tranamt)
FROM ns.COSreport c
INNER JOIN ns.ProfitabilityReport d
ON c.InvoiceID = d.tranid
AND c.item_id = d.item_id
GROUP BY d.tranid
,d.item_id
)
SELECT
i.tranid
, it.item_id
, it.displayname
, tl.Item_Count * -1 Unit_Qty
, case when tl.Item_Count=0 then 0
else ((tl.GROSS_AMOUNT * -1)/ Item_Count) * -1
END as PricePerUnit
, tc.TrueCost AS 'True Cost'
, '0' AS 'Cost Per M'
FROM ns.tinvoice i
JOIN ns.transaction_lines tl on i.transaction_id = tl.transaction_id
JOIN ns.Customers c on c.customer_id = i.ENTITY_ID
JOIN ns.items it on it.item_id = tl.item_id
LEFT JOIN ns.ITEM_CLASSIFICATION it_class on it_class.list_id = it.ITEM_CLASSIFICATION_ID
LEFT JOIN TrueCosts tc ON tc.tranid = i.tranid AND tc.item_id = tl.item_id
WHERE list_item_name IS NOT NULL
AND i.tranid = '1262INV'
You have a couple of problems.
In your subquery you use the alias c for COSReport. You also use the alias c for customers in your outer query.
The bigger problem is that your subquery isn't correlated to your outer query at all. That's why it's summing up the entire table.
To correlate your subquery, you need to join (in the subquery) to one of the tables in the outer query. Not sure of your tables or data, but at a guess, I'd say you want to use ns.tinvoice i in a WHERE clause in your subquery.

Receiving 1 row from joined (1 to many) postgresql

I have this problem:
I have 2 major tables (apartments, tenants) that have a connection of 1 to many (1 apartment, many tenants).
I'm trying to pull all my building apartments, but with one of his tenants.
The preffered tenant is the one who have ot=2 (there are 2 possible values: 1 or 2).
I tried to use subqueries but in postgresql it doesn't let you return more than 1 column.
I don't know how to solve it. Here is my latest code:
SELECT a.apartment_id, a.apartment_num, a.floor, at.app_type_desc_he, tn.otype_desc_he, tn.e_name
FROM
public.apartments a INNER JOIN public.apartment_types at ON
at.app_type_id = a.apartment_type INNER JOIN
(select t.apartment_id, t.building_id, ot.otype_id, ot.otype_desc_he, e.e_name
from public.tenants t INNER JOIN public.ownership_types ot ON
ot.otype_id = t.ownership_type INNER JOIN entities e ON
t.entity_id = e.entity_id
) tn ON
a.apartment_id = tn.apartment_id AND tn.building_id = a.building_id
WHERE
a.building_id = 4 AND tn.building_id=4
ORDER BY
a.apartment_num ASC,
tn.otype_id DESC
Thanx in advance
SELECT a.apartment_id, a.apartment_num, a.floor
,at.app_type_desc_he, tn.otype_desc_he, tn.e_name
FROM public.apartments a
JOIN public.apartment_types at ON at.app_type_id = a.apartment_type
LEFT JOIN (
SELECT t.apartment_id, t.building_id, ot.otype_id
,ot.otype_desc_he, e.e_name
FROM public.tenants t
JOIN public.ownership_types ot ON ot.otype_id = t.ownership_type
JOIN entities e ON t.entity_id = e.entity_id
ORDER BY (ot.otype_id = 2) DESC
LIMIT 1
) tn ON (tn.apartment_id, tn.building_id)=(a.apartment_id, a.building_id)
WHERE a.building_id = 4
AND tn.building_id = 4
ORDER BY a.apartment_num; -- , tn.otype_id DESC -- pointless
Crucial part emphasized.
This works in either case.
If there are tenants for an apartment, exactly 1 will be returned.
If there is one (or more) tenant of ot.otype_id = 2, it will be one of that type.
If there are no tenants, the apartment is still returned.
If, for ot.otype_id ...
there are 2 possible values: 1 or 2
... you can simplify to:
ORDER BY ot.otype_id DESC
Debug query
Try removing the WHERE clauses from the base query and change
JOIN public.apartment_types
to
LEFT JOIN public.apartment_types
and add them back one by one to see which condition excludes all rows.
Do at.app_type_id and a.apartment_type really match?

Can SQL Sub-query return two/more values but still compare against one of them?

I have this query:
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName
FROM Items WHERE Items.ProductCode IN (
SELECT TOP 10 Recommended.ProductCode
FROM
Recommended
INNER JOIN Stock ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
WHERE (Recommended.Type = 'TOPICAL') ORDER BY CHECKSUM(NEWID()));
It is fine for my data, except that the Recommended table has a SKU field I need also however I cannot put it next to Recommended.ProductCode and have the query still work.
I have used JOINS for this query and these work - but this query runs faster I just need the ProductCode and SKU from the Recommended table - how can this be done without needing yet another sub query?
Database: MS SQL Server 2000
The subquery seems to be picking 10 random recommendations. I think you can do that without a subquery:
SELECT TOP 10
Items.*,
Recommended.*,
Stock.*
FROM Items
INNER JOIN Recommended
ON Items.ProductCode = Recommended.ProductCode
AND Recommended.Type = 'TOPICAL'
INNER JOIN Stock
ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
ORDER BY CHECKSUM(NEWID())
This gives you access to all columns, without having to pass them up from the subquery.
You can only return one value with the subselect, so you have to obtain the fields from the Recommended table by a join - which I presume is what you have already:
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName, Recommended.SKU
FROM Items
INNER JOIN Recommended ON Recommended.ProductCode = Items.ProductCode
WHERE Items.ProductCode IN (
SELECT TOP 10 Recommended.ProductCode
FROM
Recommended
INNER JOIN Stock ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
WHERE (Recommended.Type = 'TOPICAL') ORDER BY CHECKSUM(NEWID()));
Most likely the Join in reality is an outer too I guess. This really shouldn't have any performance issues so long as you have both the Items and and Recommended tables indexed on ProductCode.
I think you need to move the subquery out of the where clause:
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName, R.SKU
FROM Items
INNER JOIN
(SELECT TOP 10 Recommended.ProductCode, Recommended.SKU FROM Recommended
INNER JOIN Stock ON Recommended.ProductCode = Stock.ProductCode AND
Stock.StatusCode = 1 WHERE (Recommended.Type = 'TOPICAL')
ORDER BY CHECKSUM(NEWID()))
AS Rec ON Items.ProductCode = Rec.ProductCode;
The above is valid syntax in MySQL, your mileage may vary...
Under those circumstances I would normally use an inner join to get the row filtering from the where clause I needed and the extra columns. Something like below; if this is what you did that gave you a performance hit then you might need to flip the query; go from recommended and join to items; as that will probably lead to more data filtering before the join.
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName
FROM Items
Inner Join
(
SELECT TOP 10 Recommended.ProductCode, SKUID
FROM
Recommended
INNER JOIN Stock ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
WHERE (Recommended.Type = 'TOPICAL')
) reccomended
on items.productcode - reccomended.ProductCode
ORDER BY CHECKSUM(NEWID()