I couldn't figure this out from existing kinda similar threads. I've tried left join and different sort of subqueries but no luck.
I got left join work with group by but couldn't figure out how to add where clauses, then wen't to subqueries and broke everything.
I have two tables storage and orders.
Storage has list of unique id and name
id | name
1234 | product1
1235 | product2
A123 | product3
Orders have multiple instances code, quantity and type
code | qty| type
1234 | 10 | order
1234 | 10 | quote
1234 | 10 | order
A123 | 15 | order
1235 | 13 | order
I wan't to join these tables so that I get filtered (with where) results from storage and join with summed qty where type is order.
For example filter storage where id in (1234, A123) should result:
id | name | sum qty
1234 | product1 | 20
A123 | product3 | 15
Any help appreciated!
--
Going forward, storage has products and cols. There is table prod_to_col that has productid and col_id to tie them together.
I would need to grab product codes from table prod_to_col and show total quantity for cols according to order quantity.
I tried this according to #iSR5 example:
SELECT st.id, st.name, SUM(order.qty) AS SumQty
FROM storage
JOIN prod_to_col ON st.id=prod_to_col.col_id
JOIN orders ON order.id IN (SELECT prod_id FROM prod_to_col WHERE col_id=st.id) AND type='order'
WHERE id IN (1234, A123)
GROUP BY st.id, st.name
This almost works but quantities are multiplied in some rows some are fine, can someone point where it goes wrong?
In addition to tables storage and orders above, here's example of prod_to_col and cols:
Prod_to_col
prod_id | col_id | col_qty (per product)
1235 | C101 | 2
1236 | C102 | 1
Cols
col_id | name | other data
C101 | cname1 | --
C102 | cname2 | --
Orders
prod_id | qty | type
1235 | 10 | order
1235 | 10 | order
1236 | 2 | quote
1236 | 5 | order
Storage
st.id | st.name| SumQty
C101 | cname1 | 40
C102 | cname2 | 5
I understand I need to use two different sentence to populate storage list, one for products and one for cols. The one for products works fine.
Is this is what you need ?
DECLARE #Storage TABLE(ID VARCHAR(50), name VARCHAR(250) )
DECLARE #Orders TABLE(code VARCHAR(50), qty INT, type VARCHAR(50))
INSERT INTO #Storage VALUES
('1234','product1')
, ('1235','product2')
, ('A123','product3')
INSERT INTO #Orders VALUES
('1234',10,'order')
, ('1234',10,'quote')
, ('1234',10,'order')
, ('A123',15,'order')
, ('1235',13,'order')
SELECT
s.ID
, s.name
, SUM(o.qty) TotalQty
FROM
#Storage s
JOIN #Orders o ON o.code = s.ID AND o.type = 'order'
WHERE
s.ID IN('1234','A123')
GROUP BY
s.ID
, s.name
UPDATE
You've updated your post with more logic to cover, which wasn't provided before, however, I've update it the query for you ..
DECLARE #Storage TABLE(ID VARCHAR(50), name VARCHAR(250) )
DECLARE #Orders TABLE(code VARCHAR(50), qty INT, type VARCHAR(50))
DECLARE #Prod_to_col TABLE(prod_id VARCHAR(50), col_id VARCHAR(50), col_qty INT)
DECLARE #Cols TABLE(col_id VARCHAR(50), name VARCHAR(250))
INSERT INTO #Storage VALUES
('1234','product1')
, ('1235','product2')
, ('A123','product3')
, ('1236','product3')
INSERT INTO #Orders VALUES
('1234',10,'order')
, ('1234',10,'quote')
, ('1234',10,'order')
, ('A123',15,'order')
, ('1235',10,'order')
, ('1235',10,'order')
, ('1236',2,'quote')
, ('1236',5,'order')
INSERT INTO #Prod_to_col VALUES
('1235','C101',2)
, ('1236','C102',1)
INSERT INTO #Cols VALUES
('C101','cname1')
, ('C102','cname2')
SELECT
c.col_id
, c.name
, SUM(o.qty) * MAX(ptc.col_qty) TotalQty
FROM
#Storage s
JOIN #Orders o ON o.code = s.ID AND o.type = 'order'
JOIN #Prod_to_col ptc ON ptc.prod_id = o.code
JOIN #Cols c ON c.col_id = ptc.col_id
--WHERE
-- s.ID IN('1234','A123')
GROUP BY
c.col_id
, c.name
Try to use Left Join combined with a group by
SELECT OD.CODE
,ST.PRODUCT
,SUM(quantity) as qnt
FROM ORDERS OD
LEFT JOIN STORAGE ST ON(OD.CODE = SG.CODE)
WHERE OD.type like 'order'
GROUP BY
OD.CODE
,ST.PRODUCT
You can use Having to filter
Having id in (1234, A123)
Greetings
Related
I have sample data set as follows,
| Customer | |Detail | |DataValues |
|----------| |-------| |-----------|
| ID | |ID | |CustomerID |
| Name | |Name | |DetailID |
|Values |
| Customer | |Detail | |DataValues |
|----------| |---------| |-----------|
| 1 | Jack | | 1 | sex | | 1 | 1 | M |
| 2 | Anne | | 2 | age | | 1 | 2 | 30|
| 2 | 1 | F |
| 2 | 2 | 28|
and my desired outcome is below,
Name
Sex
Age
Jack
M
30
Anne
F
28
I have failed to come up with a correct SQL Query that returns anything.
Thanks in advance.
select Customers.Name, Details.Name, DataValues.Value from Customers
inner join DataValues on DataValues.CustomersID = Customers.ID
inner join Details on DataValues.DetailsID = Details.ID
The static way, assuming you know you want exactly Sex and Age:
WITH cte AS
(
SELECT c.Name, Type = d.Name, dv.[Values]
FROM dbo.DataValues AS dv
INNER JOIN dbo.Detail AS d
ON dv.DetailID = d.ID
INNER JOIN dbo.Customer AS c
ON dv.CustomerID = c.ID
WHERE d.Name IN (N'Sex',N'Age')
)
SELECT Name, Sex, Age
FROM cte
PIVOT (MAX([Values]) FOR [Type] IN ([Sex],[Age])) AS p;
If you need to derive the query based on all of the possible attributes, then you'll need to use dynamic SQL. Here's one way:
DECLARE #in nvarchar(max),
#piv nvarchar(max),
#sql nvarchar(max);
SELECT #in = STRING_AGG(N'N' + QUOTENAME(Name, char(39)), ','),
#piv = STRING_AGG(QUOTENAME(Name), ',')
FROM (SELECT Name FROM dbo.Detail GROUP BY Name) AS src;
SET #sql = N'WITH cte AS
(
SELECT c.Name, Type = d.Name, dv.[Values]
FROM dbo.DataValues AS dv
INNER JOIN dbo.Detail AS d
ON dv.DetailID = d.ID
INNER JOIN dbo.Customer AS c
ON dv.CustomerID = c.ID
WHERE d.Name IN (' + #in + N')
)
SELECT Name, ' + #piv + N'
FROM cte
PIVOT (MAX([Values]) FOR [Type] IN (' + #piv + N')) AS p;';
EXECUTE sys.sp_executesql #sql;
Working examples in this fiddle.
There's a lot to unpack here. Let's start with how to present demo data:
If you provide the DDL and DML for your data it makes it much easier for folks to work with:
DECLARE #Customer TABLE (ID INT, Name NVARCHAR(100))
DECLARE #Detail TABLE (ID INT, Name NVARCHAR(20))
DECLARE #DataValues TABLE (CustomerID INT, DetailID INT, [Values] NVARCHAR(20))
INSERT INTO #Customer (ID, Name) VALUES
(1, 'Jack'),(2, 'Anne')
INSERT INTO #Detail (ID, Name) VALUES
(1, 'Sex'),(2, 'Age')
INSERT INTO #DataValues (CustomerID, DetailID, [Values]) VALUES
(1, 1, 'M'),(1, 2, '30'),(2, 1, 'F'),(2, 2, '28')
This sets up your tables (as variables) and populates them with the demo data.
Next let's talk about the horrible schema here.
You should always to try avoid reserved words as column names too. Values is a keyword.
This should probably be a single customers table:
DECLARE #Genders TABLE (ID INT IDENTITY, Name NVARCHAR(20))
DECLARE #Customer1 TABLE (CustomerID INT IDENTITY, Name NVARCHAR(100), BirthDate DATETIME, GenderID INT NULL, Age AS (DATEDIFF(YEAR, BirthDate, CURRENT_TIMESTAMP)))
Notice I used BirthDate instead of Age. This is because a persons age will change over time, but their birth date will not. Attributes that are calculated based on another attribute shouldn't be stored (but if you want you can used a calculated column, as we are here). You'll also note that instead of explicitly defining gender in the customers table we instead will reference it by Gender ID. This is a lookup table.
If you had used a normalized schema your query would then look like:
/* Demo Data */
DECLARE #Genders TABLE (ID INT IDENTITY, Name NVARCHAR(20));
INSERT INTO #Genders (Name) VALUES
('Male'),('Female'),('Non-Binary');
DECLARE #Customer1 TABLE (CustomerID INT IDENTITY, Name NVARCHAR(100), BirthDate DATETIME, GenderID INT NULL, Age AS (DATEDIFF(YEAR, BirthDate, CURRENT_TIMESTAMP)));
INSERT INTO #Customer1 (Name, BirthDate, GenderID) VALUES
('Jack', '2000-11-03', 1),('Anne', '2000-11-01', 2),('Chris', '2001-05-13', NULL);
/* Query */
SELECT *
FROM #Customer1 c
LEFT OUTER JOIN #Genders g
ON c.GenderID = g.ID;
Now on to how to get the data you want from the structure you have. Anyway you do this is going to be acrobatic because we have to work against the schema.
/* Demo Data */
DECLARE #Customer TABLE (ID INT, Name NVARCHAR(100));
DECLARE #Detail TABLE (ID INT, Name NVARCHAR(20));
DECLARE #DataValues TABLE (CustomerID INT, DetailID INT, [Values] NVARCHAR(20));
INSERT INTO #Customer (ID, Name) VALUES
(1, 'Jack'),(2, 'Anne');
INSERT INTO #Detail (ID, Name) VALUES
(1, 'Sex'),(2, 'Age');
INSERT INTO #DataValues (CustomerID, DetailID, [Values]) VALUES
(1, 1, 'M'),(1, 2, '30'),(2, 1, 'F'),(2, 2, '28');
/* Query */
SELECT *
FROM (
SELECT d.Name AS DetailName, c.Name AS CustomerName, DV.[Values]
FROM #DataValues dv
INNER JOIN #Detail d
ON dv.DetailID = d.ID
INNER JOIN #Customer c
ON dv.CustomerID = c.ID
) a
PIVOT (
MAX([Values]) FOR DetailName IN (Sex,Age)
) p;
CustomerName Sex Age
-----------------------
Anne F 28
Jack M 30
Let's suppose I have a temporary table which looks like this:
+----+------+
| Id | Value|
+----+------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
+----+------+
And I want my table to be like this:
+----+----------+
| Id | ValueList|
+----+----------+
| 1 | 1,2,3 |
| 2 | 1,2 |
+----+----------+
So basically I need to group my values as a comma separated list.
I already tried the following:
SELECT Id, STUFF((SELECT ',' + CAST(VALUE AS varchar) FROM #MyTable FOR XML PATH('')), 1 ,1, '') AS ValueList
FROM #MyTable
GROUP BY Id
But I get something like:
+----+---------------------+
| Id | ValueList |
+----+---------------------+
| 1 | 1,1,1,1,1,1,... |
+----+---------------------+
I cant find what I am doing wrong. Could someone help with this query? Or point me to a right direction?
Thank you.
You are missing the condition inside the sub query.
SELECT t2.Id, STUFF((SELECT ',' + CAST(VALUE AS varchar) FROM #MyTable t1 where t1.Id =t2.ID FOR XML PATH('')), 1 ,1, '') AS ValueList
FROM #MyTable t2
GROUP BY t2.Id
Demo
One alternative to using GROUP BY on the Id would be to use select distinct:
SELECT DISTINCT
Id,
STUFF((SELECT ',' + CAST(t2.VALUE AS varchar)
FROM #MyTable t2
WHERE t2.Id = t1.Id
FOR XML PATH('')), 1 ,1, '') AS ValueList
FROM #MyTable t1
Demo
One can also combine a FOR XML with a CROSS APPLY (or an OUTER APPLY) for this.
Example snippet:
declare #T table (id int, value int);
insert into #T values (1,1),(1,2),(1,3),(2,1),(2,2);
select id, stuff(x.list,1,1,'') as list
from (select distinct id from #T) as t
cross apply (
select concat(',',t2.value)
from #T t2
where t2.id = t.id
for xml path('')
) x(list)
order by id;
Result:
id list
-- -----
1 1,2,3
2 1,2
And starting from MS Sql Server 2017, STRING_AGG can be used instead.
select id, string_agg(value,',') as list
from Yourtable t
group by id;
Try this :
create table #t(id int, value int)
insert into #t values
(1,1),
(1,2),
(1,3),
(2,1),
(2,2)
SELECT t2.Id,
STUFF((SELECT ',' + CAST(VALUE AS varchar) FROM #t t1 where t1.Id =t2.ID FOR XML PATH('')), 1 ,1, '') AS list
FROM #t t2
GROUP BY t2.Id
output :
Id list
--- -------
1 1,2,3
2 1,2
Simple Solution
SELECT Id, GROUP_CONCAT(Value) as ValueList FROM MyTable GROUP BY Id;
add distinct to values if required
SELECT Id, GROUP_CONCAT(DISTINCT Value) as ValueList FROM MyTable GROUP BY Id;
I'm new to database concepts. So, i need some help in solving a particular problem.
Say i have a table named emp whose data are as below:
id | dept | doj
100 | FS | 02-04-13
100 | HST | 02-04-14
100 | ETA | 02-04-15
What i want to display is:
id | from | to | doj
100 | FS | HST | 02-04-14
100 | HST | ETA | 02-04-15
Any help would be appreciated. Thank you.
The database i'm using is MS-SQL server 13v.
You can use apply :
select t.id, t.dept as [from], t1.dept as [to], t1.doj
from table t cross apply
(select top (1) t1.*
from table t1
where t1.id = t.id and t1.doj > t.doj
order by t1.doj
) t1
With a self join to the table:
select t.id, tt.dept [from], t.dept [to], t.doj
from tablename t inner join tablename tt
on tt.id = t.id and
tt.doj = (select max(doj) from tablename where id = t.id and doj < t.doj)
See the demo
If I understand your question correctly and you want to get data from current and subsequent row in the same result set, you may use LEAD() function:
Input:
CREATE TABLE #Table (
id int,
dept varchar(10),
doj date
)
INSERT INTO #Table
(id, dept, doj)
VALUES
(100, 'FS', '2013-04-02'),
(100, 'HST', '2014-04-02'),
(100, 'ETA', '2015-04-02'),
(101, 'XTA', '2015-04-02'),
(101, 'YTA', '2015-04-02')
Statement:
SELECT *
FROM
(
SELECT
id,
dept [from],
LEAD(dept) OVER (PARTITION BY id ORDER BY id, doj) AS [to],
LEAD(doj) OVER (PARTITION BY id ORDER BY id, doj) AS [doj]
FROM #Table
) t
WHERE t.[to] IS NOT NULL
Output:
id from to doj
100 FS HST 2014-04-02
100 HST ETA 2015-04-02
101 XTA YTA 2015-04-02
I'm looking for a solution to particular query problem. I have a table Departments and table Employees designed like that:
Departments Employees
===================== ============================
ID | Name ID | Name | Surname | DeptID
--------------------- ----------------------------
1 | ADMINISTRATION 1 | X | Y | 2
2 | IT 2 | Z | Z | 1
3 | ADVERTISEMENT 3 | O | O | 1
4 | A | B | 3
I'd like to get list of all departments whose number of employees is smaller than number of employees working in Administration.
That was one of my ideas, but it did not work:
select * from Departments as Depts where Depts.ID in
(select Employees.ID from Employees group by Employees.ID
having count(Employees.ID) < count(case when Depts.Name='ADMINISTRATION' then 1 end));
Using GROUP BY and HAVING:
SELECT
d.ID, d.Name
FROM Departments d
LEFT JOIN Employees e
ON e.DeptID = d.ID
GROUP BY d.ID, d.Name
HAVING
COUNT(e.ID) < (SELECT COUNT(*) FROM Employees WHERE DeptID = 1)
Try this,
declare #Departments table (ID int, Name varchar(50))
insert into #Departments
values
(1 ,'ADMINISTRATION')
,(2 ,'IT')
,(3 ,'ADVERTISEMENT')
declare #Employees table (ID int, Name varchar(50)
,Surname varchar(50),DeptID int)
insert into #Employees
values
(1 ,'X','Y',2)
,(2 ,'Z','Z',1)
,(3 ,'O','O',1)
,(4 ,'A','B',3)
;
WITH CTE
AS (
SELECT *
,row_number() OVER (
PARTITION BY deptid ORDER BY id
) rn
FROM #Employees
WHERE deptid <> 1
)
SELECT *
FROM cte
WHERE rn < (
SELECT count(id) admincount
FROM #Employees
WHERE DeptID = 1
)
I have a one-to-many join. Works flawlessly, and quickly. The one-to-many may have zero records in the many table. If this is the case, I want to join it to ALL records in the many table.
USERS
-----
UserID | Name
CATEGORIES
------------------
CategoryID | CategoryName
USER_CATEGORIES
---------------
UserID | CategoryID
IF there are NO categories assigned to the user, I would like to join ALL categories. The reasoning behind this is, some users may manage all categories. If a category is added, it's automatically assigned to those users.
1 | Michael
2 | Bob
100 | Billing
101 | Email
102 | Technical
1 | 101
I would like the result to be:
1 | Michael | 101 | Email
2 | Bob | 100 | Billing
2 | Bob | 101 | Email
2 | Bob | 102 | Technical
So far, the way it works is:
DECLARE #USERS TABLE (UserID INT, UserName VARCHAR(10));
DECLARE #USER_CATEGORIES TABLE (UserID INT, CategoryID INT);
DECLARE #CATEGORIES TABLE (CategoryID INT, CategoryName VARCHAR(10));
INSERT INTO #USERS (UserID, UserName)
(
SELECT 1, 'Michael' UNION ALL
SELECT 2, 'Bob'
)
INSERT INTO #CATEGORIES (CategoryID, CategoryName)
(
SELECT 100, 'Billing' UNION ALL
SELECT 101, 'Email' UNION ALL
SELECT 102, 'Technical'
)
INSERT INTO #USER_CATEGORIES (UserID, CategoryID)
(
SELECT 1, 101
)
SELECT
U.UserID
, U.UserName
, C2.CategoryID
, C2.CategoryName
FROM
#USERS U LEFT JOIN
#USER_CATEGORIES UC ON U.UserID = UC.UserID LEFT JOIN
#CATEGORIES C ON UC.CategoryID = C.CategoryID LEFT JOIN
#CATEGORIES C2 ON C.CategoryID = C2.CategoryID OR (C.CategoryID IS NULL)
Is this the correct way to handle this? It seems to be a little too much overhead when I have many users/category combinations.
You can remove one of the joins to #CATEGORIES:
SELECT
U.UserID
, U.UserName
, C2.CategoryID
, C2.CategoryName
FROM #USERS U
LEFT JOIN #USER_CATEGORIES UC
ON U.UserID = UC.UserID
LEFT JOIN #CATEGORIES C2
ON UC.CategoryID = C2.CategoryID
OR (UC.CategoryID IS NULL)
Erm, if you were querying for one category or one user, then while I find it aesthetically offensive, okay.
For all users and categories? Not for me that, no
Can't say which way you should go. because I haven't a clue why you want this data
But me I'd either return say 'ALL' the result of the inner join was null, or I'd just use it returning no records for that user, to trigger a select * from categories if I needed it and hadn't already cached it.