Count multiple columns in a query for multiple criteria - sql

I have a query that should count the number of items used by department.
The first two tables give me the units and the persons who used the items.
The third table tells who used what.
STAFF(EMPID,EMPNAME,UNITCTR)
CAFUNIT(UNITCTR, UNITDSC)
CAFTRXHD(BILLNO,TRXDATE,ITEMCODE,ITEMPRICE,ITEMDESC,ITEMPRICE,EMPID)
This is the query
SELECT a.UNITCTR, b.UNITDSC, COUNT(c.ITEMCODE)
FROM UNIT.STAFF a, UNIT.CAFUNIT b, UNIT.CAFTRXHID c
WHERE a.UNITCTR = b.UNITCTR
AND c.ITEMCODE IN ('397', '398', '399', '400', '401', '402', '403')
GROUP BY a.UNITCTR, b.UNITDSC
This returns the count of all used items by department for example Department A used 200 of these items, so I get the Department ID, name and the total count of items.
123|Cafeteria|200
where 200 is the sum of all of these items (397 to 403)
I need to know the count for each item by department for instance Department A used 10 boxes of item 397 and 5 of item B and …
123|Cafeteria|20|20|50|30|50|10|70
Using what is suggested here isn't working or I am not doing it right. Any ideas?

Hello after checking you request, follows a query what I think could help you, I'm using two queries with partition, one to group all items by unit and another to group items by unit and item code.
WITH STAFF AS (
SELECT * FROM (
VALUES
(1, 'John Smith','U1'),
(2, 'David Thompson','U2'),
(3, 'Stacey Leigh','U3')
) AS _ (EMPID,EMPNAME,UNITCTR)
),CAFUNIT AS (
SELECT * FROM (
VALUES
('U1', 'Unit 1'),
('U2', 'Unit 2'),
('U3', 'Unit 3')
) AS _ (UNITCTR,UNITDSC)
),CAFTRXHD as (
SELECT * FROM (
VALUES
(1, '2022-01-01','item 1',100,'Item desc 1',1),
(2, '2022-02-01','item 2',200,'Item desc 2',2),
(3, '2022-03-01','item 3',300,'Item desc 3',3),
(4, '2022-01-01','item 1',100,'Item desc 1',1),
(5, '2022-01-01','item 2',100,'Item desc 2',1),
(6, '2022-01-01','item 2',100,'Item desc 2',1),
(7, '2022-02-01','item 2',200,'Item desc 2',2),
(8, '2022-02-01','item 2',200,'Item desc 2',2),
(9, '2022-03-01','item 3',300,'Item desc 3',3),
(10, '2022-03-01','item 3',300,'Item desc 3',3),
(11, '2022-03-01','item 3',300,'Item desc 3',3)
) AS _ (BILLNO,TRXDATE,ITEMCODE,ITEMPRICE,ITEMDESC,EMPID)
),
--Get all item group by Unit
GetAllByUnit as (
SELECT
t.* FROM
(
SELECT
IT.UNITCTR,
IT.UNITDSC,
(SUM(ITEMPRICE) OVER (partition by IT.UNITCTR order by IT.UNITDSC)) as TotalValue
FROM
CAFTRXHD as HD
INNER JOIN STAFF as FF ON HD.EMPID = FF.EMPID
INNER JOIN CAFUNIT as IT ON FF.UNITCTR = IT.UNITCTR
) as t
GROUP BY t.UNITCTR, T.UNITDSC, T.TotalValue
),
--Get all item group by Unit and Item code
GetAllByUnitAndCode as (
SELECT
t.* FROM
(
SELECT
IT.UNITCTR,
IT.UNITDSC,
HD.ITEMCODE,
(SUM(ITEMPRICE) OVER (partition by IT.UNITCTR,HD.ITEMCODE order by IT.UNITDSC)) as TotalValue
FROM
CAFTRXHD as HD
INNER JOIN STAFF as FF ON HD.EMPID = FF.EMPID
INNER JOIN CAFUNIT as IT ON FF.UNITCTR = IT.UNITCTR
) as t
GROUP BY t.UNITCTR, T.UNITDSC, T.ITEMCODE, T.TotalValue
)
SELECT * FROM GetAllByUnit
--SELECT * FROM GetAllByUnitAndCode
The result
UNITCTR UNITDSC TotalValue
U1 Unit 1 400
U2 Unit 2 600
U3 Unit 3 1200
Comment the query last line --SELECT * FROM GetAllByUnit and remove the comment over SELECT * FROM GetAllByUnitAndCode
--SELECT * FROM GetAllByUnit
SELECT * FROM GetAllByUnitAndCode
The result here is top down not only one line
UNITCTR UNITDSC ITEMCODE TotalValue
U1 Unit 1 item 1 200
U1 Unit 1 item 2 200
U2 Unit 2 item 2 600
U3 Unit 3 item 3 1200
Best Regards

