How to create a view from existing table records, but also adding new records that do not exist - sql

I am trying to create a view from an existing views data, but also if there are certain lines that do not exist per part/date combo, then have those lines be created. I have the below query that is showing what I currently have for the particular s_date/part_no combos:
SELECT
s_date,
part_no,
issue_group,
s_level,
qty_filled
FROM
current_view
WHERE
part_no = 'xxxxx'
AND s_date IN (
'201802',
'201803'
)
ORDER BY
s_date,
part_no,
issue_group,
DECODE(s_level, '80', 1, '100', 2, 'Late', 3)
Which produces the below:
I know how to create a view with that data, that's the easy part. But what I'm needing is a line for each issue_group and s_level combo, and if it's a created line, to put 0 as the qty_filled.
Every part_no / s_date combo should have 6 rows that go with it
- issue_group = '1' / s_level = '80'
- issue_group = '1' / s_level = '100'
- issue_group = '1' / s_level = 'Late'
- issue_group = '2/3 ' / s_level = '80'
- issue_group = '2/3 ' / s_level = '100'
- issue_group = '2/3 ' / s_level = 'Late'
So if one of the above combinations already exists for the current s_date/part_no, then it obviously takes the qty_filled info from the current view. If not, a new line is created, and qty_filled = 0. So I'm trying to get it to look like this:
I've only shown 1 part, with a couple dates, just to get the point across. There are 10k+ parts within the table and there will never be more than 1 part/date combo for each of the 6 issue_group/s_level combos.

The idea is to generate the rows using CROSS JOIN and then bring in the extra information with a LEFT JOIN. In Oracle syntax, this looks like:
WITH v as (
SELECT v.*
FROM current_view v
WHERE part_no = 'xxxxx' AND
s_date IN ('201802', '201803')
)
SELECT d.s_date, ig.part_no, ig.issue_group, l.s_level,
COALESCE(v.qty_filled, 0) as qty_filled
FROM (SELECT DISTINCT s_date FROM v) d CROSS JOIN
(SELECT DISTINCT part_no, ISSUE_GROUP FROM v) ig CROSS JOIN
(SELECT '80' as s_level FROM DUAL UNION ALL
SELECT '100' FROM DUAL UNION ALL
SELECT 'LATE' FROM DUAL
) l LEFT JOIN
v
ON v.s_date = d.s_date AND v.part_no = ig.part_no AND
v.issue_group = ig.issue_group AND v.s_level = l.s_level
ORDER BY s_date, part_no, issue_group,
(CASE s_level WHEN '80' THEN 1 WHEN '100' THEN 2 WHEN 'Late' THEN 3 END)

One solution could be to generate a cartesian product of all expected rows using a cartesian product between the (fixed) list of values, and then LEFT JOIN it with current_view.
The following query guarantees that you will get a record for each given s_date/part_no/issue_group/s_level tuple. If no record matches in current_view, the query will display a 0 quantity.
SELECT
sd.s_date,
pn.part_no,
ig.issue_group,
sl.s_level,
COALESCE(cv.qty_filled, 0) qty_filled
FROM
(SELECT '201802' AS s_date UNION SELECT '201803') AS sd
CROSS JOIN (SELECT 'xxxxx' AS part_no) AS pn
CROSS JOIN (SELECT '1' AS issue_group UNION SELECT '2') AS ig
CROSS JOIN (SELECT '80' AS s_level UNION SELECT '100' UNION SELECT 'Late') AS sl
LEFT JOIN current_view cv
ON cv.s_date = sd.s_date
AND cv.part_no = pn.part_no
AND cv.issue_group = ig.issue_group
AND cv.s_level = ig.s_level
ORDER BY
sd.s_date,
pn.part_no,
ig.issue_group,
DECODE(sl.s_level, '80', 1, '100', 2, 'Late', 3)
NB : you did not tag your RDBMS. This should work on most of them, excepted Oracle, where you need to add FROM DUAL to each select in the queries that list the allowed values, like :
(SELECT '201802' AS s_date FROM DUAL UNION SELECT '201803' FROM DUAL) AS sd

Related

How to find exact matching values in sql

