How to use Count SQL Server Query - sql

i write this post to ask for help on a query on the following schema:
Imagine having two formats, for example, PDF and EBOOK. My goal is to get the following result:
PushiblerName | Number_PDF | Numerber_BOOK |
--------------------------------------------------
ExampleName1 | 2 | 0
ExampleName2 | 3 | 1
Would anyone help me write this query?
Thanks in advance.

select name,(select count(*) from t_book where IdPublisher = t_publisher.Id) as Numerber_BOOK,
(select count(*) from t_book where IdPublisher = t_publisher.Id and IdFormat = 3) as Number_PDF from t_publisher
replace 3 with the Id of Pdf format

Try this:
select P.Name as PublisherName,
SUM(case when F.Code = 'PDF' then 1 else 0 end) as Number_PDF,
SUM(case when F.Code = 'BOOK' then 1 else 0 end) as Number_BOOK
from T_BOOK as B
join T_BOOK_FORMAT as BF on B.Id = BF.BookId
join T_FORMAT as F on BF.IdFormat = F.Id
join T_PUBLISHER as P on B.IdPublisher = P.Id
group by P.Name
I assumed, that format of a book is held in T_FORMAT table in column Code (part with F.Code).

You can use this code,
SELECT publisher.Name as PublisherName,
(SELECT COUNT(*)
FROM T_BOOK book
JOIN T_BOOK_FORMAT bookformat ON book.Id = bookformat.IdBook
JOIN T_FORMAT format ON bookformat.IdFormat = format.Id
WHERE book.IdPublisher = publisher.Id and format.Code='PDF'
) AS Number_PDF,
(SELECT COUNT(*)
FROM T_BOOK book
JOIN T_BOOK_FORMAT bookformat ON book.Id = bookformat.IdBook
JOIN T_FORMAT format ON bookformat.IdFormat = format.Id
WHERE book.IdPublisher = publisher.Id and format.Code='EBOOK'
) AS Number_BOOK
FROM T_PUBLISHER publisher
GROUP BY publisher.Id

I guess you can try something like this
first setup some sample data:
declare #t_book table (Id int, IdPublisher int)
declare #t_publisher table (Id int, Name varchar(50))
declare #t_book_format table (IdBook int, idFormat int)
declare #t_format table (Id int, Description varchar(50))
insert into #t_book values (1, 1), (2, 1), (3, 2), (4, 2), (5, 2), (6, 2)
insert into #t_publisher values (1, 'examplename1'), (2, 'examplename2')
insert into #t_book_format values (1, 1), (2, 1), (3, 2), (4, 1), (5, 1), (6, 1)
insert into #t_format values (1 ,'pdf'), (2, 'book')
now the query :
select p.Name as PublisherName,
f.Description,
count(f.Id) as numbers
from #t_book b
inner join #t_publisher p on b.idPublisher = p.Id
inner join #t_book_format bf on b.Id = bf.idBook
inner join #t_format f on bf.idFormat = f.Id
group by p.Name, f.Description
But without sample data from all tables this is just guessing
The result is in a different format, but it still works if you add more formats in t_book_format and t_format
PublisherName Description numbers
------------- ----------- -------
examplename1 pdf 2
examplename2 book 1
examplename2 pdf 3
If you want to have the list as in you question, you could do a pivot.
But when there is a new format, this new format will not be in the result, so you have to modify the query everytime a new format is added
select * from ( select p.Name,
f.Description,
count(f.Id) as numbers
from #t_book b
inner join #t_publisher p on b.idPublisher = p.Id
inner join #t_book_format bf on b.Id = bf.idBook
inner join #t_format f on bf.idFormat = f.Id
group by p.Name, f.Description
) t
pivot
( sum(numbers)
for Description in (pdf, book)
) piv
the result now looks like this
Name pdf book
---- --- ----
examplename1 2 null
examplename2 3 1
I guess you could put this in a stored procedure and build the last query in dynamic sql, so it would still keep working when you add new formats