Related

From a subset of foreign keys, get a list of items that contain that subset

I have two tables:
pages_interests
page_id INT NOT NULL
interest_id INT NOT NULL
items_interests
item_id INT NOT NULL
interest_id INT NOT NULL
pages_interest:
page_id
interest_id
1
1
1
7
2
1
3
1
3
7
3
89
items_interest:
item_id
interest_id
10
1
10
7
10
20
12
1
12
55
I'm trying to figure out how to get SQL to join on multiple rows. Because the page expected items to have an interest of 1 and 7, get items that have both those interests, but don't discard when an item has other interests too.
The expected output would be:
page_id
item_id
1
10
2
10
2
12
Does anyone have any idea how I could achieve this?
I think something like this might work. I added a couple of more pages for more realistic test:
;with pages as (
select *
from (
VALUES (1, 1)
, (1, 7)
, (2, 1)
, (3, 1)
, (3, 7)
, (3, 89)
, (4, 20)
, (5, 55)
, (5, 1)
, (6, 1)
, (6, 13)
) t (page_id,interest_id)
)
, items as (
select *
from (
VALUES (10, 1)
, (10, 7)
, (10, 20)
, (12, 1)
, (12, 55)
) t (item_id,interest_id)
)
select p.page_id, i.item_id
from (
select p.page_id, interest_id, COUNT(*) OVER(PARTITION BY page_id) AS total_interests
FROM pages p
) p
LEFT JOIN items i
ON i.interest_id = p.interest_id
group by p.page_id, i.item_id, p.total_interests
HAVING COUNT(i.item_id) >= p.total_interests
The idea is to keep track of total page interests and then make sure it's no less than item counts (if we miss, LEFT JOIN value becomes null and COUNT ignores is).
Using an INNER JOIN between the two tables, will make appear only interest_id in common between the two tables. To gather only <page_id, item_id> that have both the two pages, it's sufficient to enforcing COUNT(DISTINCT i.interest_id) = 2 inside the HAVING clause.
SELECT p.page_id, i.item_id
FROM pages_interest p
INNER JOIN items_interest i
ON p.interest_id = i.interest_id
GROUP BY p.page_id, i.item_id
HAVING COUNT(DISTINCT i.interest_id) = 2
If you want to generalize on the number of "pages_interest" items, you can do:
SELECT p.page_id, i.item_id
FROM pages_interest p
INNER JOIN items_interest i
ON p.interest_id = i.interest_id
GROUP BY p.page_id, i.item_id
HAVING COUNT(DISTINCT i.interest_id) = (SELECT COUNT(DISTINCT interest_id) FROM pages_interest)
Output:
page_id
item_id
1
10
Check the demo here.

JOIN exclude records in second table

