Getting the difference between two queries in SQL Server - sql-server

I am working on a poorly designed database. I need to query the table to find the solution for the following issue.
Let's say I have a table like this.
+------------+------------+--------------+-----------+
| id | SubCode | Type | NumId |
+----------------------------------------------------+
| 1 | SB1212 | TCH | 100000000 |
| 1 | SB1212 | APP | 100000000 |
| 1 | SB1212 | TCH | 100000001 |
| 1 | SB1212 | APP | 100000002 |
+----------------------------------------------------+
I need to find the NumId of the people who only belong to Type='TCH' ( They shouldn't belong to Type='APP').
Note that the NumId is can be duplicated.
I wrote the following query and it's taking too much time to load. This looks like a simple issue but I am working on this for too long to not see a solution now. Can someone point me where I am doing wrong?
SELECT NumId
FROM TeacherSubject
WHERE SubCode = 'SB1212'
AND Type = 'TCH' OR Type = 'APP'
AND id NOT IN (SELECT NumId FROM TeacherSubject
WHERE SubCode = 'SB1212'
AND Type = 'APP')
ORDER BY NumId DESC
The output I am expecting is 100000001. Because 100000000 belongs to TCH and APP types.

You could use a CTE
with cte as (
select *
from TeacherSubject
where [Type] = 'APP'
and Subcode = 'SB1212'
)
select NumID
from TeacherSubject
where [Type] = 'TCH'
and Subcode = 'SB1212'
and NumID not in (select NumID from cte)
order by NumID desc

Try this
SELECT
NumId
FROM YourTable YT
WHERE [Type] = 'TCH'
AND SubCode = 'SB1212'
AND NOT EXISTS
(
SELECT
1
FROM YourTable
WHERE NumId = YT.NumId
AND [Type] <> YT.[Type]
AND [SubCode] = YT.SubCode
)

Related

Using array and loop to update MySQL table

I have a table with 3 columns
pID | key_name | value
-----------------------
10 | 'series' |'Songs'
10 | 'wood' |'Beech'
10 | 'language' |'German'
11 | 'series' |'Songs'
11 | 'wood' |'Oak'
11 | 'language' |'French'
12 | 'series' |'Exams'
12 | 'language' |'English'
I need to update a table where the key_names are now column names, thus
pID | series | wood | language
-----------------------------
10 ! 'Songs'|'Beech'|'German'
11 | 'Songs'|'Oak' |'French'
12 | 'Exams'| |'English'
Now I could write some SQL like
UPDATE dest-tbl INNER JOIN start-tbl
ON dest-tbl.pID = start-tbl.pID
SET dest-tbl.series = start-tbl.value
WHERE dest-tbl.key_name = 'series'
but since there are 65 different key_name values that would mean having to have 65 variations on that SQL.
It strikes me that the best way to do that might be to create an array of the key_name values and loop through that, except that I haven't go a clue how to do that.
Can anyone help me with this?
Using MariaDB v10.3
MTIA
EDIT:
I think I'm close to an answer with the SQL below.
I do need to insert this into another table and to filter the results based on the value of a field in another table. The code from SELECT down to GROUP creates the output I need but I've now got a problem with the JOIN part
INSERT INTO results ('series', 'wood', 'language')
SELECT table1.pID,
MAX(CASE WHEN table1.meta_key = 'series' THEN table1.meta_value END) 'series',
MAX(CASE WHEN table1.meta_key = 'wood' THEN table1.meta_value END) 'wood',
MAX(CASE WHEN table1.meta_key = 'language' THEN table1.meta_value END) 'language'
FROM table1
GROUP BY table1.pID
INNER JOIN ON table2.id = table1.pID
WHERE table2.id.type = 'product';
SELECT
t1.pID,
t1.value,
t2.value,
t3.value
FROM Table1 t1
LEFT JOIN Table1 t2 on t2.pID=t1.pID and t2.key_name='wood'
LEFT JOIN Table1 t3 on t3.pID=t1.pID and t3.key_name='language'
WHERE t1.key_name='series';
output:
+ -------- + ---------- + ---------- + ---------- +
| 10 | Songs | Beech | German |
| 11 | Songs | Oak | French |
| 12 | Exams | | English |
+ -------- + ---------- + ---------- + ---------- +
DBFIDDLE
As i said in the comments to get a flexible solution you need dynamic sql
CREATE TABLE tab1 (
`pID` INTEGER,
`key_name` VARCHAR(10),
`value` VARCHAR(9)
);
INSERT INTO tab1
(`pID`, `key_name`, `value`)
VALUES
('10', 'series', 'Songs'),
('10', 'wood', 'Beech'),
('10', 'language', 'German'),
('11', 'series', 'Songs'),
('11', 'wood', 'Oak'),
('11', 'language', 'French'),
('12', 'series', 'Exams'),
('12', 'language', 'English');
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN `key_name` = ''',
`key_name`,
''' THEN `value` ELSe "" END) AS `',
`key_name`, '`'
)
) INTO #sql
FROM `tab1` ;
SET #sql = CONCAT('SELECT `pID`, ',#sql,' from tab1
GROUP BY `pID`');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
✓
✓
pID | language | series | wood
--: | :------- | :----- | :----
10 | German | Songs | Beech
11 | French | Songs | Oak
12 | English | Exams |
✓
db<>fiddle here
My solution, tested and working though a bit slow, is as follows:-
INSERT INTO results ('series', 'wood', 'language')
SELECT table1.pID,
MAX(CASE WHEN table1.meta_key = 'series' THEN table1.meta_value END) 'series',
MAX(CASE WHEN table1.meta_key = 'wood' THEN table1.meta_value END) 'wood',
MAX(CASE WHEN table1.meta_key = 'language' THEN table1.meta_value END) 'language'
FROM table1, table2
WHERE table2.ID=table1_pID AND table2.id.type = 'product';
GROUP BY table1.pID