Here you go:
CREATE TABLE Author (
AuthorID INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
AuthorName VARCHAR (25) NOT NULL
);
CREATE TABLE Books (
BookID INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
BookName VARCHAR (25) NOT NULL,
BookAothor INT NOT NULL,
CONSTRAINT PK_AuthorOfBook FOREIGN KEY (BookAothor) REFERENCES Author (AuthorID)
);
CREATE TABLE Formats (
FormatID INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
FormatName VARCHAR (10) NOT NULL
);
CREATE TABLE BookFormat (
BookID INT NOT NULL,
FormatID INT NOT NULL,
CONSTRAINT PK_BookFormats PRIMARY KEY (BookID, FormatID),
CONSTRAINT FK_BookRef FOREIGN KEY (BookID) REFERENCES Books (BookID),
CONSTRAINT FK_FormatRef FOREIGN KEY (FormatID) REFERENCES Formats (FormatID)
);
INSERT INTO Author VALUES
('Author1'), ('Author2'), ('Author3');
INSERT INTO Books VALUES
('Book1', 1), ('Book2', 1),
('Book3', 2), ('Book4', 2),
('Book5', 2), ('Book6', 2);
INSERT INTO Formats VALUES
('PDF'), ('E-Book');
INSERT INTO BookFormat VALUES
(1, 1), (2,1),
(3,1), (4,1), (5,1), (6,2);
SELECT A.AuthorName,
ISNULL(Pdf.PdfBooks, 0) AS PdfBooks,
ISNULL(EBook.EBooks, 0) AS EBooks
FROM
Author A LEFT JOIN
(
SELECT A.AuthorID, COUNT(B.BookID) AS PdfBooks
FROM Author A JOIN Books B ON A.AuthorID = B.BookAothor
INNER JOIN BookFormat BF ON B.BookID = BF.BookID
WHERE BF.FormatID =1
GROUP BY A.AuthorID
) Pdf ON A.AuthorID = Pdf.AuthorID
FULL JOIN
(
SELECT A.AuthorID, COUNT(B.BookID) AS EBooks
FROM Author A JOIN Books B ON A.AuthorID = B.BookAothor
INNER JOIN BookFormat BF ON B.BookID = BF.BookID
WHERE BF.FormatID =2
GROUP BY A.AuthorID
) EBook
ON Pdf.AuthorID = EBook.AuthorID;
Results:
+------------+----------+--------+
| AuthorName | PdfBooks | EBooks |
+------------+----------+--------+
| Author1 | 2 | 0 |
| Author2 | 3 | 1 |
| Author3 | 0 | 0 |
+------------+----------+--------+
Here is a demo

Related

Return multiple columns from subquery as Columns