In my system one order can have associated X documents, and each documents have an document code.
One example of my schema with data:
Order
Document
Code Doc
1
101
5E
1
102
5E
1
103
1DE
2
201
5E
The table of the orders is PDOCAS and the documents save in the table DOCCAB.
I would like when join the orders with the documents, that if one of the types of documents is 1DE, do not bring the order.
select p.DocCabIdDeb as 'Order', d.DocCabId as 'Document', d.DocCod as 'Code Doc'
from PDOCAS p
JOIN DOCCAB d on p.DocCabIdHab=d.DocCabId
WHERE NOT EXISTS(select * from DOCCAB ds where ds.DocCabId=d.DocCabId and doccod='1DE')
and p.DocCabIdDeb in (1, 2)
In this case, it returns me the order 1 and the 5E document codes, and I don't want it to return it because one of the documents of order 1 is 1DE.
Should I join the tables in another way?
Thanks.
I made few assumptions on the data
You need an inner query or CTEs which filter out the orders containing the blacklisted Doc codes
So you need a distinct order where document code in '1de'
and then you should filter out the orders from original table with an order_id not in condition.
Below is an example query with CTE
WITH
-- Setting up the data
PDOCAS AS (
SELECT * FROM (
VALUES
(1, 101),
(1, 102),
(1, 103),
(2, 201)
) t(DocCabIdDeb, DocCabIdHab)
),
DOCCAB AS (
SELECT * FROM (
VALUES
(101, '5E'),
(102, '5E'),
(103, '1DE'),
(201, '5E')
) t(DocCabId, DocCod)
),
-- Data setup ends
-- Your actual query starts from here
ORDERS AS (
select
p.DocCabIdDeb as "OrderId",
d.DocCabId as "Document",
d.DocCod as "CodeDoc"
from
PDOCAS p
JOIN DOCCAB d on p.DocCabIdHab=d.DocCabId
WHERE
p.DocCabIdDeb in (1, 2)
),
ORDERS_WITH_BLACKLISTED_DOCCOD AS (
SELECT DISTINCT
o.OrderId
FROM
ORDERS o
WHERE
o.CodeDoc in ('1DE')
),
FINAL_ORDERS AS (
SELECT
*
FROM
ORDERS o
WHERE
o.OrderId NOT IN (SELECT * FROM ORDERS_WITH_BLACKLISTED_DOCCOD)
)
SELECT * FROM FINAL_ORDERS

Calcul (multiplication) of two select-result