Using groupBy to improve my Select (select count) query

Let's say we have this and want to see all Tasks, that havent been done yet and an additional column showing how many open Tasks there are left for this customer.
I have a table like this in my database:
+------------+--------------------------+-------+
| CustomerID | Task | Done |
+------------+--------------------------+-------+
| 1 | CleanRoom | False |
| 1 | Cleandishes | True |
| 1 | WashClothes | False |
| 2 | TakeDogsOut | True |
| 2 | PlayWithKids | True |
| 3 | HaveFunWithMrSamplesWife | True |
| 3 | CleanMrSamplesCar | False |
+------------+--------------------------+-------+
I need this as returned table:
+------------+-------------------+-------------+
| CustomerID | Task | DoneOverAll |
+------------+-------------------+-------------+
| 1 | CleanRoom | 2 |
| 1 | WashClothes | 2 |
| 3 | CleanMrSamplesCar | 1 |
+------------+-------------------+-------------+
Perfect return table would be like this, but I can do that myself when I have the one above:
About this a question; Doing this will probably be a String combination task. Should I do this on the Select statement, or would it be more advisable to do that in the final application on the client computer?
+------------+-------------------+-------------+
| CustomerID | Task | DoneOverAll |
+------------+-------------------+-------------+
| 1 | CleanRoom | 1/3 |
| 1 | WashClothes | 1/3 |
| 3 | CleanMrSamplesCar | 1/2 |
+------------+-------------------+-------------+
I know I could go like
SELECT
a.CustomerID,
a.Task,
(
Select count(*) from myTable where
customerID = a.CustomerID and
done = False
) as DoneOverAll
FROM myTable as a
WHERE Done = False
But I think that this is very ineffective, since it would execute a Select Count for each row in my table. Is there a way to achieve this with a JOIN using groupBy or something? I'm not into GroupBy commands yet.
Okay I should have tried first. Came up with the following;
Select count(*), CustomerID from myTable group by CustomerID
All I need to do now is to get this into a join.
Okay, got it. Sorry again for not trying first!
SELECT
a.CustomerID,
a.Task,
b.cnt
FROM myTable as a
LEFT JOIN (select count(*) AS cnt, CustomerID FROM myTable GROUP BY CustomerID) as b on a.CustomerID = B.CustomerID
WHERE Done = False
Question left;
Perfect return table would be like this, but I can do that myself when I have the one above:
About this a question; Doing this will probably be a String combination task. Should I do this on the Select statement, or would it be more advisable to do that in the final application on the client computer?
+------------+-------------------+-------------+
| CustomerID | Task | DoneOverAll |
+------------+-------------------+-------------+
| 1 | CleanRoom | 1/3 |
| 1 | WashClothes | 1/3 |
| 3 | CleanMrSamplesCar | 1/2 |
+------------+-------------------+-------------+
I'm not sure why Done = False, but this is your logic. :-)
Here's what I would do, without the LEFT JOIN.
SELECT
a.CustomerID,
a.Task,
SUM(CASE WHEN a.Done = 'False' THEN 1 ELSE 0 END) DoneOverAll,
SUM(Case WHEN a.Done = 'True' THEN 1 ELSE 0 END) NotDone
FROM myTable as a
Group By a.CustomerID, a.Task
Do calculate separately .
;with tempfalse as(
SELECT
a.CustomerID,
a.Task,
count(*) as DoneOverAll
FROM myTable as a
WHERE Done = False
group by a.CustomerID, a.Task
)
, temptrue (
SELECT
a.CustomerID,
a.Task,
count(*) as total
FROM myTable as a
group by a.CustomerID, a.Task
)
SELECT
a.CustomerID,
a.Task,
cast(NULLIF(DoneOverAll,0) as varchar (10) ) + '/' + cast(NULLIF(b.total,0) as varchar (10) )
from temptrue as a left join tempfalse b
on a.CustomerID =a.CustomerID and
a.Task = b.Task

