Show data from table even if there is no data - sql

I have 3 tables:
Data in Compania table:
Data in Periodo table:
Data in VAC_PERIODOCIA table:
I want to show all the companies (COMPANIA) and the value in (vac_aplica column) searching by Periodo, whether or not they are registered.
I tried this:
SELECT
COMPANIA.CIA_CLAVE, COMPANIA.CIA_NOM,
CASE
WHEN VAC_PERIODOCIA.VAC_APLICA IS NULL
THEN 'N'
ELSE 'Y'
END VAC_APLICA
FROM
COMPANIA
LEFT JOIN
VAC_PERIODOCIA ON COMPANIA.CIA_CLAVE = VAC_PERIODOCIA.CIA_CLAVE
WHERE
VAC_PERIODOCIA.PERIODO = '2018 - 2019'
Result:
What I want is this:

First of all, the question is a mess: tables and columns from the question and examples you've provided us with are different. Please fix that.
I don't speak Spanish, so I can only assume the VAC_PERIODICA is Periodo. In that case you need to move what you have in where condition to the join clause. Like this
SELECT COMPANIA.CIA_CLAVE,COMPANIA.CIA_NOM,
CASE
WHEN Periodo.valor IS NULL THEN 'N'
ELSE 'Y'
END VAC_APLICA
FROM Compania
LEFT JOIN Periodo
ON COMPANIA.CIA_CLAVE = Periodo.valor
AND Periodo.PERIODO = '2018 - 2019'
order by 1
dbfiddle

Related

Use of CASE with criteria from multiple tables

I have to do a select query to create a view with specific criteria.
I have multiple tables which contains many many columns and lines.
However, I have extracted a value to use as my key (e.g.: id). I have 7000+ of those unique keys that I extracted from all my tables with the function UNION to avoid duplicates.
Now, I want to add a column INDICATOR_1 which will affect the value YES or NO based on criteria.
This is where I struggle.
I need to find the line in those tables that contain the id. After that, I'd like to check, always in that line, if the field XYZ contains the value 'N' (example). If yes, affect the value 'YES' to INDICATOR_1, else it's no.
In a matter of pseudo-code, what I want to do looks like this :
CASE
WHEN id = (id from table_1) AND (if table_1.xyz = 'N')
THEN 'YES'
ELSE 'NO'
END AS INDICATOR_1
I don't know if I'm clear enough, but your help will be greatly appreciated.
If I understand correctly, you want a separate indicator for each table. Something like this:
select i.*,
(case when exists (select 1
from table1 t1
where t1.id = i.id and t1.xyz = 'N'
)
then 'YES' else 'NO'
end) as indicator_1,
(case when exists (select 1
from table2 t2
where t2.id = i.id and t2.xyz = 'N'
)
then 'YES' else 'NO'
end) as indicator_2,
. . .
from (<your id list here>) i
I think you should fix this in the union, where you have all the data you need. You probably have something like:
SELECT Id
FROM table_1
UNION
SELECT Id
FROM table_2
How about selecting the information you want as well (I use distinct here to clarify):
SELECT DISTINCT Id
, CASE WHEN table_1.xyz = 'N' THEN 'N'
ELSE 'Y'
END INDICATOR_1
FROM table_1
This can lead to more records than you had, if id's can have records of both flavours exist. We can fix that with a row number in an outer query. You end up with something like:
SELECT Id
, INDICATOR_1
FROM (
SELECT Id
, INDICATOR_1
, ROW_NUMBER()OVER(PARTITION BY ID ORDER BY CASE WHEN INDICATOR_1 ='N' THEN 0 ELSE 1 END) RN
FROM (
SELECT Id
, CASE WHEN table_1.xyz = 'N' THEN 'N'
ELSE 'Y'
END INDICATOR_1
FROM table_1
UNION
...
) T
) S
WHERE S.RN = 1
You can in fact shorten that by using the inner most case expression in the ROW_NUMBER expression.

SQL query to show users that have null values