I'm trying to multiply two numbers I got from a SELECT statement of a unique query. I want to get the number of providers and the number of proposals (the query I made displays that), and multiply both on the same line (that I can't do).
I've made a very simple example to show you (same code as below) : DEMO ON FIDDLE
Create 2 providers working on 2 departments :
CREATE TABLE ##Provider
(
id INT,
p_name VARCHAR(50),
id_dep INT
)
INSERT INTO ##Provider (id, p_name, id_dep) VALUES
(1, 'toto', 10),
(2, 'toto', 11),
(3, 'tata', 9);
Create 4 proposal on 2 departments :
CREATE TABLE ##Proposal
(
id INT,
c_name VARCHAR(50),
id_dep INT
)
INSERT INTO ##Proposal (id, c_name, id_dep) VALUES
(1, 'propA', 10),
(2, 'propB', 09),
(3, 'propC', 10),
(4, 'propD', 10);
Create the department table :
CREATE TABLE ##Department
(
id INT,
d_name VARCHAR(50)
)
INSERT INTO ##Department (id, d_name) VALUES
(9, 'dep9')
,(10, 'dep10')
,(11, 'dep11');
Here I can display the number of providers and proposals by department (the real query is a lot more complex so I'd like to keep the 2 subrequests) :
select
id,
d_name,
nb_provider = (
SELECT COUNT(DISTINCT Id)
FROM ##Provider p
WHERE p.id_dep = dep.id
),
nb_proposal = (
SELECT COUNT(DISTINCT Id)
FROM ##Proposal pp
WHERE pp.id_dep = dep.id
)
from ##Department dep
WHERE dep.id = 10
But I CAN'T display a calcul of those two number :
select
id,
d_name,
nb_provider = (
SELECT COUNT(DISTINCT Id)
FROM ##Provider p
WHERE p.id_dep = dep.id
),
nb_proposal = (
SELECT COUNT(DISTINCT Id)
FROM ##Proposal pp
WHERE pp.id_dep = dep.id
),
calcul = (nb_provider * nb_proposal) --> DOESN'T WORK
from ##Department dep
WHERE dep.id = 10
I haven't tried a lot because I am not sure if this is even possible... maybe should I use UNION ?
I would recommend lateral joins:
select
d.id,
d.d_name,
p.nb_provider,
pp.nb_proposal
(p.nb_provider * pp.nb_proposal) calcul
from ##department d
outer apply (
select count(distinct id) nb_provider
from ##provider p
where p.id_dep = d.id
) p
outer apply (
select count(distinct id) nb_proposal
from ##proposal pp
where pp.id_dep = d.id
) pp
where d.id = 10

1 distinct row having max value

This is the data I have
I need Unique ID(1 row) with max(Price). So, the output would be:
I have tried the following
select * from table a
join (select b.id,max(b.price) from table b
group by b.id) c on c.id=a.id;
gives the Question as output, because there is no key. I did try the other where condition as well, which gives the original table as output.
You could try something like this in SQL Server:
Table
create table ex1 (
id int,
item char(1),
price int,
qty int,
usr char(2)
);
Data
insert into ex1 values
(1, 'a', 7, 1, 'ab'),
(1, 'a', 7, 2, 'ac'),
(2, 'b', 6, 1, 'ab'),
(2, 'b', 6, 1, 'av'),
(2, 'b', 5, 1, 'ab'),
(3, 'c', 5, 2, 'ab'),
(4, 'd', 4, 2, 'ac'),
(4, 'd', 3, 1, 'av');
Query
select a.* from ex1 a
join (
select id, max(price) as maxprice, min(usr) as minuser
from ex1
group by id
) c
on c.id = a.id
and a.price = c.maxprice
and a.usr = c.minuser
order by a.id, a.usr;
Result
id item price qty usr
1 a 7 1 ab
2 b 6 1 ab
3 c 5 2 ab
4 d 4 2 ac
Explanation
In your dataset, ID 1 has 2 records with the same price. You have to make a decision which one you want. So, in the above example, I am showing a single record for the user whose name is lowest alphabetically.
Alternate method
SQL Server has ranking function row_number over() that can be used as well:
select * from (
select row_number() over( partition by id order by id, price desc, usr) as sr, *
from ex1
) c where sr = 1;
The subquery says - give me all records from the table and give each row a serial number starting with 1 unique to each ID. The rows should be sorted by ID first, then price descending and then usr. The outer query picks out records with sr number 1.
Example here: https://rextester.com/KZCZ25396

How would l write SQL to label quantities until they run out?

I would like to label quantities (in the quantity table) using the labels assigned (see label assignment table) until the quantity goes to 0. Then I know that I am done labeling that particular ID.
label assignment table is as follows:
ID | Label | Quantity
1 aaa 10
1 bbb 20
2 ccc 20
And my quantity table:
ID | Total Quantity
1 60
2 20
And I would like to get the following result:
ID | Label | Quantity
1 aaa 10 (read from reference table, remaining 50)
1 bbb 20 (read from reference table, remaining 30)
1 [NULL] 30 (no label in reference table, remaining 0)
2 ccc 20 (read from reference table, remaining 0)
You can do it with a simple JOIN and UNION operation so as to include 'not covered' quantities:
SELECT la.ID, la.Label, la.Quantity
FROM label_assignment AS la
INNER JOIN quantity AS q ON la.ID = q.ID
UNION
SELECT q.ID, NULL AS Label, q.TotalQuantity - la.TotalQuantity
FROM quantity AS q
INNER JOIN (
SELECT ID, SUM(Quantity) AS TotalQuantity
FROM label_assignment
GROUP BY ID
) AS la ON q.ID = la.ID AND q.TotalQuantity > la.TotalQuantity
Demo here
DECLARE #PerLabelQuantity TABLE(Id int, Label varchar(10), Quantity int);
INSERT INTO #PerLabelQuantity
VALUES (1, 'aaa', 10), (1, 'bbb', 20), (2, 'ccc', 20);
DECLARE #QuantityRequired TABLE(Id int, TotalQuantity int);
INSERT INTO #QuantityRequired
VALUES (1, 60), (2, 20);
SELECT t.Id,
CASE WHEN o.Overflowed = 1 THEN NULL ELSE t.Label END AS Label,
CASE WHEN o.Overflowed = 1 THEN t.QuantityStillNeeded
WHEN t.QuantityStillNeeded < 0 THEN t.Quantity + t.QuantityStillNeeded
ELSE t.Quantity END AS Quantity
FROM (
SELECT p.Id, p.Label, p.Quantity,
MAX(p.Label) OVER (PARTITION BY p.Id) AS LastLabel,
r.TotalQuantity - SUM(p.Quantity)
OVER (PARTITION BY p.Id
ORDER BY Label
ROWS UNBOUNDED PRECEDING) AS QuantityStillNeeded
FROM #PerLabelQuantity p
INNER JOIN #QuantityRequired r ON p.Id = r.Id) t
INNER JOIN (VALUES (0), (1)) o(Overflowed)
ON t.LastLabel = t.Label AND t.QuantityStillNeeded > 0 OR Overflowed = 0
WHERE t.QuantityStillNeeded > -t.Quantity; -- Remove this if you want labels with
-- 0 quantity used, but you'll need to tweak
-- the CASE expression for Quantity
The subquery calculates a set of used up labels and how many items remain afterward. If there is any quantity remaining after the last label, then we need to insert a row in the result set. To do this, I join on a two-element table but the join condition is only true when we are at the last label and there is quantity remaining. This is probably a confusing way to do this, and we could combine the UNION from George's answer with the subquery from mine to avoid this Overflow table.
Here's the changed (and probably preferable) query:
SELECT Id,
Label,
CASE WHEN QuantityStillNeeded < 0 THEN Quantity + QuantityStillNeeded
ELSE Quantity END AS Quantity
FROM (SELECT p.Id, p.Label, p.Quantity,
r.TotalQuantity - SUM(p.Quantity)
OVER (PARTITION BY p.Id
ORDER BY Label
ROWS UNBOUNDED PRECEDING) AS QuantityStillNeeded
FROM #PerLabelQuantity p
INNER JOIN #QuantityRequired r ON p.Id = r.Id) t
WHERE t.QuantityStillNeeded > -t.Quantity
UNION ALL
SELECT q.Id, NULL AS Label, q.TotalQuantity - la.TotalQuantity AS Quantity
FROM #QuantityRequired AS q
INNER JOIN (
SELECT Id, SUM(Quantity) AS TotalQuantity
FROM #PerLabelQuantity
GROUP BY Id) la ON q.ID = la.ID
WHERE q.TotalQuantity > la.TotalQuantity
Simplest answer I think, after getting ideas from the other answers: Just create a "FAKE" label for the missing amount:
DECLARE #PerLabelQuantity TABLE(Id int, Label varchar(10), Quantity int);
INSERT INTO #PerLabelQuantity
VALUES (1, 'aaa', 10), (1, 'bbb', 20), (2, 'ccc', 20);
SELECT *
FROM #PerLabelQuantity
DECLARE #QuantityRequired TABLE(Id int, TotalQuantity int);
INSERT INTO #QuantityRequired
VALUES (1, 60), (2, 20);
SELECT *
FROM #QuantityRequired
-- MAKE A FAKE LABEL LET'S CALL IT [NULL] WITH THE AMOUNT THAT IS NOT LABELED
-- i.e. WITH THE REMAINING AMOUNT
-- Probably should be done by copying the original data and the following
-- into a temp table but this is just for proof of concept
INSERT INTO #PerLabelQuantity( Id, Label, Quantity )
SELECT q.ID,
NULL,
ISNULL(q.TotalQuantity - p.TotalQuantityLabeled, q.TotalQuantity)
FROM #QuantityRequired q
LEFT JOIN (SELECT p.ID, SUM(Quantity) AS TotalQuantityLabeled
FROM #PerLabelQuantity p
GROUP BY p.Id) p ON
p.ID = q.ID
AND q.TotalQuantity - p.TotalQuantityLabeled > 0
SELECT *
FROM #PerLabelQuantity p