Where clause if there are multiple of the same ID

I have following table:
ID | source | Name | Age | ... | ...
1 | SQL | John | 18 | ... | ...
2 | SAP | Mike | 21 | ... | ...
2 | SQL | Mike | 20 | ... | ...
3 | SAP | Jill | 25 | ... | ...
I want to have one record for each ID. The idea behind this is that if the ID comes only once (no matter the Source), that record will be taken. But, If there are 2 records for one ID, the one containing SQL as source will be the used record here.
So, In this case, the result will be:
ID | source | Name | Age | ... | ...
1 | SQL | John | 18 | ... | ...
2 | SQL | Mike | 20 | ... | ...
3 | SAP | Jill | 25 | ... | ...
I did this with a partition over (ordered by Source desc), but that wouldn't work well if a third source will be added one day.
Any other options/ideas?
The easiest approach(in my opinion) is using a CTE with a ranking function:
with cte as
(
select ID, source, Name, Age, ... ,
rn = row_number() over (partition by ID order by case when source = 'sql'
then 0 else 1 end asc)
from dbo.tablename
)
select ID, source, Name, Age, ...
from cte
where rn = 1
You can use ROW_NUMBER:
WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY ID
ORDER BY CASE WHEN [Source] = 'SQL' THEN 1 ELSE 2 END)
FROM dbo.YourTable
)
SELECT *
FROM CTE
WHERE RN = 1;
You can use the WITH TIES clause and the window function Row_Number()
Select Top 1 With Ties *
From YourTable
Order By Row_Number() over (Partition By ID Order By Case When Source = 'SQL' Then 0 Else 1 End)
How about
SELECT *
FROM table
WHERE ID in (
SELECT ID FROM test
group by ID
having count(ID) = 1)
OR source = 'SQL'

Rebuild window function row_number in sybase