Here is my data.
Products: <Where all list of SKU are stored>
Prod_ID | Prod_Desc | Base_Unit_ID
1 | Custom Product | 1
UOM: <Masterlist for Unit of measures>
UOM_ID | Desc
1 | Piece
2 | Box
3 | Case
UOM_Conversion: <From base unit. Multiplier is how many base unit in a To_Unit>
Prod_ID | from_Unit_ID | Multiplier | To_Unit_ID
1 | 1 | 100 | 2
1 | 1 | 400 | 3
Given This Data. How Can I display it like this?
Product | Base Unit | Pack Unit | Multiplier | Case Unit | Multiplier
Custom Product | Piece | Box | 100 | Case | 400
I have tried Left Join Lateral but sadly. What it does is return two rows.
The reason I want to do this is because Im developing a module which stores products with multiple Unit of measure(Max of 3). So I dont want to create 3 columns of Unit, Pack and Case hence the reason I created the UOM_conversion table.
I think there is a global misconception here but I will give you it after the solution for your problem. I advise you to look at it.
So firstly : what you need here is the crosstab() function.
CREATE EXTENSION tablefunc;
For the following script :
create table uom
(
uom_id int primary key
, description varchar (250)
);
create table products
(
prod_id int primary key
, prod_desc varchar(250)
, base_unit_id int references uom (uom_id)
);
create table uom_conversion
(
prod_id int references products (prod_id)
, from_unit_id int references uom (uom_id)
, multiplier int
, to_unit_id int references uom (uom_id)
);
insert into uom values (1, 'Piece'), (2, 'Box'), (3, 'Case');
insert into products values (1, 'Custom Product', 1);
insert into uom_conversion values (1,1,100,2), (1,1,400,3);
The request is :
select
p.prod_desc as "Product"
, u.description as "Base Unit"
, u2.description as "Pack Unit"
, final_res."1" as "Multiplier"
, u3.description as "Case Unit"
, final_res."2" as "Multiplier"
from crosstab(
'select
p.prod_id
, base_unit_id
, multiplier
from products p
inner join uom_conversion uc
on uc.prod_id = p.prod_id')
as final_res (prod_id int, "1" int, "2" int)
inner join crosstab('select
uc.prod_id
, u.description
, uc.to_unit_id
from uom_conversion uc
inner join uom u
on u.uom_id = uc.to_unit_id')
as final_res_2 (prod_id int, "Box" int, "Case" int)
on final_res.prod_id = final_res_2.prod_id
inner join products p
on p.prod_id = final_res.prod_id
inner join uom u
on p.base_unit_id = u.uom_id
inner join uom u2
on u2.uom_id = final_res_2."Box"
inner join uom u3
on u3.uom_id = final_res_2."Case";
This is solving your problem. BUT : How do you know the order of what is pack_unit and what is the case_unit? I think from this question a lot more will come up.

How do I pull all results form a table based on another tables many to one relationship?

Setup
Sorry for the poorly phrased question. I'm not sure how to phrase it better. Feel free to try your hand at it if you are more versed in sql phrasing.
I have 3 related tables.
Person => person_id, name, etc
Cases => case_id, person_id, incedent_date, etc
Files => file_id, case_id, file_path, etc
Problem
For a given case_id I want to pull all file_id's for the same person.
Requirements:
1 query.
without duplicates.
without using UNIQUE/DISTINCT flag.
without changing table structure.
e.g. Bob has 2 cases, auto and house.
He has 10 files on each case.
I have the case_id for auto.
I want the files for both auto and house (20 files).
My attempt
This returns all files for all cases.
SELECT
f.file_id AS id
FROM files f
LEFT JOIN Cases c1 ON f.case_id = c1.case_id
LEFT JOIN Cases c2 ON f.case_id = c2.case_id
WHERE (f.case_id = 3566 OR c1.person_id = c2.person_id)
AND f.active = 1
ORDER BY f.upload_date ASC
This returns files for only given case:
SELECT
f.file_id AS id
FROM files f
LEFT JOIN Cases c1 ON f.case_id = c1.case_id
LEFT JOIN Cases c2 ON f.case_id = c2.case_id
WHERE (f.case_id = 3566 OR (c1.case_id = 3566 AND c1.person_id = c2.person_id)
AND f.active = 1
ORDER BY f.upload_date ASC
This returns duplicate values and seems to pull only the given case:
SELECT
f.file_id AS id
FROM files f
LEFT JOIN Cases c1 ON f.case_id = c1.case_id
LEFT JOIN Cases c2 ON c1.person_id = c2.person_id
WHERE f.case_id = 3566
AND f.active = 1
ORDER BY f.upload_date ASC
I hope this is what you want.
Create table #Person (person_id int, name varchar(10))
Insert into #Person values (1,'Ajay')
Insert into #Person values (2,'Vijay')
Create table #Cases (case_id int, person_id int)
Insert into #Cases values (1,1)
Insert into #Cases values (2,1)
Insert into #Cases values (3,1)
Insert into #Cases values (4,2)
Create table #Files (file_id int, case_id int)
Insert into #Files values (1,1)
Insert into #Files values (2,1)
Insert into #Files values (3,1)
Insert into #Files values (4,2)
Insert into #Files values (5,4)
SELECT
f.file_id AS id
FROM #files f
LEFT JOIN #Cases c1 ON f.case_id = c1.case_id
LEFT JOIN #Cases c2 ON c1.person_id = c2.person_id and c2.case_id = 2
where c2.case_id is not null
--OR
SELECT *,
f.file_id AS id
FROM #files f
LEFT JOIN #Cases c1 ON f.case_id = c1.case_id
INNER JOIN #Cases c2 ON c1.person_id = c2.person_id and c2.case_id = 2

How to make an OUTER JOIN return ZERO instead of NULL

I am trying to accomplish this on SQL Server. The simplest table structure with data is shown below.
Table:Blog
BlogID, Title
----------------
1, FirstBlog
23, Pizza
Table:User
UserID, Name
-------------------
123, james
444, John
Table:UserBlogMapping
UserBlogMappingID, BlogID,UserID
----------------------------------
1, 1, 123
I want to get FormID and UserBlogMappingID in one SQL query. If provided UserID is not in the mapping table, return ZERO otherwise return the valid userBlogMappingID. I am trying to run the below query but its not correct.
SELECT
B.BlogID,
BUM.BlogUserMappingID
FROM
Blog AS B
LEFT JOIN BlogUserMapping AS BUM ON B.BlogID = BUM.BlogID
WHERE
(B.BlogID = 23) -- it exists in the table
AND BUM.userID = 444 -- it is NOT in the mmaping table but i want a ZERO return in such case
Assumption:
We can assume that the UserID provided in the WHERE clause is always valid UserID and is present in the User table.
You could put the criteria for the userID=444 in the ON clause of the LEFT JOIN.
And an ISNULL or a COALESCE to change a NULL to a 0.
Example using table variables:
declare #Blog table (BlogID int, Title varchar(30));
insert into #Blog (BlogId, Title) values
(1, 'FirstBlog'),
(23, 'Pizza');
declare #User table (UserID int, Name varchar(30));
insert into #User (UserID, Name) values
(123,'james'),
(444,'John');
declare #BlogUserMapping table (BlogUserMappingID int, BlogID int, UserID int);
insert into #BlogUserMapping (BlogUserMappingID, BlogID, UserID) values
(1, 1, 123),
(2, 23, 123),
(3, 1, 444);
-- Using the criteria in ON clause of the LEFT JOIN
SELECT
B.BlogID,
ISNULL(BUM.BlogUserMappingID,0) as BlogUserMappingID
FROM #Blog B
LEFT JOIN #BlogUserMapping BUM ON (B.BlogID = BUM.BlogID AND BUM.userID = 444)
WHERE B.BlogID = 23;
-- If there are more BlogId=23 with userID=444.
-- But only 1 row needs to be returned then you could also GROUP BY and take the maximum BlogUserMappingID
SELECT
B.BlogID,
MAX(ISNULL(BUM.BlogUserMappingID,0)) as BlogUserMappingID
FROM #Blog B
LEFT JOIN #BlogUserMapping BUM ON (B.BlogID = BUM.BlogID AND BUM.userID = 444)
WHERE B.BlogID = 23
GROUP BY B.BlogID;
-- Using an OR in the WHERE clause would also return a 0.
-- But it would also return nothing if the mapping table has a BlogID=23 with a userID<>444.
-- So not usefull in this case.
SELECT
B.BlogID,
ISNULL(BUM.BlogUserMappingID,0) as BlogUserMappingID
FROM #Blog B
LEFT JOIN #BlogUserMapping BUM ON B.BlogID = BUM.BlogID
WHERE B.BlogID = 23
AND (BUM.userID IS NULL OR BUM.userID = 444);

Set Based Table Filtering using Table-Valued Parameter

I'm looking to see if there is a Set Based way of filtering Table Data given a Table-Valued Parameter as an input to a UDF or SPROC.
The data table is defined as:
CREATE TABLE activity
(
id int identity primary key,
employeeId int NOT NULL,
stationId char(1) NOT NULL,
type int NOT NULL
);
The Table-Valued Parameter is defined as:
CREATE TYPE activityType AS TABLE(
stationId char(1) NOT NULL,
type int NOT NULL
);
Given the following Table data:
INSERT INTO activity
(employeeId, stationId, type)
VALUES
(100, 'A', 1), (100, 'B', 2), (100, 'C', 3),
(200, 'A', 1), (200, 'B', 2), (200, 'D', 1),
(300, 'A', 2), (300, 'C', 3), (300, 'D', 2);
I would like to be able to filter given a particular TVP from the UI.
Example 1: Find all employeeId who performed Activity 1 # Station A AND Activity 2 # Station B
DECLARE #activities activityType;
INSERT INTO #activities
VALUES('A', 1),('B', 2)
Expected Result from applying this TVP:
employeeId
-----------------
100
200
Example 2: Find all employeeId who performed Activity 1 # Station A, Activity 2 # Station B, AND Activity 3 # Station C
DECLARE #activities activityType;
INSERT INTO #activities
VALUES('A', 1),('B', 2),('C', 3);
Expected Result from applying this TVP:
employeeId
-----------------
100
I can apply this filter by looping over the TVP and intersecting the individually filtered results. However, I have the gut feeling there is a Set Based approach using CTEs or MERGE that I just can't wrap my head around at the moment.
Maybe not the perfect solution, but you could try something as next:
DECLARE #ExpectedActivities int
SELECT
#ExpectedActivities = COUNT(*)
FROM
#activities
SELECT
*
FROM
activity A
INNER JOIN
(
SELECT
NA.employeeId
FROM
activity NA
INNER JOIN #activities FA ON FA.stationId = NA.stationId
AND FA.type = NA.type
GROUP BY
NA.employeeId
HAVING
COUNT(*) >= #ExpectedActivities
) B ON A.employeeId = B.employeeId
This is a Relational Division with no Remainder problem. Dwain Camps has an article about this with a number of solutions:
High Performance Relational Division in SQL Server
SELECT a.employeeId
FROM (
SELECT DISTINCT employeeId, stationId, type
FROM activity
) a
INNER JOIN #activities at
ON a.stationId = at.stationId
AND a.type = at.type
GROUP BY
a.employeeId
HAVING
COUNT(*) = (SELECT COUNT(*) FROM #activities);
ONLINE DEMO
For scenario 1 - You could inner join with the table valued parameter itself. For the second scenario, you could do:
select distinct a.employeeId
from #activity as a
inner join #activities as b on b.stationId = a.stationId and b.type = a.type
For the second scenario:
select distinct act.employeeId
from #activity as act
where act.employeeId not in (
select distinct a.employeeId from #activity as a
left join #activities as b on b.stationId = a.stationId and b.type = a.type
where b.stationId is null)

Left outher join count rows from different table minus value different row

Hello I have following SQL query
SELECT K.name AS Name, K.surname AS Surname, U1.akce AS Event,
U2.[text] AS [Scheme], U1.[text] AS [Registered under>],
( U1.x - (
SELECT Count(K.ubytov)
FROM klient
WHERE ubytov = U2.[text]) ) AS [Free space]
FROM klient K
INNER JOIN ubytov U1 ON U1.[text] = K.ubytov
LEFT OUTER JOIN ubytov U2 ON U1.z = U2.id WHERE U1.akce = '140012-02'
ORDER BY U1.[text]
I'm trying to achieve that in column Free space would be (value from ubytov.x that matches U1.z = U2.id) - (total number of rows from table klient that has the same value in U1.[text]=K.ubytov)
In table klient column ubytov I have values that matches ubytov.text and in ubytov.z I have value that matches ubytov.x in different row.
Would somebody help me solve this out please?
Thank you for your time.
An example:
Table klient
ID_K ubytov
1 RoomOwner
2 RoomOwner
table ubytov
id text x z
1 roomType1 2 NULL
2 RoomOwner NULL 1
Desired Output:
Name Surname Event Scheme Registered under: Free space
Nam1 Surname1 Even1 Scheme1 RoomOwnerName 0 // (because 2 counts from klient) - (roomType1 x)
Although, it wasn't much clear because of missing columns.
I tried to build the required query using a CTE expression.
Here is the sqlfiddle code.
Let me know, if this is what you are looking for.
create table klient
(
id_k int,
ubytov varchar(25)
)
go
create table ubytov
(
id int,
text varchar(25),
x int null,
z int null
)
go
insert into klient(id_k, ubytov)
select 1, 'RoomOwner'
union select 2, 'RoomOwner'
go
insert into ubytov(id, text, x, z)
select 1, 'roomType1', 2, null
union select 2, 'RoomOwner', null, 1
go
;WITH cte_klint_counts_by_ubytov
AS
(
SELECT
ubytov,
Count(ubytov) AS ubytovCount
FROM klient
GROUP BY ubytov
)
SELECT
U2.[text] AS [Scheme],
U1.[text] AS [Registered under>],
(isnull(U1.x, 0) - isnull(c.ubytovCount, 0)) AS [Free space]
FROM
klient K
INNER JOIN ubytov U1 ON U1.[text] = K.ubytov
LEFT OUTER JOIN ubytov U2 ON U1.z = U2.id
LEFT OUTER JOIN cte_klint_counts_by_ubytov c ON c.ubytov = U2.[text]
ORDER BY u1.[text]