How can I find the exact match in case of duplicate values in SQL
I am using the below query to find customers which are having write access based on the below values.
user **A write** user - {UI.ACCESS, API.ACCESS} and
user **B read** user - {UI.ACCESS, API.ACCESS, UI.READONLY, API.READONLY}
Query -
select ul.USERNAME,sp.PERMISSION_N,cus.ID
from CUSTOMER cus
join USER_L ul on cus.ID = ul.CUSTOMER_ID
join USER_ROLE ur on ul.ID = ur.USER_ID
join SECURITY_ROLE sr on ur.SECURITY_ROLE_ID = sr.SECURITY_ROLE_ID
join SECURITY_PERMISSION sp on sr.SECURITY_PERMISSION_ID = sp.ID
where sp.PERMISSION_NAME in ('UI.ACCESS','API.ACCESS')
above query return the B user as well but I am expecting only A.
You want to check across all the rows in the SECURITY_PERMISSION table that none of them have the forbidden roles.
You have tagged both Oracle and MySQL. In Oracle, you can use:
SELECT ul.USERNAME,
sp.permission_names,
cus.ID
FROM CUSTOMER cus
INNER JOIN USER_L ul
ON (cus.ID = ul.CUSTOMER_ID)
INNER JOIN USER_ROLE ur
ON (ul.ID = ur.USER_ID)
INNER JOIN SECURITY_ROLE sr
ON (ur.SECURITY_ROLE_ID = sr.SECURITY_ROLE_ID)
INNER JOIN (
SELECT id,
LISTAGG(permission_name, ',')
WITHIN GROUP (ORDER BY permission_name)
AS permission_names
FROM SECURITY_PERMISSION
GROUP BY id
HAVING COUNT(
CASE
WHEN PERMISSION_NAME in ('UI.ACCESS','API.ACCESS')
THEN 1
END
) > 0
AND COUNT(
CASE
WHEN PERMISSION_NAME in ('UI.READONLY', 'API.READONLY')
THEN 1
END
) = 0
) sp
ON (sr.SECURITY_PERMISSION_ID = sp.ID)
Which, for the sample data:
CREATE TABLE customer (id) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE user_l (id, customer_id, username) AS
SELECT LEVEL, LEVEL, 'User' || LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE user_role (id, user_id, security_role_id) AS
SELECT LEVEL, LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE security_role (security_role_id, security_permission_id) AS
SELECT LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE security_permission (id, permission_name) AS
SELECT 1, 'UI.ACCESS' FROM DUAL UNION ALL
SELECT 1, 'API.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'UI.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'API.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'UI.READONLY' FROM DUAL UNION ALL
SELECT 2, 'API.READONLY' FROM DUAL;
Outputs:
USERNAME
PERMISSION_NAMES
ID
User1
API.ACCESS,UI.ACCESS
1
db<>fiddle here
For MySQL, you would need to replace LISTAGG with something to aggregate strings such as GROUP_CONCAT.

Single-column row-set exists in another table or a function returns positive value

I have following table structure: http://sqlfiddle.com/#!4/952e7/1
Now I am looking for a solution for the following problem:
Given an input data-time set (see below). And the SQL statement should return all of business IDs with a given business name, where every single date-times of the input set are either present in the ORDERS table or an additional function's statement is true (these both conditions are separately to be checked for each input date-time).
An example how the input date-time dataset looks like:
WITH DATES_TO_CHECK(DATETIME) AS(SELECT DATE '2021-01-03' FROM DUAL UNION ALL SELECT DATE '2020-04-08' FROM DUAL UNION ALL SELECT DATE '2020-05-07' FROM DUAL)
To be simple, the "additional function" should be a simple random number (if greather than 0.5 than true otherwise false, so the check is dbms_random.value > 0.5).
For one given date time it would look like:
SELECT BN.NAME, BD.ID
FROM BUSINESS_DATA BD, BUSINESS_NAME BN
WHERE BD.NAME_ID=BN.ID AND
BN.NAME='B1' AND
(TO_DATE('2021-01-03', 'YYYY-MM-DD') IN (SELECT OD.ORDERDATE FROM ORDERS OD WHERE OD.BUSINESS_ID=BD.ID)
OR dbms_random.value > 0.5)
ORDER BY BD.ID
Please help me, how this solution can be applied to the input date-time rowset above AND the specified name.
I don't any difference with the question you just deleted
This is the list of businesses named B1 and for which the number of order dates that match date input dates is equal to the number of input dates or dbms_random.value > 0.5
see SQL Fiddle
WITH DATES_TO_CHECK(DATETIME) AS(
SELECT DATE '2021-01-03' FROM DUAL
UNION ALL SELECT DATE '2020-04-08' fROM DUAL
UNION ALL SELECT DATE '2020-05-07' fROM DUAL
),
businesses_that_match as (
select
od.BUSINESS_ID, count(distinct OD.ORDERDATE)
from DATES_TO_CHECK dtc
left join ORDERS od on OD.ORDERDATE = dtc.datetime
group by od.BUSINESS_ID
having count(distinct OD.ORDERDATE) = (select count(distinct DATETIME) from DATES_TO_CHECK)
)
SELECT
BN.NAME, BD.ID
FROM BUSINESS_DATA BD
inner join BUSINESS_NAME BN on BD.NAME_ID=BN.ID
left join businesses_that_match btm on btm.BUSINESS_ID = bd.id
where bn.name = 'B1'
AND (btm.BUSINESS_ID is not null
OR dbms_random.value > 0.5
)

