sql cte distinct count - sql

I want to add the total number of products of the brand to the total column.
http://sqlfiddle.com/#!18/2d00a/1
this my query;
WITH cte AS
(
SELECT id
FROM category
WHERE id = 1
UNION ALL
SELECT c.id
FROM category c, cte
WHERE c.parentid = cte.id
), cte2 AS
(
SELECT brandid, d.catid
FROM products d, cte
WHERE d.catid = cte.id
UNION ALL
SELECT u.brandid, COUNT(DISTINCT u.id)
FROM products u, cte
WHERE catid = cte.id
GROUP BY brandid
)
SELECT DISTINCT
brandid, logo, brand, id, id as total
FROM
brand, cte2
WHERE
id = cte2.brandid
ORDER BY
brand ASC

SELECT b.id,
b.logo,
b.brand,
count(p.id) total
FROM brand b
LEFT JOIN products p ON p.brandid = b.id
GROUP BY b.id, b.logo, b.brand
Assuming you want to filter products based on category 1 (where you include the subcategories as well)
WITH categories_and_subcategories AS (
SELECT id FROM category
WHERE id = 1
UNION ALL
SELECT c.id
FROM category c
INNER JOIN categories_and_subcategories cs
ON c.parentid = cs.id),
filtered_products AS (
SELECT p.id,
p.name,
p.catid,
p.brandid
FROM products p
INNER JOIN categories_and_subcategories c
ON p.catid = c.id
)
SELECT b.id,
b.logo,
b.brand,
count(p.id) total
FROM brand b
LEFT JOIN filtered_products p ON p.brandid = b.id
GROUP BY b.id, b.logo, b.brand

Here is the Query as per my Understanding to get the total products per brand. DBFIDDLE working code
CREATE TABLE brand (
[id] INT
,[brand] VARCHAR(13)
,[logo] VARCHAR(13)
);
INSERT INTO brand ([id], [brand], [logo])
VALUES ('1', 'samsung', 'logo.jpg');
CREATE TABLE products (
[id] INT
,[name] VARCHAR(13)
,[catid] INT
,[brandid] INT
);
INSERT INTO products ([id], [name], [catid], [brandid])
VALUES ('1', 'samsung tv', '2', '1')
,('1', 'samsung2 tv', '2', '1')
SELECT b.Id
,b.brand
,b.logo
,p.Id
,COUNT(1) [total]
FROM brand b
INNER JOIN products p ON b.Id = p.brandId
GROUP BY b.Id
,b.brand
,b.logo
,p.Id
Results:

Related

Figure out the total number of people in an overlapping er database

I am trying to find:
the total number of doctors which aren't patients
the total number of patients which aren't doctors
the total number of people who are both patients and doctors
I can't seem to get the correct answer.
SQL:
CREATE TABLE persons (
id integer primary key,
name text
);
CREATE TABLE doctors (
id integer primary key,
type text,
FOREIGN KEY (id) REFERENCES persons(id)
);
CREATE TABLE patients (
id integer primary key,
suffering_from text,
FOREIGN KEY (id) REFERENCES persons(id)
);
INSERT INTO persons (id, name) VALUES
(1, 'bob'), (2, 'james'), (3, 'bill'), (4, 'mark'), (5, 'chloe');
INSERT INTO doctors (id, type) VALUES
(2, 'family doctor'), (3, 'eye doctor'), (5, 'family doctor');
INSERT INTO patients (id, suffering_from) VALUES
(1, 'flu'), (2, 'diabetes');
Select statement:
select count(d.id) as total_doctors, count(pa.id) as total_patients, count(d.id) + count(pa.id) as both_doctor_and_patient
from persons p
JOIN doctors d
ON p.id = d.id
JOIN patients pa
ON p.id = pa.id;
http://www.sqlfiddle.com/#!17/98ae9/2
One option uses left joins from persons and conditional aggrgation:
select
count(dr.id) filter(where pa.id is null) cnt_doctor_not_patient,
count(pa.id) filter(where dr.id is null) cnt_patient_not_doctor,
count(pa.id) filter(where do.id is not null) cnt_patient_and_doctor,
count(*) filter(where dr.id is null and pa.id is null) cnt_persons_not_dotor_nor_patient
from persons pe
left join doctors dr on dr.id = pe.id
left join patients pa on pa.id = pe.id
As a bonus, this gives you an opportunity to count the persons that are neither patient nor doctor. If you don't need that information, then a full join is simpler, and does not require bringing the persons table:
select
count(dr.id) filter(where pa.id is null) cnt_doctor_not_patient,
count(pa.id) filter(where dr.id is null) cnt_patient_not_doctor,
count(pa.id) filter(where dr.id is not null) cnt_patient_and_doctor
from doctors dr
full join patients pa using (id)
You can simply solve this using LEFT JOIN like:
--Aren't doctors:
SELECT count(*) from persons as A left join doctors as B on A.id=B.id where B.id is null
--Aren't patients:
SELECT count(*) from persons as A left join patients as B on A.id=B.id where B.id is null
--Both:
SELECT
(SELECT count(*) from persons as A left join patients as B on A.id=B.id where B.id is not null) +
(SELECT count(*) from persons as A left join doctors as B on A.id=B.id where B.id is not null)
AS summ
Here a CTE alternative:
with doc_not_pat
as(
select count(*) as Doc_Not_Pat
from doctors d
where not exists (select 1 from patients p where p.id = d.id)
),
pat_not_doc as(
select count(*) as Pat_Not_Doc
from patients p
where not exists ( select 1 from doctors d where d.id = p.id)
),
pat_and_doc as(
select count(*) as Pat_And_Doc
from patients p
where exists (select 1 from doctors d where d.id = p.id)
)
select (select Doc_Not_Pat
from doc_not_pat dcp) as Doc_Not_Pat,
(select Pat_Not_Doc
from pat_not_doc) as Pat_Not_Doc,
(select Pat_And_Doc
from pat_and_doc) as Pat_And_Doc