I am quite new to SQL and I have a slight issue with my query that I have created, What I have done is created a query that will create a time summary of all of our staffs time recording on a particular day, currently the query works but what I need it to do is bring through a list of all of our users, not just filter by the users that did time recording on that day.
SELECT
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'C' THEN TimeTransactions.QuantityOfTime/60/6 ELSE 0 END) AS ChargableUnits,
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'N' THEN TimeTransactions.QuantityOfTime/60/6 ELSE 0 END) AS NonChargableUnits,
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'C' THEN TimeTransactions.ValueOfTime ELSE 0 END) AS ChargableValue,
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'N' THEN TimeTransactions.ValueOfTime ELSE 0 END) AS NonChargableValue,
SUM(TimeTransactions.QuantityOfTime)/60/6 AS TotalUnits,
SUM(TimeTransactions.ValueOfTime) AS TotalValue,
Users.Code AS FeeEarner
FROM
Users INNER JOIN TimeTransactions ON TimeTransactions.FeeEarnerRef = Users.Code
WHERE
Users.FeeEarner = 1 AND Users.UserStatus = 0 AND
TimeTransactions.TransactionDate >= dateadd(day,datediff(day,1,GETDATE()),0)
AND TimeTransactions.TransactionDate < dateadd(day,datediff(day,0,GETDATE()),0)
GROUP BY
Users.Code
I expect it to show users that did not do time recording on that day as showing 'NULL' in each row instead of removing them from the table.
Any help or guidance will be greatly appreciated :)
Something like this will do it.
Use a CTE to build your user query and place your user related filter conditions in there, then left join this on to your time table and apply your time filter conditions there. This avoids left joining the 2 tables raw but then applying a filter on to the right table which forces an inner join under the hood.
EDIT:
This is a working example of what you are trying to achieve. I have greatly SIMPLIFIED the table structure as the columns referenced in the query supplied don't match the columns provided in the image samples. Also it looks like the query is trying to use a column alias in a where clause (which isn't possible in SQL server).
In the below example I've applied filtering on the timetransaction table in the common table expression and then left joined the user table on to that. This produces the following output.
You can see a zero is returned for users whose time transactions do not match the filtering condition, where using an inner join those users would not be returned at all.
With more comprehensive data examples (that represent the column structure and the expected output or something similar) we could work out a solution which would be far closer to cut and paste ready, whereas this example is simply that, an example of how I would construct a left join where filtering needs to happen in the table on the right hand side of the join.
Good luck, if you have any questions let me know.
declare #users table (
userid int identity(1,1),
username nvarchar(50)
);
declare #timetransaction table (
timetransactionid int identity(1,1),
userid int,
quantityoftime int
);
insert #users
values
('SomeBody'),
('AnyBody'),
('SomeoneElse');
insert #timetransaction
values
(1, 7),
(1, 12),
(2, 5),
(3, 71),
(3, 4);
declare #userid int = 1;
with timetransaction as (select userid, quantityoftime from #timetransaction where userid=1)
select u.userid, coalesce(SUM(quantityoftime), 0) as total from #users u
left join timetransaction t on u.userid=t.userid
group by u.userid;
Example of users table
enter image description here
Example of time transactions table
enter image description here
You should use a LEFT JOIN. But it is very important to get the filtering conditions right:
SELECT SUM(CASE WHEN tt.ChargeBasis = 'C' THEN tt.QuantityOfTime/60/6 ELSE 0 END) AS ChargableUnits,
SUM(CASE WHEN tt.ChargeBasis = 'N' THEN tt.QuantityOfTime/60/6 ELSE 0 END) AS NonChargableUnits,
SUM(CASE WHEN tt.ChargeBasis = 'C' THEN tt.ValueOfTime ELSE 0 END) AS ChargableValue,
SUM(CASE WHEN tt.ChargeBasis = 'N' THEN tt.ValueOfTime ELSE 0 END) AS NonChargableValue,
SUM(tt.QuantityOfTime)/60/6 AS TotalUnits,
SUM(tt.ValueOfTime) AS TotalValue,
u.Code AS FeeEarner
FROM Users u LEFT JOIN
TimeTransactions tt
ON tt.FeeEarnerRef = u.Code AND
tt.TransactionDate >= dateadd(day, -1, CONVERT(date, GETDATE())) AND
tt.TransactionDate < CONVERT(date, GETDATE())
WHERE u.FeeEarner = 1 AND u.UserStatus = 0
GROUP BY u.Code;
Notes:
The conditions on TimeTransactions need to go in the ON clause rather than the WHERE.
SQL Server supports the DATE data type. There is no need to do arcane calculations using date differences to remove the time component from a value.
Table aliases make the query easier to write and to read.

How to merge same table with different conditions

I need to write a query to produce a result to display the OLD_ACCOUNT_ID when available else NULL and the NEW_ACCOUNT_ID when available else NULL from the same table based on the conditions. I tried a query like below but this does not produce the data in separate columns as OLD and NEW. Can somebody please help.
SELECT DISTINCT
A.ACCOUNT_ID as NEW_ACCOUNT_ID,
A.ACTIVE_FLAG,A.DATA_ID
FROM Table A
WHERE DATA_SOURCE_PROVIDER_ID='X'
AND ACTIVE_FLAG='Y'
and DATA_ID= '12345678'
union
SELECT DISTINCT B.ACCOUNT_ID AS OLD_ACCOUNT_ID,
B.ACTIVE_FLAG,B.DATA_ID
FROM Table B
WHERE DATA_SOURCE_PROVIDER_ID='X'
AND ACTIVE_FLAG='N'
and DATA_ID= '12345678'
Use case
select DISTINCT
case ACTIVE_FLAG when 'Y' then ACCOUNT_ID end as NEW_ACCOUNT_ID,
case ACTIVE_FLAG when 'N' then ACCOUNT_ID end as OLD_ACCOUNT_ID,
ACTIVE_FLAG,D
DATA_ID
from Table
whene DATA_ID= '12345678';

SQL using CASE in SELECT with GROUP BY. Need CASE-value but get row-value

so basicially there is 1 question and 1 problem:
1. question - when I have like 100 columns in a table(and no key or uindex is set) and I want to join or subselect that table with itself, do I really have to write out every column name?
2. problem - the example below shows the 1. question and my actual SQL-statement problem
Example:
A.FIELD1,
(SELECT CASE WHEN B.FIELD2 = 1 THEN B.FIELD3 ELSE null FROM TABLE B WHERE A.* = B.*) AS CASEFIELD1
(SELECT CASE WHEN B.FIELD2 = 2 THEN B.FIELD4 ELSE null FROM TABLE B WHERE A.* = B.*) AS CASEFIELD2
FROM TABLE A
GROUP BY A.FIELD1
The story is: if I don't put the CASE into its own select statement then I have to put the actual rowname into the GROUP BY and the GROUP BY doesn't group the NULL-value from the CASE but the actual value from the row. And because of that I would have to either join or subselect with all columns, since there is no key and no uindex, or somehow find another solution.
DBServer is DB2.
So now to describing it just with words and no SQL:
I have "order items" which can be divided into "ZD" and "EK" (1 = ZD, 2 = EK) and can be grouped by "distributor". Even though "order items" can have one of two different "departements"(ZD, EK), the fields/rows for "ZD" and "EK" are always both filled. I need the grouping to consider the "departement" and only if the designated "departement" (ZD or EK) is changing, then I want a new group to be created.
SELECT
(CASE WHEN TABLE.DEPARTEMENT = 1 THEN TABLE.ZD ELSE null END) AS ZD,
(CASE WHEN TABLE.DEPARTEMENT = 2 THEN TABLE.EK ELSE null END) AS EK,
TABLE.DISTRIBUTOR,
sum(TABLE.SOMETHING) AS SOMETHING,
FROM TABLE
GROUP BY
ZD
EK
TABLE.DISTRIBUTOR
TABLE.DEPARTEMENT
This here worked in the SELECT and ZD, EK in the GROUP BY. Only problem was, even if EK was not the designated DEPARTEMENT, it still opened a new group if it changed, because he was using the real EK value and not the NULL from the CASE, as I was already explaining up top.
And here ladies and gentleman is the solution to the problem:
SELECT
(CASE WHEN TABLE.DEPARTEMENT = 1 THEN TABLE.ZD ELSE null END) AS ZD,
(CASE WHEN TABLE.DEPARTEMENT = 2 THEN TABLE.EK ELSE null END) AS EK,
TABLE.DISTRIBUTOR,
sum(TABLE.SOMETHING) AS SOMETHING,
FROM TABLE
GROUP BY
(CASE WHEN TABLE.DEPARTEMENT = 1 THEN TABLE.ZD ELSE null END),
(CASE WHEN TABLE.DEPARTEMENT = 2 THEN TABLE.EK ELSE null END),
TABLE.DISTRIBUTOR,
TABLE.DEPARTEMENT
#t-clausen.dk: Thank you!
#others: ...
Actually there is a wildcard equality test.
I am not sure why you would group by field1, that would seem impossible in your example. I tried to fit it into your question:
SELECT FIELD1,
CASE WHEN FIELD2 = 1 THEN FIELD3 END AS CASEFIELD1,
CASE WHEN FIELD2 = 2 THEN FIELD4 END AS CASEFIELD2
FROM
(
SELECT * FROM A
INTERSECT
SELECT * FROM B
) C
UNION -- results in a distinct
SELECT
A.FIELD1,
null,
null
FROM
(
SELECT * FROM A
EXCEPT
SELECT * FROM B
) C
This will fail for datatypes that are not comparable
No, there's no wildcard equality test. You'd have to list every field you want tested individually. If you don't want to test each individual field, you could use a hack such as concatenating all the fields, e.g.
WHERE (a.foo + a.bar + a.baz) = (b.foo + b.bar + b.az)
but either way, you're listing all of the fields.
I might tend to solve it something like this
WITH q as
(SELECT
Department
, (CASE WHEN DEPARTEMENT = 1 THEN ZD
WHEN DEPARTEMENT = 2 THEN EK
ELSE null
END) AS GRP
, DISTRIBUTOR
, SOMETHING
FROM mytable
)
SELECT
Department
, Grp
, Distributor
, sum(SOMETHING) AS SumTHING
FROM q
GROUP BY
DEPARTEMENT
, GRP
, DISTRIBUTOR
If you need to find all rows in TableA that match in TableB, how about INTERSECT or INTERSECT DISTINCT?
select * from A
INTERSECT DISTINCT
select * from B
However, if you only want rows from A where the entire row matches the values in a row from B, then why does your sample code take some values from A and others from B? If the row matches on all columns, then that would seem pointless. (Perhaps your question could be explained a bit more fully?)

SQL query and joins

Please see my query below:
select I.OID_CUSTOMER_DIM, I.segment as PISTACHIO_SEGMENT,
MAX(CASE WHEN S.SUBSCRIPTION_TYPE = '5' THEN 'Y' ELSE 'N' END ) PB_SUBS,
max(case when S.SUBSCRIPTION_TYPE ='12' then 'Y' else 'N' end) DAILY_TASTE,
MAX(CASE WHEN S.SUBSCRIPTION_TYPE ='8' THEN 'Y' ELSE 'N' END) COOKING_FOR_TWO
FROM WITH_MAIL_ID i JOIN CUSTOMER_SUBSCRIPTION_FCT S
ON I.IDENTITY_ID = S.IDENTITY_ID
WHERE S.SITE_CODE ='PB'and S.SUBSCRIPTION_END_DATE is null
group by I.oid_customer_dim, I.segment
In this one I am getting 654105 rows, which is lower than the one of the joins table with_mail_id which has 706795 rows.
Now, for the qc purpose my manager is wondering as why I am not having all the rows in my final table. I tried to remove all the filters but the results are still not same in both tables. What am I doing wrong?
I am not very good in SQL yet and this thing is really confusing me.
You're doing an inner join on the two tables, so only rows from WITH_MAIL_ID that can join against CUSTOMER_SUBSCRIPTION_FCT will be returned. Additionally you have a group clause.
First the join. If you want to return all rows regardless of the join condition, you can use a left join, but in this case all the S. columns will be NULL, and you'll have to deal with that.
If you run this, you might see the count is the difference:
select count(*) from WITH_MAIL_ID i
left join CUSTOMER_SUBSCRIPTION_FCT S
on I.IDENTITY_ID = S.IDENTITY_ID
where s.IDENTITY_ID is NULL
The most likely thing however is that it's just the grouping. If you are grouping on two columns and selecting the max of various other columns based on that grouping, you would expect that the number of rows returned is less than the original table, otherwise why bother grouping?
If I have data like this:
groupkey1 value
1 2
1 10
2 1
2 1
Then I group by groupkey1, and select MAX(value) I would get 2 rows [1,2], [2,1], not 4 rows.