How to add new row from another table with union?

I should add a new row from table products which owner is sales(sh) to product_name_us and prod_desc to product_desc_us using an union .
SELECT temp.*
FROM (SELECT DISTINCT CASE WHEN pd.LANGUAGE_ID = 'US' THEN
COALESCE(TO_CHAR(pd.TRANSLATED_NAME), ' ')
END AS PRODUCT_NAME_US,
CASE WHEN pd.LANGUAGE_ID = 'US' THEN
CONCAT(SUBSTR(pd.TRANSLATED_DESCRIPTION, 1, 30), '...')
END AS PRODUCT_DESC_US,
CASE WHEN pd.LANGUAGE_ID = 'RU' AND pd.translated_name
IS NULL THEN COALESCE(TO_CHAR(pd.TRANSLATED_NAME), ' ')
END AS PRODUCT_NAME_RU,
CASE WHEN pd.LANGUAGE_ID = 'RU' AND
pd.translated_description IS NULL THEN
CONCAT(SUBSTR(pd.TRANSLATED_DESCRIPTION, 1, 30), '...')
END AS PRODUCT_DESC_RU,
CASE
WHEN EXTRACT(MONTH FROM pin.WARRANTY_PERIOD) =
'00' THEN EXTRACT(YEAR FROM pin.WARRANTY_PERIOD) || ' years'
END AS "Warranty", inv.QUANTITY_ON_HAND AS IN_STOCK
FROM PRODUCT_DESCRIPTIONS pd JOIN PRODUCT_INFORMATION pin
ON pd.PRODUCT_ID = pin.PRODUCT_ID
JOIN INVENTORIES inv
ON inv.PRODUCT_ID = pin.PRODUCT_ID) temp
WHERE temp.PRODUCT_NAME_US LIKE '%Monitor%'
AND temp.IN_STOCK < 100
union all (select PROD_NAME,PROD_DESC from sh.products);
If I understand right you want to add a row as a footer to your first select.
Using union is only available if your 2 selects having same amount of columns
select 'field1' as a, 'field2' as b, 'field3' as c from dual
union all
select '1', '2', null from dual;
enter image description here
But if you want to use in your second query column from your first one to match it is impossible because its outside the select statement

Using Analytical Clauses with DISTINCT