I have a problem that I could easily solve if I had window functions available in Sybase, but I dont:
Consider a table test:
+------------+----------------+-------------+
| Account_Id | Transaction_Id | CaptureDate |
+------------+----------------+-------------+
| 1 | 1 | 2014-01-01 |
| 1 | 2 | 2013-12-31 |
| 1 | 3 | 2015-07-20 |
| 2 | 1 | 2012-02-20 |
| 2 | 2 | 2010-01-10 |
| ... | ... | ... |
+------------+----------------+-------------+
I want to get a result set containing for each Account The most recent CaptureDate with the corresponding Transaction_Id. With the window function row_number this would be easy:
select Accounts_Id, CaptureDate, Transaction_Id from
(select
CallAccounts_Id,
CaptureDate,
Transaction_Id,
ROW_NUMBER() OVER(partition by Accounts_Id order by CaptureDate desc) row
from test) tbl
where tbl.row = 1
but my sybase version does not have this. Obviously, sth like
select max(Transaction_Id ), max(Transaction_Id ), Account_Id
from test
group by Account_Id
does not work because it does not always give me the correct Transaction_Id.
How can I do this then in Sybase and not make it terribly verbose?
Thanks!
Try below:
SELECT Account_Id, Transaction_Id, CaptureDate
FROM test a
WHERE CaptureDate = (
SELECT MAX(CaptureDate)
FROM test b
WHERE a.Account_Id = b.Account_Id
)
EDIT 1:
Duplicate CaptureDate was not in your example, so I did not take care of that scenario. Try below:
SELECT Account_Id, Transaction_Id, CaptureDate
FROM test a
WHERE CaptureDate = (
SELECT MAX(CaptureDate)
FROM test b
WHERE a.Account_Id = b.Account_Id
)
AND Transaction_Id =
(
SELECT MAX(Transaction_Id)
FROM test c
WHERE a.Account_Id = c.Account_Id
AND a.CaptureDate = c.CaptureDate
)

SQL Server making rows into columns

I'm trying to take three tables that I have and show the data in a way the user asked me to do it. The tables look like this. (I should add that I am using MS SQL Server)
First Table: The ID is varchar, since it's an ID they use to identify assets and they use numbers as well as letters.
aID| status | group |
-----------------------
1 | acti | group1 |
2 | inac | group2 |
A3 | acti | group1 |
Second Table: This table is fixed. It has around 20 values and the IDs are all numbers
atID| traitname |
------------------
1 | trait1 |
2 | trait2 |
3 | trait3 |
Third Table: This table is used to identify the traits the assets in the first table have. The fields that have the same name as fields in the above tables are obviously linked.
tID| aID | atID | trait |
----------------------------------
1 | 1 | 1 | NAME |
2 | 1 | 2 | INFO |
3 | 2 | 3 | GOES |
4 | 2 | 1 | HERE |
5 | A3 | 2 | HAHA |
Now, the user wants the program to output the data in the following format:
aID| status | group | trait1 | trait2 | trait 3
-------------------------------------------------
1 | acti | group1 | NAME | INFO | NULL
2 | inac | group2 | HERE | NULL | GOES
A3 | acti | group1 | NULL | HAHA | NULL
I understand that to achieve this, I have to use the Pivot command in SQL. However, I've read and tried to understand it but I just can't seem to get it. Especially the part where it asks for a MAX value. I don't get why I need that MAX.
Also, the examples I've seen are for one table. I'm not sure if I can do it with three tables. I do have a query that joins all three of them with the information I need. However, I don't know how to proceed from there. Please, any help with this will be appreciated. Thank you.
There are several ways that you can get the result, including using the PIVOT function.
You can use an aggregate function with a CASE expression:
select t1.aid, t1.status, t1.[group],
max(case when t2.traitname = 'trait1' then t3.trait end) trait1,
max(case when t2.traitname = 'trait2' then t3.trait end) trait2,
max(case when t2.traitname = 'trait3' then t3.trait end) trait3
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
group by t1.aid, t1.status, t1.[group];
See SQL Fiddle with Demo
The PIVOT function requires an aggregate function this is why you would need to use either the MIN or MAX function (since you have a string value).
If you have a limited number of traitnames then you could hard-code the query:
select aid, status, [group],
trait1, trait2, trait3
from
(
select t1.aid,
t1.status,
t1.[group],
t2.traitname,
t3.trait
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
) d
pivot
(
max(trait)
for traitname in (trait1, trait2, trait3)
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of values, then you will want to look at using dynamic SQL to get the final result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(traitname)
from Table2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT aid, status, [group],' + #cols + '
from
(
select t1.aid,
t1.status,
t1.[group],
t2.traitname,
t3.trait
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
) x
pivot
(
max(trait)
for traitname in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo

Resources