Returning the sum of a column without using group by

The following sql...
declare #parent table (id int, description varchar(50))
declare #child table (parentid int, amount money)
insert into #parent(id, description) values (1, 'test')
insert into #child(parentid, amount) values (1, 3)
insert into #child(parentid, amount) values (1, 3)
select p.*,
sum(c.amount) as amount
from #parent p
inner join #child c on c.parentid = p.id
Generates the following error...
Msg 8120, Level 16, State 1, Line 10
Column '#parent.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
This fixes it...
select p.*,
sum(c.amount) as amount
from #parent p
inner join #child c on c.parentid = p.id
group by p.id,
p.description
How do I change it so the query returns the sum of the child records, but without using the group by expression? Maybe a subquery of some sort?
You can use window function :
select distinct p.*, sum(c.amount) over (partition by p.id) as amount
from #parent p inner join
#child c
on c.parentid = p.id;
You can also use apply :
select distinct p.*, c.amount
from #parent p cross apply
(select sum(c.amount) as amount
from #child c
where c.parentid = p.id
) c;
You can express this as:
select p.*,
(select sum(c.amount) as amount
from #child c
where c.parentid = p.id
) as child_amount
from #parent p;
However, this is not exactly equivalent, because it include parents with no children. One solution is:
select p.*,
(select sum(c.amount) as amount
from #child c
where c.parentid = p.id
) as child_amount
from #parent p
where exists (select 1 from #child c where c.parentid = p.id);
That said, you can also use apply:
select p.*, c.amount
from #parent p cross apply
(select sum(c.amount) as amount
from #child c
where c.parentid = p.id
) c;
cross apply filters out non-matches (outer apply keeps them).
window aggregates:
select p.*,
sum(c.amount) over(partition by p.id) as amountperparent,
sum(c.amount) over() as amountofallchildren
from #parent p
inner join #child c on c.parentid = p.id;
--subquery
select p.*,
(select sum(c.amount) from #child as c where c.parentid = p.id) as amountperparent,
(select sum(c.amount) from #child as c ) as amountofallchildren
from #parent p ;
--derived aggregation
select p.*, c.amountperparent
from #parent p
join
(
select parentid, sum(amount) as amountperparent
from #child
group by parentid
) as c on p.id = c.parentid;

join within recursive with adjacency

I have something like this:
CREATE TABLE categories (
id varchar(250) PRIMARY KEY,
name varchar(250) NOT NULL,
parentid varchar(250)
);
CREATE TABLE products (
id varchar(250) PRIMARY KEY,
name varchar(250) NOT NULL,
price double precision,
category varchar(250) NOT NULL
);
INSERT INTO categories VALUES ('1', 'Rack', '');
INSERT INTO categories VALUES ('2', 'Women', '1');
INSERT INTO categories VALUES ('3', 'Shorts', '2');
INSERT INTO products VALUES ('1', 'Jean', 2.99, '3');
INSERT INTO products VALUES ('2', 'Inflatable Boat', 5.99, '1');
Now, if I wanted to see the total price of products for each category, I could do something like this:
SELECT
categories.name,
SUM(products.price) AS CATPRICE
FROM
categories,
products
WHERE products.category = categories.id
GROUP BY categories.name
;
Which produces output:
name | catprice
--------+----------
Rack | 5.99
Shorts | 2.99
(2 rows)
But notice that "Shorts" is an ancestor of "Rack". I want a query that will produce output like this:
name | catprice
--------+----------
Rack | 8.98
(1 row)
So that all product prices are added together under the root category. There are multiple root categories in the category table; only one has been shown for simplicity.
This is what I have thus far:
-- "nodes_cte" is the virtual table that is being created as the recursion continues
-- The contents of the ()s are the columns that are being built
WITH RECURSIVE nodes_cte(name, id, parentid, depth, path) AS (
-- Base case?
SELECT tn.name, tn.id, tn.parentid, 1::INT AS depth, tn.id::TEXT AS path FROM categories AS tn, products AS tn2
LEFT OUTER JOIN categories ON tn2.CATEGORY = categories.ID
WHERE tn.parentid IS NULL
UNION ALL
-- nth case
SELECT c.name, c.id, c.parentid, p.depth + 1 AS depth, (p.path || '->' || c.id::TEXT) FROM nodes_cte AS p, categories AS c, products AS c2
LEFT OUTER JOIN categories ON c2.CATEGORY = categories.ID
WHERE c.parentid = p.id
)
SELECT * FROM nodes_cte AS n ORDER BY n.id ASC;
I have no clue what I've done wrong. The above query returns zero results.
Your recursive query is off by a little. Give this a try:
EDIT -- To make this work with the SUM, use this:
WITH RECURSIVE nodes_cte(name, id, id2, parentid, price) AS (
-- Base case?
SELECT c.name,
c.id,
c.id id2,
c.parentid,
p.price
FROM categories c
LEFT JOIN products p on c.id = p.category
WHERE c.parentid = ''
UNION ALL
-- nth case
SELECT n.name,
n.id,
c.id id2,
c.parentid,
p.price
FROM nodes_cte n
JOIN categories c on n.id2 = c.parentid
LEFT JOIN products p on c.id = p.category
)
SELECT id, name, SUM(price) FROM nodes_cte GROUP BY id, name
And here is the Fiddle: http://sqlfiddle.com/#!1/7ac6d/19
Good luck.

How to get last children records with parent record from database

I have database with two tables:
Customers (Id PK, LastName)
and
Orders (Id PK, CustomerId FK, ProductName, Price, etc.)
I want to retrieve only customer' last orders details together with customer name.
I use .NET L2SQL but I think it's SQL question more than LINQ question so I post here SQL query I tried:
SELECT [t0].[LastName], (
SELECT [t2].[ProductName]
FROM (
SELECT TOP (1) [t1].[ProductName]
FROM [Orders] AS [t1]
WHERE [t1].[CustomerId] = [t0].[Id]
ORDER BY [t1].[Id] DESC
) AS [t2]
) AS [ProductName], (
SELECT [t4].[Price]
FROM (
SELECT TOP (1) [t3].[Price]
FROM [Orders] AS [t3]
WHERE [t3].[CustomerId] = [t0].[Id]
ORDER BY [t3].[Id] DESC
) AS [t4]
) AS [Price]
FROM [Customers] AS [t0]
Problem is that Orders has more columns (30) and with each column the query gets bigger and slower because I need to add next subqueries.
Is there any better way?
In SQL Server 2005 and above:
SELECT *
FROM (
SELECT o.*,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY o.id DESC) rn
FROM customers c
LEFT JOIN
orders o
ON o.customerId = c.id
) q
WHERE rn = 1
or this:
SELECT *
FROM customers c
OUTER APPLY
(
SELECT TOP 1 *
FROM orders o
WHERE o.customerId = c.id
ORDER BY
o.id DESC
) o
In SQL Server 2000:
SELECT *
FROM customers с
LEFT JOIN
orders o
ON o.id =
(
SELECT TOP 1 id
FROM orders oi
WHERE oi.customerId = c.id
ORDER BY
oi.id DESC
)

SQL - identifying rows for a value in one table, where all joined rows only has a specific value

IN SQL Server, I have a result set from a joined many:many relationship.
Considering Products linked to Orders via a link table ,
Table - Products
ID
ProductName
Table - Orders
ID
OrderCountry
LinkTable OrderLines (columns not shown)
I'd like to be able to filter these results to show only the results where for an entity from one table, all the values in the other table only have a given value in a particular column. In terms of my example, for each product, I want to return only the joined rows when all the orders they're linked to are for country 'uk'
So if my linked result set is
productid, product, orderid, ordercountry
1, Chocolate, 1, uk
2, Banana, 2, uk
2, Banana, 3, usa
3, Strawberry, 4, usa
I want to filter so that only those products that have only been ordered in the UK are shown (i.e. Chocolate). I'm sure this should be straight-forward, but its Friday afternoon and the SQL part of my brain has given up for the day...
You could do something like this, where first you get all products only sold in one country, then you proceed to get all orders for those products
with distinctProducts as
(
select LinkTable.ProductID
from Orders
inner join LinkTable on LinkTable.OrderID = Orders.ID
group by LinkTable.ProductID
having count(distinct Orders.OrderCountry) = 1
)
select pr.ID as ProductID
,pr.ProductName
,o.ID as OrderID
,o.OrderCountry
from Products pr
inner join LinkTable lt on lt.ProductID = pr.ID
inner join Orders o on o.ID = lt.OrderID
inner join distinctProducts dp on dp.ProductID = pr.ID
where o.OrderCountry = 'UK'
In the hope that some of this may be generally reusable:
;with startingRS (productid, product, orderid, ordercountry) as (
select 1, 'Chocolate', 1, 'uk' union all
select 2, 'Banana', 2, 'uk' union all
select 2, 'Banana', 3, 'usa' union all
select 3, 'Strawberry', 4, 'usa'
), countryRankings as (
select productid,product,orderid,ordercountry,
RANK() over (PARTITION by productid ORDER by ordercountry) as FirstCountry,
RANK() over (PARTITION by productid ORDER by ordercountry desc) as LastCountry
from
startingRS
), singleCountry as (
select productid,product,orderid,ordercountry
from countryRankings
where FirstCountry = 1 and LastCountry = 1
)
select * from singleCountry where ordercountry='uk'
In the startingRS, you put whatever query you currently have to generate the intermediate results you've shown. The countryRankings CTE adds two new columns, that ranks the countries within each productid.
The singleCountry CTE reduces the result set back down to those results where country ranks as both the first and last country within the productid (i.e. there's only a single country for this productid). Finally, we query for those results which are just from the uk.
If you want, for example, all productid rows with a single country of origin, you just skip this last where clause (and you'd get 3,strawberry,4,usa in your results also)
So is you've got a current query that looks like:
select p.productid,p.product,o.orderid,o.ordercountry
from product p inner join order o on p.productid = o.productid --(or however these joins work for your tables)
Then you'd rewrite the first CTE as:
;with startingRS (productid, product, orderid, ordercountry) as (
select p.productid,p.product,o.orderid,o.ordercountry
from product p inner join order o on p.productid = o.productid
), /* rest of query */
Hmm. Based on Philip's earlier approach, try adding something like this to exclude rows where there's been the same product ordered in another country:
SELECT pr.Id, pr.ProductName, od.Id, od.OrderCountry
from Products pr
inner join LinkTable lt
on lt.ProductId = pr.ID
inner join Orders od
on od.ID = lt.OrderId
where
od.OrderCountry = 'UK'
AND NOT EXISTS
(
SELECT
*
FROM
Products MatchingProducts
inner join LinkTable lt
on lt.ProductId = MatchingProducts.ID
inner join Orders OrdersFromOtherCountries
on OrdersFromOtherCountries.ID = lt.OrderId
WHERE
MatchingProducts.ID = Pr.ID AND
OrdersFromOtherCountries.OrderCountry != od.OrderCountry
)
;WITH mytable (productid,ordercountry)
AS
(SELECT productid, ordercountry
FROM Orders od INNER JOIN LinkTable lt ON od.orderid = lt.OrderId)
SELECT * FROM mytable
INNER JOIN dbo.Products pr ON pr.productid = mytable.productid
WHERE pr.productid NOT IN (SELECT productid FROM mytable
GROUP BY productid
HAVING COUNT(ordercountry) > 1)
AND ordercountry = 'uk'
SELECT pr.Id, pr.ProductName, od.Id, od.OrderCountry
from Products pr
inner join LinkTable lt
on lt.ProductId = pr.ID
inner join Orders od
on od.ID = lt.OrderId
where od.OrderCountry = 'UK'
This probably isn't the most efficient way to do this, but ...
SELECT p.ProductName
FROM Product p
WHERE p.ProductId IN
(
SELECT DISTINCT ol.ProductId
FROM OrderLines ol
INNER JOIN [Order] o
ON ol.OrderId = o.OrderId
WHERE o.OrderCountry = 'uk'
)
AND p.ProductId NOT IN
(
SELECT DISTINCT ol.ProductId
FROM OrderLines ol
INNER JOIN [Order] o
ON ol.OrderId = o.OrderId
WHERE o.OrderCountry != 'uk'
)
TestData
create table product
(
ProductId int,
ProductName nvarchar(50)
)
go
create table [order]
(
OrderId int,
OrderCountry nvarchar(50)
)
go
create table OrderLines
(
OrderId int,
ProductId int
)
go
insert into Product VALUES (1, 'Chocolate')
insert into Product VALUES (2, 'Banana')
insert into Product VALUES (3, 'Strawberry')
insert into [order] values (1, 'uk')
insert into [order] values (2, 'uk')
insert into [order] values (3, 'usa')
insert into [order] values (4, 'usa')
insert into [orderlines] values (1, 1)
insert into [orderlines] values (2, 2)
insert into [orderlines] values (3, 2)
insert into [orderlines] values (4, 3)
insert into [orderlines] values (3, 2)
insert into [orderlines] values (3, 3)