The purpose is to query multiple tables using DISTINC (if not I get millions of rows as results), but at the same time using sample to gather a 10% sample from the results that should all be unique. I am getting the following error:
ORA-01446: cannot select ROWID from, or sample, a view with DISTINCT, GROUP BY, etc.
Here is the code I have written:
WITH V AS (SELECT DISTINCT AL1."NO", AL3."IR", AL1."ACCT", AL3."CUST_DA", AL1."NA",
AL3."1_LINE", AL3."2_LINE", AL3."3_LINE", AL1."DA",
AL1."CD", AL1."TITLE_NA", AL1."ENT_NA", AL3."ACCT",
AL3."ACCTLNK_ENRL_CNT"
FROM "DOC"."DOCUMENT" AL1, "DOC"."VNDR" AL2, "DOC"."CUST_ACCT" AL3
WHERE (AL1."ACCT"=AL2."VNDR"
AND AL2."ACCT"=AL3."ACCT")
AND ((AL1."IMG_DA" >= Trunc(sysdate-1)
AND AL1."PROC"='A'
AND AL3."ACCT"<>'03')))
SELECT * FROM V SAMPLE(10.0)
You can't sample a join view like this.
Simpler test case (MCVE):
with v as
( select d1.dummy from dual d1
join dual d2 on d2.dummy = d1.dummy
)
select * from v sample(10);
Fails with:
ORA-01445: cannot select ROWID from, or sample, a join view without a key-preserved table
The simplest fix would be to move the sample clause to the driving table:
with v as
( select d1.dummy from dual sample(10) d1
join dual d2 on d2.dummy = d1.dummy
)
select * from v;
I would therefore rewrite your view as:
with v as
( select distinct
d.no
, a.ir
, d.acct
, a.cust_da
, d.na
, a."1_LINE", a."2_LINE", a."3_LINE"
, d.da, d.cd, d.title_na, d.ent_na
, a.acct
, a.acctlnk_enrl_cnt
from doc.document sample(10) d
join doc.vndr v
on v.vndr = d.acct
join doc.cust_acct a
on a.acct = v.acct
and d.img_da >= trunc(sysdate - 1)
and d.proc = 'A'
and a.acct <> '03'
)
select * from v;

How to Select * Where Everything is Distinct Except One Field

I'm trying to pull 6 records using the code below but there are some cases where the information is updated and therefore it is pulling duplicate records.
My code:
SELECT column2, count(*) as 'Count'
FROM ServiceTable p
join HIERARCHY h
on p.LOCATION_CODE = h.LOCATION
where Report_date between '2017-04-01' and '2017-04-30'
and Column1 = 'Issue '
and LOCATION = '8789'
and
( record_code = 'INCIDENT' or
(
SUBMIT_METHOD = 'Web' and
not exists
(
select *
from ServiceTable p2
where p2.record_code = 'INCIDENT'
and p2.incident_id = p.incident_id
)
)
)
The problem is that instead of the six records it is pulling eight. I would just use distinct * but the file_date is different on the duplicate entries:
FILE_DATE Incident_ID Column1 Column2
4/4/17 123 Issue Service - Red
4/4/17 123 Issue Service - Blue
4/5/17 123 Issue Service - Red
4/5/17 123 Issue Service - Blue
The desired output is:
COLUMN2 COUNT
Service - Red 1
Service - Blue 1
Any help would be greatly appreciated! If you need any other info just let me know.
If you turn your original select statement without the aggregation function into a subquery, you can distinct that on your values that are not the changing date, then select a COUNT from there. Don't forget your GROUP BY clause at the end.
SELECT Column2, COUNT(Incident_ID) AS Service_Count
FROM (SELECT DISTINCT Incident_ID, Column1, Column2
FROM ServiceTable p
JOIN HIERARCHY h ON p.LOCATION_CODE = h.LOCATION
WHERE Report_date BETWEEN '2017-04-01' AND '2017-04-30'
AND Column1 = 'Issue '
AND LOCATION = '8789'
AND
( record_code = 'INCIDENT' or
(
SUBMIT_METHOD = 'Web' and
NOT EXISTS
(
SELECT *
FROM ServiceTable p2
WHERE p2.record_code = 'INCIDENT'
AND p2.incident_id = p.incident_id)
)
)
)
GROUP BY Column2
Also, if you are joining tables it is a good practice to fully qualify the field you are selecting. Example: p.Column2, p.Incident_ID, h.LOCATION. That way, even your distinct fields are easier to follow where they came from and how they relate.
Finally, don't forget that COUNT is a reserved word. I modified your alias accordingly.
If you are using an aggregation function (count), you should use group by for the column not in the aggregation function:
SELECT column2, count(*) as 'Count'
FROM ServiceTable p
join HIERARCHY h
on p.LOCATION_CODE = h.LOCATION
where Report_date between '2017-04-01' and '2017-04-30'
and Column1 = 'Issue '
and LOCATION = '8789'
and
( record_code = 'INCIDENT' or
(
SUBMIT_METHOD = 'Web' and
not exists
(
select *
from ServiceTable p2
where p2.record_code = 'INCIDENT'
and p2.incident_id = p.incident_id
)
)
)
group by column2